The Model class¶
The Model class ties the system dynamics, state, inputs and parameters
together. It is used to run simulations, including finding equilibria. A model
is created by instantiating the Model class with the appropriate arguments.
from physiomodeler import Model
def my_dynamics_function(...):
...
my_model = Model(
dynamics=my_dynamics_function,
state_components=["x", "v"],
inputs={"external_force": 1.0},
parameters={"mass": 1.0},
)
Submodels¶
Models themselves can be used as system dynamics in the form of submodels.
from physiomodeler import Model
model_a = Model(
dynamics=model_a_function,
state_components=["a"],
inputs={"external_force": 1.0},
parameters={"mass": 1.0},
)
model_b = Model(
dynamics=model_b_function,
state_components=["b"],
parameters={"volume": 3.0}
)
combined_model = Model(
dynamics=[model_a, model_b]
)
When a model includes other models as submodels in its dynamics, the state components from those submodels are automatically added to the parent model's state components. In this example, combined_model automatically has state components ["a", "b"] without needing to specify them explicitly.
This is roughly equivalent to the code below, but requires changes in the dynamics functions:
singular_model = Model(
dynamics=[model_a_function, model_b_function],
state_components=["a", "b"],
inputs={"external_force": 1.0},
parameters={"mass": 1.0, "volume": 3.0},
)
However, there are some differences:
- State components from other submodels are available as inputs in the current
submodel. In this example, when using
combined_model,model_b_functiondoes not get access tostate["a"], but toinputs["a"]instead. Similarly,model_a_functionaccessesmodel_b's state asinputs["b"]. When using thesingular_model, both dynamics functions get access to both state components. model_aandmodel_bmight rely on an input or parameter with the same name, but a different value. This cannot be replicated with the singular model. This is especially noteworthy when using multiple models with the same dynamics function(s), but different inputs/parameters.
Parameter and input cascading in nested models¶
When a nested model is used as a submodel, parameters and inputs cascade through the model hierarchy:
- Parameters: Parameters from all ancestor models are merged together, with more specific (deeper) models' parameters overriding those from outer models. For example, if both the outer and inner model define a
"decay_rate"parameter, the inner model's value takes precedence when the inner model's functions are executed. - Inputs: All inputs defined at any level of the model hierarchy are available throughout. An inner submodel receives both its own inputs and those defined in outer models.
- Initial state: Initial state values cascade similarly to parameters—the most specific (deepest) definition takes precedence.
This design allows nested models to be self-contained while still participating in the larger system.
Time units¶
By default, the time unit used in sumulations is seconds. By default, the
dataframe resulting from a simulation has a float index representing time in
seconds, with the name "Time (seconds)". If you pass result_index_as_time as
True to the run_simulation method, the index will be converted to a
pandas.TimedeltaIndex, with the unit set to seconds. You can change the time
unit used in the simulation results by specifying the time_unit argument when
creating the model. The time unit should be a string recognized by
pandas.Timedelta,
such as "ms" for milliseconds or "min" for minutes.
Note: all time-related arguments, inputs, parameters, etc. are influenced by the time unit. Adapt values accordingly.
State component derivative name mapping¶
By default, derivative names are formed by prefixing the state component label
with d, e.g. x becomes dx. A function describing the model with the state
\([x]\) should return a dictionary containing at least the key dx. In case the
derivative has a specific name, you can declare the mapping of the state
component to the derivative name in the model definition, using
state_derivative_label_map.
my_model = Model(
...,
state_components=["position", "velocity", "acceleration"],
state_derivative_label_map={
"position": "velocity",
"velocity": "acceleration",
"acceleration": "jerk",
"jerk": "snap",
"snap": "crackle":
"crackle": "pop",
# https://en.wikipedia.org/wiki/Fourth,_fifth,_and_sixth_derivatives_of_position
},
)