Fractal#

Fractal-flavoured harmonic geometries.

Phase 4 ships the deterministic group:

  • stern_brocot_tree() — the canonical mediant tree of all positive rationals, annotated with biotuner harmonicity scores.

  • continued_fraction_rectangles() — the “Euclid-algorithm” recursive square / rectangle subdivision of a ratio.

  • farey_sequence_layout() — the Farey sequence of order n placed on a circle or line.

  • subharmonic_tree() — recursive subharmonic expansion of an input using biotuner.metrics.compute_subharmonics.

  • ifs_harmonic() — chaos-game iterated-function-system attractor whose contractions are derived from the input ratios.

The generative group (L-systems, harmonic Julia sets, recursive polygons, Cantor rhythms, self-similar tunings) lands in Phase 5.

stern_brocot_tree(input: HarmonicInput | None = None, max_depth: int = 6, layout: str = 'hyperbolic') GeometryData[source]#

Stern-Brocot mediant tree to max_depth levels.

Starts from the canonical bounds 0/1 and 1/0; each node is the mediant of its bracketing pair. The tree at depth d has exactly 2^d - 1 interior nodes (the bounds are excluded from the output).

Each node is annotated with a harmonicity score in metadata['harmonicity'] — by default dyad_similarity(p/q). If input is provided, an additional metadata['nearest_input_dist_cents'] array records the cents distance from each tree node to the closest ratio in input, helpful for highlighting where the chord lives in the rational lattice.

Parameters:
  • input (HarmonicInput, optional) – If given, used only for the nearest-input-distance annotation.

  • max_depth (int, default=6) – Tree depth. The number of nodes grows as 2^depth - 1.

  • layout ({‘hyperbolic’, ‘tree’}, default=’hyperbolic’) – 'hyperbolic' places nodes on the Poincaré disk by traversal position and depth; 'tree' uses a flat dendrogram layout.

Returns:

GeometryDatageom_type='tree'.

continued_fraction_rectangles(ratio: Fraction | int | float | Tuple[int, int], depth: int = 10) GeometryData[source]#

Recursive Euclid-algorithm square / rectangle decomposition of a ratio.

Visualizes the continued-fraction expansion of p/q (assumed > 1; smaller values are inverted internally and the output is flagged in metadata). The starting rectangle is p × q; the largest possible squares of side min(p, q) are stripped off repeatedly, each time rotating the residual strip by 90°. The sequence of squares is the continued-fraction expansion of p/q.

Parameters:
  • ratio (Fraction, int, float, or (int, int))

  • depth (int, default=10) – Maximum number of squares to record. The full expansion terminates earlier if p/q is rational.

Returns:

GeometryDatageom_type='polygon_set' — one rectangular polygon per square, in original-rectangle units (the bounding rectangle is the unit-area rectangle [0, 1] × [0, q/p]).

farey_sequence_layout(order: int, layout: str = 'circle') GeometryData[source]#

Farey sequence F_n placed on a circle, line, or as Ford circles.

Parameters:
  • order (int) – Sequence order, >= 1.

  • layout ({‘circle’, ‘line’, ‘ford’}, default=’circle’) –

    • 'circle' / 'line' — points only; weight encodes 1 / denominator.

    • 'ford' — each fraction p/q F_n becomes a circle of radius 1 / (2 q²) tangent to the x-axis at x = p/q. Adjacent Farey fractions correspond to tangent Ford circles — the classic visual of the Farey structure.

Returns:

GeometryData – For 'circle' / 'line': geom_type='point_cloud_2d'. For 'ford': geom_type='polygon_set' — each entry is a polyline approximation of one Ford circle. metadata['radii'] and metadata['centers'] carry the analytic geometry.

subharmonic_tree(input: HarmonicInput, depth: int = 4, n_harmonics: int = 5, min_freq: float = 0.1, layout: str = 'depth') GeometryData[source]#

Recursive subharmonic expansion as a tree.

Each input peak f is the root of a sub-tree whose children are its first n_harmonics subharmonics f / 2, f / 3, ..., f / (k + 1). Each child is expanded the same way to depth levels. Nodes with frequency below min_freq are pruned.

Notes

The plan originally suggested using biotuner.metrics.compute_subharmonics, which finds common subharmonics across a chord. That’s a different operation from the per-peak subharmonic series this tree visualizes; we use the classical f / k definition directly.

Parameters:
  • input (HarmonicInput) – Source peaks for the root level.

  • depth (int, default=4) – Number of expansion levels below the root.

  • n_harmonics (int, default=5) – Number of subharmonics per node.

  • min_freq (float, default=0.1) – Frequencies below this are not expanded further.

  • layout ({‘depth’, ‘polar’}, default=’depth’) – 'depth' — original dendrogram layout (depth on Y, sorted on X). 'polar' — each input peak gets its own angular sector; depth becomes radial distance, so different chords produce visibly different fan-out shapes instead of identical depth-stacks.

Returns:

GeometryDatageom_type='tree'. metadata['root_index_per_node'] tags every node with its originating root-peak index, useful for colour-coding.

ifs_harmonic(input: HarmonicInput, n_points: int = 50000, contraction: str = 'ratio_inverse', transient: int = 200, rng: Generator | None = None) GeometryData[source]#

Iterated-function-system attractor driven by harmonic ratios.

Each input ratio defines an affine contraction z -> z · s_i + v_i, where s_i is the contraction factor (derived from the ratio per contraction) and v_i is the i-th vertex of an N-gon scaled to the unit disk. The classic chaos game then samples the attractor.

Parameters:
  • input (HarmonicInput) – Provides the N ratios.

  • n_points (int, default=50_000)

  • contraction ({‘ratio_inverse’, ‘log_ratio’, ‘fixed_half’}, default=’ratio_inverse’) –

    • 'ratio_inverse': s_i = 1 / r_i (rebound to < 1).

    • 'log_ratio': s_i = 1 / (1 + log(r_i)).

    • 'fixed_half': s_i = 0.5 for all i (Sierpinski-like).

  • transient (int, default=200) – Number of warm-up iterations to discard before recording points.

  • rng (np.random.Generator, optional) – Source of randomness. Default: np.random.default_rng().

Returns:

GeometryDatageom_type='point_cloud_2d' with (n_points, 2) coordinates.