feat(identification): max-share frequency-band identification - #200
Draft
thomaspinder wants to merge 7 commits into
Draft
feat(identification): max-share frequency-band identification#200thomaspinder wants to merge 7 commits into
thomaspinder wants to merge 7 commits into
Conversation
This was referenced Jul 29, 2026
Open
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #200 +/- ##
======================================
Coverage 95.0% 95.1%
======================================
Files 45 45
Lines 3099 3240 +141
Branches 380 397 +17
======================================
+ Hits 2945 3082 +137
- Misses 111 113 +2
- Partials 43 45 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This was referenced Jul 29, 2026
thomaspinder
force-pushed
the
feat/145-max-share-identification
branch
from
July 29, 2026 14:08
80825cf to
f06fbe5
Compare
thomaspinder
marked this pull request as draft
July 29, 2026 20:23
…145) Two rank-agnostic helpers that maximum-share identification needs and that other schemes will want too. `companion_spectral_radius` separates the *interpretation* failure (explosive draw — the spectral density is not that of a stationary process) from the arithmetic one, so a scheme can report the two independently instead of blanking both. `householder_from_e1` lifts the orthogonal-completion construction that partial-identification schemes use to turn one identified column into a full invertible `P` with `P P' = Sigma`. ProxySVAR keeps its inline copy for now; deduplicating it is a separate change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
Identifies the single structural shock explaining the largest share of one variable's variance over a stated frequency band — the main business-cycle shock of Angeletos, Collard and Dellas (2020), in the frequency-domain form of Faust (1998) and Uhlig (2004). Nothing is searched for. Writing the band variance of a candidate column `p = L q` as `q' M q` with `M = int_B (C_i L)^* (C_i L) dw`, the maximiser is the leading eigenvector of the real symmetric `Re(M)` and the achieved share is `lambda_max / trace`. `M` factors as `L' Re(K) L` where `K` depends only on the posterior, the target and the band — so the frequency sweep is memoised and each `identify()` costs one triple product and one `eigh`, which keeps the per-period loop under time-varying volatility linear rather than quadratic. Design notes: - The band is stated in *periods of the sampling interval*, not radians: quarterly business cycles are `band=(6, 32)`. Radians in the API would be a needless translation step at every call site. - Quadrature is a uniform midpoint rule, which never lands on `w = 0` and so makes `high_period=inf` (the low-frequency band) safe. - One column is identified; the rest are an orthogonal completion labelled `unidentified_*`, so the existing FEVD masking and historical decomposition remainder collapse apply unchanged. - Rows stay in the data's own variable order by construction — there is no ordering parameter, so the permutation bug class cannot arise. - Explosive draws are warned about and kept (blanking them would condition the posterior on stability); draws whose transfer function is numerically singular *inside the band* are blanked, since that is an arithmetic failure rather than an interpretive one. - A near-repeated top eigenvalue means the maximiser is a plane rather than a ray, so the returned rotation within it is arbitrary. That is surfaced as a diagnostic and a warning, not silently fixed. Tests pin exact analytic recovery on a fixture built backwards from the answer, optimality against a brute-force grid over an independently computed spectral density, and the Parseval identity that the full-band share equals the infinite-horizon FEVD share. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
) Explains the scheme as a quadratic form whose maximiser is an eigenvector, documents the periods-not-radians band convention with a frequency conversion table, and gives the how-to a climate example alongside the business-cycle one. Both pages carry the same warning in different words: a band organises variance, it does not name a mechanism. The scheme finds a direction in shock space; whether that direction deserves an economic name is an argument the user has to make elsewhere. The class docstring cites in prose rather than with `{cite:t}` — no docstring in the codebase uses the role, and the reference stubs are not processed by sphinxcontrib-bibtex. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
…#145) Every MaxShare test was VAR(1), where every lag exponent collapses to `e^{-i w}` and the lag index is unobservable. A reversed lag order — a mutation invisible to the entire previous suite — now fails three of the new tests; starting the exponent at `j = 0` fails nine. The test-local reference is generalised to `p` lags and builds the sum term by term with an explicit `e^{-i w j}`, still inverting `F(w)` outright rather than solving, so it shares no code path with the implementation. On a stable 3-variable VAR(2) the reported band share is 0.990355098 against 0.990355117 from an 8193-point trapezoid, and beats 200k directions sampled on the sphere. Also documents that `_last_diagnostics` is overwritten per time slice, so the attrs on `shock_matrix(at="all")` describe the last slice only — which matters for the two L-dependent diagnostics and for nothing else, since the screens are properties of the posterior alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
`MaxShare._spectral_cache` keyed its memoised frequency sweep on `id(posterior)`. An address is not a validity token: once the posterior is collected the address can be reused, and a lookup with a different posterior that lands on it returns another model's accumulator `K`, singular mask and spectral radii silently. Swap it for the weakref-validated `_PosteriorCache` helper added in dead or different referent as a miss. Semantics are unchanged: the sweep still computes once per (posterior, n_lags, target_index), and `_report` still fires its singular/explosive warnings only on a miss, so the per-t identification loop warns once rather than T times. `_warn_weakly_identified` is untouched — the eigenvalue ratio depends on `L`, so it is legitimately evaluated on every call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
thomaspinder
force-pushed
the
feat/145-max-share-identification
branch
from
July 29, 2026 21:10
f06fbe5 to
5d5d731
Compare
…base conflict The rebase conflict resolution merged the new bibliography entry into the blanchardQuah1989 entry, dropping its closing brace. sphinxcontrib-bibtex then failed to parse references.bib, breaking build-docs and docs-linkcheck.
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
New
MaxShareidentification scheme: per posterior draw, find the structural shock explaining the largest share of a target variable's variance over a user-selected frequency band, returned through the standardIdentifiedVARpipeline.M = ∫_B (C_i(e^{-iω})L)*(C_i(e^{-iω})L) dωis Hermitian PSD; for real rotations the maximiser is the top eigenvector ofRe(M)— exact, not an approximation (the two-sided band integral is2·Re(M)). Implemented via the factorisationM = Lᵀ Re(K) Lwhere the frequency accumulatorKdepends only on the posterior coefficients — computed once and cached, so per-Lwork (including the stochastic-volatility per-period path) is a cheap batched eigendecomposition. Row extraction uses the plain-transpose solveFᵀx = eᵢ(the subtlest point — independently re-derived and numerically confirmed by the reviewer).band=(low, high)in periods of the sampling interval (business cycle =(6, 32)quarters;high=infreaches the zero-frequency limit for low-frequency climate bands, safe under the midpoint quadrature which never evaluates ω=0). Nyquist-validated.unidentified_*, flowing through the existing FEVD masking and historical-decomposition remainder collapse unchanged.on_undefined); explosive draws warn-and-keep (the factorisation is exact; only the variance interpretation weakens). Diagnostics — achieved share quantiles, top-2 eigenvalue-ratio (weak-identification indicator, warned at >0.9), singular/explosive counts — rideshock_matrix().attrsand a publicmax_share_diagnostics()._sharesfix as PR feat(identification): long-run (Blanchard-Quah) structural restrictions #183 (copied byte-verbatim so the branches auto-merge; reviewer confirmed byte-equality). Docs: explanation section with labelled equations, a how-to with business-cycle and climate examples plus an explicit "variance dominance is not causal identification" admonition, reference entry, CONTEXT.md term, three bib entries.Closes #145
Review
Planned by a Fable-tier planning agent that verified the math numerically before design (exact analytic recovery; eigen-solution beating a 1024-point rotation grid; Parseval to 5e-16). Independently reviewed with the row-extraction identity and K-accumulator re-derived from scratch and validated on a VAR(2) against an 8193-point trapezoid reference with explicit inversion. Two mechanical P2s fixed in a follow-up commit — including new VAR(2) tests whose necessity was mutation-verified: a reversed lag-order bug invisible to the entire VAR(1) suite fails exactly the three new tests.
Tests
+50 tests: exact analytic recovery on a single-driver DGP (share = 1.0, impact column recovered to machine precision, deliberately non-triangular so the test discriminates from Cholesky); brute-force optimality against an independent quadrature and a 200k-direction sphere sweep; Parseval/FEVD flow-through (frequency-domain share equals time-domain FEVD share to 1e-16); VAR(2) multi-lag pins; singular/explosive/weak-identification screens; full pipeline composition (IRF/FEVD/HD with
unidentified_*handling); NaN-draw FEVD preservation.ruff/ty/ fast suite: all green (577 passed, 29 deselected). New docs content renders clean (the only docs-CI warnings are pre-existing stale stubs from other branches' builds, identical on a clean tree).🤖 Generated with Claude Code
https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV