Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
33c31c3
feat(#102): hypothesis-wise evidence matrix, support_score, reachabil…
claude Aug 14, 2026
4492b83
fix(#102): make REACHABLE_A_CLASSES load-bearing via import-time cove…
claude Aug 14, 2026
472c4b2
fix(#108): scale-relative zero-mode separation + Codex review follow-ups
claude Aug 15, 2026
f424769
fix: address Codex review — backward-error zero-mode threshold, requi…
claude Aug 15, 2026
9bc909f
fix: third Codex review — dtype-aware eps, one-sided exp cap, A12 une…
claude Aug 15, 2026
83f3d79
fix: fourth Codex review — grid-relative fit seed, NaN evidence, hone…
claude Aug 15, 2026
8008445
fix: drop redundant isinstance in _is_unavailable (mypy redundant-expr)
claude Aug 15, 2026
1038f7e
fix: revert dtype-derived eps — storage dtype is not the solver's pre…
claude Aug 15, 2026
b4ddca4
fix: enforce the double-precision solver contract + sixth review round
claude Aug 15, 2026
32a5186
fix: reject mismatched support_score/confidence (seventh review round)
claude Aug 15, 2026
3850dc0
fix: eighth review round — fit-boundary validation, NaN encoding-inde…
claude Aug 15, 2026
ccf3c08
fix: keep established catalogs JSON-serializable (ninth review round)
claude Aug 15, 2026
e9d4ff4
fix: tenth review round — A12 missing propagation, stiff-solver limit…
claude Aug 15, 2026
24886cd
fix: eleventh review round — ladder never crashes on partial evidence…
claude Aug 15, 2026
d3c5049
fix(#109,#112,#113): scale-relative Hermiticity validation + structur…
marcohost33-maker Aug 15, 2026
48c7b94
fix: patch certified_eigvals via monkeypatch string target (CodeQL py…
claude Aug 15, 2026
c74464f
fix: twelfth review round — five hardening fixes on the #112/#113 cer…
claude Aug 15, 2026
23ed0b1
fix: local synthetic builders instead of cross-test import (CI Module…
claude Aug 15, 2026
d4f9004
fix: thirteenth review round — one zero-mode scale, certified D24, co…
claude Aug 15, 2026
467ed8f
fix: fourteenth review round — certified Petermann factors, radius fa…
claude Aug 15, 2026
ebdd30b
fix: fifteenth review round — eigenvector-residual gate, ladder conti…
claude Aug 15, 2026
e26df05
fix: sixteenth review round — grid-relative Prony fallback, consumpti…
claude Aug 15, 2026
4a8ae9c
refactor: extract D24 eigenvectors only in the branch that consumes t…
claude Aug 15, 2026
f3b73b4
fix(#118): close the three review findings that survived PR #107
marcohost33-maker Aug 27, 2026
98370aa
Merge branch 'main' into pr107-fix
marcohost33-maker Aug 28, 2026
11e117f
fix(#121): one zero-mode cutoff, obtained one way (four review findings)
marcohost33-maker Aug 28, 2026
2b59f1a
fix(#121): withhold, refuse, continue -- the three standalone finding…
marcohost33-maker Aug 28, 2026
85022fe
fix(#121): key model selection on success, not on a finite AICc
marcohost33-maker Aug 28, 2026
880af6c
test(#121): do not pin the D11 fixture to a rescued-mode COUNT
marcohost33-maker Aug 28, 2026
f194c97
fix(#121): der zweite Reparaturschritt beendete die Leiter, statt sie…
marcohost33-maker Aug 28, 2026
c4bc81f
fix(#121): drei weitere Gates bescheinigten, was sie nicht geprueft h…
marcohost33-maker Aug 28, 2026
2cc9491
fix(#121): the narrower predicate published what the wider one withhe…
marcohost33-maker Aug 29, 2026
2eafedd
fix(#121): a band that accepts the whole spectrum has certified nothi…
marcohost33-maker Aug 29, 2026
9581f39
fix(#121): no interval from a resample of non-fits, and no fit withou…
marcohost33-maker Aug 29, 2026
57c17a2
fix(#121): a scale that overflows cannot certify anything (round 21)
marcohost33-maker Aug 29, 2026
6ecf9df
fix(#123): pin an initial state that actually relaxes
marcohost33-maker Aug 29, 2026
37c76ea
fix(#121): four tolerances that decided from what they never had (rou…
marcohost33-maker Aug 29, 2026
fbdea4e
fix(#121): four defaults published as measurements (round 22, part 2)
marcohost33-maker Aug 29, 2026
50bf245
test(#121): two probes that could not judge what they were watching
marcohost33-maker Aug 29, 2026
be6553a
test(#121): a probe whose own death was a warning could not be certified
marcohost33-maker Aug 29, 2026
e7b4150
Merge remote-tracking branch 'origin/main' into pr107-fix
marcohost33-maker Aug 29, 2026
10dc291
fix(#121): a reference scale lost to underflow certified a non-genera…
marcohost33-maker Aug 30, 2026
ac05491
fix(#121): D16 published from the spectrum D1/D3/D4 refused to report…
marcohost33-maker Aug 30, 2026
c3c7f93
test(#121): two guard lines that sat off every test path (round 23)
marcohost33-maker Aug 30, 2026
3cc0bbc
fix(#121): the repair for finding 12 contained finding 12 (round 23)
marcohost33-maker Aug 30, 2026
436bd93
test(#121): the subnormal guard covered only one of the two ladders (…
marcohost33-maker Aug 30, 2026
d7086ca
test(#121): a probe that died instead of judging (round 23)
marcohost33-maker Aug 30, 2026
da4c6ca
fix(#124): make GLS optimizer residuals amplitude-scale invariant
marcohost33-maker Sep 1, 2026
8a694a7
test(#124): discriminate tiny-amplitude seed false convergence
marcohost33-maker Sep 1, 2026
e675e61
fix(#125): fail closed on degenerate BCa uncertainty
marcohost33-maker Sep 1, 2026
b46daa8
test(#125): discriminate degenerate from ordinary BCa uncertainty
marcohost33-maker Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,17 @@ python examples/quickstart.py # smoke run

## Conventions

- **Imports:** absolute from `liouscope`, no `..` traversal.
- **Imports:** *intra-package* imports are **relative** (`from .._consts import ...`,
`from ..numerics.linalg import ...`); *external* imports are absolute.
Tests, examples and benchmarks import the installed package absolutely
(`from liouscope... import ...`).
Rationale (issue #110, decided 2026-08-15): the rule here previously demanded
absolute intra-package imports, which the package has never followed —
measured at 135 relative statements across 39 of 53 modules and **0** absolute
ones. Relative intra-package imports are the norm for a `src/` layout and keep
the package re-namable; the divergence only produced false review findings
against conforming code, so the convention was corrected to the codebase rather
than the codebase to the convention.
- **Numerical libraries:** prefer `numpy`/`scipy`; `qutip` is an optional
extra used for cross-checks, not a core runtime dependency.
- **Plots:** matplotlib only, no interactive backends in CI; save to
Expand Down
668 changes: 668 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

48 changes: 47 additions & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,53 @@ abstract: >-
# mechanism hypotheses are reported alongside the dominant label with their
# priority-shadowing status (`ClassificationResult.triggered_hypotheses`,
# issue #102), and the support score is a documented deterministic heuristic
# rather than a calibrated probability.
# rather than a calibrated probability; a hypothesis-wise evidence matrix
# spanning the full A1-A12 taxonomy reports supporting measurements,
# counterevidence, missing evidence and explicit fail-closed claim floors, with
# the schema-reserved classes excluded from claims and coverage denominators
# (`ClassificationResult.hypothesis_matrix`, issue #102); and the zero-mode
# separation underlying D1/D3/D4/D9/D16/D19/D20/D24 is scale-relative, so those
# diagnostics no longer depend on the choice of rate unit (issue #108). The
# last item CHANGES NUMERICAL RESULTS for generators whose spectral radius is
# far from unity and must be described as a correction, not merely an addition,
# when the release is cut. It must NOT be described as making the mechanism
# verdict unit-invariant in general: the A10/F5 branch still gates on the
# rate-dimensioned henrici_eta threshold (open in #101).
# Also pending for the next cut: the spectral layer verifies the computed
# spectrum against the exact identity vec(I)^H L = 0 before reading D1/D3/D4
# off it, and re-solves by an alternative LAPACK route when a stiff generator
# makes zgeev deflate the zero mode away (issue #112). This too CHANGES
# NUMERICAL RESULTS -- on stiff generators only -- and must be described as a
# correction rather than an addition: the previously reported gap could be a
# spurious mode rather than an inaccurate one (measured 7.28e-6 against a true
# 1.074e-5 on a four-level classical jump network). It must NOT be described as
# making the dense spectral layer reliable for arbitrarily stiff generators:
# when no route satisfies the certificate the layer is marked unresolved, which
# is a disclosure, not a repair. Certification and downstream zero-mode
# filtering share ONE operator-derived scale (rtol * eps * ||L||_2) at every
# call site that holds the operator -- spectral layer, Mpemba layer, Petermann
# factors and the D24 predictor, which is now itself routed through the
# certified eigensolve and returns an unconverged record on unresolved spectra
# (round-13 review). This corrects spurious near-zero or negative D1 gaps on
# strongly non-normal generators, where the spectral radius understates the
# eigensolver backward error by the ||L||_2 / max|lambda| ratio (measured
# 3.9e3 at dimension four). The operator-derived bound applies only when the
# certificate is applicable (trace preservation established); otherwise the
# filters fall back to the radius-based tolerance, and the Petermann factors
# (D9) likewise consume the certified eigendecomposition, withholding NaN on
# unresolved spectra (round-14 review). The eigenvector-producing certificate
# additionally validates per-mode left/right eigenvector residuals before
# certifying (a small |lambda| does not vouch for the vectors D19/D9 consume),
# and the repair ladder continues past ambiguous candidates so a later route
# can resolve what the incumbent cannot (round-15 review). The Prony fallback
# seeds for the M3b fit are grid-relative (round-16 review), removing a
# rate-unit dependence that corrupted fitted rates on non-uniform grids far
# from unit span; D24's partial recomputation and D11's eigenvalue input now
# match the certificate of what they actually consume. Hermiticity
# validation of the Hamiltonian is
# likewise scale-relative rather than absolute (issue #109), which closes a
# fail-open path that admitted non-GKSL generators at small ||H||; this changes
# which inputs are ACCEPTED, not the numerics of accepted ones.
keywords:
- open quantum systems
- Lindblad
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ Letting it drive decisions is deferred to the preregistered calibration study
in issue #102. The field is additive and defaults to `()`, so results
serialised by older versions stay valid.

`hypothesis_matrix` (issue #102) goes one step further and reports **every**
hypothesis of the taxonomy — including the ones that did *not* fire and the
schema-reserved A6/A7/A9 — each with its supporting measurements,
counterevidence, missing required evidence, an explicit fail-closed
`claim_floor` (`RESERVED`/`UNEVALUABLE` → `UNDEFINED`, `NOT_SUPPORTED` →
`NOT_EXCLUDED`, `SUPPORTED` → the verdict it would receive as winner) and its
ordinal `support_score` — which is a number **only for `SUPPORTED` entries**
and `None` otherwise: the score answers "what grade would this class get as
the winner", so attaching it to a failed or unevaluable rung would print a
confirmation-grade number beside that rung's own counterevidence. Decision
ladder and matrix are derived from the same
declarative rung spec, so the audit surface cannot drift from the decision;
the matrix is report-only. See
`docs/explanation/layers-and-taxonomy.md` for the full vocabulary.

The same example with a 1D lattice geometry:

```python
Expand Down Expand Up @@ -271,7 +286,8 @@ require physics-domain interpretation; no claim of universality is made beyond t
covered by the V1-V5 validation systems.

Two honesty notes on the classifier surface (tracked in issues #101/#102):
`classification.confidence` is a deterministic **heuristic support score**,
`classification.support_score` (and its legacy alias `confidence`) is a
deterministic **ordinal heuristic**,
not a calibrated probability; and the A10/F5 (phantom-relaxation) verdict
path is **not yet invariant under a change of rate units** — the
scale-relative successor diagnostics (`henrici_relative`, `kreiss_scaled`,
Expand Down
68 changes: 61 additions & 7 deletions docs/explanation/layers-and-taxonomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,71 @@ The design rule behind all three bullets is the same: **fail closed.**
When evidence is missing, malformed, or merely asserted, the report degrades
to the weaker claim rather than trusting the caller.

## `confidence` is a heuristic support score, not a probability
## `support_score` (né `confidence`) is ordinal, not a probability

`ClassificationResult.confidence` is a **deterministic, rule-based support
`ClassificationResult.support_score` is a **deterministic, rule-based support
score** in `[0, 1]` (fixed values such as `0.70`, `0.85`, `0.95` attached to
specific evidence combinations). It is **not** a posterior probability and it
has **not** been calibrated against held-out labelled reference families — do
not read `0.85` as "85 % probability the label is right". Calibrated,
hypothesis-wise evidence reporting is tracked in issue #102; until that
lands, treat the number as an ordinal ranking of rule strength and rely on
the *verdict/tier* vocabulary (which is evidence-graded and fail-closed) for
claims.
not read `0.85` as "85 % probability the label is right". Treat the number as
an *ordinal ranking of rule strength* (`0.20 < 0.50 < 0.70 < 0.85 < 0.95`)
and rely on the *verdict/tier* vocabulary (which is evidence-graded and
fail-closed) for claims.

`confidence` is the **legacy alias** for the same value: issue #102 offered
rename-with-honest-semantics or calibrate, and the rename shipped first
(option 1). Both fields carry identical values (pinned by test); a genuinely
*calibrated* score would have to pass the preregistered validation design in
issue #102 (family-split calibration/holdout sets, reliability curves,
adversarial negatives) before it may replace the ordinal one.

## The hypothesis evidence matrix

`ClassificationResult.hypothesis_matrix` (issue #102) reports, for **every**
hypothesis of the taxonomy — each decision rung, the A12 fallback, and the
schema-reserved classes — one entry with:

| key | meaning |
|---|---|
| `status` | `SUPPORTED` / `NOT_SUPPORTED` / `UNEVALUABLE` / `RESERVED` |
| `supporting` | atomic conditions that hold, with the evidence values read |
| `counterevidence` | conditions that fail, with their values |
| `missing` | required evidence keys absent from this run (kept as an audit trail even when the rung is already refuted) |
| `claim_floor` | what this run could claim about the hypothesis |
| `support_score` | the ordinal score this class would receive; `None` unless the entry is `SUPPORTED` |

Each rung is a conjunction, so status precedence is conclusive-first: one
evaluated-**false** condition refutes the rung (`NOT_SUPPORTED`) no matter
what a missing sibling measurement would have said; `UNEVALUABLE` is
reserved for the genuinely open case where no evaluated condition is false
and the missing required evidence could still flip the rung to supported.
This keeps the A12 fallback consistent with the decision ladder on
partially collected runs: when every unfired rung is conclusively refuted,
the fallback is `SUPPORTED` — exactly the ladder's deterministic A12.

The claim floor follows explicit, fail-closed rules: `RESERVED` and
`UNEVALUABLE` floor to `UNDEFINED` (no rule / no evidence — no claim);
`NOT_SUPPORTED` floors to `NOT_EXCLUDED` (a threshold that did not fire is
absence of support, **not** proof of absence); `SUPPORTED` receives the
verdict the hypothesis would get were it the winner — so for the reported
class the floor equals the reported verdict exactly (pinned by test).

Both the decision ladder and the matrix are derived from one declarative
rung specification (`_ladder_spec`), so the audit surface cannot drift from
the decision. The matrix is **report-only**: no verdict consumes it, and the
per-hypothesis numerical-uncertainty / perturbation-robustness columns from
the issue-#102 wishlist are *not faked* — they remain open until the
underlying machinery exists.

### Reserved classes are excluded from coverage denominators

A6/A7/A9 have no code-backed decision rule (`RESERVED_A_CLASSES` records the
per-class rationale). They appear in the matrix as `RESERVED` with a
permanent `UNDEFINED` claim floor, and any "n of N classes" coverage
statement must use the reachable denominator
`liouscope.REACHABLE_A_CLASSES` (9 classes), not the full taxonomy — the
reachability contract is pinned by an AST-level test that scans the actual
decision source.

## Known limitation: the F5 decision path is not rate-unit invariant

Expand Down
3 changes: 2 additions & 1 deletion docs/tutorials/first-diagnostic-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ print(r.classification.a_class) # one of "A1".."A12"
print(r.classification.f_family) # gap-failure family "F1".."F5" or "none"
print(r.classification.verdict) # CONFIRMED / CANDIDATE / NOT_EXCLUDED / UNDEFINED
print(r.classification.tier) # e.g. EXPLORATION vs PUBLICATION_GRADE
print(r.classification.confidence) # heuristic support score 0..1 (NOT calibrated)
print(r.classification.support_score) # ordinal heuristic 0..1 (NOT calibrated;
# `confidence` is the legacy alias)
```

How to read the classification:
Expand Down
4 changes: 4 additions & 0 deletions src/liouscope/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
DIAGNOSTIC_SCHEMA_VERSION,
F_FAMILIES,
F_FAMILY_DESCRIPTIONS,
REACHABLE_A_CLASSES,
RESERVED_A_CLASSES,
TAXONOMY_VERSION,
)
from ._diagnostics import diagnose
Expand Down Expand Up @@ -54,6 +56,8 @@
"ENSEMBLE_MPEMBA_CONFIRMED",
"F_FAMILIES",
"F_FAMILY_DESCRIPTIONS",
"REACHABLE_A_CLASSES",
"RESERVED_A_CLASSES",
"TAXONOMY_VERSION",
"ClassificationResult",
"DiagnosticReport",
Expand Down
Loading
Loading