--- jupyter: description: 'Sunflower seeds and the golden angle with planex2d: Vogel''s model of phyllotaxis, seeds as discs placed at n times the divergence angle and the radius c sqrt(n), their nearest-neighbor distances against the bounds for the golden angle, the overlap of the discs measured with Boolean operations for angles near it, and the Fibonacci spirals.' --- # Sunflower seeds and the golden angle Why do the seeds of a sunflower lie 137.5° apart? The seeds form at the center of the head, one after the other, each turned from the one before by the same divergence angle, and in the sunflower this angle is close to the golden angle, $360^\circ/\varphi^2 \approx 137.508^\circ$. Vogel's model of the head places the $n$-th seed at the angle $n \cdot 137.508^\circ$ and at the radius $c\sqrt{n}$, so that every seed gets the same area, $\pi c^2$. At the golden angle, every seed keeps its distance from the others, and the head fills evenly. At an angle a tenth of a degree off, the seeds crowd together along spiral arms and leave gaps between them. The spirals that the eye picks out in a head come in consecutive Fibonacci numbers, such as 34 and 55. The example places a thousand seeds by Vogel's model. At the golden angle, the smallest distance between neighboring seeds must approach $\sqrt{2\pi/\sqrt{5}}\,c \approx 1.676\,c$ away from the center, close to the spacing of a hexagonal packing of the same density, the densest there is. Discs of a diameter of $1.6\,c$ must not overlap there, and at angles nearby they must. ![A sunflower head of a thousand seeds at the golden angle, every other one of its 34 spirals in a darker color](../_static/gallery/sunflower-golden-angle.png){.result} ```python import math import matplotlib.pyplot as plt import numpy as np import planex as px ``` ```python nbsphinx="hidden" from docs_figures import GUIDE, INK, LIGHT, PLANEX ``` ## Vogel's model `seeds` places the seeds 1 to $n$ for a divergence angle, with $c = 1$, and `nearest` gives each seed's distance to its nearest neighbor: ```python golden = 360 / ((1 + math.sqrt(5)) / 2) ** 2 def seeds(angle, n=1000): """the centers of the seeds 1 to n at the divergence angle, in degrees, by Vogel's model with c = 1""" k = np.arange(1, n + 1) return np.c_[np.sqrt(k) * np.cos(np.radians(k * angle)), np.sqrt(k) * np.sin(np.radians(k * angle))] def nearest(points): """each point's distance to its nearest neighbor""" d = np.sqrt(((points[:, None] - points[None]) ** 2).sum(axis=-1)) np.fill_diagonal(d, np.inf) return d.min(axis=1) print("golden angle", golden, "degrees") ``` ## The golden angle For the seeds from the hundredth on, away from the center, the smallest distance to the nearest neighbor is compared with $\sqrt{2\pi/\sqrt{5}}\,c$, and with $\sqrt{2\pi/\sqrt{3}}\,c$, the distance between neighbors in a hexagonal packing of the same density: ```python smallest = nearest(seeds(golden))[99:].min() hexagonal = math.sqrt(2 * math.pi / math.sqrt(3)) print("smallest distance", smallest, "| √(2π/√5)", math.sqrt(2 * math.pi / math.sqrt(5))) print("hexagonal packing", hexagonal, "| ratio", smallest / hexagonal) ``` The smallest distance comes within a thousandth of $\sqrt{2\pi/\sqrt{5}}\,c$, the limit for a head of many seeds, and it is 88 % of the spacing of a hexagonal packing. ## Angles near it As discs, the seeds of a diameter of $1.6\,c$ fit between the bounds at the golden angle. `px.Unify` joins the discs, and the area that the union lacks against the sum of the discs is their overlap. At the golden angle the overlap is zero, and at 137.3°, 137.6° and at $\tfrac{8}{21}$ and $\tfrac{13}{34}$ of a turn, fractions near the golden angle, it is not: ```python def head(angle): """the seeds as discs of radius 0.8 and the overlap of the discs""" discs = [px.Circle(tuple(p), 0.8).Face() for p in seeds(angle)] union = px.Unify(discs) return union, max(len(discs) * math.pi * 0.8**2 - union.mass, 0.0) # no overlap: zero to rounding angles = {"golden": golden, "137.3°": 137.3, "137.6°": 137.6, "8/21 of a turn": 360 * 8 / 21, "13/34 of a turn": 360 * 13 / 34} heads = {} for name, angle in angles.items(): heads[name], overlap = head(angle) print(name, round(angle, 4), "| smallest distance", round(nearest(seeds(angle))[99:].min(), 4), "| overlap", round(overlap, 3)) ``` At the fractions of a turn, the seeds line up in 21 or 34 straight rays. At 137.3° and 137.6°, they lie along curved arms with gaps between them: ```python nbsphinx="hide-input" fig, axes = plt.subplots(1, 3, figsize=(15, 5.2)) for ax, name in zip(axes, ("137.3°", "golden", "137.6°")): heads[name].Plot(ax=ax, face_col=LIGHT, line_col=PLANEX, legend=False) ax.set_title(f"{name}, overlap {max(len(seeds(angles[name])) * math.pi * 0.64 - heads[name].mass, 0):.1f}") ax.set_aspect("equal") ax.axis("off") plt.show() ``` ## The Fibonacci spirals Seeds whose numbers differ by a Fibonacci number lie close together, because the Fibonacci fractions $\tfrac{13}{34}$, $\tfrac{21}{55}$, $\tfrac{34}{89}$ of a turn come ever closer to the golden angle. The seeds $n$, $n + 34$, $n + 68$, ... therefore form one spiral, and the head has 34 of them winding one way. The seeds $n$, $n + 55$, $n + 110$, ... form 55 spirals winding the other way. The figure colors every other spiral of each family: ```python nbsphinx="hide-input" points = seeds(golden) k = np.arange(1, len(points) + 1) fig, axes = plt.subplots(1, 2, figsize=(11, 5.5)) for ax, family in zip(axes, (34, 55)): on = (k % family) % 2 == 0 ax.scatter(*points[~on].T, s=18, color="#c3cbd3") ax.scatter(*points[on].T, s=18, color=PLANEX if family == 34 else GUIDE) ax.set_title(f"{family} spirals") ax.set_aspect("equal") ax.axis("off") plt.show() ``` See [Many faces at once](../core/booleans/many-faces.md) for `px.Unify`. ```python nbsphinx="hidden" import sys sys.path.insert(0, "..") from gallery import save_figure fig, ax = plt.subplots(figsize=(6, 6)) for parity, face, line in ((0, "#f2b544", "#8a5a12"), (1, "#c98a1b", "#6b430c")): spirals = px.Unify([px.Circle(tuple(p), 0.8).Face() for p, n in zip(points, k) if (n % 34) % 2 == parity]) spirals.Plot(ax=ax, face_col=face, line_col=line, legend=False) ax.set_aspect("equal") ax.axis("off") save_figure(fig, "sunflower-golden-angle") plt.close(fig) ```