Metrics#
biotuner.harmonic_geometry.metrics#
Quantitative measurements over GeometryData (and time-resolved
HarmonicSequence after a generator is applied) — the entry point
for running science on top of the harmonic-geometry module.
This module is strictly geometry-side. For harmonic-content metrics on
the underlying signal (consonance, dyad_similarity, euler, tenney,
subharmonic_tension), use biotuner.metrics and
biotuner.biotuner_group.BiotunerGroup directly — those tools
already exist and are not duplicated here.
Public API#
- geometry_metrics(geom) – scalar dict of structural +
per-method stats for a
GeometryData- list_supported_kinds() – names of every recognised
metadata[‘kind’]
- sequence_metrics(seq, generator, **kw) – apply
generatorto each HarmonicInputframe and return per-framegeometry_metrics()as a dict of 1-D ndarrays- compare(geometries, labels=None) – N-way comparison table
(column-oriented dict)
- normalize_metrics(rows, …) – min-max scale rows to
[0, 1] (data-driven; optional
sensesargument inverts metric direction)- MetricsLog – append-only log of metric rows
(geometry / generator output); CSV / JSON export
All extractors fall back gracefully — missing fields produce nan rather
than raising.
- geometry_metrics(geom: GeometryData) Dict[str, float][source]#
Structural + per-method metrics for any
GeometryData.Always returns the generic stats (
n_vertices,n_faces/n_edgeswhen applicable, spatial spans, edge-length and field stats). When the geometry’smetadata['kind']matches a registered generator (lissajous_2d, chladni_field_*, stern_brocot_tree, ifs_harmonic, recursive_polyhedron, harmonic_point_cloud, …), method-specific scalars are merged on top.The list of recognised kinds is
_GEOM_EXTRACTORS. Geometries without a recognised kind still receive the generic stats.- Parameters:
geom (GeometryData)
- Returns:
dict[str, float]
- list_supported_kinds() List[str][source]#
Return every metadata[‘kind’] string recognised by
geometry_metrics().Useful for sanity-checking instrumentation coverage across the module.
- sequence_metrics(seq: HarmonicSequence, generator: Callable[[...], GeometryData], **generator_kwargs: Any) Dict[str, ndarray][source]#
Apply
generatorto every frame ofseqand return per-frame geometry-metric trajectories.- Parameters:
seq (HarmonicSequence)
generator (callable) – Any harmonic-geometry generator that takes a
HarmonicInputas its first positional argument and returns aGeometryData(e.g.harmonic_knot,chladni_from_input,recursive_polyhedron, …).**generator_kwargs – Forwarded to
generatoron every frame.
- Returns:
dict[str, ndarray] –
{metric_name: 1-D ndarray of length n_frames}. The metric set is the union over all frames; missing values arenan.
Examples
>>> traj = sequence_metrics(seq, harmonic_knot, n_points=400) >>> traj["winding_p"].shape (T,)
- compare(geometries: Sequence[GeometryData], labels: Sequence[str] | None = None) Dict[str, List[float]][source]#
Side-by-side metric comparison across a list of
GeometryData.- Parameters:
geometries (sequence of GeometryData)
labels (sequence of str, optional) – One label per geometry. Defaults to
geom_0, geom_1, ….
- Returns:
dict –
{metric_name: [v_for_item_1, …]}plus a special'__labels__'key carrying the user-supplied or auto labels.
- normalize_metrics(rows: Sequence[Dict[str, float]], metrics: Sequence[str] | None = None, bounds: Dict[str, tuple] | None = None, senses: Dict[str, int] | None = None) List[Dict[str, float]][source]#
Map metric values to
[0, 1]for radar plotting.Bounds are data-driven by default (per-metric min/max across rows). Pass
bounds={name: (lo, hi)}to override.- Parameters:
rows (sequence of dict)
metrics (sequence of str, optional) – Subset of metric keys to normalise; defaults to
rows[0]keys.bounds (dict, optional) – Explicit
{metric: (lo, hi)}per-metric scales.senses (dict, optional) –
{metric: +1 | -1}.+1(default) keeps the data-driven mapping;-1inverts (i.e. higher raw value → lower normalised value). Useful for “lower is better” metrics like edge irregularity.Behaviour notes
—————
* Non-finite raw values stay ``nan`` (not rendered on a radar).
* Zero-variance metrics (all rows agree) map to ``0.5`` — interpreted as – “consensus / no information” rather than misleadingly collapsing to 0.
- class MetricsLog(rows: ~typing.List[~typing.Dict[str, ~typing.Any]] = <factory>)[source]#
Bases:
objectAppend-only log of metric measurements with CSV / JSON export.
Each row is a
dictof metric values plus optional metadata fields (label,timestamp, anything user-supplied vialog()). Suitable for accumulating measurements across many chords / frames / experiments and exporting them for downstream analysis.- rows: List[Dict[str, Any]]#
- log_geometry(geom: GeometryData, label: str | None = None, **extra: Any) None[source]#
Convenience: compute
geometry_metrics()and append the row.
- log_sequence(seq: HarmonicSequence, generator: Callable[[...], GeometryData], label_prefix: str = 'frame', generator_kwargs: Dict[str, Any] | None = None, **extra: Any) None[source]#
Apply
generatorto each frame and log the resulting geometry-metrics row.- Parameters:
seq (HarmonicSequence)
generator (callable(HarmonicInput, **kw) -> GeometryData)
label_prefix (str, default
"frame")generator_kwargs (dict, optional) – Forwarded to
generatorfor every frame.**extra (Any) – Constant fields appended to every row (e.g.
trial=1).