Skip to content

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_function does not get access to state["a"], but to inputs["a"] instead. Similarly, model_a_function accesses model_b's state as inputs["b"]. When using the singular_model, both dynamics functions get access to both state components.
  • model_a and model_b might 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
    },
)