Simple Demonstration of a Symbolic System Structure

In this tutorial we will show how to implement a system structure type for defining the symbolic indexing of a domain-specific language. This tutorial will show how the SymbolCache type is defined to take in arrays of symbols for its independent, dependent, and parameter variable names and uses that to define the symbolic indexing interface.

Defining the ODE

For this example, we will use the Robertson equations:

\[\begin{aligned} \frac{dy_1}{dt} &= -0.04y₁ + 10^4 y_2 y_3 \\ \frac{dy_2}{dt} &= 0.04 y_1 - 10^4 y_2 y_3 - 3*10^7 y_{2}^2 \\ \frac{dy_3}{dt} &= 3*10^7 y_{2}^2 \\ \end{aligned}\]

The in-place function for this ODE system can be defined as:

function rober!(du, u, p, t)
    y₁, y₂, y₃ = u
    k₁, k₂, k₃ = p
    du[1] = -k₁ * y₁ + k₃ * y₂ * y₃
    du[2] = k₁ * y₁ - k₂ * y₂^2 - k₃ * y₂ * y₃
    du[3] = k₂ * y₂^2
    nothing
end
rober! (generic function with 1 method)

To add symbolic names for the states in this example, a SymbolCache can be created and passed as the sys keyword argument to the ODEFunction constructor, as shown below:

using OrdinaryDiffEq, SymbolicIndexingInterface

sys = SymbolCache([:y₁, :y₂, :y₃])
odefun = ODEFunction(rober!; sys = sys)

This is then used to create and solve the ODEProblem

prob = ODEProblem(odefun, [1.0, 0.0, 0.0], (0.0, 1e5), [0.04, 3e7, 1e4])
sol = solve(prob, Rosenbrock23())
retcode: Success
Interpolation: specialized 2nd order "free" stiffness-aware interpolation
t: 61-element Vector{Float64}:
      0.0
      3.196206628740808e-5
      0.00014400709669791316
      0.00025605212710841824
      0.00048593872520561496
      0.0007179482298405134
      0.0010819240431281
      0.0014801655696439716
      0.0020679567767069723
      0.0028435846274559506
      ⋮
  25371.934242574636
  30784.11997687391
  37217.42637183451
  44850.61378119757
  53893.69155452613
  64593.73799781129
  77241.71960460565
  92180.81944906656
 100000.0
u: 61-element Vector{Vector{Float64}}:
 [1.0, 0.0, 0.0]
 [0.9999987215181657, 1.2780900152625978e-6, 3.9181897521319503e-10]
 [0.9999942397327672, 5.718510591908378e-6, 4.175664083814196e-8]
 [0.9999897579685716, 9.992106860321925e-6, 2.499245680985423e-7]
 [0.99998056266788, 1.7833624284594367e-5, 1.6037078354141817e-6]
 [0.9999712826600025, 2.403488607694387e-5, 4.682453920643883e-6]
 [0.9999567250106862, 3.0390689558961382e-5, 1.2884299754832173e-5]
 [0.999940798607161, 3.388427373871301e-5, 2.531711910029394e-5]
 [0.9999172960308617, 3.583508668595173e-5, 4.686888245237194e-5]
 [0.9998862913719596, 3.641240165367128e-5, 7.729622638673522e-5]
 ⋮
 [0.05563507731000894, 2.3546320422346656e-7, 0.9443646872267863]
 [0.047925348764047165, 2.0121495043224827e-7, 0.952074450021001]
 [0.041233421490033735, 1.7192788166891112e-7, 0.9587664065820826]
 [0.035436998370307615, 1.4688361975260182e-7, 0.9645628547460712]
 [0.030425371816164216, 1.2546809318472803e-7, 0.9695745027157406]
 [0.026099132201150312, 1.0715608431718074e-7, 0.9739007606427644]
 [0.02236969169455032, 9.149844876161202e-8, 0.9776302168069995]
 [0.019158563313533397, 7.81109638013881e-8, 0.9808413585755011]
 [0.017827893850751935, 7.258919982166416e-8, 0.9821720335600467]

The solution can now be indexed symbolically:

sol[:y₁]
61-element Vector{Float64}:
 1.0
 0.9999987215181657
 0.9999942397327672
 0.9999897579685716
 0.99998056266788
 0.9999712826600025
 0.9999567250106862
 0.999940798607161
 0.9999172960308617
 0.9998862913719596
 ⋮
 0.05563507731000894
 0.047925348764047165
 0.041233421490033735
 0.035436998370307615
 0.030425371816164216
 0.026099132201150312
 0.02236969169455032
 0.019158563313533397
 0.017827893850751935
sol(1e3, idxs=:y₁)
0.3367546834697414

However, we did not give names to the parameters or the independent variables. They can be specified using SymbolCache as well:

sys = SymbolCache([:y₁, :y₂, :y₃], [:k₁, :k₂, :k₃], :t)
odefun = ODEFunction(rober!; sys = sys)
prob = ODEProblem(odefun, [1.0, 0.0, 0.0], (0.0, 1e5), [0.04, 3e7, 1e4])
sol = solve(prob, Rosenbrock23())
getk1 = getp(sys, :k₁)

getk1(prob)
0.04
getk1(sol)
0.04
sol[:t]
61-element Vector{Float64}:
      0.0
      3.196206628740808e-5
      0.00014400709669791316
      0.00025605212710841824
      0.00048593872520561496
      0.0007179482298405134
      0.0010819240431281
      0.0014801655696439716
      0.0020679567767069723
      0.0028435846274559506
      ⋮
  25371.934242574636
  30784.11997687391
  37217.42637183451
  44850.61378119757
  53893.69155452613
  64593.73799781129
  77241.71960460565
  92180.81944906656
 100000.0