Model Description

A faithful, code-aligned description of the inhibition-field model behind the simulator.

TL;DR — how it works (one screen)

  • New primordia are born on a fixed ring at radius R.
  • Each step all primordia drift outward by v * dt.
  • The next angle is chosen by minimizing an inhibition field F(θ) = Σ K(d) across angleSamples candidates.
  • The discrete argmin is refined by a parabolic refinement to obtain θ*.
  • Optional Gaussian noise perturbs θ*; divergence mean/std are reported.

Overview (what we model)

We model a growing shoot apex as a 2D disk with a birth ring of radius RR. Primordia are point-like organs defined by polar coordinates (r,θ)(r,\theta). They are created on the ring (r=Rr=R) and then drift outward over time. Each existing primordium contributes an inhibitory influence. A new primordium appears at the angle where the total inhibition is minimal.

This is an inhibition‑field model in the spirit of Douady–Couder, implemented as a fast discrete optimizer over candidate angles.

[!KEY IDEA] Spiral counts emerge from repeated argmin of an inhibition field on the birth ring while older primordia drift outward.

Diagram A — Choosing θ*: birth ring, active primordia, distances dᵢ
birth ring (r=R)p₁p₂p₃p₄p₅candidate c(θ)d₁d₂d₃θ

The field value at angle θ is computed by summing kernel contributions from active primordia using distances dᵢ(θ) from the ring point c(θ).

Objects & Parameters

Geometry and time

  • RR: birth ring radius (cfg.R)
  • vv: radial drift speed (cfg.v)
  • dtdt: time step (cfg.dt)
  • maxR\texttt{maxR}: cutoff radius for "active" primordia (cfg.maxR)
  • angleSamples\texttt{angleSamples}: number of discrete candidate angles on [0,2π)[0,2\pi) (cfg.angleSamples)
  • Parabolic refinement: sub-sample minimizer around the best discrete sample

Kernel (inhibition strength vs distance)

The kernel configuration is a tagged union:

  • exp: K(d)=Aed/λK(d) = A\,e^{-d/\lambda}
  • gaussian: K(d)=Aed2/(2σ2)K(d) = A\,e^{-d^2/(2\sigma^2)}
  • softPower: K(d)=A/(dp+ε)K(d) = A / (d^p + \varepsilon)
  • hardCoreExp: exp kernel + hard exclusion for d<d0d < d_0
  • custom: user expression K(d)=f(d,params)K(d)=f(d,\text{params}) via expr-eval

Noise

Optional angular noise applied after θ\theta^* is computed:

  • enabled (cfg.noise.enabled)
  • σθ\sigma_\theta in degrees (cfg.noise.sigmaThetaDeg)
  • seed (cfg.noise.seed) for determinism

[!TRY THIS] Set noise.enabled=false to see a near‑deterministic convergence of divergence statistics; then enable noise with σθ0.1\sigma_\theta \approx 0.1^\circ to observe a broader divergence distribution.

Step algorithm

Each simulation step repeats:

  1. Drift — all primordia move outward: rr+vdtr \leftarrow r + v \cdot dt
  2. Update active set — keep only primordia with rmaxRr \le \mathrm{maxR}
  3. Compute inhibition field — for each candidate angle, sum kernel contributions from active primordia
  4. Find θ* — pick the angle that minimizes F(θ)F(\theta)
  5. Add primordium — place new point at (R,θ)(R,\, \theta^*)

Optional Gaussian noise can be added to θ* before placement.

Diagram C — Active set: primordia within maxR are included
RmaxRactive (r ≤ maxR)ignored (r > maxR)

In the code, the active set is implemented as a moving suffix index (firstActiveIndex) because radii increase monotonically with age under constant drift.

Diagram B — Field F(θ); minimum at θ*
θFθ*

The placement angle θ* is the argmin of F(θ) over the birth ring.

Inhibition field & argmin(θ)

For a candidate angle θ\theta, define the candidate point on the ring:

c(θ)=(Rcosθ, Rsinθ).c(\theta) = (R\cos\theta,\ R\sin\theta).

Each active primordium jj has Cartesian position:

xj=rjcosθj,yj=rjsinθj.x_j = r_j\cos\theta_j,\quad y_j = r_j\sin\theta_j.

Distance to the candidate:

dj(θ)=(Rcosθxj)2+(Rsinθyj)2.d_j(\theta) = \sqrt{(R\cos\theta - x_j)^2 + (R\sin\theta - y_j)^2}.

Total inhibition field:

F(θ)=jactiveK(dj(θ)).F(\theta)=\sum_{j \in \text{active}} K(d_j(\theta)).

Placement rule:

θ=argminθ[0,2π)F(θ).\theta^* = \arg\min_{\theta \in [0,2\pi)} F(\theta).

In code, the optimizer is "grid search + parabolic refinement":

[!IMPLEMENTATION MAPPING]

  • Discrete scan and argmin: src/sim/simulator.tsfindMinimumFieldAngle()
  • Refinement uses F(θi1),F(θi),F(θi+1)F(\theta_{i-1}),F(\theta_i),F(\theta_{i+1}) and clamps offset to [0.5,0.5][-0.5,0.5]

Kernels

exp

K(d)=Aexp(d/λ)K(d)=A\exp(-d/\lambda)

gaussian

K(d)=Aexp(d22σ2)K(d)=A\exp\left(-\frac{d^2}{2\sigma^2}\right)

softPower

K(d)=Adp+εK(d)=\frac{A}{d^p+\varepsilon}

hardCoreExp

Same as exp, but invalidates a candidate angle if any active primordium is closer than d0d_0:

j: dj(θ)<d0F(θ)=HUGE_PENALTY.\exists j:\ d_j(\theta) < d_0 \Rightarrow F(\theta)=\text{HUGE\_PENALTY}.

custom

Users can provide an expression string like:

A * exp(-d / lambda)

The simulator evaluates it with a stable parameter scope and safety guards (exceptions / NaN / Infinity → penalty).

[!IMPLEMENTATION MAPPING] src/sim/kernel.tscompileKernel() (all kernel types) and validateExpression() (custom kernels).

Metrics (divergence mean/std)

The simulator reports divergence angle statistics over the most recent NN steps (default N=200N=200):

Δn=wrap[0,2π)(θnθn1),mean=1NΔn,std=1N(Δnmean)2.\Delta_n = \mathrm{wrap}_{[0,2\pi)}(\theta_n - \theta_{n-1}),\quad \text{mean} = \frac{1}{N}\sum \Delta_n,\quad \text{std} = \sqrt{\frac{1}{N}\sum (\Delta_n-\text{mean})^2}.

[!IMPLEMENTATION MAPPING] src/sim/simulator.tscomputeDivergenceMetrics(N=200) returns {mean, stdDev, count} in degrees.

Why you sometimes see 19 spirals

Spiral counts (parastichies) are tied to how the emergent divergence angle α\alpha is approximated by rationals.

  • If α/(2π)\alpha/(2\pi) is close to a rational p/qp/q, you get near‑alignments every qq steps, which can make a qq-family of spirals visually prominent.
  • The golden angle corresponds to the "most irrational" number (continued fraction [1;1,1,1,][1;1,1,1,\dots]), whose convergents are Fibonacci ratios — hence Fibonacci spiral pairs dominate.
  • With different kernel reach, maxR, sampling resolution, or noise, the effective α\alpha can drift toward other good rational approximations, temporarily highlighting non‑Fibonacci counts (like 19).

[!KEY IDEA] The simulator doesn't "pick Fibonacci numbers". It repeatedly minimizes F(θ)F(\theta); visible spiral counts are an emergent geometric consequence of the resulting divergence statistics.

Mapping to code

Core algorithm and data structures:

  • Simulation step: phyllotaxis-modelling/src/sim/simulator.tsstep()
  • Drift: driftAllPrimordia()
  • Active set as suffix: firstActiveIndex + advanceFirstActiveIndex()
  • Active arrays: buildActiveArrays() stores dense activeXs/activeYs for speed
  • Argmin field + refinement: findMinimumFieldAngle()
  • Noise: GaussianRNG in src/sim/prng.ts + wrapAngle()
  • Kernel compilation: src/sim/kernel.ts
  • Config & presets: src/sim/config.ts (defaultConfig(), PRESETS)
  • Validation: src/sim/validation.ts

UI hooks (for context):

  • src/ui/ui.ts wires controls, import/export, and field plot toggles.

Performance notes

This implementation is optimized for a tight inner loop:

  • Candidate angle cos/sin\cos/\sin arrays are precomputed once at init (candidateCos, candidateSin).
  • Active set is a moving suffix (older primordia drift outward monotonically).
  • Active coordinates are stored in dense Float64Arrays each step for cache-friendly iteration.
  • Hard-core kernel can early-exit with a large penalty.

FAQ

What does the kernel actually do?

The kernel K(d)K(d) sets how strongly a primordium inhibits candidate positions at distance dd. Changing the kernel shape (and its length scale like λ\lambda or σ\sigma) changes how many primordia matter at once and how sharp the minimum of F(θ)F(θ) is.

Why do we need angle sampling + refinement?

We approximate argminF(θ)\arg\min F(θ) by scanning a discrete grid of angles for speed, then recover sub-grid precision by fitting a parabola to the minimum sample and its two neighbors.

References

How these papers relate to the model

Model featureKey references
Argmin on inhibition field ("least crowded spot")Hofmeister → Douady & Couder
Kernel K(d)K(d) (local repulsion)Douady & Couder, Levitov, Adler
Golden angle / Fibonacci emergenceDouady & Couder, Adler, Levitov
Mathematical lattice theoryvan Iterson
L-systems / computational approachPrusinkiewicz & Lindenmayer

Core bibliography

  • Hofmeister, W. (1868). Allgemeine Morphologie der Gewächse. Leipzig: Engelmann. — First statement of the rule: each primordium forms in the "least crowded" spot on the meristem.

  • van Iterson, G. (1907). Mathematische und mikroskopisch-anatomische Studien über Blattstellungen. Jena: Fischer. — First purely mathematical treatment; introduced cylindrical lattice packing that underlies spiral counts.

  • Adler, I. (1974). A model of contact pressure in phyllotaxis. Journal of Theoretical Biology, 45, 1–79. — Shows that contact pressure leads to Fibonacci progressions and convergence to the golden angle.

  • Levitov, L. S. (1991). Energetic approach to phyllotaxis. Europhysics Letters, 14(6), 533–539. — Variational/energy-minimization derivation of why noble numbers (including golden ratio) are selected.

  • Douady, S. & Couder, Y. (1992). Phyllotaxis as a physical self-organized growth process. Physical Review Letters, 68(13), 2098–2101. — Landmark experiment: ferrofluid droplets on a ring spontaneously produce Fibonacci spirals via local inhibition.

  • Douady, S. & Couder, Y. (1996). Phyllotaxis as a dynamical self-organizing process (Parts I–III). Journal of Theoretical Biology, 178, 255–312. — Full theoretical model: iterative placement, bifurcation diagrams, and the "Hofmeister rule" formalized.

  • Prusinkiewicz, P. & Lindenmayer, A. (1990). The Algorithmic Beauty of Plants. New York: Springer-Verlag. — Computational models (L-systems) for plant morphogenesis including phyllotaxis.

Further reading (biological mechanisms)

  • Reinhardt, D. et al. (2003). Regulation of phyllotaxis by polar auxin transport. Nature, 426, 255–260. — Molecular evidence that auxin redistribution implements effective repulsion between primordia.

  • Kuhlemeier, C. (2007). Phyllotaxis. Trends in Plant Science, 12(4), 143–150. — Review connecting classical models to modern auxin-based understanding.