feat(scenario): probabilistic conditioning and reverse stress via entropic tilting - #213
Open
thomaspinder wants to merge 6 commits into
Open
feat(scenario): probabilistic conditioning and reverse stress via entropic tilting#213thomaspinder wants to merge 6 commits into
thomaspinder wants to merge 6 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #213 +/- ##
=======================================
+ Coverage 95.0% 95.3% +0.3%
=======================================
Files 45 47 +2
Lines 3099 3498 +399
Branches 380 415 +35
=======================================
+ Hits 2945 3336 +391
- Misses 111 114 +3
- Partials 43 48 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
thomaspinder
force-pushed
the
feat/150-entropic-tilting
branch
from
July 29, 2026 07:22
8f40c24 to
4cd35a4
Compare
thomaspinder
force-pushed
the
feat/150-entropic-tilting
branch
from
July 29, 2026 13:41
4cd35a4 to
3739b1e
Compare
Adds the post-hoc reweighting half of #150. `ProbabilityTarget` and `MomentTarget` join the scenario vocabulary; `_tilting.py` solves the minimum-relative-entropy problem over existing forecast draws (closed form for a single event probability, log-sum-exp-stabilised convex dual otherwise) and owns the weighted quantile / HDI summaries. `ForecastResult.tilt()` and `ConditionalForecastResult.tilt()` (which `ScenarioResult` inherits) return a frozen `TiltedForecastResult` holding the parent draws by reference plus the weights, the per-target requested/achieved vectors, and the ESS / relative-entropy diagnostics. Chaining is the supported way to mix hard and soft conditioning: pins hold pathwise on every draw, and reweighting never moves a draw, so they survive any tilt. `ReverseStressResult` and `plot_reverse_stress` land here too so the result surface stays in one file; the method that produces them follows. Refs #150 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
…vent Completes #150's structural half. `_scenario.structural_forecast_draws` returns unconditional forecast draws together with the structural shocks that generated them — neither `forecast()` nor the existing engines expose those — reproducing `structural_scenario_engine`'s no-ingredient branch under the same RNG stream contract, so matched seeds nest exactly. `IdentifiedVAR.reverse_stress(variable, threshold, steps, ...)` runs scenario analysis backwards: name the outcome, get the average structural configuration that delivers it. The event is imposed by entropic tilting (`probability=1.0` is exact conditioning) and the cocktail is the tilted-weighted mean of the retained shocks, so it inherits the model's own shock correlations rather than needing a projection norm. Its magnitude `q = ‖E_w[ε]‖²` is in the same one-standard-deviation units as the scenario plausibility statistic; `q_cal` extends the ADPRR binomial calibration to the tilt's now-finite relative entropy. The exogenous-regressor guards common to the forecast-side methods move into `IdentifiedVAR._validate_forecast_exog` (pure extraction). Refs #150 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
Documents the #150 layer. New how-to `probabilistic-conditioning` covers stating a probability target, reading ESS / achieved-vs-requested before trusting the bands, chaining a tilt after `conditional_forecast`, and the reverse-stress walkthrough. ADR-0009 records the decision (post-hoc reweighting rather than a second solver; chaining rather than mixing; closed-form/dual split; cocktail as tilted conditional mean of retained draws) and the rejected alternatives. CONTEXT.md gains entropic tilting, probability/moment targets, effective sample size, and reverse stress, and the plausibility entry now points at the relative entropy as a real quantity rather than a reserved one. Reference pages pick up the new targets, result objects, and plots; bib adds Robertson-Tallman-Whiteman (2005) and Krueger-Clark-Ravazzolo (2017). Refs #150 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
Review follow-up on #150. `structural_forecast_draws`'s docstring claims matched-seed nesting under time-varying volatility, but the only coverage ran under `Constant()`, where `_forecast_shock_matrices` consumes no randomness — so a reordered stream would have gone unnoticed in exactly the branch that can break. Adds a pin against `structural_scenario`'s no-ingredient branch using the existing `_RngConsumingVol` stub; swapping the two RNG consumers fails the new test and passes the old one. Also corrects the reverse-stress how-to: `q_cal` calibrates the tilt's relative entropy, not the cocktail magnitude `q`. Refs #150 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
#237 made VARData reject exog columns that are constant within the sample, so the all-ones "const" column this guard used no longer constructs. Swap it for a draw from the shared `rng` fixture; the test only needs the model to carry *some* exogenous data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV
thomaspinder
force-pushed
the
feat/150-entropic-tilting
branch
from
July 29, 2026 21:01
4b41c78 to
c17f055
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
Soft probability targets over existing forecast draws, and the inversion into reverse stress — extending the hard-conditioning stack (ADPRR/Waggoner-Zha) per issue #150:
ProbabilityTarget/MomentTargetjoin the scenario vocabulary;ForecastResult.tilt(targets)/ConditionalForecastResult.tilt(targets)return a frozenTiltedForecastResultcarrying minimum-KL tilting weights over the parent draws (held by reference), achieved-vs-requested targets, Kish ESS (+ degeneracy warning), and the exact KL divergence. Hard and soft conditions compose by chaining — pins hold pathwise on every draw, so preservation under reweighting is a theorem, not a code path (pinned bit-exactly in tests anyway).IdentifiedVAR.reverse_stress(variable, threshold, steps, ...): baseline event probability, the tilted distribution, and the most plausible shock cocktail — the tilted-weighted mean of retained structural shock draws, via a new_scenario.structural_forecast_drawsthat reproduces the scenario engine's RNG stream draw-for-draw (matched-seed nesting pinned bit-exactly under both constant and time-varying volatility, the latter mutation-verified). Plausibility reported asq = ‖cocktail‖²plusq_cal— the ADPRR calibration applied to the tilt's relative entropy, explicitly documented as an extension, not a citation (ADR-0009).np.quantile(method="hazen")under uniform weights; min-width weighted HDI pinned againstaz.hdi). Fan-chart and cocktail plots included.Closes #150
Review
Planned by a Fable-tier planning agent; independently reviewed with the two-mass solution, dual gradient/Hessian, and the RTW Gaussian oracle re-derived from scratch and confirmed numerically. The reviewer endorsed all seven implementation deviations — including the implementer's catch that the plan's own weighted-quantile test was mathematically inconsistent (the mid-point convention is not replication-consistent; resolved via the exact Hazen equivalence). Two P2s (a missing time-varying matched-seed regression pin — verified to hold at diff 0.0 before pinning — and a one-word q_cal doc misattribution) fixed in a follow-up commit.
Tests
75 new tests: exact analytic oracles (two-mass weights/ESS/KL at 1e-13; RTW λ against an independent
brentqroot-find; dual vs direct SLSQP primal), hard-pin preservation bit-exact, RNG nesting pinned three ways plus the mutation-verified time-varying pin, weighted-quantile/HDI convention pins, and every guard path. Fast suite: 602 passed, 29 deselected.ruff/ty/docs build clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01Egjd7ToFeb9TQqFnfRQZxV