# Interfacing problems, integrators, and solutions

When simulating a model, one begins with creating a problem. Next, a simulation is performed on a problem, during which the state of the simulation is recorded through an integrator. Finally, the simulation output is returned as a solution. This tutorial describes how to access, or modify the state, or parameter, values of problems, integrators, and solutions structures.

Generally, when we have a structure `simulation_struct`

and want to interface with the state (or parameter) `G`

, we use `simulation_struct[:G]`

to access the value, and `simulation_struct[:G] = 5.0`

to set it to a new value. However, see the following examples for full details.

## Interfacing problem objects

We begin by demonstrating how we can interface with problem objects. We will demonstrate using a `ODEProblem`

, however, it works similarily for other problem types.

```
using Catalyst
rn = @reaction_network begin
(k1,k2), X1 <--> X2
end
u0 = [:X1 => 1.0, :X2 => 5.0]
p = [:k1 => 5.0, :k2 => 2.0]
oprob = ODEProblem(rn, u0, (0.0,10.0), p)
```

We can find the value of a state simply by interfacing with the corresponding symbol:

`oprob[:X1]`

`1.0`

with the notation being identical for parameters:

`oprob[:k1]`

`5.0`

If we want to change a state's initial condition value, we use the following notation

`oprob[:X1] = 10.0`

`10.0`

with parameters using the same notation.

#### Remaking problems using the `remake`

function

Typically, when modifying problems, it is recommended to use the `remake`

function. Unlike when we do `oprob[:X1] = 10.0`

(which modifies the problem in question), `remake`

creates a new problem object. The `remake`

function takes a problem as input, and any fields you wish to modify (and their new values) as optional inputs. Thus, we can do:

```
using DifferentialEquations
@unpack X1, X2, k1, k2 = rn
oprob1 = ODEProblem(rn, u0, (0.0,10.0), p)
oprob2 = remake(oprob1; u0=[X1 => 10.0, X2 => 50.0], tspan=(0.0,100.0), p=[k1 => 50.0,k2 => 20.0])
```

and we can now check the fields of `oprob2`

`oprob2.u0`

```
2-element Vector{Float64}:
10.0
50.0
```

`oprob2.tspan`

`(0.0, 100.0)`

`oprob2.p`

```
2-element Vector{Float64}:
50.0
20.0
```

Please note that, currently, `remake`

does not work while giving `Symbol`

s as input (e.g `[:X1 => 10.0, :X2 => 50.0]`

), but we need to unpack the symbolic variables and use them instead (please see the end of this tutorial for more information on using symbolic variables rather than `Symbol`

s).

When using `remake`

, we only have to provide the fields that we actually wish to change, e.g.

`oprob3 = remake(oprob1; u0=[X1 => 10.0, X2 => 50.0])`

will only update the initial conditions.

## Interfacing integrator objects

During a simulation, the solution is stored in an integrator object, we will here describe how to interface with these. The primary circumstance under which a user may wish to do so is when using callbacks. We can create an integrator by calling `init`

on our problem (while circumstances where the user might want to use `init`

function exist, since integrators are automatically created during simulations, these are rare):

`integrator = init(oprob)`

Using a similar syntax to problems, we can get the current values of a state within the integrator:

`integrator[:X1]`

or a parameter:

`integrator[:k1]`

Similarly, we can update their values using:

`integrator[:X1] = 10.0`

Please read this with regards to updating integrators of `JumpProblem`

s.

## Interfacing solution objects

Finally, we consider solution objects. First, we simulate our problem:

`sol = solve(oprob)`

For solutions, when we access a state, we get its whole simulation vector:

`sol[:X1]`

while when we access a parameter we only get a single value:

`sol[:k1]`

Finally, we note that we cannot change the values of solution states or parameters (i.e. both `sol[:X1] = 0.0`

and `sol[:k1] = 0.0`

generate errors).

## Interfacing using symbolic representation

Catalyst is built on an *intermediary representation* implemented by (ModelingToolkit.jl)[https://github.com/SciML/ModelingToolkit.jl]. ModelingToolkit is a modelling framework where one first declares a set of symbolic variables and parameters using e.g.

```
using ModelingToolkit
@parameters σ ρ β
@variables t x(t) y(t) z(t)
```

and then uses these to build systems of equations. Here, these symbolic variables (`x`

, `y`

, and `z`

) and parameters (`σ`

, `ρ`

, and `β`

) can be used to interface a `problem`

, `integrator`

, and `solution`

object (like we did previously, but using Symbols, e.g. `:X`

). Since Catalyst models are built on ModelingToolkit, these models also contain similar symbolic variables and parameters.

```
using Catalyst
rn = @reaction_network begin
(k1,k2), X1 <--> X2
end
@unpack k1,k2,X1,X2 = rn
```

\[ \begin{align*} \mathrm{X1} &\xrightleftharpoons[k2]{k1} \mathrm{X2} \end{align*} \]

Here, we first list the parameters and variables (for reaction systems the latter are typically species) we wish to import (in this case we select all, but we could select only a subset), next we denote from which model (here `rn`

) from which we wish to import from. Next, these values can be used directly to interface with e.g. an `ODEProblem`

:

```
u0 = [X1 => 1.0, X2 => 5.0]
p = [:k1 => 5.0, :k2 => 2.0]
oprob = ODEProblem(rn, u0, (0.0,10.0), p)
oprob[k1]
```

`5.0`

To interface with integrators and solutions we use a similar syntax.

Finally, instead of using `@unpack`

to access a symbolic variable or parameter, we can access it directly using `rn.X1`

, and thus access a state of our `ODEProblem`

using

`oprob[rn.X1]`

`1.0`