Skip to content

SNMesh.is_same_phase_space is vacuous at d≥3 (None is None ⟹ different 3-D problems compare equal) and false for same-data from_axes builds at d≤2 #459

Description

@deOliveira-R

The defect

SNMesh.is_same_phase_space (orpheus/sn/mesh/augmented_mesh.py:545-596) is the Problem's identity predicate — the pairing guard Solution.compare (orpheus/sn/solution.py, its only production caller) uses to refuse combining fields from two different discrete problems. It is spelled as CONSTITUENT identity: self.mesh is other.mesh and self.quad is other.quad and <per-material Mixture is> and type(scheme) is type(scheme).

[M] 2026-09-08 (explorer census scratch/_consumers/explorer_problem_solution_census.md §4, probe run at HEAD 7e9b6210), the predicate is wrong in BOTH directions:

  • d ≥ 3 — vacuously TRUE for DIFFERENT problems. Axis-native carriers carry mesh = None (SNMesh.from_axes synthesizes a legacy adapter only at d ≤ 2), so self.mesh is other.mesh reads None is None and two 3-D meshes with DIFFERENT edges (volumes unequal, bulk_space unequal) compare as the same phase space. Solution.compare therefore accepts comparing two different 3-D problems.
  • d ≤ 2 — FALSE for the SAME data. Two from_axes calls with the same edges, the same Quadrature object and the same materials read False, because each call mints a fresh legacy Mesh1D adapter. A saved-and-reloaded Problem can never be "the same phase space" as its original — which defeats the save-state concept the consumers campaign is about to build (.claude/plans/cs4c_binding_design.md §22.5 addendum).

The docstring rules the constituent tier deliberately ("never relax an invariant to a weaker downstream check", L29). The d ≥ 3 arm is not a strong tier, it is no tier: None is None proves nothing. The d ≤ 2 arm compares a per-construction ADAPTER (inbound provenance) rather than the generating datum it adapts (the axes: edges + BC tags; mat_map).

The fix (the consumers campaign's FIRST item — identity is the Problem's first question)

The honest identity is the identity of the GENERATING DATA: axes (edges + boundary-law tags) × mat_map × quadrature × materials × scheme × closure class — content equality where the datum is a value (edges, tags, mat_map), object identity where it is a shared constituent (Quadrature, each Mixture). Comparing axes by content is STRONGER than mesh is, not weaker, so L29 is honoured. Design the predicate together with __eq__/__hash__ of the future SNProblem (structural identity of FunctionSpace landed at CS4c step 6 and is the template: the metric is part of the identity). Gate: a positive control (same data, two constructions → True at d = 1, 2, 3), a negative control per generating datum (edges / a BC tag / mat_map / quadrature / a mixture / the scheme type each flipped → False), and the d = 3 case that reads True today MUST read False.

Blast radius to census (plan-authoring §6b): is_same_phase_space [M] 3 production sites / 1 file + 0 tests?? — re-measure; Solution.compare's callers; the adjoint-weighted collapse (#281) that relies on the guard.

Provenance

Found by the consumers-campaign opener census (2026-09-08), filed after gh issue list --search "is_same_phase_space" → 0 hits. Not fixed inline because the honest predicate IS the campaign's first design question (Problem identity), ruled by the user at the opener; the interim hazard is confined to Solution.compare on d ≥ 3 problems and on d ≤ 2 problems built by two from_axes calls.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    module:sn02.Discrete.Ordinatestype:bugSomething is wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions