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.nameFunction
name(int::AbstractComponentIntegrator)
name(comp::AbstractComponent)

Get the name of the integrator or component.

source
Sirens.timestepFunction
timestep(int::AbstractComponent)
timestep(comp::AbstractComponentIntegrator)

Get the proposed time step of the integrator or component. It can depend on the current state.

source
Sirens.variablesFunction
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 #time and #model if applicable.
source

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.
source
step!(int::AbstractComponentIntegrator)

Advance the state of the integrator int by one time step.

Arguments

  • int::AbstractComponentIntegrator: The integrator to advance.
source

init

The init function takes as inputs a Component and returns the corresponding ComponentIntegrator.

CommonSolve.initFunction
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.
    • :none or String[]: 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 times tspan[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.
source
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.
source

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.getstateFunction
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: If true, a deep copy of the state is returned; otherwise, a reference to the state is returned (assuming the state is mutable).
source
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.
source

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.gettimeFunction
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.
source
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.
source

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.ConnectedVariableType
ConnectedVariable <: AbstractConnectedVariable

Points 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.
source

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.ConnectedVariableMethod
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:

SyntaxMeaning
component.variableA variable in a component
component.variable[i]An index into a variable
component[j].variableInstance j of a duplicated component
component[j].variable[i]Both kinds of indexing

Examples

  • ConnectedVariable("comp.var"): Variable var in component comp.
  • ConnectedVariable("comp.var[1:5]"): Variable indices 1 through 5 (variable index).
  • ConnectedVariable("comp[2].var"): Variable var from 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:

  • 1 for a single index.
  • 1:5 for a range.
  • [1, 3] for a vector of indices.
source

Examples

To see some examples, look in the ext directory on GitHub.