1.2. What’s new

This page records user-visible changes to the RISE Toolbox – new features, behaviour changes, and things you may need to know when upgrading. Newest entries go on top.

Note

This log is being (re)started together with the documentation overhaul. It is not yet exhaustive of the toolbox’s full history; older changes are summarised in passing where relevant.

1.2.1. Current release

  • New rise.microfound – write the economics, not the algebra. A model definition builder: declare the atoms and each agent’s optimization problem (period objective, choice variables, named constraints) in MATLAB, and RISE derives the first-order conditions symbolically and generates a plain RISE model – no @optimization_problem block, nothing hidden. Hand the builder to dsge_model directly, as a cell array via code(p), or as a written .rs file via rs(p,file); calibration and solving remain dsge_model’s job. Derived Lagrange multipliers are named <agent>_lambda_<constraint> and every generated equation is tagged with its origin. Switching parameters in an agent’s problem yield the correct probability-weighted switching FOCs automatically – a capability gEcon lacks. Validated by re-authoring nine gEcon example models. Heterogeneous-agent (HANK) support is a planned extension. See Deriving models with rise.microfound.

  • Leads greater than one are reduced without violating Jensen’s inequality. When you write a variable two or more periods ahead, RISE rewrites the model so that no equation carries a lead greater than one. Terms that are nonlinear in a forward variable (e.g. exp(x{+2})) are now wrapped in an auxiliary carrying the whole term’s one-period-ahead conditional expectation, so E[g(x)] is preserved rather than collapsed to g(E[x]); affine leads (a constant-weighted sum such as 0.08*V{+2}) use the ordinary atomic chain. This matters only beyond first order – at first order and under perfect foresight there is certainty equivalence and the wrapping is inert. Every auxiliary keeps a contemporaneous variable, so the contemporaneous matrix A0 stays non-singular and iterative solvers (cyclic reduction, the mfi optimal-policy iteration) still apply. The old violate_jensen_inequality option and its whole-equation backshift alternative have been removed: there is now a single, always-correct reduction, and linear models and derived optimal-policy first-order conditions are reduced exactly as before. A note deriving the effect in closed form (including where the sigma^2 term comes from) ships under development/jensen_memo.

  • New ``get(m,’options’)``. Returns a model’s current settable options and their values – the get-side companion to set. Handy for inspecting what a model is actually configured with.

  • Model objects now record the RISE version that built and estimated them. Every constructed model carries a provenance stamp, queryable with get(m,'rise_version') – a struct with fields construction (the version that parsed/built the object) and estimation (the version that ran estimate, '' until the model is estimated). The shortcuts get(m,'construction_version') and get(m,'estimation_version') return the individual strings, and summary(m) now prints a Built with RISE / Estimated with RISE line. The stamps are frozen into the object and survive a saveObj/loadObj round-trip, so a model built under one version and estimated under a later one reports both faithfully. This is distinct from rise.version, which reports the currently installed toolbox. Objects saved before this change load unchanged and report '' (unknown) for the missing stamps.

  • MCMC correctness: independence Metropolis-Hastings and DRAM fixed. Two procedural defects in the samplers were corrected:

    • The independence sampler drew its proposal as a random walk centered on the current state rather than from the fixed proposal distribution, while the acceptance applied the independence-sampler density correction – so the chain targeted pi(x)/q(x) instead of pi(x). It now draws from the fixed distribution. (Note: tuning an independence sampler to a fixed random-walk acceptance target such as 0.234 is not meaningful – its acceptance is governed by how well the fixed proposal matches the target.)

    • The delayed-rejection (DRAM) second-stage acceptance omitted the 1/adascale^2 scaling of the first-stage proposal covariance in its proposal-density ratio, biasing the second-stage acceptance whenever adascale ~= 1 (the default 2.38/sqrt(d)).

  • Posterior density plots: complex-valued bivariate kernel density fixed. The multivariate kernel-density bandwidth used a matrix square root cov(X)^0.5 that turned complex when the sample covariance of near-degenerate draws acquired a tiny negative eigenvalue from roundoff, making the whole density complex and breaking surf/mesh. It now uses a real symmetric positive-definite square root – identical to the old value when the covariance is well conditioned, real and finite when it is not.

  • set: combined optimal-policy options no longer silently dropped (MPE + discretion). The internal slot that pushes the special policy parameters (ole, commitment) was a scalar struct overwritten on each match, so passing both solve_policy_equilibrium and solve_policy_type in a single set/solve call dropped one of them – MPE + discretion actually ran OLE + discretion. The slot now accumulates its entries, so combined optimal-policy option pushes are all honored.

  • Unified predict-step for filtering: OBC anticipation and observed expectations now share one code path. Two long-standing gaps in the filtering layer have been closed, in the same surgical edit to a single shared predict-step closure:

    • Filtering under OBC with an anticipation horizon. With solve_shock_horizon > 0 on a model that declares simul_constraints, the filter’s predict step now runs a constraint-aware multi-step forecast across the full k+1 anticipation window via obcas.optim.forecast_with_constraints, and keeps only the one-step-ahead result. The previous fast path (one-step unconstrained, then check, then optionally invoke the complementarity engine) is preserved bit-identical for the no-anticipation case (shock_horizon == 0), so locked filtering baselines on OBC models do not move.

    • Filtering with observed expectations. Multi-page conditional data (forecast_cond_endo_vars set, ts observables with more than one page so each t carries observed values for t+1, t+2, ...) now enters the same multi-step solve as equality restrictions. The previous dispatch to msre_kalman_cell_real_time was broken: that function had been moved into +archive/ and was no longer on the MATLAB path, so any user calling filter on a model with forecast_cond_endo_vars and multi-page data hit an undefined-function error. The dispatch is removed; the regular regime-switching Kalman filter handles real-time conditioning natively by handing per-t target values into the predict-step closure, which pre-fills them at the t+1 slot of the multi-step forecast array and lets obcas.optim.forecast_with_constraints solve for the stochastic shocks that satisfy them.

    Both cases use the same gating: when an anticipation horizon is active or per-t conditioning targets are supplied, the predict step routes through the multi-step solver; when neither is active it falls through to the fast path. New cpf auto-route gate also excludes any non-zero shock-horizon model so the wrapped constant-parameter filter is not selected on models whose impact matrix is 3-D.

  • Documentation build no longer leaves a dated PDF backup. Previously every successful update_documentation.m run renamed help/risetoolbox.pdf to help/risetoolbox <date>.pdf before the new PDF was copied in, as a defensive fallback against bad builds. In practice it just cluttered help/ and the repo with dated copies. The fallback has been removed; copyfile overwrites in place, and bad builds can be reverted via git like any other commit.

  • bvar_dsge estimation: fixed regression introduced by the @epilogue auto-append work. The recent rewiring of @transition_functions / @epilogue into every output engine (see the entry below) introduced a method-dispatch failure on the bvar_dsge likelihood path. The bvar_dsge solver leaves its internal dsge representation in struct form (rather than as a @dsge object), and the added call to append_epilogue inside the Kalman save-filters step then could not resolve. The runtime evaluator has been moved out of @dsge into the dsge_tools.epilogue namespace so it dispatches uniformly on either representation. No change to user-facing API; bvar_dsge estimate, filter, forecast and mode_curvature go through the standard paths again.

  • sstate_endo_param_swap: target values now preserved to full double precision. The internal constraint equation built from a sstate_endo_param_swap = {endogName, value, paramName} triple was rendered with sprintf('%g', value), which truncates to six significant digits. For irrational targets such as log(1/3), this baked a ~2e-6 rounding error directly into the residual the solver was driving to zero. The render now uses %.17g (round-trip precision for IEEE-754 doubles), so constraints with log, exp, pi and similar transcendental targets solve to machine precision again.

  • @transition_functions and @epilogue: fully wired into all output paths. Time-varying transition probabilities and post-solve derived series declared in @transition_functions / @epilogue blocks now appear automatically on the output of every engine that produces a simulated ts database: simulate, irf, forecast, filter, perfect_foresight, counterfactual, resimulate, and brute_force_simulate. Previously only simulate / irf / forecast (which share a common engine) auto-appended them; users on the other paths saw the series silently missing and had to call obj.append_epilogue(db) by hand.

    Also fixed in the same round:

    • Switching parameters now follow the realised regime path. A switching parameter referenced on the RHS of an @epilogue or @transition_functions equation used to take its regime-1 value everywhere — wrong whenever the realised regime trajectory included other regimes. The runtime now builds a ts indexed by db.regime for switching parameters, so the value at each period follows the realised path. Const-chain parameters are unchanged.

    • #-definitions are usable on the RHS. A #-definition like # mid_p = (p_lo + p_hi)/2; can now be referenced by its bare name inside a @transition_functions or @epilogue equation; previously the name was left unbound and the runtime crashed on first call.

    • Multiple ``@transition_functions`` / ``@epilogue`` blocks in the same model file are stitched together — you can split a chain’s two probabilities across two blocks or interleave them with other declarations.

  • Perfect-foresight solver: faster and more robust. The deterministic perfect_foresight solver gained several improvements, all behaviour-preserving at their defaults (see Deterministic and quasi-deterministic solutions):

    • the default 'sparse' solver now judges convergence on the max-abs (infinity-norm) residual rather than the 2-norm. The 2-norm of a stacked residual grows like \(\sqrt{T\times\text{endo\_nbr}}\), so a fixed tolerance was unreachable for long horizons even when every equation residual was tiny; the max-abs criterion is per-equation and size-independent.

    • a new JacobianUpdate option on the 'sparse' solver enables modified-Newton factorization reuse: factorize the stacked Jacobian once and reuse the LU factors (inf), or refresh every K iterations; 1 (default) is full Newton. On transition problems this leaves the solution unchanged while substantially cutting the time in the linear solve.

    • a new simul_reuse_sstate option (default false) skips the internal steady-state re-solve when the model is already solved – for pre-solved / repeated / extended-path runs.

  • Steady-state solve: false “could not solve” failures fixed. The steady-state success verdict compared the residual against the solver’s iterate tolerance (sqrt(eps)), which sits right at the achievable residual floor, so well-converged solutions on some models (e.g. the canonical ramst) were falsely reported as failures. The acceptance verdict is now decoupled from the solver tolerance via a floor, judged on the max-abs residual; achieved precision is unchanged. The engine also refreshes the parameter/definition values on every solve, so parameter changes (estimation, calibration) propagate correctly into the steady state.

  • Non-cooperative games: Markov-perfect equilibrium (MPE) as a solve-time option. Optimal-policy models that declare more than one policymaker (Nash or Stackelberg) now expose a second equilibrium concept alongside the historical open-loop equilibrium (OLE):

    m = solve(m, 'solve_policy_equilibrium', 'OLE');   % default
    m = solve(m, 'solve_policy_equilibrium', 'MPE');
    

    Under OLE each player optimises against the opponent’s path; under MPE each player optimises against the opponent’s policy function. For single-player models MPE and OLE coincide (Proposition 3); for multi-player Nash on the standard benchmark, the gap matches the theoretical reference. Internally the MPE branch adds a small cross-player chain-rule term to each predetermined-state FOC row – no new endogenous variables, the augmented system has the same size as the OLE-only system, and at the default solve_policy_equilibrium='OLE' results are byte-identical to releases that did not have the option. See Optimal (Ramsey) policy for the conceptual details and the small restrictions (stochastic replanning rejects the option; @no_u_turn = true makes the MPE addition a no-op).

  • Very large models now parse end-to-end. Models with thousands of equations that previously could not be loaded by rise(...) at all (notably the marco.rs / “very large quest” model used internally) now parse in roughly three minutes on a desktop machine. The fix removed several O(n^2)-in-#equations hotspots in the post-parse_model pipeline (shadowize_engine, occurrences, regexpmatch, update_leads_and_lags, create_functions, initialize_symbols, stamper.status), switched rsymbdiff.occurrence from a dense 1 x nwrt logical mask to a sorted index list (peak memory on the same model dropped from ~9.6 GB to ~4.6 GB), and broke the reference cycle in the symbolic-differentiation CSE backend that used to keep MATLAB’s cycle-collector busy for tens of minutes at workspace teardown. The validation suite (model_creation, modeling_language, optimal_policy, steadyState, loglinear, solvers_generic, perturbation, dynare) passes with no regressions.

  • Reporting figure x-axis fixed. Figures produced through rprt.create_figure with highlighted date ranges (e.g. highlighted_dates) were rendering with the data crushed into a thin strip at the left edge of the plot and the x-axis extending to year ~4016. Cause: fill was receiving rise_dates.dates values that MATLAB silently downcast to datenum against a datetime axis. The highlight band is now converted to datetime before being passed to fill, so the patch lands in the same coordinate system as the line plot and xlim stays at the real data range.

  • Progress monitor: explicit close handle. progress_monitor now returns a fourth optional output, a function handle that tears down the GUI window (and tidies up the cancel-flag file) on demand. The built-in auto-close still fires when every chain reaches its announced iteration count; the new handle covers the case where the work terminates early (MaxTime / MaxFunEvals / an exception in one chain), where the auto-close condition would never have held and the GUI used to orphan.

  • ``@log_variables`` / ``@level_variables`` are deprecated. The recommended way to declare log-approximated variables is the inline @endogenous(log) form (with @endogenous(log_vars) and @endogenous(log_variables) as accepted aliases). The legacy block-level keywords @log_variables, @log_vars and @level_variables (with the @all_but complement) are still parsed for backwards compatibility but the documentation now flags them as deprecated. The two styles cannot be mixed inside the same model file.

  • Time series indexing overhaul (partial break). The @ts class now distinguishes () and {} the same way built-in table and timetable do: db(...) returns a sub-time-series, db{...} returns the raw numeric block. db.VARNAME returns the corresponding single-variable ts when the name is not a property. The familiar db{-1} / db(+3) “time-shift” shortcut is kept on dated time series, where a scalar integer is unambiguous (no date has a plain numeric value) - it is equivalent to lag(db, 1) / lead(db, 3) and returns a ts (NaN-padded, same length). On undated time series, a scalar numeric is the implicit date value instead (db{1990} on ts(1990, data) returns row 1); time shifts on undated ts require the explicit lag/lead/shift methods. Scalar-numeric assignment (db{-1} = X) is not supported in either case. See Indexing and assignment for the full contract.

  • ``transform`` preserves the date axis. Period-to-period growth and difference variants (pct_ch, diff, log_ch and their YoY cousins) now return a time series of the same length as the input, with the leading rows that have no defined past observation set to NaN. The previous behaviour silently dropped those rows.

  • Mixed-frequency date selection. Indexing a quarterly time series with an annual date, e.g. db(ry(1991)), now correctly returns the four quarters of 1991 (previously it returned only the closest single quarter).

  • Chow-Lin temporal disaggregation works again. chowlin had been calling a helper that no longer existed in the toolbox path; rewired on the live rise_dates API and gated on integer high-/low-frequency ratios so non-supported pairs (e.g. W->D) error cleanly. Inputs and outputs are unchanged.

  • Date system polish. 'A' is now accepted as a public annual synonym for 'Y' (date2serial('1990A1') is equivalent to date2serial('1990') / ra(1990) / ry(1990); previously the date parser rejected 'A' even though every other part of the date system already accepted it). is_date short-circuits on rise_dates.dates and finite-integer double inputs instead of going through the heavy try/catch date2serial path. @ts/convert.m now uses dictionary rather than containers.Map for its frequency-step lookups.

  • MATLAB R2023b is now required. RISE uses the dictionary / configureDictionary data type, introduced in R2023b; rise_startup checks the running release and stops with a clear error on anything older.

  • Distribution and licensing. This edition (the “Beta” edition) is shipped in compiled (p-coded) form and is distributed personally by the author; it is proprietary and not for redistribution. See RISE Toolbox License (the full terms are also in LICENSE.txt).

  • The ``+rise`` package. Housekeeping commands are grouped under the rise package: rise.startup (rise_startup; accepts quiet for a one-line load, and true to unload), rise.exit (rise_exit), rise.demo (rise_demo – installation check + a quick tour), rise.doc (opens the PDF manual), rise.version, rise.release, rise.toolbox. See Documentation of the +rise package.

  • Refreshed start-up banner summarising your MATLAB version, the optional toolboxes (Optimization, Statistics and Machine Learning), the LaTeX status, and where the manual lives.

  • PDF reporting degrades gracefully: if pdflatex is not on the PATH, RISE still loads – only PDF reporting is disabled, and the banner says so.

  • Documentation overhaul in progress: a corrected Setting up your RISE environment page, a single-source license, this page and a Quickstart, with a chapter-by-chapter refresh to follow (see DOC_STRATEGY.md in the docs repository).

1.2.2. Where the canonical changelog lives

The single source of truth for release notes is now CHANGELOG.md in the toolbox root, next to README.md. This page mirrors it; the two are kept in sync by the documentation build (or by hand during the transition). New entries should be added to CHANGELOG.md first, then reflected here under the appropriate section – not the other way around.

In MATLAB, the most recent entries are surfaced by rise.whatsnew (or its shortcut rise_whatsnew):

rise.whatsnew         % print the entire CHANGELOG
rise.whatsnew(3)      % print the 3 most recent dated/Unreleased sections
rise_whatsnew(1)      % shortcut, same semantics

The function lives at +rise/whatsnew.m in the toolbox; the shortcut at m/shortcuts/rise_whatsnew.m mirrors the pattern of rise_demo / rise_doc.