--- jupyter: description: 'Why bees build hexagons, with planex2d: triangle, square and hexagon cells of equal area glued into tilings, the wall per cell against the closed forms, and the gaps that round cells leave, against 1 - pi/(2 sqrt 3) and 1 - pi/4.' --- # 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](../_static/gallery/honeycomb.png){.result} ```python import math import planex as px ``` ```python nbsphinx="hidden" from docs_figures import GUIDE, Row ``` ## 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): ```python 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))) ``` ```python nbsphinx="hide-input" Row(*[(cell, f"The {name}, area 1") for name, cell in cells.items()], caption="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): ```python 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)) ``` ```python nbsphinx="hide-input" Row(*[(comb, f"{len(comb.faces)} {name}s", {"lw": 0.6}) for name, comb in combs.items()], caption="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: ```python 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) ``` 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): ```python 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) ``` ```python nbsphinx="hide-input" Row(*[(combs[name], f"Circles in the {name}s, the gaps shaded", {"lw": 0.6, "fills": [(gaps[name], GUIDE)], "view": view}) for name, view in (("hexagon", ((5, 9.5), (7, 11.5))), ("square", ((4, 8.5), (4, 8.5))))], caption="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](../core/basics/faces-shapes-compounds.md#Gluing:-faces-that-share-edges) for `px.Glue`. ```python nbsphinx="hidden" import sys import matplotlib.pyplot as plt sys.path.insert(0, "..") from gallery import save_figure honey = combs["hexagon"] * px.QuickSketch((4, 9)).Rectangle(9, 6).Face() fig, ax = plt.subplots(figsize=(9, 6)) honey.Plot(ax=ax, face_col="#f2b544", line_col="#8a5a12", legend=False) ax.set_aspect("equal") ax.axis("off") save_figure(fig, "honeycomb") plt.close(fig) ```