Math Series#

Match a biosignal’s peak ratios against classic mathematical sequences (Fibonacci, Lucas, harmonics, Farey, …) to find which is most present, and derive scales from the best match. Walkthrough: Matching biosignals to mathematical series.

biotuner.math_series — match biosignal ratios to mathematical sequences.

Module type: Object

Generate ratio sets from classic integer sequences (Fibonacci, Lucas, Padovan, Pell, Jacobsthal, Mersenne, Hofstadter-Q, harmonics, subharmonics, triangular, Farey), score how present each series is in a biosignal’s peak ratios, and derive musical structures (scales and consonance-selected modes) from the matched subset of a series.

The entry point is the math_series pipeline, which accepts either a fitted compute_biotuner instance or a HarmonicInput descriptor, normalises it to a single list of octave-folded peak ratios, and compares those against each series. Matching conservatism is controlled by maxdenom (the maximum denominator of the rational approximation used to decide whether two ratios are “the same”): a low maxdenom collapses nearby ratios onto simple fractions (lenient matching), a high maxdenom keeps fine distinctions (strict matching).

fibonacci(order: int) List[int][source]#

Generate the first order Fibonacci numbers.

Examples

>>> fibonacci(8)
[0, 1, 1, 2, 3, 5, 8, 13]
lucas(order: int, seed: Sequence[int] = (2, 1)) List[int][source]#

Generate the first order terms of a Lucas-type sequence.

Parameters:
  • order (int) – Number of terms to return.

  • seed (sequence of int, default=(2, 1)) – The two starting values. (2, 1) is the classic Lucas sequence.

Examples

>>> lucas(7)
[2, 1, 3, 4, 7, 11, 18]
padovan(order: int) List[int][source]#

Generate the first order Padovan numbers.

Examples

>>> padovan(7)
[1, 1, 1, 2, 2, 3, 4]
pell(order: int) List[int][source]#

Generate the first order Pell numbers.

Examples

>>> pell(6)
[0, 1, 2, 5, 12, 29]
jacobsthal(order: int) List[int][source]#

Generate the first order Jacobsthal numbers.

Examples

>>> jacobsthal(6)
[0, 1, 1, 3, 5, 11]
mersenne(order: int) List[int][source]#

Generate the first order Mersenne numbers 2**i - 1.

Examples

>>> mersenne(5)
[0, 1, 3, 7, 15]
hofstadter_q(order: int) List[int][source]#

Generate the first order terms of the Hofstadter Q sequence.

Examples

>>> hofstadter_q(8)
[1, 1, 2, 3, 3, 4, 5, 5]
harmonics(order: int) List[int][source]#

Generate the first order terms of the harmonic series.

Examples

>>> harmonics(5)
[1, 2, 3, 4, 5]
subharmonics(order: int) List[float][source]#

Generate the first order terms of the subharmonic series.

Examples

>>> subharmonics(4)
[1.0, 0.5, 0.3333333333333333, 0.25]
triangular(order: int) List[int][source]#

Generate the first order triangular numbers.

Examples

>>> triangular(5)
[1, 3, 6, 10, 15]
farey(order: int) List[Tuple[int, int]][source]#

Generate the Farey sequence of a given order as (num, den) pairs.

Unlike the integer sequences, each Farey term is already a fraction in [0, 1]; the returned (num, den) pairs are treated directly as ratios (num / den) by series_ratio_pairs().

Examples

>>> farey(4)
[(0, 1), (1, 4), (1, 3), (1, 2), (2, 3), (3, 4), (1, 1)]
SERIES_FUNCS = {'farey': <function farey>, 'fibonacci': <function fibonacci>, 'harmonics': <function harmonics>, 'hofstadter_q': <function hofstadter_q>, 'jacobsthal': <function jacobsthal>, 'lucas': <function lucas>, 'mersenne': <function mersenne>, 'padovan': <function padovan>, 'pell': <function pell>, 'subharmonics': <function subharmonics>, 'triangular': <function triangular>}#

Mapping of series name -> generator. Add a row here to register a series.

series_ratio_pairs(name: str, order: int, octave: float = 2.0, which: str = 'both', lucas_seed: Sequence[int] = (2, 1)) List[Tuple[float, Tuple[float, float]]][source]#

Build the octave-folded ratio set of a series, keeping pair provenance.

Parameters:
  • name (str) – A key of SERIES_FUNCS.

  • order (int) – Number of terms (or, for "farey", the Farey order).

  • octave (float, default=2.0) – Period the ratios are folded into ([1, octave)).

  • which ({‘both’, ‘high/low’, ‘low/high’}, default=’both’) – For integer sequences, whether to keep ratios where the first element is larger (high/low), smaller (low/high), or both. Ignored for "farey" (whose terms are already fractions).

  • lucas_seed (sequence of int, default=(2, 1)) – Starting pair forwarded to lucas() when name == "lucas".

Returns:

list of (float, (float, float)) – Each entry is (folded_ratio, (element_1, element_2)). The pair is kept unfolded so it can be plotted on the element/element scatter.

class math_series(source: Any | None = None, ratios: Sequence[float] | None = None, *, ratios_source: str = 'peaks_ratios', series_names: Sequence[str] | None = None, order: int = 20, maxdenom: int = 24, octave: float = 2.0, which: str = 'both', lucas_seed: Sequence[int] = (2, 1))[source]#

Bases: object

Identify which mathematical series best matches a biosignal’s ratios.

Accepts a fitted compute_biotuner instance or a HarmonicInput, extracts a list of octave-folded peak ratios, and scores each candidate series by the proportion of biosignal ratios it reproduces (under a max-denominator rational match). The matched subset of the best series can then be turned into a scale (series_scale()) or a consonance-selected mode (series_mode()).

Parameters:
  • source (compute_biotuner or HarmonicInput, optional) – The biosignal descriptor. Mutually exclusive with ratios.

  • ratios (list of float, optional) – Peak ratios supplied directly (mainly for testing). Used when source is None.

  • ratios_source (str, default=’peaks_ratios’) – Which scale to read off source. For a compute_biotuner this is an attribute name (e.g. "peaks_ratios" or "extended_peaks_ratios"). For a HarmonicInput it is looked up in ratios_alternates, falling back to the canonical ratios.

  • series_names (sequence of str, optional) – Series to compare. Defaults to DEFAULT_SERIES.

  • order (int, default=20) – Number of terms generated per series (Farey order for "farey").

  • maxdenom (int, default=24) – Maximum denominator of the rational match. Lower = more lenient (nearby ratios collapse onto simple fractions), higher = stricter. Useful discrimination typically lives in roughly 1648: below that the small fraction grid saturates (every series matches), above it full-precision peak ratios rarely snap onto any simple fraction.

  • octave (float, default=2.0) – Period the ratios are folded into.

  • which ({‘both’, ‘high/low’, ‘low/high’}, default=’both’) – Pair direction passed to series_ratio_pairs().

  • lucas_seed (sequence of int, default=(2, 1)) – Starting pair for the Lucas series.

ratios#

The extracted biosignal peak ratios (octave-folded, in [1, octave)).

Type:

list of float

series_scores#

Per-series result dict (populated by analyze()). Keys per series: proportion, proportion_normalized, n_matched, n_target, n_series_ratios, matched_keys, matched_series_pairs, matched_target_ratios.

Type:

dict

best_series#

Name of the series with the highest raw proportion.

Type:

str or None

Examples

>>> ms = math_series(ratios=[1.5, 1.6, 1.25, 1.333], series_names=["fibonacci", "harmonics"], maxdenom=16)
>>> ms.analyze().best_series in {"fibonacci", "harmonics"}
True
analyze() math_series[source]#

Score every candidate series against the biosignal ratios.

Populates series_scores and best_series. Returns self so calls can be chained (math_series(bt).analyze()).

summary() DataFrame[source]#

Return a per-series score table sorted by raw proportion (descending).

series_scale(name: str | None = None, include_unison: bool = True) List[float][source]#

Return the matched subset of a series as a sorted scale in [1, octave).

Parameters:
  • name (str, optional) – Series to use. Defaults to best_series.

  • include_unison (bool, default=True) – Prepend 1.0 (the unison) if absent.

Returns:

list of float – Sorted, de-duplicated ratios of the matched series subset.

series_mode(name: str | None = None, n_steps: int = 7, method: str = 'subset', function: ~typing.Any = <function compute_consonance>) List[float][source]#

Reduce a series scale to an n_steps consonance-selected mode.

Wraps the house mode-selection helpers create_mode() (method="subset") and tuning_reduction() (method="pairwise").

Parameters:
  • name (str, optional) – Series to use. Defaults to best_series.

  • n_steps (int, default=7) – Number of steps in the reduced mode (capped at the scale size).

  • method ({‘subset’, ‘pairwise’}, default=’subset’) – Selection strategy. "subset" searches all step combinations (best for small scales); "pairwise" greedily adds the most consonant pairs.

  • function (callable, default=compute_consonance) – Consonance metric. One of compute_consonance(), dyad_similarity(), metric_denom() (or pass the string name).

Returns:

list of float – The mode, sorted ascending.

scale_cents(name: str | None = None) List[float][source]#

Return series_scale() expressed in cents.

plot_proportions(normalized: bool = True, ax: Axes | None = None, plot: bool = True, save: bool = False, savename: str = 'series_proportions') Figure[source]#

Bar chart of the match proportion per series.

Parameters:
  • normalized (bool, default=True) – Plot proportions normalised to sum to 1 (across the compared series) rather than raw proportions.

  • ax (matplotlib.axes.Axes, optional) – Draw onto an existing axis (for composing multi-panel figures). A new figure is created when omitted.

  • plot (bool, default=True) – Call plt.show() (ignored when ax is supplied).

  • save (bool, default=False) – Save the figure to <savename>.png.

  • savename (str, default=’series_proportions’) – File stem used when save is True.

Returns:

matplotlib.figure.Figure

plot_ratio_pairs(names: Sequence[str] | None = None, ax: Axes | None = None, plot: bool = True, save: bool = False, savename: str = 'series_ratio_pairs') Figure[source]#

Log-log scatter of sequence element pairs, matched pairs highlighted.

Each series’ pairs are drawn as small translucent dots; the pairs whose folded ratio matches a biosignal ratio are overdrawn large with a dark edge (the figure from the notebook).

Parameters:
  • names (sequence of str, optional) – Series to draw. Defaults to all compared series.

  • ax (matplotlib.axes.Axes, optional) – Draw onto an existing axis (for composing multi-panel figures). A new figure is created when omitted.

  • plot, save, savename – As in plot_proportions().

Returns:

matplotlib.figure.Figure

plot_octave_wheel(order: int | None = None, ax: Axes | None = None, plot: bool = True, save: bool = False, savename: str = 'series_octave_wheel') Figure[source]#

Octave wrapped to a circle (angle = cents): a ratio-ring per series, the signal peaks as spokes, and a filled dot where a spoke matches.

order overrides the instance order for display only (thins the rings).

plot_cents_ruler(order: int | None = None, ax: Axes | None = None, plot: bool = True, save: bool = False, savename: str = 'series_cents_ruler') Figure[source]#

Each series as a lane of ratio-ticks on a 0-1200 cents axis (bold = matched); guide lines drop from each signal peak.

plot_fit_landscape(order: int | None = None, ax: Axes | None = None, plot: bool = True, save: bool = False, savename: str = 'series_fit') Figure[source]#

For each signal peak, the cents distance to the nearest ratio of each series (lower = the series hugs the spectrum more tightly).

plot_simplicity_bubbles(order: int | None = None, ax: Axes | None = None, plot: bool = True, save: bool = False, savename: str = 'series_simplicity_bubbles') Figure[source]#

Each series ratio as a bubble sized by simplicity (bigger = smaller denominator); outlined bubbles are matched. Shows whether the signal peaks land on simple rungs.

plot_series_comb(peaks_hz: Sequence[float] | None = None, fmin: float = 2.0, fmax: float = 45.0, order: int | None = None, ax: Axes | None = None, plot: bool = True, save: bool = False, savename: str = 'series_comb') Figure[source]#

Each series as an across-octave frequency comb (scaled to best fit the signal); the signal peaks (Hz) snap onto the nearest comb step, and each lane is labelled with the mean miss in cents.

Needs peak frequencies: uses peaks_hz (captured from a biotuner / HarmonicInput) or an explicit peaks_hz.