@@ -5,12 +5,11 @@ This tutorial will show how to define the entire Symbolic Indexing Interface on
55
66``` julia
77struct ExampleSystem
8- state_index:: Dict{Symbol,Int}
9- parameter_index:: Dict{Symbol,Int}
10- independent_variable:: Union{Symbol,Nothing}
11- defaults:: Dict{Symbol, Float64}
12- # mapping from observed variable to Expr to calculate its value
13- observed:: Dict{Symbol,Expr}
8+ state_index:: Dict{Symbol, Int}
9+ parameter_index:: Dict{Symbol, Int}
10+ independent_variable:: Union{Symbol, Nothing}
11+ # mapping from observed variable to Expr to calculate its value
12+ observed:: Dict{Symbol, Expr}
1413end
1514```
1615
@@ -25,58 +24,58 @@ These are the simple functions which describe how to turn symbols into indices.
2524
2625``` julia
2726function SymbolicIndexingInterface. is_variable (sys:: ExampleSystem , sym)
28- haskey (sys. state_index, sym)
27+ haskey (sys. state_index, sym)
2928end
3029
3130function SymbolicIndexingInterface. variable_index (sys:: ExampleSystem , sym)
32- get (sys. state_index, sym, nothing )
31+ get (sys. state_index, sym, nothing )
3332end
3433
3534function SymbolicIndexingInterface. variable_symbols (sys:: ExampleSystem )
36- collect (keys (sys. state_index))
35+ collect (keys (sys. state_index))
3736end
3837
3938function SymbolicIndexingInterface. is_parameter (sys:: ExampleSystem , sym)
40- haskey (sys. parameter_index, sym)
39+ haskey (sys. parameter_index, sym)
4140end
4241
4342function SymbolicIndexingInterface. parameter_index (sys:: ExampleSystem , sym)
44- get (sys. parameter_index, sym, nothing )
43+ get (sys. parameter_index, sym, nothing )
4544end
4645
4746function SymbolicIndexingInterface. parameter_symbols (sys:: ExampleSystem )
48- collect (keys (sys. parameter_index))
47+ collect (keys (sys. parameter_index))
4948end
5049
5150function SymbolicIndexingInterface. is_independent_variable (sys:: ExampleSystem , sym)
52- # note we have to check separately for `nothing`, otherwise
53- # `is_independent_variable(p, nothing)` would return `true`.
54- sys. independent_variable != = nothing && sym === sys. independent_variable
51+ # note we have to check separately for `nothing`, otherwise
52+ # `is_independent_variable(p, nothing)` would return `true`.
53+ sys. independent_variable != = nothing && sym === sys. independent_variable
5554end
5655
5756function SymbolicIndexingInterface. independent_variable_symbols (sys:: ExampleSystem )
58- sys. independent_variable === nothing ? [] : [sys. independent_variable]
57+ sys. independent_variable === nothing ? [] : [sys. independent_variable]
5958end
6059
6160function SymbolicIndexingInterface. is_time_dependent (sys:: ExampleSystem )
62- sys. independent_variable != = nothing
61+ sys. independent_variable != = nothing
6362end
6463
6564SymbolicIndexingInterface. constant_structure (:: ExampleSystem ) = true
6665
6766function SymbolicIndexingInterface. all_solvable_symbols (sys:: ExampleSystem )
68- return vcat (
69- collect (keys (sys. state_index)),
70- collect (keys (sys. observed)),
71- )
67+ return vcat (
68+ collect (keys (sys. state_index)),
69+ collect (keys (sys. observed))
70+ )
7271end
7372
7473function SymbolicIndexingInterface. all_symbols (sys:: ExampleSystem )
75- return vcat (
76- all_solvable_symbols (sys),
77- collect (keys (sys. parameter_index)),
78- sys. independent_variable === nothing ? Symbol[] : sys. independent_variable
79- )
74+ return vcat (
75+ all_solvable_symbols (sys),
76+ collect (keys (sys. parameter_index)),
77+ sys. independent_variable === nothing ? Symbol[] : sys. independent_variable
78+ )
8079end
8180
8281function SymbolicIndexingInterface. default_values (sys:: ExampleSystem )
@@ -95,36 +94,38 @@ RuntimeGeneratedFunctions.init(@__MODULE__)
9594
9695# this type accepts `Expr` for observed expressions involving state/parameter/observed
9796# variables
98- SymbolicIndexingInterface. is_observed (sys:: ExampleSystem , sym) = sym isa Expr || sym isa Symbol && haskey (sys. observed, sym)
97+ function SymbolicIndexingInterface. is_observed (sys:: ExampleSystem , sym)
98+ sym isa Expr || sym isa Symbol && haskey (sys. observed, sym)
99+ end
99100
100101function SymbolicIndexingInterface. observed (sys:: ExampleSystem , sym:: Expr )
101- # generate a function with the appropriate signature
102- if is_time_dependent (sys)
103- fn_expr = :(
104- function gen (u, p, t)
105- # assign a variable for each state symbol it's value in u
106- $ ([:($ var = u[$ idx]) for (var, idx) in pairs (sys. state_index)]. .. )
107- # assign a variable for each parameter symbol it's value in p
108- $ ([:($ var = p[$ idx]) for (var, idx) in pairs (sys. parameter_index)]. .. )
109- # assign a variable for the independent variable
110- $ (sys. independent_variable) = t
111- # return the value of the expression
112- return $ sym
113- end
114- )
115- else
116- fn_expr = :(
117- function gen (u, p)
118- # assign a variable for each state symbol it's value in u
119- $ ([:($ var = u[$ idx]) for (var, idx) in pairs (sys. state_index)]. .. )
120- # assign a variable for each parameter symbol it's value in p
121- $ ([:($ var = p[$ idx]) for (var, idx) in pairs (sys. parameter_index)]. .. )
122- # return the value of the expression
123- return $ sym
124- end
125- )
126- end
127- return @RuntimeGeneratedFunction (fn_expr)
102+ # generate a function with the appropriate signature
103+ if is_time_dependent (sys)
104+ fn_expr = :(
105+ function gen (u, p, t)
106+ # assign a variable for each state symbol it's value in u
107+ $ ([:($ var = u[$ idx]) for (var, idx) in pairs (sys. state_index)]. .. )
108+ # assign a variable for each parameter symbol it's value in p
109+ $ ([:($ var = p[$ idx]) for (var, idx) in pairs (sys. parameter_index)]. .. )
110+ # assign a variable for the independent variable
111+ $ (sys. independent_variable) = t
112+ # return the value of the expression
113+ return $ sym
114+ end
115+ )
116+ else
117+ fn_expr = :(
118+ function gen (u, p)
119+ # assign a variable for each state symbol it's value in u
120+ $ ([:($ var = u[$ idx]) for (var, idx) in pairs (sys. state_index)]. .. )
121+ # assign a variable for each parameter symbol it's value in p
122+ $ ([:($ var = p[$ idx]) for (var, idx) in pairs (sys. parameter_index)]. .. )
123+ # return the value of the expression
124+ return $ sym
125+ end
126+ )
127+ end
128+ return @RuntimeGeneratedFunction (fn_expr)
128129end
129130```
130131
@@ -136,16 +137,17 @@ defined to always return `false`, and `observed` does not need to be implemented
136137Note that the method definitions are all assuming ` constant_structure(p) == true ` .
137138
138139In case ` constant_structure(p) == false ` , the following methods would change:
139- - ` constant_structure(::ExampleSystem) = false `
140- - ` variable_index(sys::ExampleSystem, sym) ` would become
141- ` variable_index(sys::ExampleSystem, sym i) ` where ` i ` is the time index at which
142- the index of ` sym ` is required.
143- - ` variable_symbols(sys::ExampleSystem) ` would become
144- ` variable_symbols(sys::ExampleSystem, i) ` where ` i ` is the time index at which
145- the variable symbols are required.
146- - ` observed(sys::ExampleSystem, sym) ` would become
147- ` observed(sys::ExampleSystem, sym, i) ` where ` i ` is either the time index at which
148- the index of ` sym ` is required or a ` Vector ` of state symbols at the current time index.
140+
141+ - ` constant_structure(::ExampleSystem) = false `
142+ - ` variable_index(sys::ExampleSystem, sym) ` would become
143+ ` variable_index(sys::ExampleSystem, sym i) ` where ` i ` is the time index at which
144+ the index of ` sym ` is required.
145+ - ` variable_symbols(sys::ExampleSystem) ` would become
146+ ` variable_symbols(sys::ExampleSystem, i) ` where ` i ` is the time index at which
147+ the variable symbols are required.
148+ - ` observed(sys::ExampleSystem, sym) ` would become
149+ ` observed(sys::ExampleSystem, sym, i) ` where ` i ` is either the time index at which
150+ the index of ` sym ` is required or a ` Vector ` of state symbols at the current time index.
149151
150152## Optional methods
151153
@@ -163,7 +165,7 @@ them is not necessary.
163165
164166``` julia
165167function SymbolicIndexingInterface. parameter_values (sys:: ExampleSystem )
166- sys. p
168+ sys. p
167169end
168170```
169171
@@ -179,10 +181,10 @@ Consider the following `ExampleIntegrator`
179181
180182``` julia
181183mutable struct ExampleIntegrator
182- u:: Vector{Float64}
183- p:: Vector{Float64}
184- t:: Float64
185- sys:: ExampleSystem
184+ u:: Vector{Float64}
185+ p:: Vector{Float64}
186+ t:: Float64
187+ sys:: ExampleSystem
186188end
187189
188190# define a fallback for the interface methods
@@ -193,6 +195,7 @@ SymbolicIndexingInterface.current_time(sys::ExampleIntegrator) = sys.t
193195```
194196
195197Then the following example would work:
198+
196199``` julia
197200sys = ExampleSystem (Dict (:x => 1 , :y => 2 , :z => 3 ), Dict (:a => 1 , :b => 2 ), :t , Dict ())
198201integrator = ExampleIntegrator ([1.0 , 2.0 , 3.0 ], [4.0 , 5.0 ], 6.0 , sys)
@@ -215,10 +218,10 @@ the [`Timeseries`](@ref) trait. The type would then return a timeseries from
215218
216219``` julia
217220struct ExampleSolution
218- u:: Vector{Vector{Float64}}
219- t:: Vector{Float64}
220- p:: Vector{Float64}
221- sys:: ExampleSystem
221+ u:: Vector{Vector{Float64}}
222+ t:: Vector{Float64}
223+ p:: Vector{Float64}
224+ sys:: ExampleSystem
222225end
223226
224227# define a fallback for the interface methods
@@ -233,6 +236,7 @@ SymbolicIndexingInterface.current_time(sol::ExampleSolution) = sol.t
233236```
234237
235238Then the following example would work:
239+
236240``` julia
237241# using the same system that the ExampleIntegrator used
238242sol = ExampleSolution ([[1.0 , 2.0 , 3.0 ], [1.5 , 2.5 , 3.5 ]], [4.0 , 5.0 ], [6.0 , 7.0 ], sys)
@@ -262,8 +266,8 @@ follows:
262266
263267``` julia
264268function SymbolicIndexingInterface. set_state! (integrator:: ExampleIntegrator , val, idx)
265- integrator. u[idx] = val
266- integrator. u_modified = true
269+ integrator. u[idx] = val
270+ integrator. u_modified = true
267271end
268272```
269273
@@ -279,24 +283,25 @@ performed for a bulk parameter update.
279283# The ` ParameterIndexingProxy `
280284
281285[ ` ParameterIndexingProxy ` ] ( @ref ) is a wrapper around another type which implements the
282- interface and allows using [ ` getp ` ] ( @ref ) and [ ` setp ` ] ( @ref ) to get and set parameter
286+ interface and allows using [ ` getp ` ] ( @ref ) and [ ` setp ` ] ( @ref ) to get and set parameter
283287values. This allows for a cleaner interface for parameter indexing. Consider the
284288following example for ` ExampleIntegrator ` :
285289
286290``` julia
287291function Base. getproperty (obj:: ExampleIntegrator , sym:: Symbol )
288- if sym === :ps
289- return ParameterIndexingProxy (obj)
290- else
291- return getfield (obj, sym)
292- end
292+ if sym === :ps
293+ return ParameterIndexingProxy (obj)
294+ else
295+ return getfield (obj, sym)
296+ end
293297end
294298```
295299
296300This enables the following API:
297301
298302``` julia
299- integrator = ExampleIntegrator ([1.0 , 2.0 , 3.0 ], [4.0 , 5.0 ], 6.0 , Dict (:x => 1 , :y => 2 , :z => 3 ), Dict (:a => 1 , :b => 2 ), :t )
303+ integrator = ExampleIntegrator ([1.0 , 2.0 , 3.0 ], [4.0 , 5.0 ], 6.0 ,
304+ Dict (:x => 1 , :y => 2 , :z => 3 ), Dict (:a => 1 , :b => 2 ), :t )
300305
301306integrator. ps[:a ] # 4.0
302307getp (integrator, :a )(integrator) # functionally the same as above
@@ -310,25 +315,25 @@ setp(integrator, :b)(integrator, 3.0) # functionally the same as above
310315The ` SymbolicTypeTrait ` is used to identify values that can act as symbolic variables. It
311316has three variants:
312317
313- - [ ` NotSymbolic ` ] ( @ref ) for quantities that are not symbolic. This is the default for all
314- types.
315- - [ ` ScalarSymbolic ` ] ( @ref ) for quantities that are symbolic, and represent a single
316- logical value.
317- - [ ` ArraySymbolic ` ] ( @ref ) for quantities that are symbolic, and represent an array of
318- values. Types implementing this trait must return an array of ` ScalarSymbolic ` variables
319- of the appropriate size and dimensions when ` collect ` ed.
318+ - [ ` NotSymbolic ` ] ( @ref ) for quantities that are not symbolic. This is the default for all
319+ types.
320+ - [ ` ScalarSymbolic ` ] ( @ref ) for quantities that are symbolic, and represent a single
321+ logical value.
322+ - [ ` ArraySymbolic ` ] ( @ref ) for quantities that are symbolic, and represent an array of
323+ values. Types implementing this trait must return an array of ` ScalarSymbolic ` variables
324+ of the appropriate size and dimensions when ` collect ` ed.
320325
321326The trait is implemented through the [ ` symbolic_type ` ] ( @ref ) function. Consider the following
322327example types:
323328
324329``` julia
325330struct MySym
326- name:: Symbol
331+ name:: Symbol
327332end
328333
329334struct MySymArr{N}
330- name:: Symbol
331- size:: NTuple{N,Int}
335+ name:: Symbol
336+ size:: NTuple{N, Int}
332337end
333338```
334339
@@ -343,10 +348,8 @@ SymbolicIndexingInterface.symbolic_type(::Type{<:MySymArr}) = ArraySymbolic()
343348SymbolicIndexingInterface. hasname (:: MySymArr ) = true
344349SymbolicIndexingInterface. getname (sym:: MySymArr ) = sym. name
345350function Base. collect (sym:: MySymArr )
346- [
347- MySym (Symbol (sym. name, :_ , join (idxs, " _" )))
348- for idxs in Iterators. product (Base. OneTo .(sym. size)... )
349- ]
351+ [MySym (Symbol (sym. name, :_ , join (idxs, " _" )))
352+ for idxs in Iterators. product (Base. OneTo .(sym. size)... )]
350353end
351354```
352355
0 commit comments