qpm_singular_F. Set
options(qpmR.use_cpp = FALSE) to force the R path;
qpm_use_cpp() reports which is in use.N^2 x N^2 system — O(N^3) per iteration
instead of O(N^6). This turned out to matter more than the filter: it is
39x faster on the Czech model and also speeds up
model_properties(), qpm_rule_eval() and
qpm_identify(), which all solve the same equation.A, B, C or
D that each coefficient lands in — is now computed once per
model and cached on it. Estimation re-solves the same equations
thousands of times with different numbers, so only the values are
recomputed: build_first_order() went from 27.5 ms to 1.7 ms
(16x) and qpm_solve() from 37 ms to 8.3
ms. The cache is rebuilt automatically if it is missing (a model
serialised by an older version) or no longer matches its equations.
Linearity is checked once at construction rather than on every solve,
since it is a property of the equations.eigen_table() is assembled
directly from the QZ output rather than through
data.frame(), whose constructor, reorder and row-name reset
were about a fifth of a posterior draw on small models even though the
estimation objective never reads the table. The table itself is
unchanged.qpm_risk() / risk_log(): express a balance
of risks. Bands become two-piece normal, so the mode stays on
the model’s projection while the mean shifts by the stated skew
and total variance is held fixed — a skew redistributes risk rather than
adding it. Fan charts centred on the mode, as published fan charts are.
Unlike add_judgment(), which moves the projection and
back-solves the supporting shocks, this changes only the shape of the
distribution around an unchanged path.qpm_rule_eval(): score alternative policy rules over a
grid by the unconditional loss, computed exactly from the stationary
covariance rather than by simulation, and trace the inflation-output
variability frontier. Rules that fail Blanchard-Kahn are reported as
indeterminate or explosive rather than silently dropped.qpm_counterfactual(): replay history with shocks
switched off or scaled — “what if the central bank had simply followed
its rule?”. Replaying the unmodified shocks reproduces the smoothed
history to machine precision, which the function checks.qpm_compare_models(): compare model behaviour
(impulse responses and implied moments), complementing
qpm_diff(), which compares structure.qpm_disaggregate(): temporal disaggregation of annual
data to quarterly by Denton-Cholette or Chow-Lin, both satisfying the
aggregation constraint exactly — the first step for the many economies
that publish national accounts only annually.fevd(): forecast error variance decomposition — how
much of each variable’s forecast uncertainty each structural shock
accounts for, at every horizon. Shares sum to one by construction
(verified to 1e-10 in the tests), exogenous processes come back as
entirely own-driven, and the decomposition is well defined for unit-root
models even though the variances themselves are not.model_properties(): the standard calibration check —
model-implied standard deviations and autocorrelations from the
stationary covariance, the shock dominating each variable’s
unconditional variance, and the same statistics computed from data
alongside, with a warning when model and data volatility differ by more
than a factor of two.logLik() and nobs() for
filtrations and estimates (which makes AIC() and
BIC() work), residuals() (one-step
innovations, standardised innovations, or smoothed structural shocks)
and fitted() for filtrations, vcov() and
confint() for estimates, and summary() methods
returning data frames for both.write_dynare(): export any model as a Dynare
.mod file. The original equations are exported rather than
qpmR’s internal first-order system, so Dynare builds its own auxiliary
variables for long leads and lags and the two implementations agree only
if both are right. The test suite now pins qpmR’s impulse responses to
golden files generated by Dynare 6.0: across 4080 points (12 shocks x 17
variables x 20 quarters) the largest discrepancy is 1.4e-14, and steady
states agree to 8e-14. The food-block model agrees to 1.7e-14, which
independently validates add_block(). Regenerate the golden
files with data-raw/dynare_golden.R.-Inf as the largest
stable root.qpm_condition(),
add_judgment(), scenarios) are computed from the
conditioned rows and the diagonal of the stacked-path covariance rather
than from the full (H N) x (H N) matrix, which was the
package’s one large matrix product and imposed a size cap above which
bands fell back to the unconditional ones. The cap is gone; band values
agree with the previous formula to 1e-9.write_dynare() no longer writes a file by default:
file = NULL (the new default) returns the Dynare source as
a character vector, and a file is written only when a path is given.
qpm_report() and save_round() likewise require
an output path and a store directory rather than defaulting to the
working directory.qpm_estimate() reports progress with
message() rather than cat(), so
suppressMessages() silences it.seed given to simulate() or
qpm_estimate() no longer disturbs the caller’s random
number stream: the previous RNG state is restored on exit, as
stats::simulate() does for linear models.\donttest{} examples were resized to
run in a few seconds each.First stable release. The full forecasting-and-policy-analysis workflow now runs end to end — data, filtering, gaps, model, baseline, judgment, policy, scenarios, rounds, revisions, verification, report — and is exercised on a real quarterly dataset for Czechia shipped with the package.
This release adds the reporting and audit layer and country-adaptation blocks on top of 0.4.
qpm_report(): turns a round into the document a policy
meeting is run from — executive summary with the numbers filled in,
forecast table and fan charts, filtered gaps, shock decomposition, the
judgment ledger with its implied shocks, an optional revision
decomposition against the previous round, and a reproducibility
appendix. The .Rmd source is always written (institutions
replace the template’s text, not its plumbing) and rendered to
HTML/PDF/Word when pandoc or Quarto is available; where neither is —
air-gapped forecasting machines, bare CI runners — it says so and
returns the source rather than failing.chart_pack(): the standard round chart set (forecast
fans, filtered latent states, shock decomposition, monetary
transmission) as a multi-page PDF or numbered PNGs.verify_round(): re-runs an archived round from its own
contents and checks that the published numbers come back, reporting the
largest deviation, the worst variables, and any qpmR version drift. When
the round is loaded from a store it also checks the human-readable CSV
sidecars against the object, so a hand-edited audit trail is
detected.qpm_block() / add_block(): reusable
bundles of variables, shocks, parameters and equations that adapt a
template to a country without forking it. Equations whose left-hand side
names an existing variable replace that variable’s equation; equations
for newly declared variables are appended. Blocks compose, and each one
is recorded in the model’s meta$blocks.block_food_cpi(): headline CPI split into food and
core. The Phillips curve moves to core, food gets its own persistence,
stronger exchange-rate pass-through, and error correction on the
relative food price, and headline becomes the weighted identity. Food is
30-50 percent of the basket across most of sub-Saharan Africa and South
Asia, where a single-inflation model is unusable; a food supply shock in
this block raises headline while leaving core essentially untouched,
which is the relative-price story policy should look through.block_fx_intervention(): a leaning-against-the-wind
intervention rule entering the UIP block, so one model spans a continuum
of exchange-rate regimes — intensity = 0 reproduces the
free float exactly, moderate values a managed float, large values
approach a peg (the peak exchange-rate response to a risk-premium shock
falls from 0.97 to 0.53 to 0.08 across those settings).qpm_template() gains the "bkl_food" and
"managed_fx" shortcuts.qpm_diff(): structural comparison of two models —
variables, shocks, parameters and equations added, removed or changed,
plus recalibrations — so a country team’s customization is reviewable as
a diff rather than a fork.The estimation layer is complete.
qpm_identify(): Iskrev-style local identification
diagnostics before any sampling — numerical Jacobians of the solved
model (solution level) and of the observables’ population moments
(moment level, stationary models) with respect to the chosen parameters.
Reports parameters with no effect, rank-deficient combinations, and
near-collinear pairs that are only jointly identified. Unit-root models
get the solution-level check with an explanatory note.marginal_likelihood(): log marginal likelihood by the
modified harmonic mean (Geweke 1999) across truncation probabilities
with a stability spread, plus a Laplace approximation at the mode in
transformed space as a cross-check. Differences across models on the
same data are log Bayes factors. truncate() priors are now
renormalized numerically at construction so they contribute proper
densities.qpmR-estimation: priors, the AR(1)
estimation laboratory, identification, Bayes factors, and the full Czech
estimation with its results discussed (including the honestly
weakly-identified policy-response coefficient).priors(): the prior mini-language.
normal(), beta(), gamma(),
invgamma(), uniform(), and
truncate() exist only inside priors()
(evaluated in a controlled environment), so base R’s beta()
and gamma() functions are never masked. Beta/gamma/
inverse-gamma use the mean/sd parametrization economists write
down.qpm_estimate(): Bayesian estimation of any subset of
structural parameters and shock standard deviations over the
Kalman-filter likelihood. Posterior mode in transformed (unconstrained)
space, BFGS Hessian as the proposal seed, adaptive random-walk
Metropolis (Haario-style covariance adaptation during burn-in,
acceptance targeted at 0.25), multiple sequential chains, split R-hat
and Geyer effective sample sizes. Draws violating Blanchard-Kahn get
zero weight (the usual determinacy truncation).
method = "mle" reuses the machinery with flat priors on the
declared supports.plot() overlays prior and
posterior densities. coef() extracts point estimates;
apply_estimate() recalibrates the model at them.posterior_forecast(): fan charts integrating over the
posterior – each draw re-solves the model and re-filters the data, so
the bands combine future-shock and parameter uncertainty.kalman_loglik(), a storage-free filter pass
for estimation speed.The policy-analysis layer is complete.
qpm_round(): one replayable artifact per forecast –
model, calibration, data vintage, filtration, and the conditioned
forecast together. [qpm_condition()], qpm_scenario(), and
add_judgment() apply to rounds directly.save_round() / load_round() /
list_rounds(): a plain-directory round store; each round is
a self-contained round.rds plus human-readable CSV sidecars
(forecast, data, calibration, judgment) for auditing without R.compare_rounds(): the revision decomposition. The
forecast revision between two rounds is split into parameters, data
revisions, new data (outturns), conditions, and judgment by re-running
the full pipeline swapping one ingredient at a time. Contributions
telescope (they sum to the total exactly); the endpoints are verified
against the archived rounds, so a version drift is reported rather than
silently absorbed. Judgment overtaken by data (a conditioned quarter
that has become an outturn) is dropped and reported. Waterfall printing
and stacked revision charts.next_quarters() exported for quarter-label arithmetic;
formulas in models are stored without environments, keeping serialized
rounds small.qpm_condition(): hard conditional forecasts. Impose
paths on any variables at any horizons; qpmR backs out the minimum-norm
structural shocks (in standard-deviation units, optionally restricted to
instruments) that deliver them. The
anticipated switch is explicit: TRUE means the
conditioned path is announced at the start of the forecast and
expectations react ahead of it, FALSE means
period-by-period surprises. Anticipated propagation uses the exact news
recursion F_j = N^j Q, N = -(AP+B)^{-1} A,
verified in the tests against a brute-force perfect-foresight solve. Fan
bands are recomputed as the Gaussian conditional distribution given the
conditions (zero width at conditioned points). Announced rate holds
reproduce the Laseen-Svensson (2011) anticipated-path reversal, as they
should.qpm_scenario(): shock-based alternative scenarios
(announced or surprise), additive on any forecast.add_judgment() / judgment_log(): the
judgment ledger. State the adjustment in percentage points; qpmR
back-solves the supporting shocks, keeps the forecast model-consistent,
records author, timestamp and rationale, and flags judgment requiring
shocks above two standard deviations. Entries are stored as absolute
targets and the full condition/judgment set is re-solved jointly, so the
ledger is replayable.2026-Q3 style)
inherited from the filtration; conditions and judgment can be addressed
by label or by horizon (h3). Conditioned and judgment
points are marked on fan charts.The filtration layer is complete.
qpm_filter(): Kalman filter + RTS smoother over the
solved model. Jointly infers every latent state (output gap, neutral
rate, equilibrium exchange rate, trends) and the historical structural
shocks from any subset of observed variables, with missing data and
ragged edges handled naturally. Innovation diagnostics (Ljung-Box,
outlier flags) are computed and printed. The likelihood is tested
against the exact closed-form Gaussian likelihood.qpm_decompose(): exact historical shock decompositions
of the smoothed history (additivity verified internally), with
stacked-bar plots.state_space(): exports the exact T,
R, Z, H, Qc,
P1 matrices used internally, so other estimators can build
on qpmR.qpm_forecast() accepts a qpm_filtration as
from, forecasting from the smoothed end-of-sample state
with the smoothed history kept for fan charts.unit_tol of the unit circle count as stable (the usual
qz-criterium convention) and are reported separately. Steady states with
free trend levels use a minimum-norm least-squares normalization; a
drifted random walk (no fixed point) is a typed
qpm_no_steady_state error explaining the balanced-growth
limitation.qpm_filter()/state_space() switch
automatically to an approximate diffuse initialization (damped-Lyapunov
large-variance prior, kappa = 1e6) when the model has unit
roots. Exact Durbin-Koopman diffuse recursions remain on the
roadmap.qpm_template("bkl", trends = "rw"): equilibrium real
exchange rate and potential growth as driftless random walks; the
neutral rate stays anchored by real interest parity (a free random walk
there would make steady-state gaps indeterminate).dy_obs = dy_bar + 4 * (y_gap - y_gap[-1])), so the model
filters on actual national-accounts data without modelling the level of
potential output.czechia: quarterly Czech data 1996Q1 onward in model
units (CPI inflation QoQ and YoY, 3M PRIBOR, real CZK/EUR, GDP growth,
EURIBOR, euro-area HICP), compiled reproducibly by
data-raw/czechia.R from FRED/OECD/Eurostat/ECB public
endpoints. Filtering it with the rw template reproduces the known
history: the pre-GFC boom, the 2009 and 2013 recessions, the COVID
crater, the koruna’s trend real appreciation, and the post-GFC fall in
potential growth (pinned in the test suite).plot() on decompositions gains a
periods window argument.qpm_model(),
x[-1] / E(x[+1]), automatic auxiliary states),
Klein/QZ solver with Blanchard-Kahn diagnostics, steady states,
irf(), simulate(), qpm_forecast()
with fan bands, qpm_lint(), and the canonical
Berg-Karam-Laxton small-open-economy template
qpm_template("bkl").