Given a Simulation Configuration, cadCAD produces datasets that represent the evolution of the state of a system over discrete time. The state of the system is described by a set of State Variables. The dynamic of the system is described by Policy Functions and State Update Functions, which are evaluated by cadCAD according to the definitions set by the user in Partial State Update Blocks.
A Simulation Configuration is comprised of a System Model and a set of Simulation Properties.
cadCAD.configuration.Experiment
is a unique representation of an experiment of one or more configured System Models.
The append_model
method of Experiment
appends a System Model configurations, each representing a single run
.
from cadCAD.configuration import Experiment
exp = Experiment()
exp.append_model(
model_id = ..., # OPTIONAL: System Model label
initial_state = ..., # System Model
partial_state_update_blocks = ..., # System Model
policy_ops = ..., # System Model
sim_configs = ..., # Simulation Properties
user_id = ..., # OPTIONAL: Configuration User ID
)
Parameters: append_model
- model_id : str - OPTIONAL: System Model label
- initial_state : dict - State Variables and their initial values
- partial_state_update_blocks : List[dict[dict]] - List of Partial State Update Blocks
- policy_ops : List[functions] - See Policy Aggregation
- sim_configs - See System Model Parameter Sweep
- user_id : str - OPTIONAL: Configuration User ID
Simulation properties are passed to append_model
in the sim_configs
parameter. To construct this parameter, we
use the config_sim
function in cadCAD.configuration.utils
from cadCAD.configuration.utils import config_sim
from cadCAD.configuration import Experiment
sim_config_dict = {
"N": ...,
"T": range(...),
"M": ...
}
c = config_sim(sim_config_dict)
exp = Experiment()
exp.append_model(
...
sim_configs = c # Simulation Properties
)
Computer simulations run in discrete time:
Discrete time views values of variables as occurring at distinct, separate "points in time", or equivalently as being unchanged throughout each non-zero region of time ("time period")—that is, time is viewed as a discrete variable. (...) This view of time corresponds to a digital clock that gives a fixed reading of 10:37 for a while, and then jumps to a new fixed reading of 10:38, etc. (source: Wikipedia)
As is common in many simulation tools, in cadCAD too we refer to each discrete unit of time as a timestep. cadCAD increments a "time counter", and at each step it updates the state variables according to the equations that describe the system.
The main simulation property that the user must set when creating a Simulation Configuration is the number of timesteps in the simulation. In other words, for how long do they want to simulate the system that has been modeled.
cadCAD facilitates running multiple simulations of the same system sequentially, reporting the results of all those runs in a single dataset. This is especially helpful for running Monte Carlo Simulations.
Parameters of the system, passed to the state update functions and the policy functions in the params
parameter are
defined here. See System Model Parameter Sweep for more information.
The System Model describes the system that will be simulated in cadCAD. It is comprised of a set of State Variables and the State Update Functions that determine the evolution of the state of the system over time. Policy Functions (representations of user policies or internal system control policies) may also be part of a System Model.
A state variable is one of the set of variables that are used to describe the mathematical "state" of a dynamical system. Intuitively, the state of a system describes enough about the system to determine its future behaviour in the absence of any external forces affecting the system. (source: Wikipedia)
cadCAD can handle state variables of any Python data type, including custom classes. It is up to the user of cadCAD to determine the state variables needed to sufficiently and accurately describe the system they are interested in.
State Variables are passed to append_model
along with its initial values, as a Python dict
where the dict_keys
are the names of the variables and the dict_values
are their initial values.
from cadCAD.configuration import Experiment
genesis_states = {
'state_variable_1': 0,
'state_variable_2': 0,
'state_variable_3': 1.5,
'timestamp': '2019-01-01 00:00:00'
}
exp = Experiment()
exp.append_model(
initial_state = genesis_states,
...
)
State Update Functions represent equations according to which the state variables change over time. Each state update function must return a tuple containing a string with the name of the state variable being updated and its new value. Each state update function can only modify a single state variable. The general structure of a state update function is:
def state_update_function_A(_params, substep, sH, s, _input, **kwargs):
...
return 'state_variable_name', new_value
Parameters:
- _params : dict - System parameters
- substep : int - Current substep
- sH : list[list[dict]] - Historical values of all state variables for the simulation. See Historical State Access (DEPRECATED) for details
- s : dict - Current state of the system, where the
dict_keys
are the names of the state variables and thedict_values
are their current values. - _input : dict - Aggregation of the signals of all policy functions in the current Partial State Update Block
- **kwargs - State Update feature extensions
Return:
- tuple containing a string with the name of the state variable being updated and its new value.
State update functions should not modify any of the parameters passed to it, as those are mutable Python objects that cadCAD relies on in order to run the simulation according to the specifications.
A Policy Function computes one or more signals to be passed to State Update Functions (via the _input parameter). Read this article for details on why and when to use policy functions.
The general structure of a policy function is:
def policy_function_1(_params, substep, sH, s, **kwargs):
...
return {'signal_1': value_1, ..., 'signal_N': value_N}
Parameters:
- _params : dict - System parameters
- substep : int - Current substep
- sH : list[list[dict]] - Historical values of all state variables for the simulation. See Historical State Access (DEPRECATED) for details
- s : dict - Current state of the system, where the
dict_keys
are the names of the state variables and thedict_values
are their current values. - **kwargs - Policy Update feature extensions
Return:
- dict of signals to be passed to the state update functions in the same Partial State Update Block
Policy functions should not modify any of the parameters passed to it, as those are mutable Python objects that cadCAD relies on in order to run the simulation according to the specifications.
At each Partial State Update Block (PSUB), the dicts
returned by all policy functions
within that PSUB dictionaries are aggregated into a single dict
using an initial reduction function
(a key-wise operation, default: dic1['keyA'] + dic2['keyA']
) and optional subsequent map functions. The resulting
aggregated dict
is then passed as the _input
parameter to the state update functions in that PSUB. For more
information on how to modify the aggregation method, see Policy Aggregation.
A Partial State Update Block (PSUB) is a set of State Update Functions and Policy Functions such that State Update Functions in the set are independent from each other and Policies in the set are independent from each other and from the State Update Functions in the set. In other words, if a state variable is updated in a PSUB, its new value cannot impact the State Update Functions and Policy Functions in that PSUB - only those in the next PSUB.
Partial State Update Blocks are passed to append_model
as a List of Python dicts
where the dict_keys
are named
"policies"
and "variables"
and the values are also Python dicts
where the keys are the names of the policy and
state update functions and the values are the functions.
from cadCAD.configuration import Experiment
PSUBs = [
{
"policies": {
"b_1": policy_function_1,
...
"b_J": policy_function_J
},
"variables": {
"s_1": state_update_function_1,
...
"s_K": state_update_function_K
}
}, #PSUB_1,
{...}, #PSUB_2,
...
{...} #PSUB_M
]
exp = Experiment()
exp.append_model(
...
partial_state_update_blocks = PSUBs,
...
)
At each timestep, cadCAD iterates over the partial_state_update_blocks
list. For each Partial State Update Block,
cadCAD returns a record containing the state of the system at the end of that PSUB. We refer to that subdivision of a
timestep as a substep
.
cadCAD returns a dataset containing the evolution of the state variables defined by the user over time, with three int
indexes:
subset
- identifies the subset per sweepable parameter produced by a parameter sweep (ver.0.3.1
's result was multiple datasets; A single dataset per sweepable parameter).run
- identifies the runtimestep
- discrete unit of time (the total number of timesteps is defined by the user in the T Simulation Parameter)substep
- subdivision of timestep (the number of substeps is the same as the number of Partial State Update Blocks)simulation
- Alpha: Ignore
Therefore, the total number of records in the resulting dataset is N
x T
x len(partial_state_update_blocks)