1. Extending RISE through paradigms
This chapter describes how RISE separates what a model is from how it is solved, through the notion of a paradigm. A paradigm is a self-contained computational strategy – perturbation, recursive local linearization (RLL), \(k\)-th order Taylor projection (TP), occasionally-binding-constraint (occbin) piecewise linearization, and so on – that plugs into mainland RISE through a small, fixed interface. The aim is to add advanced solution methods without modifying RISE’s core and without paradigms interfering with one another.
This chapter is primarily for developers extending RISE. The three paradigms currently targeted by this framework are:
occbin – piecewise-linear, OccBin-style occasionally-binding constraints (being migrated out of mainland RISE into this framework);
recursive local linearization (RLL);
\(k\)-th order Taylor projection (TP).
1.1. Goals
The paradigm architecture is designed to deliver:
a clean separation between model structure and computational method;
support for multiple parameterizations of the same model;
extensibility to advanced methods without touching RISE core;
prevention of cross-talk between parameterizations;
strictly read-only access to RISE internals by paradigm code.
A model contains exactly one paradigm at a time, but may carry multiple parameter vectors, each producing its own solution object.
1.2. What a paradigm is
A paradigm \(\mathcal{P}\) is a computational universe that
solves a parameterized model \(m(\theta)\),
defines the internal representation of the solution \(S(\theta)\), and
provides operators acting on that solution (e.g. a one-step forecast).
It is not the model, not a parameter vector, and not a container for multiple solutions. Formally,
where \(S(\theta)\) is partly transparent to RISE (through the standard
envelope below) and partly opaque (through the S.solution payload).
1.3. Responsibilities of a paradigm
A paradigm must:
operate on a single parameter vector at a time;
return a single solution object
Sconforming to the standard envelope;provide operators derived from that solution;
remain unaware of parameter multiplicity;
treat the model object
Mas read-only – it may callregular_solver(M, ...)to obtain derivatives, but must never modifyMnor return a modifiedMto RISE;avoid modifying RISE core structures or global options.
A paradigm may use RISE’s baseline solver internally, use RISE’s
constraint enforcement, and keep mutable internal state inside
S.solution (see Mutable solution objects).
A paradigm must not store global state across parameterizations, return a
modified M, or manage arrays of solutions.
1.4. The standard solution envelope
Every paradigm’s solve_engine returns a struct S with the fields
below. This standard envelope lets RISE orchestrate the workflow
(convergence checking, re-solve decisions, filter routing) without knowing
anything about the paradigm’s internals.
Field |
Type |
Meaning |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
matrix or |
measurement-error covariance for the Kalman filter; |
|
handle object |
opaque, paradigm-owned payload. RISE stores it but never inspects it. All paradigm-specific state lives here. |
The envelope is deliberately minimal. Fields specific to perturbation theory
(eigenvalues, number of solutions, Tz, …) are not required, because
they are meaningless or misleading for paradigms such as RLL and TP; those
paradigms keep such quantities inside S.solution.
A paradigm should start from a failed default and populate fields as each stage succeeds, so partial failures are reported correctly:
function S = make_envelope()
S.is_solved = [false, false];
S.warrant_resolving = true;
S.retcode = 1;
S.H = [];
S.solution = [];
end
1.5. The minimum interface contract
A paradigm exposes an engines struct with these fields.
- name (required)
A short string identifier, e.g.
'perturbation','rll','rtp'.- solve_engine (required)
S = solve_engine(M, regular_solver)
Mis read-only: the paradigm may callregular_solver(M, ...)internally, but the solved model is used internally only and never returned.Smust conform to the standard envelope, and no global or shared state may be modified.- one_step_engine (required)
one_step = one_step_engine(S)
The only input is
S– the modelMis not passed here, so everything the closure needs must have been captured inS.solutionduringsolve_engine. It returns a closure[x_t, retcode, extra...] = one_step(x_tm1, eps_t, regime)
with
x_tm1the lagged endogenous vector \(x_{t-1}\),eps_tthe shock vector \(\varepsilon_t\), andregimethe integer index of the current Markov regime. It returns a nonzeroretcodeif the step fails;extra...are optional outputs such as the local linear coefficients(k, H, G)consumed by the smoother.- filter_engine (optional)
Future extension for filtering / smoothing.
- use_rise_constraint_enforcer (optional)
Logical, default
true.
1.6. Mutable solution objects
For RLL and TP the effective policy function is state-dependent: it is
updated at each simulation step as the economy evolves. This conflicts with
RISE’s assumption that the stored solution S is static. The recommended
resolution is to implement S.solution as a MATLAB handle class.
Because handle objects are passed by reference, the one_step closure and
RISE’s stored copy of S.solution point to the same object: when
one_step updates it in place, RISE’s copy reflects the change
automatically, without RISE needing to know an update occurred.
classdef SolutionHandle < handle
properties
% paradigm-specific fields populated by solve_engine, e.g.
T % TP: current polynomial coefficients
k, H, G % RLL: current local linear policy
% ...
end
end
The workflow is:
solve_engineconstructs a freshSolutionHandle, populates it, and stores it asS.solution;one_step_engine(S)extractspayload = S.solutionand builds a closure capturingpayloadby reference;at each step the closure updates
payloadin place; RISE’s storedS.solutionreflects the updates automatically;RISE reads only the envelope fields and never the payload.
Note
Because RISE never reads S.solution directly, this pattern is safe. The
only contract is that one_step(x_tm1, eps_t, regime) always returns a
valid x_t and retcode, whatever the payload’s internal state.
1.7. Responsibilities of RISE
RISE manages model structure and parameter multiplicity; calls
solve_engine(M, regular_solver) for each \(\theta_i\) (passing M
read-only); stores each \(S_i\) in model.solution(i); and routes
simulation, forecasting and filtering through the closures returned by
one_step_engine (and filter_engine). The workflow is
RISE must not inspect or modify S.solution, assume any format inside
it, or reuse a one_step closure across different parameter vectors.
1.8. Isolation, safety and persistence
To prevent cross-talk between parameterizations, each call to
solve_engine must construct a fresh SolutionHandle (copying a
handle copies only the reference, not the data), no paradigm may keep
persistent state outside S.solution, and RISE must not reuse engines
across parameter vectors unless the paradigm is stateless.
For saving models to disk, paradigms should provide serialization hooks
blob = serialize_solution(S)
S = deserialize_solution(blob)
which RISE stores without inspecting.
1.9. Examples
1.9.1. Recursive local linearization (RLL)
solve_enginecallsregular_solverread-only, extracts the baseline local policy \((k_0, H_0, G_0)\), stores everything in anrll.SolutionHandle, and returns the standard envelope withS.solution = payload.one_step_engine(S)returns a closure that builds a zero-shock stance, re-evaluates the structural derivatives at that stance, solves the local linear system for updated \((k_t, H_t, G_t)\), updates the payload in place, and returns \(x_t\) together with \((k_t, H_t, G_t)\) for the smoother.
1.9.2. \(k\)-th order Taylor projection (TP)
solve_enginecallsregular_solverread-only, uses the perturbation solution as a warm start, runsrtp.solveto obtain \(T^* = \{T_1^*,\dots,T_h^*\}\), and stores \(T^*\), the topology andget_Fin anrtp.SolutionHandle.one_step_engine(S)returns a closure that builds the expansion point \(z_{\text{anchor}} = (p_{t-1}, b_{t-1}, \sigma{=}0, \varepsilon{=}0)\), re-solvesrtp.solvearound it (warm-started from the payload), updates the payload in place, and evaluates\[x_t = \sum_{q=0}^{K} T^*_{\text{regime}}\{q{+}1\}\, \bigl(z_t - z_{\text{anchor}}\bigr)^{\otimes q},\]with \(z_t = (p_{t-1}, b_{t-1}, \sigma{=}1, \varepsilon_t)\).
1.10. Design principles
A model has exactly one paradigm.
A model may have multiple parameterizations; each has one solution
S.Mis always read-only inside a paradigm.S.solutionis always opaque to RISE.The envelope gives RISE just enough to orchestrate workflows.
Mutable simulation-time state lives inside
S.solutionvia handle semantics.Paradigm-specific solution concepts (eigenvalues, number of solutions, Taylor coefficients) live inside
S.solution, not in the envelope.