@@ -5,11 +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- # mapping from observed variable to Expr to calculate its value
12- 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}
1313end
1414```
1515
@@ -24,58 +24,58 @@ These are the simple functions which describe how to turn symbols into indices.
2424
2525``` julia
2626function SymbolicIndexingInterface. is_variable (sys:: ExampleSystem , sym)
27- haskey (sys. state_index, sym)
27+ haskey (sys. state_index, sym)
2828end
2929
3030function SymbolicIndexingInterface. variable_index (sys:: ExampleSystem , sym)
31- get (sys. state_index, sym, nothing )
31+ get (sys. state_index, sym, nothing )
3232end
3333
3434function SymbolicIndexingInterface. variable_symbols (sys:: ExampleSystem )
35- collect (keys (sys. state_index))
35+ collect (keys (sys. state_index))
3636end
3737
3838function SymbolicIndexingInterface. is_parameter (sys:: ExampleSystem , sym)
39- haskey (sys. parameter_index, sym)
39+ haskey (sys. parameter_index, sym)
4040end
4141
4242function SymbolicIndexingInterface. parameter_index (sys:: ExampleSystem , sym)
43- get (sys. parameter_index, sym, nothing )
43+ get (sys. parameter_index, sym, nothing )
4444end
4545
4646function SymbolicIndexingInterface. parameter_symbols (sys:: ExampleSystem )
47- collect (keys (sys. parameter_index))
47+ collect (keys (sys. parameter_index))
4848end
4949
5050function SymbolicIndexingInterface. is_independent_variable (sys:: ExampleSystem , sym)
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
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
5454end
5555
5656function SymbolicIndexingInterface. independent_variable_symbols (sys:: ExampleSystem )
57- sys. independent_variable === nothing ? [] : [sys. independent_variable]
57+ sys. independent_variable === nothing ? [] : [sys. independent_variable]
5858end
5959
6060function SymbolicIndexingInterface. is_time_dependent (sys:: ExampleSystem )
61- sys. independent_variable != = nothing
61+ sys. independent_variable != = nothing
6262end
6363
6464SymbolicIndexingInterface. constant_structure (:: ExampleSystem ) = true
6565
6666function SymbolicIndexingInterface. all_solvable_symbols (sys:: ExampleSystem )
67- return vcat (
68- collect (keys (sys. state_index)),
69- collect (keys (sys. observed)),
70- )
67+ return vcat (
68+ collect (keys (sys. state_index)),
69+ collect (keys (sys. observed))
70+ )
7171end
7272
7373function SymbolicIndexingInterface. all_symbols (sys:: ExampleSystem )
74- return vcat (
75- all_solvable_symbols (sys),
76- collect (keys (sys. parameter_index)),
77- sys. independent_variable === nothing ? Symbol[] : sys. independent_variable
78- )
74+ return vcat (
75+ all_solvable_symbols (sys),
76+ collect (keys (sys. parameter_index)),
77+ sys. independent_variable === nothing ? Symbol[] : sys. independent_variable
78+ )
7979end
8080```
8181
@@ -90,36 +90,38 @@ RuntimeGeneratedFunctions.init(@__MODULE__)
9090
9191# this type accepts `Expr` for observed expressions involving state/parameter/observed
9292# variables
93- SymbolicIndexingInterface. is_observed (sys:: ExampleSystem , sym) = sym isa Expr || sym isa Symbol && haskey (sys. observed, sym)
93+ function SymbolicIndexingInterface. is_observed (sys:: ExampleSystem , sym)
94+ sym isa Expr || sym isa Symbol && haskey (sys. observed, sym)
95+ end
9496
9597function SymbolicIndexingInterface. observed (sys:: ExampleSystem , sym:: Expr )
96- # generate a function with the appropriate signature
97- if is_time_dependent (sys)
98- fn_expr = :(
99- function gen (u, p, t)
100- # assign a variable for each state symbol it's value in u
101- $ ([:($ var = u[$ idx]) for (var, idx) in pairs (sys. state_index)]. .. )
102- # assign a variable for each parameter symbol it's value in p
103- $ ([:($ var = p[$ idx]) for (var, idx) in pairs (sys. parameter_index)]. .. )
104- # assign a variable for the independent variable
105- $ (sys. independent_variable) = t
106- # return the value of the expression
107- return $ sym
108- end
109- )
110- else
111- fn_expr = :(
112- function gen (u, p)
113- # assign a variable for each state symbol it's value in u
114- $ ([:($ var = u[$ idx]) for (var, idx) in pairs (sys. state_index)]. .. )
115- # assign a variable for each parameter symbol it's value in p
116- $ ([:($ var = p[$ idx]) for (var, idx) in pairs (sys. parameter_index)]. .. )
117- # return the value of the expression
118- return $ sym
119- end
120- )
121- end
122- return @RuntimeGeneratedFunction (fn_expr)
98+ # generate a function with the appropriate signature
99+ if is_time_dependent (sys)
100+ fn_expr = :(
101+ function gen (u, p, t)
102+ # assign a variable for each state symbol it's value in u
103+ $ ([:($ var = u[$ idx]) for (var, idx) in pairs (sys. state_index)]. .. )
104+ # assign a variable for each parameter symbol it's value in p
105+ $ ([:($ var = p[$ idx]) for (var, idx) in pairs (sys. parameter_index)]. .. )
106+ # assign a variable for the independent variable
107+ $ (sys. independent_variable) = t
108+ # return the value of the expression
109+ return $ sym
110+ end
111+ )
112+ else
113+ fn_expr = :(
114+ function gen (u, p)
115+ # assign a variable for each state symbol it's value in u
116+ $ ([:($ var = u[$ idx]) for (var, idx) in pairs (sys. state_index)]. .. )
117+ # assign a variable for each parameter symbol it's value in p
118+ $ ([:($ var = p[$ idx]) for (var, idx) in pairs (sys. parameter_index)]. .. )
119+ # return the value of the expression
120+ return $ sym
121+ end
122+ )
123+ end
124+ return @RuntimeGeneratedFunction (fn_expr)
123125end
124126```
125127
@@ -131,16 +133,17 @@ defined to always return `false`, and `observed` does not need to be implemented
131133Note that the method definitions are all assuming ` constant_structure(p) == true ` .
132134
133135In case ` constant_structure(p) == false ` , the following methods would change:
134- - ` constant_structure(::ExampleSystem) = false `
135- - ` variable_index(sys::ExampleSystem, sym) ` would become
136- ` variable_index(sys::ExampleSystem, sym i) ` where ` i ` is the time index at which
137- the index of ` sym ` is required.
138- - ` variable_symbols(sys::ExampleSystem) ` would become
139- ` variable_symbols(sys::ExampleSystem, i) ` where ` i ` is the time index at which
140- the variable symbols are required.
141- - ` observed(sys::ExampleSystem, sym) ` would become
142- ` observed(sys::ExampleSystem, sym, i) ` where ` i ` is either the time index at which
143- the index of ` sym ` is required or a ` Vector ` of state symbols at the current time index.
136+
137+ - ` constant_structure(::ExampleSystem) = false `
138+ - ` variable_index(sys::ExampleSystem, sym) ` would become
139+ ` variable_index(sys::ExampleSystem, sym i) ` where ` i ` is the time index at which
140+ the index of ` sym ` is required.
141+ - ` variable_symbols(sys::ExampleSystem) ` would become
142+ ` variable_symbols(sys::ExampleSystem, i) ` where ` i ` is the time index at which
143+ the variable symbols are required.
144+ - ` observed(sys::ExampleSystem, sym) ` would become
145+ ` observed(sys::ExampleSystem, sym, i) ` where ` i ` is either the time index at which
146+ the index of ` sym ` is required or a ` Vector ` of state symbols at the current time index.
144147
145148## Optional methods
146149
@@ -158,7 +161,7 @@ them is not necessary.
158161
159162``` julia
160163function SymbolicIndexingInterface. parameter_values (sys:: ExampleSystem )
161- sys. p
164+ sys. p
162165end
163166```
164167
@@ -174,10 +177,10 @@ Consider the following `ExampleIntegrator`
174177
175178``` julia
176179mutable struct ExampleIntegrator
177- u:: Vector{Float64}
178- p:: Vector{Float64}
179- t:: Float64
180- sys:: ExampleSystem
180+ u:: Vector{Float64}
181+ p:: Vector{Float64}
182+ t:: Float64
183+ sys:: ExampleSystem
181184end
182185
183186# define a fallback for the interface methods
@@ -188,6 +191,7 @@ SymbolicIndexingInterface.current_time(sys::ExampleIntegrator) = sys.t
188191```
189192
190193Then the following example would work:
194+
191195``` julia
192196sys = ExampleSystem (Dict (:x => 1 , :y => 2 , :z => 3 ), Dict (:a => 1 , :b => 2 ), :t , Dict ())
193197integrator = ExampleIntegrator ([1.0 , 2.0 , 3.0 ], [4.0 , 5.0 ], 6.0 , sys)
@@ -210,10 +214,10 @@ the [`Timeseries`](@ref) trait. The type would then return a timeseries from
210214
211215``` julia
212216struct ExampleSolution
213- u:: Vector{Vector{Float64}}
214- t:: Vector{Float64}
215- p:: Vector{Float64}
216- sys:: ExampleSystem
217+ u:: Vector{Vector{Float64}}
218+ t:: Vector{Float64}
219+ p:: Vector{Float64}
220+ sys:: ExampleSystem
217221end
218222
219223# define a fallback for the interface methods
@@ -228,6 +232,7 @@ SymbolicIndexingInterface.current_time(sol::ExampleSolution) = sol.t
228232```
229233
230234Then the following example would work:
235+
231236``` julia
232237# using the same system that the ExampleIntegrator used
233238sol = ExampleSolution ([[1.0 , 2.0 , 3.0 ], [1.5 , 2.5 , 3.5 ]], [4.0 , 5.0 ], [6.0 , 7.0 ], sys)
@@ -257,32 +262,33 @@ follows:
257262
258263``` julia
259264function SymbolicIndexingInterface. set_state! (integrator:: ExampleIntegrator , val, idx)
260- integrator. u[idx] = val
261- integrator. u_modified = true
265+ integrator. u[idx] = val
266+ integrator. u_modified = true
262267end
263268```
264269
265270# The ` ParameterIndexingProxy `
266271
267272[ ` ParameterIndexingProxy ` ] ( @ref ) is a wrapper around another type which implements the
268- interface and allows using [ ` getp ` ] ( @ref ) and [ ` setp ` ] ( @ref ) to get and set parameter
273+ interface and allows using [ ` getp ` ] ( @ref ) and [ ` setp ` ] ( @ref ) to get and set parameter
269274values. This allows for a cleaner interface for parameter indexing. Consider the
270275following example for ` ExampleIntegrator ` :
271276
272277``` julia
273278function Base. getproperty (obj:: ExampleIntegrator , sym:: Symbol )
274- if sym === :ps
275- return ParameterIndexingProxy (obj)
276- else
277- return getfield (obj, sym)
278- end
279+ if sym === :ps
280+ return ParameterIndexingProxy (obj)
281+ else
282+ return getfield (obj, sym)
283+ end
279284end
280285```
281286
282287This enables the following API:
283288
284289``` julia
285- 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 )
290+ integrator = ExampleIntegrator ([1.0 , 2.0 , 3.0 ], [4.0 , 5.0 ], 6.0 ,
291+ Dict (:x => 1 , :y => 2 , :z => 3 ), Dict (:a => 1 , :b => 2 ), :t )
286292
287293integrator. ps[:a ] # 4.0
288294getp (integrator, :a )(integrator) # functionally the same as above
@@ -296,25 +302,25 @@ setp(integrator, :b)(integrator, 3.0) # functionally the same as above
296302The ` SymbolicTypeTrait ` is used to identify values that can act as symbolic variables. It
297303has three variants:
298304
299- - [ ` NotSymbolic ` ] ( @ref ) for quantities that are not symbolic. This is the default for all
300- types.
301- - [ ` ScalarSymbolic ` ] ( @ref ) for quantities that are symbolic, and represent a single
302- logical value.
303- - [ ` ArraySymbolic ` ] ( @ref ) for quantities that are symbolic, and represent an array of
304- values. Types implementing this trait must return an array of ` ScalarSymbolic ` variables
305- of the appropriate size and dimensions when ` collect ` ed.
305+ - [ ` NotSymbolic ` ] ( @ref ) for quantities that are not symbolic. This is the default for all
306+ types.
307+ - [ ` ScalarSymbolic ` ] ( @ref ) for quantities that are symbolic, and represent a single
308+ logical value.
309+ - [ ` ArraySymbolic ` ] ( @ref ) for quantities that are symbolic, and represent an array of
310+ values. Types implementing this trait must return an array of ` ScalarSymbolic ` variables
311+ of the appropriate size and dimensions when ` collect ` ed.
306312
307313The trait is implemented through the [ ` symbolic_type ` ] ( @ref ) function. Consider the following
308314example types:
309315
310316``` julia
311317struct MySym
312- name:: Symbol
318+ name:: Symbol
313319end
314320
315321struct MySymArr{N}
316- name:: Symbol
317- size:: NTuple{N,Int}
322+ name:: Symbol
323+ size:: NTuple{N, Int}
318324end
319325```
320326
@@ -329,10 +335,8 @@ SymbolicIndexingInterface.symbolic_type(::Type{<:MySymArr}) = ArraySymbolic()
329335SymbolicIndexingInterface. hasname (:: MySymArr ) = true
330336SymbolicIndexingInterface. getname (sym:: MySymArr ) = sym. name
331337function Base. collect (sym:: MySymArr )
332- [
333- MySym (Symbol (sym. name, :_ , join (idxs, " _" )))
334- for idxs in Iterators. product (Base. OneTo .(sym. size)... )
335- ]
338+ [MySym (Symbol (sym. name, :_ , join (idxs, " _" )))
339+ for idxs in Iterators. product (Base. OneTo .(sym. size)... )]
336340end
337341```
338342
0 commit comments