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:
name — Canonical 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:
- 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.
.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.
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-164states the contract forReferenceMeasure.name:[M]at HEAD, "Lebesgue measure on[-1,1]" has three mutually-unequal spellings:.name.supportLEGENDRElegendre[-1,1]LEGENDRE.on(-1, 1)legendre_on[-1.0,1.0][-1.0,1.0]UniformMeasure(...)— built byequispaced,measure.py:1516uniform([-1.0,1.0])[-1.0,1.0]All three denote the same measure. All three compare
!=.Two independent causes:
__eq__never looks at fields.GeneratingMeasurecompares(name, support);UniformMeasurecompares(support, orthogonal_system)and itsnameis a derived property that does not participate. A dataclass__eq__returnsNotImplementedfor a different class first, so no field agreement can save it..on()does not canonicalise its identity case.LEGENDRE.on(-1, 1)mints a third name and a differently-formatted support string rather than returningLEGENDRE.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_measuresgates 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:
GeneratingMeasurecovers references with a Golub–Welsch generator (every weight on an interval, includingw=1),UniformMeasurecovers 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:
equispacedto carryLEGENDRE.on(a, b)rather than an anonymousUniformMeasure(it is the only interval-UniformMeasureproducer);.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.