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.