@@ -5,11 +5,11 @@ This tutorial will show how to define the entire Symbolic Indexing Interface on
5
5
6
6
``` julia
7
7
struct 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}
13
13
end
14
14
```
15
15
@@ -24,58 +24,58 @@ These are the simple functions which describe how to turn symbols into indices.
24
24
25
25
``` julia
26
26
function SymbolicIndexingInterface. is_variable (sys:: ExampleSystem , sym)
27
- haskey (sys. state_index, sym)
27
+ haskey (sys. state_index, sym)
28
28
end
29
29
30
30
function SymbolicIndexingInterface. variable_index (sys:: ExampleSystem , sym)
31
- get (sys. state_index, sym, nothing )
31
+ get (sys. state_index, sym, nothing )
32
32
end
33
33
34
34
function SymbolicIndexingInterface. variable_symbols (sys:: ExampleSystem )
35
- collect (keys (sys. state_index))
35
+ collect (keys (sys. state_index))
36
36
end
37
37
38
38
function SymbolicIndexingInterface. is_parameter (sys:: ExampleSystem , sym)
39
- haskey (sys. parameter_index, sym)
39
+ haskey (sys. parameter_index, sym)
40
40
end
41
41
42
42
function SymbolicIndexingInterface. parameter_index (sys:: ExampleSystem , sym)
43
- get (sys. parameter_index, sym, nothing )
43
+ get (sys. parameter_index, sym, nothing )
44
44
end
45
45
46
46
function SymbolicIndexingInterface. parameter_symbols (sys:: ExampleSystem )
47
- collect (keys (sys. parameter_index))
47
+ collect (keys (sys. parameter_index))
48
48
end
49
49
50
50
function 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
54
54
end
55
55
56
56
function SymbolicIndexingInterface. independent_variable_symbols (sys:: ExampleSystem )
57
- sys. independent_variable === nothing ? [] : [sys. independent_variable]
57
+ sys. independent_variable === nothing ? [] : [sys. independent_variable]
58
58
end
59
59
60
60
function SymbolicIndexingInterface. is_time_dependent (sys:: ExampleSystem )
61
- sys. independent_variable != = nothing
61
+ sys. independent_variable != = nothing
62
62
end
63
63
64
64
SymbolicIndexingInterface. constant_structure (:: ExampleSystem ) = true
65
65
66
66
function 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
+ )
71
71
end
72
72
73
73
function 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
+ )
79
79
end
80
80
```
81
81
@@ -90,36 +90,38 @@ RuntimeGeneratedFunctions.init(@__MODULE__)
90
90
91
91
# this type accepts `Expr` for observed expressions involving state/parameter/observed
92
92
# 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
94
96
95
97
function 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)
123
125
end
124
126
```
125
127
@@ -131,16 +133,17 @@ defined to always return `false`, and `observed` does not need to be implemented
131
133
Note that the method definitions are all assuming ` constant_structure(p) == true ` .
132
134
133
135
In 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.
144
147
145
148
## Optional methods
146
149
@@ -158,7 +161,7 @@ them is not necessary.
158
161
159
162
``` julia
160
163
function SymbolicIndexingInterface. parameter_values (sys:: ExampleSystem )
161
- sys. p
164
+ sys. p
162
165
end
163
166
```
164
167
@@ -174,10 +177,10 @@ Consider the following `ExampleIntegrator`
174
177
175
178
``` julia
176
179
mutable 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
181
184
end
182
185
183
186
# define a fallback for the interface methods
@@ -188,6 +191,7 @@ SymbolicIndexingInterface.current_time(sys::ExampleIntegrator) = sys.t
188
191
```
189
192
190
193
Then the following example would work:
194
+
191
195
``` julia
192
196
sys = ExampleSystem (Dict (:x => 1 , :y => 2 , :z => 3 ), Dict (:a => 1 , :b => 2 ), :t , Dict ())
193
197
integrator = 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
210
214
211
215
``` julia
212
216
struct 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
217
221
end
218
222
219
223
# define a fallback for the interface methods
@@ -228,6 +232,7 @@ SymbolicIndexingInterface.current_time(sol::ExampleSolution) = sol.t
228
232
```
229
233
230
234
Then the following example would work:
235
+
231
236
``` julia
232
237
# using the same system that the ExampleIntegrator used
233
238
sol = 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:
257
262
258
263
``` julia
259
264
function 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
262
267
end
263
268
```
264
269
265
270
# The ` ParameterIndexingProxy `
266
271
267
272
[ ` 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
269
274
values. This allows for a cleaner interface for parameter indexing. Consider the
270
275
following example for ` ExampleIntegrator ` :
271
276
272
277
``` julia
273
278
function 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
279
284
end
280
285
```
281
286
282
287
This enables the following API:
283
288
284
289
``` 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 )
286
292
287
293
integrator. ps[:a ] # 4.0
288
294
getp (integrator, :a )(integrator) # functionally the same as above
@@ -296,25 +302,25 @@ setp(integrator, :b)(integrator, 3.0) # functionally the same as above
296
302
The ` SymbolicTypeTrait ` is used to identify values that can act as symbolic variables. It
297
303
has three variants:
298
304
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.
306
312
307
313
The trait is implemented through the [ ` symbolic_type ` ] ( @ref ) function. Consider the following
308
314
example types:
309
315
310
316
``` julia
311
317
struct MySym
312
- name:: Symbol
318
+ name:: Symbol
313
319
end
314
320
315
321
struct MySymArr{N}
316
- name:: Symbol
317
- size:: NTuple{N,Int}
322
+ name:: Symbol
323
+ size:: NTuple{N, Int}
318
324
end
319
325
```
320
326
@@ -329,10 +335,8 @@ SymbolicIndexingInterface.symbolic_type(::Type{<:MySymArr}) = ArraySymbolic()
329
335
SymbolicIndexingInterface. hasname (:: MySymArr ) = true
330
336
SymbolicIndexingInterface. getname (sym:: MySymArr ) = sym. name
331
337
function 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)... )]
336
340
end
337
341
```
338
342
0 commit comments