Fractal layouts and generative L-systems#

Phases 4–5 of harmonic_geometry expose deterministic fractal generators that turn a chord into a self-similar layout: Stern-Brocot tree, continued fractions, Farey sequences, iterated-function systems, L-systems, and recursive polygons. Every generator returns a GeometryData that the plotting submodule renders without further arguments.

import warnings
from fractions import Fraction

import numpy as np
import matplotlib.pyplot as plt

from biotuner.harmonic_geometry import HarmonicInput, plotting

warnings.filterwarnings("ignore")
plt.rcParams["figure.dpi"] = 110
from biotuner.harmonic_geometry import (
    HarmonicInput, plotting,
    stern_brocot_tree, continued_fraction_rectangles, farey_sequence_layout,
    subharmonic_tree, ifs_harmonic,
    lsystem_from_ratios, recursive_polygon, self_similar_tuning,
)

CHORDS = {
    "Major": HarmonicInput(ratios=[Fraction(1), Fraction(5, 4), Fraction(3, 2)]),
    "Sus4":  HarmonicInput(ratios=[Fraction(1), Fraction(4, 3), Fraction(3, 2)]),
    "Dom7":  HarmonicInput(ratios=[Fraction(1), Fraction(5, 4), Fraction(3, 2),
                                    Fraction(7, 4)]),
    "Dim7":  HarmonicInput(ratios=[Fraction(1), Fraction(6, 5),
                                    Fraction(7, 5), Fraction(12, 7)]),
}

Stern-Brocot tree — chord-driven layouts#

stern_brocot_tree builds the binary tree of mediants up to a given depth and lays it out either as a regular dyadic tree (layout="tree") or in the Poincaré disk (layout="hyperbolic").

geoms  = [stern_brocot_tree(CHORDS["Major"], max_depth=6, layout=lay)
          for lay in ("tree", "hyperbolic")]
plotting.gallery(geoms, titles=["tree (dyadic)", "hyperbolic (Poincaré disk)"],
                 n_cols=2,
                 suptitle="stern_brocot_tree — two layouts (Major)");
../../_images/6e27d38f8cedef058597f0ca1abe35bd0cf0a622afb7dcfb4f9a5e2fbf2c98fb.png

Continued-fraction rectangles#

ratios = [Fraction(22, 7), Fraction(355, 113), Fraction(7, 5)]
geoms = [continued_fraction_rectangles(r) for r in ratios]
plotting.gallery(geoms, titles=[str(r) for r in ratios], n_cols=3,
                 suptitle="continued_fraction_rectangles");
../../_images/809e8d959edfeef12a3cd258601cca891c5b148ef330f61775a6a1ce2fa6d95d.png

Farey sequence layouts#

orders = [5, 8, 12]
geoms_circle = [farey_sequence_layout(o, layout="circle") for o in orders]
geoms_line   = [farey_sequence_layout(o, layout="line")   for o in orders]
plotting.gallery(geoms_circle + geoms_line,
                 titles=[f"circle, order {o}" for o in orders]
                       + [f"line, order {o}"   for o in orders],
                 n_cols=3,
                 suptitle="farey_sequence_layout — circle vs line");
../../_images/da539ae1254014a7d9996a7a1844678f4bff6f8dd1cfd4f67e375c084cdad6b0.png

Subharmonic tree — chord-driven polar layout#

geoms  = [subharmonic_tree(CHORDS[n], depth=4, n_harmonics=4, layout="polar")
          for n in ("Major", "Sus4", "Dom7", "Dim7")]
plotting.gallery(geoms, titles=list(CHORDS.keys()), n_cols=4,
                 suptitle="subharmonic_tree (polar)");
../../_images/c090cdd211a024d978363fa5aa450fbe2211cf856dd140dbc05758e89ed4a7af.png

Iterated-function system from a chord#

Each ratio defines a contractive affine map; their non-empty intersection is the IFS attractor. Sampled via the chaos game.

rng = np.random.default_rng(0)
geoms = [ifs_harmonic(CHORDS[n], n_points=30_000,
                       contraction="ratio_inverse", rng=rng)
         for n in ("Major", "Sus4", "Dom7", "Dim7")]
plotting.gallery(geoms, titles=list(CHORDS.keys()), n_cols=4,
                 suptitle="ifs_harmonic — chord attractors");
../../_images/905ac7d34648e24f4650fc3c4c70eb96949ff1bc4d22a2b7b5493ebffd890748.png

L-system curves and recursive polygons#

geoms = [lsystem_from_ratios(CHORDS[n], depth=4) for n in CHORDS]
plotting.gallery(geoms, titles=list(CHORDS.keys()), n_cols=4,
                 suptitle="lsystem_from_ratios (depth 4)");
../../_images/3f3a3b8c0d815171bab81da5db68a2137a3c35204b13a639364fb177cc1053af.png
geoms = [recursive_polygon(CHORDS[n], depth=3) for n in CHORDS]
plotting.gallery(geoms, titles=list(CHORDS.keys()), n_cols=4,
                 suptitle="recursive_polygon (depth 3)");
../../_images/f99d656025ac19a19b921b08dcf93f8481b0784ac13a7728c0273680299f0a3d.png

Self-similar tuning (nested ratio scaffolds)#

geoms = [self_similar_tuning(CHORDS[n], n_levels=4) for n in CHORDS]
plotting.gallery(geoms, titles=list(CHORDS.keys()), n_cols=4,
                 suptitle="self_similar_tuning (4 levels)");
../../_images/25686411eb4327a432d1714ce56d14fea20d26bf3b7c962e988fcfc4798e5a5b.png