reno.pymc#

Functions for translating a system dynamics model into a PyMC equivalent.

Because an SDM run here is a sequential simulation, the core enabling functionality is the scan function in PyTensor: https://pytensor.readthedocs.io/en/latest/library/scan.html An example for how scan ties into PyMC: https://www.pymc.io/projects/examples/en/latest/time_series/Time_Series_Generative_Graph.html

The output generated by these translations follows a template that looks something like:

def step_function(*all_stocks_flows_variables):
    # this is generated by pt_sim_step() or pt_sim_step_str()

    s, ... = all_stocks_flows_variables

    # difference/update equations for each stock/flow/variable
    s_next = s + ...
    ...

    # return all updated values (pymc/pytensor has specific boilerplate to use)
    return [s_next, ...], ...

with pymc.Model() as model:
    # define all pymc distributions/variables and initial stock/flow values
    # e.g. for stock "s":
    s_init = pm.Deterministic("s_init", ...)
    ...

    # run the scan function to generate full sequence data for simulation
    [s_seq, ...] = pytensor.scan(fn=step_function, [s_init, ...], ...)

    # create pymc containers for the full sequence data (this way they get
    # added into any output arviz datasets from pymc sampling operations)
    s = pm.Deterministic("s", pt.concatenate([s_init, s_seq], ...)

Reno’s underlying math framework works fairly similarly to PyTensor, (symbolic tree and numpy-ish) so translation is done by calling the pt() or pt_str() function that every EquationPart has, which returns the PyTensor equivalent of that operation/thing.

Note that there are functions to directly create pymc models/operations and functions to create strings of PyMC/PyTensor code (suffixed with _str). These exist for two reasons:

  1. Debugging - figuring out errors or issues with PyMC models can be an adventure,

    and doubly so if you can’t directly see or modify the model code. to_pymc_model_str() should _in theory_ generate a string of code equivalent to what’s used in to_pymc_model(), but can be edited and run separately as needed to test changes.

  2. Flexibility - if trying to do something beyond what Reno’s PyMC naive translation

    can support, the string code output gives a starting point that can be directly incorporated or modified to do more advanced things.

Functions

expected_pt_scan_arg_names(model[, as_dict, ...])

Pytensor scan target functions are called with a very specific ordering of function arguments (see https://pytensor.readthedocs.io/en/latest/library/scan.html).

find_historical_tracked_refs(model)

Get a dictionary of all tracked references in the model used in historical values, and get the associated tap list for each.

pt_sim_step(model)

Returns a target function for pytensor scan, this equates to a single step in the system dynamics model simulation.

pt_sim_step_imports()

Get the string of code for the imports needed for pt_sim_step_str() to run correctly.

pt_sim_step_str(model)

Construct a string of python code for the step function for this model that could be used by a pytensor scan operation.

pymc_model_imports()

Get the string of code for the imports needed for to_pymc_model_str() to run correctly.

resolve_pt_scan_args(model, args)

Take the set of arguments passed by pytensor to a target scan function, and turn it into a dictionary for passing into reno math components' pt() calls.

to_pymc_model(model[, observations, steps])

Generate a pymc model for bayesian analysis of this system dynamics model.

to_pymc_model_str(model[, observations, steps])

Construct a string of python code to create a pymc model wrapping this system dynamics model.