Skip to content

fix(#152): refuse Liouvillians whose assembly overflows to non-finite entries - #153

Draft
marcohost33-maker wants to merge 6 commits into
mainfrom
claude/issue152-nonfinite-generator
Draft

fix(#152): refuse Liouvillians whose assembly overflows to non-finite entries#153
marcohost33-maker wants to merge 6 commits into
mainfrom
claude/issue152-nonfinite-generator

Conversation

@marcohost33-maker

@marcohost33-maker marcohost33-maker commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Closes #152.

The defect (present on main 4f9592b)

A Hamiltonian with finite entries whose differences overflow was accepted, and the returned Liouvillian contained non-finite entries. The Hermiticity gate checks the input; nothing checked the output. Reproduction from the issue (H = diag(1e308, -1e308)) returned a non-finite generator from both builders (core/lindblad.py:173-188, sparse/build.py:105-117 on 4f9592b).

What changes

  • New src/liouscope/numerics/generator_guard.py: require_finite_generator raises ValueError — the error family of the other non-representable-input guards (lindblad.py:101, :157; build.py:43, :93) — naming how many entries are non-finite and the overflow cause.
  • Dense builder: every entry is checked. Sparse builder: .data of the canonical form, O(nnz), no densification. Why .data suffices: entries outside the stored structure are exactly 0, SciPy only drops results equal to 0, and inf/NaN never compare equal to 0. The one exception — duplicate entries whose sum overflows — is handled by summing duplicates on a copy first.
  • Both builders assemble under np.errstate(over="ignore", invalid="ignore") and check afterwards. Behaviour change (accepted, fail-closed): callers who previously got a RuntimeWarning plus a broken generator now get a ValueError.
  • Controls that must stay accepted and are tested: diag(1, -1) (against the closed form), diag(4e307, -4e307), a dissipator with entries 1e150.

Evidence

  • Before-tests committed first (d2d8338): against 4f9592b sources, 11 failed / 8 passed (builder's run, not re-measured by the orchestrator); builder cases die at AssertionError: expected ValueError, got RuntimeWarning. Refusal tests fail at an assertion, not at "DID NOT RAISE" (5b671f0).
  • Discrimination with Tools/diskriminierung.py, re-run by the orchestrator on head eff950e: dense 4/4 (DK-20260912T210845-eff8e2ef270c), sparse 4/4 (DK-20260912T210909-b3f99c91079b), helper 3/3 (DK-20260912T210859-e04045778b07); file hashes restored after each run.
  • Full suite re-run by the orchestrator at eff950e, Python 3.14.4, import verified from this worktree: 1243 passed, exit 0. The builder additionally ran ruff, mypy, coverage (--cov-fail-under=90), the qutip marker, check_workflow_hardening.py and check_claim_safety.py on 3.10 / 3.12 / 3.14 with exit 0 (builder's run, not repeated here). 3.13 not available locally.

Known interaction

PR #127 also edits core/lindblad.py, sparse/build.py and CHANGELOG Unreleased. Expected conflicts are small (CHANGELOG, neighbouring imports); the assembly blocks #127 touches are not the ones changed here. Merge order to be decided when both are ready.

Draft per AGENTS.md until the Codex round and the independent review are terminal.

🤖 Generated with Claude Code

https://claude.ai/code/session_019UEuRxXvWxUfXVYHKT68z4

marcohost33-maker and others added 4 commits September 12, 2026 22:29
…4f9592b)

Rejection tests for both builders (overflowing diagonal difference,
overflowing dissipator product, finite rate overflowing a finite
dissipator), controls that must stay accepted (diag(1,-1) closed form,
large-but-representable diagonal and dissipator, dense/sparse parity), and
tests for the shared output guard (non-canonical CSR/COO duplicates, no
densification). 11 failed / 8 passed on 4f9592b.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UEuRxXvWxUfXVYHKT68z4
… entries

Both builders now pass their assembled generator through a shared output
guard, numerics.generator_guard.require_finite_generator, which raises
ValueError (the family of the existing non-finite input gates) when any
entry is NaN/inf. Dense: all entries. Sparse: .data of the canonical form,
O(nnz), never densified; non-canonical duplicates are summed on a copy
first because finite duplicates can overflow when summed. Assembly runs
under np.errstate(over/invalid=ignore): an overflow can never return to a
finite value through the remaining finite +,-,* so the guard sees every
one, and callers get the ValueError instead of a RuntimeWarning cascade.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UEuRxXvWxUfXVYHKT68z4
pytest.raises reports a silent accept as Failed (DID NOT RAISE), which is
not an assertion; the refusal helpers now catch broadly and assert both
that something was raised and that it is a ValueError. Still red on the
4f9592b sources (11 failed, 8 passed) and green on the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UEuRxXvWxUfXVYHKT68z4
… guard

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019UEuRxXvWxUfXVYHKT68z4
@marcohost33-maker marcohost33-maker added the agent:claude PR authored by Claude Code label Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:claude PR authored by Claude Code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Liouvillian builders accept H whose diagonal differences overflow and return a non-finite generator (present on main)

1 participant