10. Extending RISE
RISE is pluggable at every step of the standard workflow –
solve, filter, optimize, sample. You can replace any of those
steps with a custom routine without touching the rest of the
toolbox. This chapter lists the hooks that the rise_model
object exposes for that purpose. For users who need to plug in a
whole alternative computational strategy (occbin-style
piecewise linearization, recursive local linearization, k-th
order Taylor projection, …), the broader interface is described
in Extending RISE through paradigms; the hooks below are
the lighter, per-routine extension points.
10.1. Extracting the structural form
The matrices of the parsed and solved model are reachable via
get on the model object: get(m, 'definitions'),
get(m, 'parameters'), get(m, 'endogenous'),
get(m, 'exo_list'), get(m, 'obs_list'),
get(m, 'sstate') (steady state by regime),
get(m, 'par_list'), get(m, 'chain_list') (Markov chain
names), and so on. The full list of accessor strings is
documented on the get method of the model object.
10.2. Extracting the solution
After solve succeeds, the solution lives in m.state_space{1}
(one entry per parameterization) and includes the regime-specific
transition matrices T, the shock-impact matrices R, and
the steady state. The convenience map_solution method packs
the state-space matrices into a struct keyed by Markov regime;
together with get(m, 'sstate') it is the canonical interface
for any post-processing that does not want to depend on internal
field layouts.
10.3. Your own solver
Set the solver option to a function handle to replace the
built-in fixed-point iteration with a user routine:
m = set(m, solver = @my_solver);
The signature is documented in Solving. The bundled
solvers (rise_1, mfi, mn, mnk, mfi_full,
loose_commitment) all conform to that signature; the simplest
way to write your own is to copy one of them and modify the inner
loop.
10.4. Your own filtering algorithm
The Kalman / smoother family is replaceable through the
kf_user_algo option:
m = set(m, kf_user_algo = @my_filter);
m = set(m, kf_user_algo = {@my_filter, opt1, opt2, ...});
The expected signature is
[LogLik, Incr, retcode, Filters] = my_filter(syst, y, U, z, options, vargs{:})
where syst is the solved state-space (or the model object
when the filter needs more than the default), y and U are
the observable and exogenous data, z is a callable that maps
states to observables at each time, and options carries the
filter options.
A * prefix on the function name ('*my_filter') tells RISE
that the filter wants the model object rather than the
state-space struct – useful when the user algorithm needs to
call back into the toolbox. The bundled
switching_unscented_kalman_filter,
switching_divided_difference_filter and
switching_cubature_kalman_filter are the reference
implementations.
10.5. Your own optimizer
Posterior maximization, mode search and global optimization are
routed through the optimizer option, which accepts a function
handle or a {handle, options-struct} cell:
m = set(m, optimizer = @my_opt);
m = set(m, optimizer = {@my_opt, struct(MaxIter = 500)});
The expected signature is
[x, fval, exitflag, output] = my_opt(objfun, x0, lb, ub, options).
The bundled optimizers in m/optimizers/ (wcsminwel,
wnewrat, wgmhmaxlik, bee_gate, the +globalopt
family rise_de / rise_cma_es / rise_abc /
rise_lshade / rise_jso / …) all conform to that
signature and serve as templates; see Estimation.
10.6. Your own likelihood function
The Bayesian estimation entry point routes the likelihood
through m.routines.likelihood. To plug in a custom
likelihood – e.g. a DSGE-VAR likelihood or a moment-matching
pseudo-likelihood – set that field after constructing the model
and before calling estimate:
m = add_to_routines(m, 'likelihood', @my_likelihood);
The expected signature is
[LogLik, Incr, retcode, m, filtration] = my_likelihood(params, m).
The default is the Kalman-based likelihood for a linearised model
(the configured nonlinear filter is invoked automatically at
higher order), and the DSGE-VAR variant likelihood_dsge_var
shows how a non-Kalman likelihood plugs in.
10.6.1. Relation to Indirect Inference
The indirect_inference entry point is a thin wrapper around
the same optimization machinery, but with a criterion function
in place of the likelihood. The user supplies the criterion via
indirect_inference(m, myobjective, estim_priors = priors), where
[critmin, retcode] = myobjective(m) returns the (weighted)
distance between the model’s auxiliary statistics and their data
counterparts. Replace myobjective to switch between GMM
(theoretical moments), SMM (sample moments from simulation) and
IRF matching (simulated vs empirical IRFs) without touching
anything else in the pipeline.
10.7. Your own posterior sampler
The MCMC samplers (rsamplers.rwmh, rsamplers.imh,
rsamplers.slice, rsamplers.apt) are independent of the
model object – they take a target-log-density function handle,
an initial point, bounds, and an options struct. To use a custom
sampler, build a small wrapper that calls pull_objective(m)
to obtain the target handle and then drives the sampler. The
rsamplers_* files in classes/stats/+rsamplers/ are the
reference implementations and demonstrate the contract that the
utils.sampling infrastructure (progress monitor,
checkpointing, diagnostics) expects.
10.8. Where this is not enough
The hooks above replace one routine at a time. If the goal is to plug in a whole solution paradigm – piecewise-linear OccBin, recursive local linearization, k-th order Taylor projection – the per-routine extension surface is too small, because such paradigms need to own the solution representation as well. That broader surface is the subject of Extending RISE through paradigms.