modules

geometor.phyllotaxis

Symbolic modeling, 2D planar analysis, and 3D recursive meristem path propagation.

Everything centers around Phyllotaxis, MeristemNode, and MeristemConfig.

Submodules

Attributes

__version__

GOLDEN_ANGLE_DEG

GOLDEN_ANGLE_RAD

GOLDEN_SWEEP_LONG_DEG

GOLDEN_SWEEP_LONG_RAD

GOLDEN_SWEEP_SHORT_DEG

GOLDEN_SWEEP_SHORT_RAD

PHI

Classes

AnglesMixin

Mixin for Phyllotaxis class containing divergence angle functionality.

ModelBuilderMixin

Mixin for Phyllotaxis class containing geometor.model.Model construction helpers.

MeristemConfig

Configuration for 3D Node-as-Path and recursive meristem propagation.

MeristemNode

A 3D phyllotaxic node that grows as a path from its origin and may act as a meristem.

Propagation3DMixin

Mixin for Phyllotaxis class containing 3D path and meristem propagation methods.

Phyllotaxis

Main class for symbolic and parametric 2D/3D phyllotaxis modeling.

PlanarMixin

Mixin for Phyllotaxis class containing 2D planar growth and parastichy methods.

Functions

fibonacci(n) → int

Return the $n$-th Fibonacci number ($F_0 = 0, F_1 = 1$).

fibonacci_convergents([count]) → list[tuple[int, int, float]]

Return consecutive Fibonacci rational approximations $(F_k, F_{k+2}, text{degrees})$.

golden_sweep_presets() → list[dict[str, str | float]]

Return a catalog of short/long golden sweeps, $phi^{-k}$ powers, and rational sweep factors.

sweep_lucas() → sympy.Expr

Return the exact symbolic Lucas divergence angle $2pi / (3 + 1/phi) approx 99.5^circ$.

sweep_phi() → sympy.Expr

Return the exact symbolic short golden sweep $2pi(1 - 1/phi) = 2pi / phi^2$.

sweep_phi_factor([numerator, denominator, long_sweep]) → sympy.Expr

Return a rational factor $(text{numerator}/text{denominator})$ of the short or long golden sweep.

sweep_phi_long() → sympy.Expr

Return the exact symbolic long golden sweep $2pi / phi approx 222.492236^circ$.

sweep_phi_power([k]) → sympy.Expr

Return the $k$-th golden section sweep $2pi / phi^k$ ($k=1$: Long, $k=2$: Short).

sweep_phi_short() → sympy.Expr

Return the exact symbolic short golden sweep $2pi / phi^2 approx 137.507764^circ$.

sweep_rational(n, m) → sympy.Expr

Return a rational divergence angle $2pi(1 - n/m)$ from two integers.

build_rings_model([num_rings, sweep, name]) → geometor.model.Model

Construct a geometor.model.Model containing phyllotaxic node points and dot circles.

compute_node_path(origin, parent_tangent, radial_dir, ...) → tuple[list[Vec3], Vec3, Vec3, Vec3]

Evaluate a node's 3D trajectory $gamma(s)$ for $s in [0, text{age}]$ from origin.

propagate_3d(steps[, config]) → list[MeristemNode]

Propagate a 3D phyllotaxic meristem system for steps plastochrons.

analyze_parastichy_curves(nodes[, num_nearest]) → dict[str, object]

Count parastichy curves in each closest-neighbor rank and radial zone until they bend.

classify_meristem_phase(node_id, total_nodes[, leaf_span]) → str

Classify a node's ontogenetic organ type in the meristem's production life cycle.

find_nearest_points(test_points[, num_nearest]) → dict[sympy.geometry.Point2D, list[sympy.geometry.Point2D]]

For each point in test_points, find the num_nearest closest subsequent points.

find_parastichies(num_nodes[, families]) → dict[int, list[list[int]]]

Group node indices into parastichy spiral chains for each modulo family $m in text{families}$.

find_point_distances(pt, other_pts) → dict[float, sympy.geometry.Point2D]

Compute Euclidean distances from pt to each point in other_pts.

get_node_points(num_nodes[, sweep]) → list[sympy.geometry.Point2D]

Generate a single planar growth frame with num_nodes symbolic points.

grow(steps[, sweep]) → list[list[sympy.geometry.Point2D]]

Grow planar frames from $0$ up to steps - 1 nodes.

vogel_points(num_nodes[, scale, angle_rad, base_size, ...]) → list[dict[str, float | int | str]]

Compute 2D apical meristem coordinates with radial acceleration, node growth, and life-cycle phases.

Package Contents

geometor.phyllotaxis.__version__ = '0.2.2'
class geometor.phyllotaxis.AnglesMixin

Mixin for Phyllotaxis class containing divergence angle functionality.

get_sweep_phi() → sympy.Expr
get_sweep_phi_short() → sympy.Expr
get_sweep_phi_long() → sympy.Expr
get_sweep_phi_power(k: int = 2) → sympy.Expr
get_sweep_phi_factor(numerator: int = 1, denominator: int = 1, *, long_sweep: bool = False) → sympy.Expr
get_golden_sweep_presets() → list[dict[str, str | float]]
get_sweep_rational(n: int, m: int) → sympy.Expr
get_sweep_lucas() → sympy.Expr
get_fibonacci_convergents(count: int = 8) → list[tuple[int, int, float]]
geometor.phyllotaxis.GOLDEN_ANGLE_DEG: float
geometor.phyllotaxis.GOLDEN_ANGLE_RAD: float
geometor.phyllotaxis.GOLDEN_SWEEP_LONG_DEG: float
geometor.phyllotaxis.GOLDEN_SWEEP_LONG_RAD: float
geometor.phyllotaxis.GOLDEN_SWEEP_SHORT_DEG: float
geometor.phyllotaxis.GOLDEN_SWEEP_SHORT_RAD: float
geometor.phyllotaxis.PHI: sympy.Expr
geometor.phyllotaxis.fibonacci(n: int) → int

Return the $n$-th Fibonacci number ($F_0 = 0, F_1 = 1$).

geometor.phyllotaxis.fibonacci_convergents(count: int = 8) → list[tuple[int, int, float]]

Return consecutive Fibonacci rational approximations $(F_k, F_{k+2}, text{degrees})$.

Parameters:

count – Number of convergent pairs to generate starting from $(1, 2)$.

Returns:

List of tuples (numerator, denominator, angle_degrees).

geometor.phyllotaxis.golden_sweep_presets() → list[dict[str, str | float]]

Return a catalog of short/long golden sweeps, $phi^{-k}$ powers, and rational sweep factors.

geometor.phyllotaxis.sweep_lucas() → sympy.Expr

Return the exact symbolic Lucas divergence angle $2pi / (3 + 1/phi) approx 99.5^circ$.

geometor.phyllotaxis.sweep_phi() → sympy.Expr

Return the exact symbolic short golden sweep $2pi(1 - 1/phi) = 2pi / phi^2$.

geometor.phyllotaxis.sweep_phi_factor(numerator: int = 1, denominator: int = 1, *, long_sweep: bool = False) → sympy.Expr

Return a rational factor $(text{numerator}/text{denominator})$ of the short or long golden sweep.

geometor.phyllotaxis.sweep_phi_long() → sympy.Expr

Return the exact symbolic long golden sweep $2pi / phi approx 222.492236^circ$.

geometor.phyllotaxis.sweep_phi_power(k: int = 2) → sympy.Expr

Return the $k$-th golden section sweep $2pi / phi^k$ ($k=1$: Long, $k=2$: Short).

geometor.phyllotaxis.sweep_phi_short() → sympy.Expr

Return the exact symbolic short golden sweep $2pi / phi^2 approx 137.507764^circ$.

geometor.phyllotaxis.sweep_rational(n: int, m: int) → sympy.Expr

Return a rational divergence angle $2pi(1 - n/m)$ from two integers.

class geometor.phyllotaxis.ModelBuilderMixin

Mixin for Phyllotaxis class containing geometor.model.Model construction helpers.

build_rings_model(num_rings: int = 8, sweep: sympy.Expr | float | None = None, name: str = 'phyllotaxis_rings') → geometor.model.Model
geometor.phyllotaxis.build_rings_model(num_rings: int = 8, sweep: sympy.Expr | float | None = None, name: str = 'phyllotaxis_rings') → geometor.model.Model

Construct a geometor.model.Model containing phyllotaxic node points and dot circles.

Parameters:
  • num_rings – Number of primordia rings to construct.

  • sweep – Symbolic or numeric divergence angle (defaults to sweep_phi()).

  • name – Name of the created Model.

Returns:

Populated geometor.model.Model instance.

class geometor.phyllotaxis.MeristemConfig

Configuration for 3D Node-as-Path and recursive meristem propagation.

divergence_angle_deg: float
branch_pitch_deg: float = 54.0
stem_step: float = 0.35
fast_growth_steps: int = 50
slow_growth_factor: float = 1.0
radial_scale: float = 1.6
radial_exponent: float = 0.55
path_arch: float = 0.16
path_twist_deg: float = 0.0
path_segments: int = 12
base_size: float = 0.18
size_growth: float = 0.045
max_depth: int = 2
maturation_age: int = 6
spawn_cadence: int = 3
meristem_stride: int = 2
child_scale_decay: float = 0.6180339887
class geometor.phyllotaxis.MeristemNode

A 3D phyllotaxic node that grows as a path from its origin and may act as a meristem.

id: int
generation: int
parent_id: int | None
index_on_parent: int
birth_step: int
age: int
is_meristem: bool
origin: Vec3
tangent: Vec3
normal: Vec3
binormal: Vec3
path: list[Vec3] = []
tip: Vec3 = (0.0, 0.0, 0.0)
tip_tangent: Vec3 = (0.0, 0.0, 1.0)
tip_normal: Vec3 = (1.0, 0.0, 0.0)
tip_binormal: Vec3 = (0.0, 1.0, 0.0)
tip_size: float = 0.2
children: list[int] = []
to_dict() → dict[str, Any]

Serialize the node, its 3D path from origin to tip, and child meristem IDs.

class geometor.phyllotaxis.Propagation3DMixin

Mixin for Phyllotaxis class containing 3D path and meristem propagation methods.

propagate_3d(steps: int, config: MeristemConfig | None = None) → list[MeristemNode]
geometor.phyllotaxis.compute_node_path(origin: Vec3, parent_tangent: Vec3, radial_dir: Vec3, binormal_dir: Vec3, age: int, generation: int, config: MeristemConfig) → tuple[list[Vec3], Vec3, Vec3, Vec3]

Evaluate a node’s 3D trajectory $gamma(s)$ for $s in [0, text{age}]$ from origin.

At $s = 0$, $gamma(0) = text{origin}$. As $s to text{age}$, the path advances outward along radial_dir and lifts/curves along parent_tangent with optional helical twist around binormal_dir.

Parameters:
  • origin – 3D point on the parent meristem where this node originated.

  • parent_tangent – Unit tangent of the parent meristem at origin.

  • radial_dir – Unit radial outward vector at the node’s divergence angle.

  • binormal_dir – Unit binormal vector perpendicular to parent_tangent and radial_dir.

  • age – Plastochron age ($t - t_{text{birth}} ge 0$) of the node.

  • generation – Branching depth ($1$ = primary lateral node, $2$ = secondary, etc.).

  • config – Propagation parameters.

Returns:

Tuple (path_points, tip_tangent, tip_normal, tip_binormal).

geometor.phyllotaxis.propagate_3d(steps: int, config: MeristemConfig | None = None) → list[MeristemNode]

Propagate a 3D phyllotaxic meristem system for steps plastochrons.

Node 0 is the primary apical meristem growing along $+Z$. At each step $t$, active meristems grow along their paths and emit new lateral child nodes at the divergence angle $Phi$ around their local tangent frame.

Parameters:
  • steps – Number of plastochron steps to simulate.

  • config – Optional MeristemConfig instance.

Returns:

List of MeristemNode instances (including the root stem and all lateral/recursive paths).

class geometor.phyllotaxis.Phyllotaxis(name: str = 'phyllotaxis')

Bases: geometor.phyllotaxis.angles.AnglesMixin, geometor.phyllotaxis.planar.PlanarMixin, geometor.phyllotaxis.paths3d.Propagation3DMixin, geometor.phyllotaxis.builder.ModelBuilderMixin

Main class for symbolic and parametric 2D/3D phyllotaxis modeling.

name = 'phyllotaxis'
class geometor.phyllotaxis.PlanarMixin

Mixin for Phyllotaxis class containing 2D planar growth and parastichy methods.

get_node_points(num_nodes: int, sweep: sympy.Expr | float | None = None) → list[sympy.geometry.Point2D]
grow(steps: int, sweep: sympy.Expr | float | None = None) → list[list[sympy.geometry.Point2D]]
classify_meristem_phase(node_id: int, total_nodes: int, leaf_span: int = 34) → str
vogel_points(num_nodes: int, scale: float = 5.0, angle_rad: float = GOLDEN_ANGLE_RAD, base_size: float = 1.5, growth_factor: float = 0.25, radial_acceleration: float = 0.0, leaf_span: int = 34) → list[dict[str, float | int | str]]
find_point_distances(pt: sympy.geometry.Point2D, other_pts: list[sympy.geometry.Point2D]) → dict[float, sympy.geometry.Point2D]
find_nearest_points(test_points: list[sympy.geometry.Point2D], num_nearest: int = 3) → dict[sympy.geometry.Point2D, list[sympy.geometry.Point2D]]
find_parastichies(num_nodes: int, families: tuple[int, ...] = (5, 8, 13, 21)) → dict[int, list[list[int]]]
analyze_parastichy_curves(nodes: list[dict[str, float | int | str]], num_nearest: int = 3) → dict[str, object]
geometor.phyllotaxis.analyze_parastichy_curves(nodes: list[dict[str, float | int | str]], num_nearest: int = 3) → dict[str, object]

Count parastichy curves in each closest-neighbor rank and radial zone until they bend.

Demonstrates that the two primary closest-neighbor curve families wind in opposite directions (CW vs CCW) and always form a consecutive pair of Fibonacci numbers $(F_m, F_{m+1})$ within each radial zone between curve bends.

Parameters:
  • nodes – List of node dictionaries from vogel_points().

  • num_nearest – Number of closest inner nodes connected per node ($1 le k le 4$).

Returns:

  • outer_counts: Curve count $Delta$, winding direction ("CW" or "CCW"), and Fibonacci status for each rank at the outer rim.

  • opposing_pair: Tuple (cw_count, ccw_count) of the two primary opposing spiral families in the outer zone.

  • is_fibonacci_pair: True if opposing_pair consists of consecutive Fibonacci numbers with opposite chirality.

  • zones: Radial zones from inner to outer separated by bend radii.

Return type:

Dictionary containing

geometor.phyllotaxis.classify_meristem_phase(node_id: int, total_nodes: int, leaf_span: int = 34) → str

Classify a node’s ontogenetic organ type in the meristem’s production life cycle.

In a composite capitulum (such as a sunflower), the apical meristem transitions through distinct organ-production phases as the sequence progresses: 1. “leaf”: Early vegetative/involucral bracts (0 <= node_id < leaf_span). 2. “petal”: Ray florets / corolla petals (leaf_span <= node_id < leaf_span + round(leaf_span * φ)). 3. “floret”: Mature tubular disc florets in the capitulum interior. 4. “bud”: Newly initiated unopened primordia at the apical center (age < round(leaf_span / φ)).

geometor.phyllotaxis.find_nearest_points(test_points: list[sympy.geometry.Point2D], num_nearest: int = 3) → dict[sympy.geometry.Point2D, list[sympy.geometry.Point2D]]

For each point in test_points, find the num_nearest closest subsequent points.

geometor.phyllotaxis.find_parastichies(num_nodes: int, families: tuple[int, ...] = (5, 8, 13, 21)) → dict[int, list[list[int]]]

Group node indices into parastichy spiral chains for each modulo family $m in text{families}$.

Parameters:
  • num_nodes – Total number of nodes.

  • families – Parastichy numbers (typically consecutive Fibonacci numbers).

Returns:

Mapping from family number $m$ to a list of $m$ index chains [r, r+m, r+2m, …].

geometor.phyllotaxis.find_point_distances(pt: sympy.geometry.Point2D, other_pts: list[sympy.geometry.Point2D]) → dict[float, sympy.geometry.Point2D]

Compute Euclidean distances from pt to each point in other_pts.

geometor.phyllotaxis.get_node_points(num_nodes: int, sweep: sympy.Expr | float | None = None) → list[sympy.geometry.Point2D]

Generate a single planar growth frame with num_nodes symbolic points.

Each node $i in [0, N-1]$ has radius $N - i$ and angle $i cdot text{sweep}$.

Parameters:
  • num_nodes – Number of primordia in the frame.

  • sweep – Divergence angle in radians (symbolic or float). Defaults to sweep_phi().

Returns:

List of SymPy Point2D instances.

geometor.phyllotaxis.grow(steps: int, sweep: sympy.Expr | float | None = None) → list[list[sympy.geometry.Point2D]]

Grow planar frames from $0$ up to steps - 1 nodes.

geometor.phyllotaxis.vogel_points(num_nodes: int, scale: float = 5.0, angle_rad: float = GOLDEN_ANGLE_RAD, base_size: float = 1.5, growth_factor: float = 0.25, radial_acceleration: float = 0.0, leaf_span: int = 34) → list[dict[str, float | int | str]]

Compute 2D apical meristem coordinates with radial acceleration, node growth, and life-cycle phases.

New primordia emerge at the center ($k_{text{eff}} = (N - 1) - i$) while older primordia are displaced radially as $r = csqrt{k_{text{eff}}}left(frac{k_{text{eff}} + 1}{K_0}right)^{a}$, and node size grows as $s = s_0 + gsqrt{k_{text{eff}}}left(frac{k_{text{eff}} + 1}{K_0}right)^{1.15a}$.

Parameters:
  • num_nodes – Total number of primordia.

  • scale – Radial spread constant $c$.

  • angle_rad – Divergence angle in radians.

  • base_size – Initial radius of a newly emerged primordium ($s_0$).

  • growth_factor – Rate $g$ at which primordium size scales as it moves outward.

  • radial_acceleration – Acceleration exponent $a ge 0$ controlling how much faster older outer nodes move and expand compared to young inner primordia.

  • leaf_span – Characteristic Fibonacci cohort span for meristem life-cycle phases.

Returns:

List of dictionaries with keys id, age, phase, x, y, r, theta, size.