Skip to content

ReferenceMeasure equality is not canonical across realizations — Lebesgue on [-1,1] has three incomparable spellings #369

Description

@deOliveira-R

Found while building the quadrature selector's reference conjunct (Q6-C, 626dc855). Not blocking it — the selector works around this with a closed, gated vocabulary — but the workaround is a fence, and this is the fix.

The defect

orpheus/numerics/exactness.py:161-164 states the contract for ReferenceMeasure.name:

nameCanonical mathematical identity — what equality compares.

[M] at HEAD, "Lebesgue measure on [-1,1]" has three mutually-unequal spellings:

expression .name .support
LEGENDRE legendre [-1,1]
LEGENDRE.on(-1, 1) legendre_on[-1.0,1.0] [-1.0,1.0]
UniformMeasure(...) — built by equispaced, measure.py:1516 uniform([-1.0,1.0]) [-1.0,1.0]

All three denote the same measure. All three compare !=.

Two independent causes:

  1. Cross-class __eq__ never looks at fields. GeneratingMeasure compares (name, support); UniformMeasure compares (support, orthogonal_system) and its name is a derived property that does not participate. A dataclass __eq__ returns NotImplemented for a different class first, so no field agreement can save it.
  2. .on() does not canonicalise its identity case. LEGENDRE.on(-1, 1) mints a third name and a differently-formatted support string rather than returning LEGENDRE.

So the promise holds within each class and is silent across them, while the wording implies it holds globally.

Why it matters

Nothing in production compares two ReferenceMeasures today except the new selector conjunct, and that is sound only because the registry's vocabulary happens to be {LEGENDRE, UNIFORM_ON_SPHERE}. tests/numerics/test_registry.py::test_every_registered_rule_speaks_one_of_the_two_reference_measures gates that closure, so a rule spelling its reference the third way reddens at registration rather than being silently mis-selected. That gate is the fence; it is not the repair.

[M] the mis-selection this prevents is not hypothetical: Gauss-Legendre and Gauss-Chebyshev at n=4 agree on support, orthogonal system and degree (7), and differ by 0.696 on the unweighted ∫x⁶.

Suggested shape

The cleanest resolution looks like a partition rather than a reconciliation: GeneratingMeasure covers references with a Golub–Welsch generator (every weight on an interval, including w=1), UniformMeasure covers the ones without (the circle, the sphere). Once the two classes have disjoint domains, cross-class inequality becomes a theorem rather than an accident.

That needs:

  • equispaced to carry LEGENDRE.on(a, b) rather than an anonymous UniformMeasure (it is the only interval-UniformMeasure producer);
  • .on() to canonicalise the identity case, and the support-string formatting to be single-sourced;
  • UniformMeasure's docstring to stop listing "the midpoint / equispaced rule on an interval" among its consumers;
  • test_exactness.py's 21 gates re-read — none currently compares across realizations, so this is uncovered ground rather than a rewrite.

⚠ Shared numerics: exactness.py + generating_measure.py + measure.py. Natural opener for a reshaping campaign rather than a drive-by.

Metadata

Metadata

Assignees

No one assigned

    Labels

    level:L0Term verificationmodule:datadata/ cross-section infrastructuretype:improvementEnhancement to existing functionality

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions