Repository navigation
fix(identification): return the ordered Cholesky factor in data row order - #208
Merged
Merged
Conversation
…rder `Cholesky.identify` rebuilt Sigma, permuted it, and returned `cholesky(Pi Sigma Pi')` — a matrix whose rows are indexed by `self.ordering`. `IdentifiedVAR.shock_matrix` labels that row axis with `var_names` (data order) and `impulse_response`/`fevd` left-multiply it by MA coefficients built in data order. For any non-identity ordering the result was therefore mislabelled *and* the product `Phi @ P` mixed two coordinate systems, so IRFs, FEVDs and historical decompositions were wrong — silently, since the output still looked triangular. Replace the Sigma round-trip with an LQ factorisation of `Pi @ L`, obtained from `qr((Pi @ L).T)`: `G = R.T` is lower-triangular with `G @ G.T = Pi Sigma Pi'`, columns are sign-fixed to give a positive diagonal, and the rows are then un-permuted back into data order. This also closes #81 — Sigma is never formed, so the conditioning of the decomposition is not squared, and the structural zeros are exact rather than rounded. Batch dims are addressed with an ellipsis, so the path no longer assumes rank-4 input. `SignRestriction` (`L @ Q`) and `ProxySVAR` (`L @ Q`) rotate columns only and are unaffected. The identity-ordering fast path still returns `L` byte-identically, so `tests/test_cholskey_irf_pins.py` is untouched. Breaking for anyone using a non-identity `ordering`: those numbers change, because the previous ones were wrong. Fixes #184 Closes #81 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
Regression cover for the row-order bug. Unit level: the ordered factor matches `cholesky(Pi Sigma Pi')` after row permutation, `identify` is deterministic, and reordering then reordering back round-trips to `L` (a transposed `perm`/`inv` pair survives a reversal but not this). Pipeline level (`IdentifiedVAR` over the synthetic 2-var posterior, no MCMC): the structural zero must land in the cell its *labels* claim, and IRFs and FEVDs selected by `(response, shock)` label must be invariant to relabelling the variables — case A runs the identity fast path on a permuted posterior, case B runs the reordering path on the original. The FEVD case squares Theta, so it catches mislabelling that a sign error would otherwise mask. Both failed loudly before the fix. Refs #184 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #208 +/- ##
=====================================
Coverage 94.8% 94.8%
=====================================
Files 40 40
Lines 2416 2419 +3
Branches 280 280
=====================================
+ Hits 2292 2295 +3
Misses 81 81
Partials 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This was referenced Jul 29, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the P1 mislabelling bug #184 and implements #81's QR-based reordering in the same eight lines.
The bug:
IdentifiedVAR.shock_matrixlabels response rows withvar_names(data order) and multiplies by MA coefficients built in data order — for every scheme.Cholesky.identifywith a non-identityorderingreturnedchol(ΠΣΠ')with rows left in ordering order: mislabelled response rows and a mixed-coordinateΦ @ Pproduct in IRF/FEVD/HD. The red-first proof is in the test commit — pre-fix, the structural zero sat at 2.04 in the cell its labels claimed, and reconstructed variances were transposed between variables.The fix (#81's construction): the permuted factor is now obtained by LQ-via-QR of the row-permuted factor —
qr((ΠL)')givesG = R'withGG' = ΠΣΠ'exactly, sign-fixed to a positive diagonal, then un-permuted back to data row order. Σ is never formed, so input conditioning is not squared (verified at cond 1.4e12: max relative error 1.5e-16, structural zeros exactly zero), and one batched decomposition per call is saved. Columns stay inorderingorder, matchingshock_coords— the contract is now stated at both the producer and consumer ends.Breaking (pre-v0.1): any analysis using a custom
Cholesky(ordering=...)will produce different — previously mislabelled — numbers. Identity orderings are byte-identical (fast path returnsLunchanged; the pinned IRF/FEVD/HD regression file is untouched and green). A side effect worth knowing: rank-deficient input now yields a zero diagonal instead ofLinAlgError— unreachable through the pipeline (volatility adapters always produce valid factors), accepted deliberately.Notable blast radius: the monetary-policy tutorial's prose ("orderings A and B give identical policy IRFs") was false in the rendered notebook and becomes true on the next render — no docs edit needed.
Fixes #184
Closes #81
Review
Independently reviewed (verdict: approve, zero findings): the LQ construction re-derived from scratch, column-vs-row sign scaling explicitly discriminated numerically (row-scaling error 5.3 vs column 1.8e-15), a non-involution 4-cycle permutation stress-tested beyond the committed tests, and test discrimination proven by monkeypatching the old implementation back in — all six behavioural tests go red while the pin file stays green under both implementations.
Merge note
Lands cleanly before #183 (feat/143) — the only contact point is the
_linalgimport line (mechanical rebase;sigma_from_choleskyremains used byfitted.py).ruff/ty/ fast suite: all green (533 passed, 29 deselected).🤖 Generated with Claude Code
https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV