Sirens Interface
In this section, we will:
- See the integrator interface that Sirens uses,
- Find out how this fits with CommonSolve.
Interface requirements
The interface used in Sirens is compatible with, and uses, the SciML CommonSolve interface. In particular, each Component is some immutable problem type that stores required data to solve a Component (for example, an ODEProblem). We then also have an Integrator for each Component which stores the current state and handles solving over time.
Component
A component should be immutable and store all of the information required to solve a given sub-problem/model:
It should also have implementations of the following functions:
Sirens.name — Function
name(int::AbstractComponentIntegrator)
name(comp::AbstractComponent)Get the name of the integrator or component.
Sirens.timestep — Function
timestep(int::AbstractComponent)
timestep(comp::AbstractComponentIntegrator)Get the proposed time step of the integrator or component. It can depend on the current state.
Sirens.variables — Function
variables(comp::AbstractComponentIntegrator)
variables(comp::AbstractComponent)Retrieve the variable names of a component.
Arguments
comp::Union{AbstractComponent, AbstractComponentIntegrator}: The component (or component integrator) whose variable names are to be retrieved.
Returns
- A collection of variable names (as strings) associated with the component. This includes all special variables such as
#timeand#modelif applicable.
If the component has fields for name and timestep, then those functions don't need to be implemented.
Integrator
A ComponentIntegrator is a mutable struct which can be freely modified over a simulation, since it will be typically discarded at the end. It only stores the current state of that component, and has some associated functions for handling this state.
Functions
Most of the interface for Sirens is built around functions that manipulate the integrator state.
step!
The step! function should advance the ComponentIntegrator one time step (defined by timestep(ComponentIntegrator)).
CommonSolve.step! — Function
step!(int::AbstractSirenIntegrator)Advance the state of the integrator int by one time step.
Arguments
int::Union{AbstractSirenIntegrator, AbstractComponentIntegrator}: The integrator to advance.
step!(int::AbstractComponentIntegrator)Advance the state of the integrator int by one time step.
Arguments
int::AbstractComponentIntegrator: The integrator to advance.
init
The init function takes as inputs a Component and returns the corresponding ComponentIntegrator.
CommonSolve.init — Function
init(prob::AbstractSirenProblem, alg::AbstractSirenSolver;
save_vars = nothing, saveat = nothing)Defines the integrator for a Sirens hybrid simulation.
Arguments
prob::AbstractSirenProblem: The problem to be solved.alg::AbstractSirenSolver: The Sirens solver algorithm to be used.save_vars: Variables to be saved during the simulation. Options include:nothing(default): Save all non-special variables (those not starting with '#').:all: Save all variables, including special variables.:noneorString[]: Save no variables (time is still recorded).Vector{String}: A vector of connected variable fullnames to save, including optional indices like"forest.life[1]"or"tree[1:10].life".Tuple{Vararg{ConnectedVariable}}: A tuple of ConnectedVariable objects to save.
saveat: When to save the variables during the simulation. Options include:nothing(default): Save after initialization and after every Sirens syncronisation event.- A number
Δt: Save at timestspan[1]:Δt:tspan[2]. - A vector of times: Save at exactly these time points.
- A function
(integrator, t) -> Bool: Save when it returns true (checked at scheduled stops).
Returns
SirenIntegrator: A mutable integrator ready for solving.
init(comp::AbstractComponent)Initialises an integrator (AbstractComponentIntegrator) for the given AbstractComponent.
Arguments
comp::AbstractComponent: The component to be initialised.
Returns
SirenIntegrator: The initialised integrator for the problem.
getstate and setstate!
The function getstate reads the state of an AbstractComponentIntegrator at a given ConnectedVariable and returns it. The setstate! function similarly mutates the current state of the ComponentIntegrator to assign an inputted value to the ConnectedVariable. Additionally, getstate and setstate! should have methods that do not take a ConnectedVariable, and instead return the full state (in a form that should be compatible between the two functions, but need not be documented).
The function signatures should be getstate(integrator, variable)/setstate!(integrator, variable, value) and getstate(integrator)/setstate!(integrator, state).
Sirens.getstate — Function
getstate(comp::AbstractComponentIntegrator; copy = false)
getstate(comp::AbstractComponentIntegrator, key; copy = false)Retrieve the state of a component.
Arguments
comp::AbstractComponentIntegrator: The component whose state is to be retrieved.key: The key specifying which part of the component's state to retrieve.
Keyword Arguments
copy::Bool: Iftrue, a deep copy of the state is returned; otherwise, a reference to the state is returned (assuming the state is mutable).
Sirens.setstate! — Function
setstate!(comp::AbstractComponentIntegrator, state)
setstate!(comp::AbstractComponentIntegrator, key, value)Set the state of a component.
Arguments
comp::AbstractComponentIntegrator: The component whose state is to be set.state: The new state to set for the entire component.key: The key specifying which part of the component's state to set.value: The value to set for the specified part of the component's state.
gettime and settime!
Simply returns the current simulated time of a ComponentIntegrator. Defaults to calling getstate and setstate! with the special variable "#time".
These functions don't need to be defined for new components, but getstate and setstate! should handle the "#time" special variable.
Sirens.gettime — Function
gettime(sirenInt::AbstractComponentIntegrator)Get the current time of the integrator.
Arguments
int::AbstractComponentIntegrator: The integrator whose time is to be retrieved.
Returns
- The current time of the integrator.
Sirens.settime! — Function
settime!(sirenInt::AbstractComponentIntegrator, t)Set the current time of the integrator.
Arguments
int::AbstractComponentIntegrator: The integrator whose time is to be set.t: The time to set.
Other functions
The interface for a Component is also required to be satisfied for a ComponentIntegrator. That is, implementations of name, timestep, and variables should exist.
If the ComponentIntegrator has a field/property for the Component called component, then these functions don't need new implementations.
Variable Names
Sirens has its own interface for connections too. A Connector uses instances of ConnectedVariables as part of applying the connections. If you want to write your own components, it is worth learning how the ConnectedVariables are defined.
Sirens.ConnectedVariable — Type
ConnectedVariable <: AbstractConnectedVariablePoints to a variable within a component.
Fields
component::String: Name of the component.variable::String: Name of the variable.variableindex::Union{Nothing,Vector{Int}}: Index or range for the variable, if applicable.duplicatedindex::Union{Nothing,Vector{Int}}: Index for duplicated components, if applicable.
We can see that each ConnectedVariable has two names, the first is the component name and the second is the variable name (which should match state_names for that component).
It also has a variable index, which allows you to access only some parts of a full vector. For example, you may index on only some agents in an AgentsComponent.
Finally, it has a duplicated index. Like the variable index, it allows you to act on some subset, but in this case, it is a subset of integrators from a DuplicatedComponent.
A ConnectedVariable can also be constructed from a single string input, ie ConnectedVariable("component[duplicatedindex].variable[variableindex]").
Sirens.ConnectedVariable — Method
ConnectedVariable(name::AbstractString)Construct a ConnectedVariable from its canonical fullname.
Arguments
name::AbstractString: The full variable name.
Syntax and Examples
Connected variables are specified as strings in one of these forms:
| Syntax | Meaning |
|---|---|
component.variable | A variable in a component |
component.variable[i] | An index into a variable |
component[j].variable | Instance j of a duplicated component |
component[j].variable[i] | Both kinds of indexing |
Examples
ConnectedVariable("comp.var"): Variablevarin componentcomp.ConnectedVariable("comp.var[1:5]"): Variable indices 1 through 5 (variable index).ConnectedVariable("comp[2].var"): Variablevarfrom duplicated instance 2 (duplicated index).ConnectedVariable("comp[1:3].var[4]"): Variable index 4 from duplicated instances 1-3.
Parsing
Indices are parsed as Julia expressions, so use literal integer indices and ranges:
1for a single index.1:5for a range.[1, 3]for a vector of indices.
Examples
To see some examples, look in the ext directory on GitHub.