Why bees build hexagons

A honeycomb divides a comb into cells of equal size, and its walls are wax that the bees have to make. Of the three regular polygons that tile the plane, the triangle, the square and the hexagon, the hexagon needs the least wall for a cell of a given area. The honeycomb conjecture, proved by Thomas Hales, says that no division of the plane into cells of equal area does better, not even one with curved walls.

The example builds the three tilings from cells of unit area. Glued, each wall between two cells counts once, and the wall per cell must come out as half the cell’s perimeter, which is 2.280 for the triangle, 2 for the square and 1.861 for the hexagon. Round cells have the shortest wall for one cell alone, but they leave gaps, \(1 - \pi/(2\sqrt{3})\) of the plane when they are packed as tightly as possible.

A honeycomb of hexagonal cells

[1]:
import math
import planex as px

Cells of unit area

A regular polygon with \(n\) sides and the area 1 has the perimeter \(P = 2\sqrt{n \tan(\pi/n)}\). The three cells:

  • the hexagon Hexagon(af), with the width across flats af = \(\sqrt{2/\sqrt{3}}\);

  • the square Rectangle(1, 1);

  • the triangle Polygon(r, 3), its corners on a circle of radius \(r = s/\sqrt{3}\), for the side \(s = 2/3^{1/4}\).

Each is a face around the origin, the square with its corner there (Figure 1):

[3]:
af = math.sqrt(2 / math.sqrt(3))
s = 2 / 3**0.25
r = s / math.sqrt(3)
cells = {
    "triangle": px.QuickSketch().Polygon(r, 3).Face(),
    "square": px.QuickSketch().Rectangle(1, 1).Face(),
    "hexagon": px.QuickSketch().Hexagon(af).Face(),
}
for (name, cell), n in zip(cells.items(), (3, 4, 6)):
    print(name, "area", cell.mass, "perimeter", cell.outer.mass, "formula", 2 * math.sqrt(n * math.tan(math.pi / n)))
triangle area 1.0 perimeter 4.559014113909555 formula 4.559014113909554
square area 1.0 perimeter 4.0 formula 3.9999999999999996
hexagon area 1.0000000000000002 perimeter 3.722419436408399 formula 3.7224194364083982
The triangle, area 1
(a) The triangle, area 1
The square, area 1
(b) The square, area 1
The hexagon, area 1
(c) The hexagon, area 1
Figure 1: Three cells of unit area

Tilings

tiling places \(n \times n\) copies of its cells on a lattice of two vectors \(a\) and \(b\), and px.Glue joins them into one shape whose cells keep their own faces and share their walls. The squares lie on a lattice of 1 along \(x\) and 1 along \(y\). The hexagons lie on one of af up and af at 30°, since a hexagon’s neighbors lie across its flats. The triangles come in pairs of the cell, pointing along \(x\), and a copy turned by 180° and moved by \(r\), which points the other way and shares a side with it. The pairs lie on a lattice of \(s\) up and \(s\) at 30° (Figure 2):

[5]:
def tiling(cells, lattice, n):
    """n x n copies of the cells, the copy (i, j) moved by i a + j b, glued into one shape"""
    (ax, ay), (bx, by) = lattice
    return px.Glue([cell.Translated((i * ax + j * bx, i * ay + j * by))
                    for i in range(n) for j in range(n) for cell in cells])


c, d = math.cos(math.radians(30)), math.sin(math.radians(30))
lattices = {"triangle": ((0, s), (s * c, s * d)), "square": ((1, 0), (0, 1)), "hexagon": ((0, af), (af * c, af * d))}
pair = [cells["triangle"], cells["triangle"].Rotated((0, 0), 180).Translated((-r, 0))]
combs = {
    "triangle": tiling(pair, lattices["triangle"], 12),
    "square": tiling([cells["square"]], lattices["square"], 16),
    "hexagon": tiling([cells["hexagon"]], lattices["hexagon"], 16),
}
for name, comb in combs.items():
    print(name, len(comb.faces), "cells of area", round(min(f.mass for f in comb.faces), 12), "to",
          round(max(f.mass for f in comb.faces), 12))
triangle 288 cells of area 1.0 to 1.0
square 256 cells of area 1.0 to 1.0
hexagon 256 cells of area 1.0 to 1.0
288 triangles
(a) 288 triangles
256 squares
(b) 256 squares
256 hexagons
(c) 256 hexagons
Figure 2: The three tilings, every wall shared by two cells counted once

Every cell’s perimeter counts each wall it shares twice and each wall on the outline once, so the walls of a patch of \(N\) cells add up to \((N P + B)/2\), with \(B\) the length of the patch’s outline, the boundary of the cells united. Without the outline, which an endless tiling does not have, each cell has \(P/2\) of wall:

[7]:
for name, comb in combs.items():
    N, P = len(comb.faces), cells[name].outer.mass
    walls = comb.Measure()["length"]
    outline = px.Unify(comb.faces).Measure()["length"]
    print(name, "walls", walls, "(N P + B) / 2", (N * P + outline) / 2)
    print("    per cell without the outline", (walls - outline / 2) / N, "P / 2", P / 2)
triangle walls 709.6865303985809 (N P + B) / 2 709.6865303985875
    per cell without the outline 2.2795070569547544 P / 2 2.2795070569547775
square walls 544.0 (N P + B) / 2 544.0
    per cell without the outline 2.0 P / 2 2.0
hexagon walls 515.5550919425705 (N P + B) / 2 515.5550919425632
    per cell without the outline 1.861209718204228 P / 2 1.8612097182041996

The hexagons need about 7 % less wall than the squares and 18 % less than the triangles.

Round cells

A circle encloses its area with the shortest boundary of all. For the area 1, its perimeter is \(2\sqrt{\pi} = 3.545\), less than the hexagon’s 3.722. But circles share no wall, they only touch, and they leave gaps. Packed as tightly as possible, each touching six others, circles leave \(1 - \pi/(2\sqrt{3})\) of the plane empty, about 9.3 %. In rows and columns, they leave \(1 - \pi/4\), about 21.5 %. The check subtracts the circles inscribed in the hexagons and in the squares, tiled on the same lattices, from the cells (Figure 3):

[8]:
gaps = {}
for name, center, radius, exact in (("hexagon", (0, 0), af / 2, 1 - math.pi / (2 * math.sqrt(3))),
                                    ("square", (0.5, 0.5), 0.5, 1 - math.pi / 4)):
    circles = tiling([px.Circle(center, radius).Face()], lattices[name], 16)
    gaps[name] = combs[name] - circles
    print(name, "packing: empty", gaps[name].mass / combs[name].mass, "exact", exact)
hexagon packing: empty 0.09310031788289204 exact 0.09310031788289108
square packing: empty 0.21460183660255389 exact 0.21460183660255172
Circles in the hexagons, the gaps shaded
(a) Circles in the hexagons, the gaps shaded
Circles in the squares, the gaps shaded
(b) Circles in the squares, the gaps shaded
Figure 3: Round cells and the gaps they leave, a part of each patch

Round cells waste room in the comb, and every cell needs a wall of its own. The hexagon shares each of its walls with a neighbor and fills the plane. See Faces, shapes and compounds for px.Glue.