Polygon & Circular#

Polygon and circular geometric structures.

Phase 2 implements the basic ratio-driven shapes. Phase 4 adds the biotuner-metric-driven variants: interval_vector_diagram, polygon_chord_pattern, and consonance_polygon.

star_polygon(n: int, k: int, radius: float = 1.0) GeometryData[source]#

Schläfli star polygon {n/k}.

Vertices are placed on a circle of given radius. Edges connect vertex i to vertex (i + k) mod n. When gcd(n, k) > 1 the figure decomposes into gcd(n, k) disjoint compound polygons; the return type is then polygon_set.

Parameters:
  • n (int) – Number of vertices on the circle, n >= 3.

  • k (int) – Step size, 1 <= k < n.

  • radius (float, default=1.0)

Returns:

GeometryData

  • geom_type='polygon' when gcd(n, k) == 1

  • geom_type='polygon_set' when gcd(n, k) > 1

times_table_circle(n_points: int, multiplier: float, radius: float = 1.0) GeometryData[source]#

Modular-multiplication “times-table” pattern on a circle.

n_points points are placed evenly on a circle of given radius. For each i [0, n_points), an edge is drawn from i to int(round(i * multiplier)) mod n_points. Self-loops (the i==j case) are dropped.

Parameters:
  • n_points (int, must be >= 2)

  • multiplier (float) – Modular multiplier. Integer multipliers produce the classic Mardi-Gras patterns; non-integer values produce richer textures.

  • radius (float, default=1.0)

Returns:

GeometryDatageom_type='graph'.

times_table_from_input(input: HarmonicInput, n_points: int = 360, mode: str = 'ratio', radius: float = 1.0) GeometryData[source]#

Chord-driven times-table: one edge family per harmonic ratio.

Each component of input contributes its own multiplier; all edge families share the same n_points-vertex circle and are returned in a single GeometryData so they can be drawn as overlaid colour layers.

Parameters:
  • input (HarmonicInput)

  • n_points (int, default=360) – Number of points on the circle. 360 is a convenient default because most rational chord ratios give clean modular periods.

  • mode ({‘ratio’, ‘pitch_class’, ‘integer’}, default=’ratio’) – How each ratio is converted into a multiplier:

    • 'ratio' — multiplier = ratio (float). i int(round(i * ratio)) mod n_points.

    • 'pitch_class' — multiplier = round(n_points * log2(ratio)). One “octave” wraps the circle once.

    • 'integer' — multiplier = Fraction(ratio).limit_denominator(32).numerator.

  • radius (float, default=1.0)

Returns:

GeometryDatageom_type='graph'. edges carries every edge across all ratio families; metadata['ratio_index'] is an int array aligned with edges mapping each edge to the originating ratio index (so a renderer can colour each family separately). metadata['multipliers'] lists the resolved multiplier per ratio.

tuning_circle(input: HarmonicInput, radius: float = 1.0) GeometryData[source]#

Place input components on a circle by their log-equave pitch class.

For each ratio r, the angle is · log_equave(r), wrapped into [0, 2π). Amplitudes are exposed as per-point weights.

Parameters:
  • input (HarmonicInput)

  • radius (float, default=1.0)

Returns:

GeometryDatageom_type='point_cloud_2d' with shape (n_components, 2) and weights of length n_components.

rose_curve(ratio: Fraction | int | float | Tuple[int, int], n_points: int = 2000, n_periods: int | None = None, radius: float = 1.0) GeometryData[source]#

Polar rose: r(θ) = radius · cos((p/q) · θ).

For coprime (p, q), the curve closes after: - θ [0, q · π] if p + q is even, - θ [0, 2 · q · π] if p + q is odd.

If n_periods is given, the curve is sampled over [0, n_periods · π] explicitly; otherwise the closure-aware default above is used.

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

  • n_points (int, default=2000)

  • n_periods (int, optional) – Override the auto-computed sampling range (in units of π).

  • radius (float, default=1.0)

Returns:

GeometryDatageom_type='curve_2d'.

epicycloid(ratio: Fraction | int | float | Tuple[int, int], R: float = 1.0, n_points: int = 2000) GeometryData[source]#

Epicycloid traced by a point on a small circle rolling outside a large one.

With ratio = R / r = p / q (coprime), the curve has p cusps and closes after the small circle completes q revolutions.

Parameters:
  • ratio (Fraction, int, float, or (int, int)) – Ratio of fixed circle radius to rolling circle radius, R / r.

  • R (float, default=1.0) – Radius of the fixed (large) circle.

  • n_points (int, default=2000)

Returns:

GeometryDatageom_type='curve_2d'.

hypocycloid(ratio: Fraction | int | float | Tuple[int, int], R: float = 1.0, n_points: int = 2000) GeometryData[source]#

Hypocycloid traced by a point on a small circle rolling inside a large one.

With ratio = R / r = p / q (coprime, p > q), the curve has p - q cusps. For p = q the trace is a degenerate point.

Parameters:
  • ratio (Fraction, int, float, or (int, int)) – R / r. Must satisfy R > r > 0 for a non-degenerate curve; i.e., the coprime form must have p > q.

  • R (float, default=1.0)

  • n_points (int, default=2000)

Returns:

GeometryDatageom_type='curve_2d'.

interval_vector_diagram(input: HarmonicInput, radius: float = 1.0, bin_cents: float = 50.0) GeometryData[source]#

Graph of pairwise intervals binned into cents-classes.

Nodes are placed on the tuning circle. For each pair (i, j), the interval in cents is computed (modulo equave). Intervals are bucketed in bin_cents-wide classes; an edge’s weight is the interval-class count — how many other pairs share that bucket. Edges thus highlight the chord’s interval-vector multiplicities.

Parameters:
  • input (HarmonicInput)

  • radius (float, default=1.0)

  • bin_cents (float, default=50.0) – Bucket width for grouping intervals into classes.

Returns:

GeometryDatageom_type='graph' with edges of shape (E, 2) and per-edge weights.

polygon_chord_pattern(input: HarmonicInput, metric: str | Callable[[float], float] = 'dyad_similarity', threshold: float | None = None, radius: float = 1.0) GeometryData[source]#

Chord-pattern graph weighted by a biotuner harmonicity metric.

Nodes are placed on the tuning circle. Every pair of distinct components has an edge weighted by metric(ratio_j / ratio_i) (after rebounding the quotient into [1, ∞)). Optionally threshold to keep only the strongest pairs — yields the polygonal “chord skeleton” of the most consonant relationships.

Parameters:
  • input (HarmonicInput)

  • metric (str or callable, default=’dyad_similarity’) – Either a single-ratio biotuner metric name (one of 'dyad_similarity', 'compute_consonance', 'tenneyHeight', 'log_distance') or a callable ratio -> score. tenneyHeight is sign-flipped so higher always means “more consonant” downstream.

  • threshold (float, optional) – Drop edges with weight below threshold. None keeps all.

  • radius (float, default=1.0)

Returns:

GeometryDatageom_type='graph'.

consonance_polygon(input: HarmonicInput, metric: str | Callable[[float], float] = 'dyad_similarity', radius: float = 1.0) GeometryData[source]#

Convex polygon whose vertex angles encode each ratio’s consonance share.

Each component’s “consonance share” is the sum of pairwise metric scores against all other components. Vertices are then placed at cumulative-angle positions: the polygon’s angular density spikes around the most-connected ratios and thins around outliers.

Parameters:
  • input (HarmonicInput)

  • metric (str or callable, default=’dyad_similarity’)

  • radius (float, default=1.0)

Returns:

GeometryDatageom_type='polygon' with weights carrying the per-vertex consonance share. The first vertex is placed at angle 0.