Skip to content

Simulation

SetterObject

Base class handling the updating of attributes of parameters and variables.

set_initial_values(initial_values)

Set initial values.

Initial values must be int or float instances only.

PARAMETER DESCRIPTION
initial_values

a dictionary of variable: value pairs where variable is the string identifier of a variable in self.variables and value is int or float.

TYPE: dict[str, int | float]

Source code in psymple/simulate/simulation.py
def set_initial_values(self, initial_values: dict[str, int | float]):
    """
    Set initial values.

    Initial values must be `int` or `float` instances only.

    Args:
        initial_values: a dictionary of `variable: value` pairs where `variable` is the string
            identifier of a variable in `self.variables` and `value` is `int` or `float`.
    """
    for var, value in initial_values.items():
        self.variables[var].initial_value = value

set_parameters(parameter_values={})

Set input parameters.

Parameter values must be constant, or functions of system variable time and/or existing system parameters.

PARAMETER DESCRIPTION
parameter_values

a dictionary of parameter: value pairs, where parameter is the string identifier of a parameter (an entry from self.parameters) and value is the value or function to assign.

TYPE: dict[str, str | int | float] DEFAULT: {}

RAISES DESCRIPTION
TypeError

if a value which is not of type str, int, float is entered.

ParsingError

if the value expression contains forbidden symbols.

TypeError

if the parameter is fixed and cannot be updated.

Source code in psymple/simulate/simulation.py
def set_parameters(self, parameter_values: dict[str, str | int | float] = {}):
    """
    Set input parameters.

    Parameter values must be constant, or functions of system variable `time` and/or existing system 
    parameters.

    Args:
        parameter_values: a dictionary of `parameter: value` pairs, where `parameter` is the string
            identifier of a parameter (an entry from `self.parameters`) and `value` is the value
            or function to assign.

    Raises:
        TypeError: if a value which is not of type `str`, `int`, `float` is entered.
        ParsingError: if the value expression contains forbidden symbols.
        TypeError: if the parameter is fixed and cannot be updated.
    """
    for parameter, value in parameter_values.items():
        parameter = self.parameters[parameter]
        if isinstance(value, str):
            value = parse_expr(
                value, local_dict=self.system_parameters | self.utility_functions
            )
        elif isinstance(value, (int, float)):
            value = Number(value)
        else:
            raise TypeError(f"Parameter {parameter.name} was provided value {value} "
                            f"of type {type(value)} which is not of type [str, float, int].")
        if bad_symbols := (
            value.free_symbols
            - {self.time.symbol}
            - set(self.system_parameters.values())
        ):
            raise ParsingError(f"The symbols {bad_symbols} cannot be used.")
        if parameter.type in {"default_optional", "default_exposable", "required"}:
            parameter.change_parameter_value(value, {self.time.symbol}, value.free_symbols - {self.time.symbol})
            parameter.type = "default_optional"
        else:
            raise TypeError(
                f"The value of parameter {parameter.name} is fixed and cannot be updated."
            )

Simulation(system, solver='continuous', initial_values={}, input_parameters={})

Bases: SetterObject

A Simulation is a basic object which produces a simulable system which is passed to a solver.

Information:

The simulation capability of psymple is fairly rudimentary. These features are currently designed to exemplify the functionality of the rest of the package.

PARAMETER DESCRIPTION
system

the system to simulate

TYPE: System

solver

solver type

TYPE: str DEFAULT: 'continuous'

initial_values

initial values

TYPE: dict DEFAULT: {}

input_parameters

input_parameters

TYPE: dict DEFAULT: {}

Source code in psymple/simulate/simulation.py
def __init__(
    self,
    system: System,
    solver: str = "continuous",
    initial_values: dict = {},
    input_parameters: dict = {},
):
    """
    Create a Simulation instance.

    Args:
        system: the system to simulate
        solver: solver type
        initial_values: initial values
        input_parameters: input_parameters
    """
    self.variables = deepcopy(system.variables)
    self.parameters = self._create_ordered_parameters(
        system.parameters, system.compute_parameter_update_order()
    )
    self.time = deepcopy(system.time)

    self.solver = solver

    self.system_parameters = deepcopy(system.system_parameters)
    self.utility_functions = deepcopy(system.utility_functions)
    self.set_initial_values(initial_values)
    self.set_parameters(input_parameters)

    self.lambdify_ns = deepcopy(system.lambdify_ns)
    self.solver_symbols = system.get_readable_symbols()

plot_solution(variables=None, t_range=None)

Produce a simple solution plot of time against a selection of variables.

PARAMETER DESCRIPTION
variables

a list of keys identifying variables in self.variables, or a dict of key: option pairs, where key identifies a variable in self.variables and option is a str or dict which is passed to matplotlib.pyplot.plot. If None, all variables are plotted on the same axes.

TYPE: list | dict DEFAULT: None

t_range

a list of the form [t_start, t_end] defining the range displayed on the time axis. If None, the time axis is determined by self.variables.time.time_series.

TYPE: list DEFAULT: None

Source code in psymple/simulate/simulation.py
def plot_solution(self, variables: list | dict = None, t_range: list = None):
    """
    Produce a simple solution plot of time against a selection of variables.

    Args:
        variables: a `list` of keys identifying variables in `self.variables`, or a `dict` of
            `key: option` pairs, where `key` identifies a variable in `self.variables` and
            `option` is a `str` or `dict` which is passed to `matplotlib.pyplot.plot`. If `None`,
            all variables are plotted on the same axes.
        t_range: a list of the form `[t_start, t_end]` defining the range displayed on the time
            axis. If `None`, the time axis is determined by `self.variables.time.time_series`.
    """
    t_series = self.time.time_series
    if t_range is None:
        sl = slice(None, None)
    else:
        lower = bisect(t_series, t_range[0])
        upper = bisect(t_series, t_range[1])
        sl = slice(lower, upper)
    if not variables:
        variables = {v: {} for v in self.variables}
    if isinstance(variables, set):
        variables = {v: {} for v in variables}
    legend = []
    for var_name, options in variables.items():
        variable = self.variables[var_name]
        if isinstance(options, str):
            plt.plot(t_series[sl], variable.time_series[sl], options)
        else:
            plt.plot(t_series[sl], variable.time_series[sl], **options)
        legend.append(variable.symbol.name)
    plt.legend(legend, loc="best")
    plt.xlabel("time")
    plt.grid()
    plt.show()

simulate(t_end, print_solve_time=False, **options)

Simulate a system by calling the instance of Solver specified by self.solver. Currently, this is either a discrete (Euler forward) integrator, or a a continuosu integrator implemented as a call to scipy.integrate.solve_ivp.

PARAMETER DESCRIPTION
t_end

when to terminate the simulation. Currently this must be a positive integer.

TYPE: int

print_solve_time

if True, the time taken by the solver will be printed in the terminal.

TYPE: bool DEFAULT: False

**options

options to pass to the Solver instance.

DEFAULT: {}

Source code in psymple/simulate/simulation.py
def simulate(self, t_end: int, print_solve_time: bool = False, **options):
    """
    Simulate a system by calling the instance of `Solver` specified by `self.solver`. Currently, 
    this is either a discrete (Euler forward) integrator, or a a continuosu integrator implemented
    as a call to `scipy.integrate.solve_ivp`.

    Args:
        t_end: when to terminate the simulation. Currently this must be a positive integer.
        print_solve_time: if `True`, the time taken by the solver will be printed in the terminal.
        **options: options to pass to the `Solver` instance.
    """
    self._compute_substitutions()
    if self.solver in SOLVER_ALIASES["discrete"]:
        assert "n_steps" in options.keys()
        n_steps = options["n_steps"]
        solver = DiscreteIntegrator(self, t_end, n_steps)
    elif self.solver in SOLVER_ALIASES["continuous"]:
        solver = ContinuousIntegrator(self, t_end)
    elif self.solver in SOLVER_ALIASES["proceedural"]:
        raise NotImplementedError("proceedural solving is not implemented")
    else:
        raise NameError(
            f"The solver {self.name} is not recognised. Please use one "
            f"from {SOLVER_ALIASES.keys()}"
        )
    t_start = time()
    solver.run()
    t_end = time()
    if print_solve_time:
        print(
            f"Solution time with method '{self.solver}': {t_end - t_start} seconds."
        )