Repository navigation
Per-equation exog_prior_scale and a truncated own lag for one latent series with lags=1 (0.1.4) - #398
Merged
Merged
Conversation
Strict-xfail tests for a tuple `exog_prior_scale`: through the graph `build_in_model` registers (each row of `B_exog`'s prior sd scales with its own entry, the latent equation gets its own scale, a wrong-length tuple raises naming the expected length), through `fit`, and through `VAR` construction (non-positive and non-finite entries rejected) and its JSON round trip. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`VAR.exog_prior_scale` takes a float or a tuple with one entry per endogenous equation, in `endog_names` order. A field validator requires every entry to be positive and finite, so the scalar and the tuple are validated the same way. The tuple's length is checked where the exogenous prior is built, against the number of endogenous variables; row `i` of the prior sd is `scale_i * sigma_i / s_j`, and the scalar path is unchanged. `fit`, `prior_predictive` and `build_in_model` all go through that construction. The docstring drops the stale "applies only to `fit`" and ADR-0012 records the per-equation form. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
New tests, strict-xfail where the current graph cannot pass them: TestTruncatedOwnLag (B a Deterministic of B_free and latent_own_lag, a smooth boundary, jitter inside the region without a retry, no initial value for own-lag means 1 and -0.9, a truncated prior predictive, JAX, and a slow run under the default own-lag mean), and the (1, 1) cell of the joint-density reference, which now also checks the coefficients' density against the Minnesota prior less the truncation constant. Passing already, so unmarked: the own-lag mean 0 case of the initial-value test, and FittedVAR.from_posterior on a synthetic posterior in the new layout. The Potential tests move off the (lags=1, one latent) cell: the inside/outside grid drops it, the jitter-retry test uses two latent series, and the non-finite-block test uses (2, 1) and (1, 2). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ne latent series with lags=1 (#395) For one latent series with lag order 1 the latent block is the own lag B[0, 0] alone and its stationary region is |B[0, 0]| < 1. build_in_model now registers the own lag as latent_own_lag, its Minnesota Normal truncated to (-1, 1) under PyMC's interval transform, every other lag coefficient as the free Normal block B_free, and B as a Deterministic assembled from the two under its existing name and (var, coeff) dims. No Potential and no initial value are registered in that case, so the boundary is smooth, the own lag starts at 0 whatever its prior mean, jitter stays inside the region, prior predictive draws are stationary, the host model is always clonable, and there is no eig in the graph. The posterior equals the truncated prior the Potential imposed. Every other case is unchanged. _register_coefficients owns B and the latent-block constraint in both forms. The build_in_model docstring tells the two cases apart, ADR-0017 records the decision, ADR-0016 points at it, and the glossary entry says how the constraint is enforced in each case. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review fixes: `B_free` is described as one-dimensional rather than "flat", which reads as uninformative in a Bayesian library; ADR-0017 names #397 as the follow-up for the general latent block; ADR-0016's "what it does not do" bullet says the constrained case is exempt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Implements spec #393: the two root fixes behind pymc-labs/pymc-marketing#3293 (review items P1.1 and P1.2), for release 0.1.4.
Closes #393
Closes #394
Closes #395
Per-equation
exog_prior_scale(#394)VAR.exog_prior_scaletakes a float or a tuple with one entry per endogenous equation, inendog_namesorder, mirroringMinnesotaPrior.own_lag_mean. A field validator requires every entry to be positive and finite, so the scalar and the tuple are validated the same way (a scalarinf, whichgt=0let through, is now rejected). The tuple's length is checked where the exogenous prior is built, against the number of endogenous variables. Rowiof the prior sd isscale_i * sigma_i / s_j; the scalar path gives the same numbers as before.fit,prior_predictiveandbuild_in_modelall go through that construction. ADR-0012 records the amendment; the docstring drops the stale "applies only tofit".Truncated own lag for one latent series with
lags=1(#395)For exactly one latent endogenous series with lag order 1 the latent block is the own lag
B[0, 0]and the stationary region is|B[0, 0]| < 1, so the constraint is now exact and smooth:latent_own_lagis the own lag's Minnesota Normal truncated to (−1, 1) under PyMC's interval transform,B_freeis a flat Normal over every other lag coefficient, andBis a Deterministic assembled from the two under its existing name and(var, coeff)dims. No Potential and no initial value are registered in that case; every other case (no latent series,lags > 1, several latent series) is unchanged. The posterior is the same truncated prior the Potential imposed, up to its normalising constant.Consequences: the own lag starts at 0 whatever its prior mean, so the host model is clonable for any own-lag mean, Impulso's default of 1 included; jitter stays inside the region; prior predictive draws of the own lag are truncated, so prior-predictive latent paths are stationary; no
eigin the graph, so the JAX backend differentiates the model for this case (#378 stays open for the Potential cases). On the slow one-latent sampling setup under the default own-lag mean of 1, the Potential gave 268–300 divergent draws of 400 on three seeds; the truncated own lag gives none, with R-hat at most 1.011 on the own lag._register_coefficientsownsBand the latent-block constraint in both forms, written as a free block plus a constrained block so that a general parametrisation of the latent block (#397) replaces the constrained block without a second rewrite. ADR-0017 records the decision; ADR-0016's initial-value, Potential and bounded-own-lag bullets point at it; the glossary entry for the stationarity constraint says how it is enforced in each case.Commits
Tests first (strict xfail), then the implementation that unmarks them, per ticket: four commits.
Verification
uv run python -m pytest -m "not slow" -q,ruff check,ruff format --check src tests,ty check: clean on the merged branch; all slow tests intests/test_build_in_model.pypass.main:ruff format --check .fails on 11 Markdown files (ruff 0.17.2 formats Python blocks in Markdown); none are touched here. The existing JAX test for two latent series withlags=2fails onmaintoo when JAX is installed (JAX samplers fail on models with latent series: the latent_stationarity gradient goes through eig #378); CI skips it.🤖 Generated with Claude Code