Skip to content

Two public per-level convergence verdicts sit one attribute hop from the tree verdict #366

Description

@deOliveira-R

[M] 2026-08-13 at 0f5ca91c, from the #350 ruling investigation.

#342 and #340 N6b closed the headline path: Solution.converged() (orpheus/sn/solution.py:500-526) answers fully_converged, so a caller who asks the obvious question gets the tree-wide answer. Its own docstring names the pre-N6b behaviour as the campaign's founding defect (:508-517).

Two public per-level surfaces still answer the narrow question, and neither requires the caller to know there is a wider one.

1. IterationHistory.convergedorpheus/sn/solution.py:201-217

return self.record.converged

Public, documented, reachable as sol.history.convergedone extra attribute hop from the correct answer. Its own docstring already warns "A gate asserting physics wants fully_converged" (:214-215), and IterationHistory's class docstring says not to grow this surface (:186-192). The sibling fully_converged sits at :219-226.

[M] read by tests/sn/solve/test_d3_admission.py:186, 229 and tests/sn/solve/test_convergence_contract.py:380-407, 850.

2. PowerIterationOutcome.convergedorpheus/numerics/eigenvalue.py:348-371

return self.record.converged

Public on the numerics primitive, per-level scoped. [M] zero production readers — SN/CP/MoC/diffusion all read outcome.record instead (orpheus/sn/solver.py:2569; orpheus/cp/solver.py:999; orpheus/moc/solver.py:195; orpheus/diffusion/solver.py:473). Latent only — a hole nothing currently falls into.

Why this is worth a decision rather than a shrug

The per-level fact is load-bearing and must not be deleted: distinguishing "the outer met its own criteria but an inner was starved" from "the outer itself did not converge" is exactly what #340 N2b-ii established, and collapsing them is the plan's already-refuted candidate (.claude/plans/nested_iteration_diagnostics.md:1571 — "Loses the per-level fact… needed to tell 'inner starved' from 'genuinely non-critical'").

So this is not "make them return fully_converged". It is: should a per-level verdict be public and identically named to the tree verdict, one attribute apart, when the whole of #342 was about a caller reading the narrow answer and believing it was the wide one?

What "done" looks like

A caller cannot reach a per-level convergence verdict by accident, while a caller who genuinely wants the per-level fact still has it.

Not decided. Options, unmeasured: rename the per-level surfaces so the two cannot be confused (converged_at_this_level, per feedback_high_signal_names); keep the names and rely on the existing in-place docstring warnings; or narrow IterationHistory per its own class docstring's instruction not to grow.

Blast radius, measured

[M] static assertion sites that would move under a rename: 43 on .converged in the IterationRecord/IterationHistory family — tests/numerics/test_power_iteration_record.py (9), tests/numerics/test_iteration_record.py (9), tests/sn/solve/test_convergence_contract.py (8), tests/sn/primitives/test_solution.py (8), plus 9 singletons.

A further 87 .converged assertion lines in tests/derivations/** are a DIFFERENT class — scipy's res.converged and the derivations-local PowerIterationResult (orpheus/derivations/continuous/trajectory_resolvent/power_iteration.py:78), which has a stored converged field. They cannot move and must not be counted. (The plan records an earlier version of this same miscount at .claude/plans/nested_iteration_diagnostics.md:108-112.)

⚠ Also unresolved: tests/cross_method/adapters.py:196, 270, 340, 404 each write "converged": bool(res.converged) with a per-method res type — UNKNOWN which class each resolves to.

⚠ Two gates would need re-scoping, not re-baselining, if the two verdicts ever stop being distinguishable: tests/sn/primitives/test_solution.py:214 (test_converged_answers_for_the_LEVEL_and_fully_converged_for_the_TREE, whose :249 asserts the exact statement at issue) and tests/numerics/test_iteration_record.py:481 (test_fully_converged_IMPLIES_converged).

Spun out of #350. Siblings: #364 (twin default literals), #365 (the verdict's precision).

Metadata

Metadata

Assignees

No one assigned

    Labels

    level:L0Term verificationmodule:numericsnumerics/ shared solver infrastructuremodule:sn02.Discrete.Ordinatestype:improvementEnhancement to existing functionality

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions