@@ -4,7 +4,7 @@ using ModelingToolkit: Symbolic, iscall, operation, arguments, build_function
4
4
using ModelingToolkit: ModelingToolkit, Equation, ODESystem, Differential
5
5
using ModelingToolkit: full_equations, get_variables, structural_simplify, getname, unwrap
6
6
using ModelingToolkit: full_parameters, unknowns, independent_variables, observed, defaults
7
- using ModelingToolkit. Symbolics: Symbolics, fixpoint_sub
7
+ using ModelingToolkit. Symbolics: Symbolics, fixpoint_sub, substitute
8
8
using RecursiveArrayTools: RecursiveArrayTools
9
9
using ArgCheck: @argcheck
10
10
using LinearAlgebra: Diagonal, I
@@ -16,18 +16,20 @@ import NetworkDynamics: VertexModel, EdgeModel, AnnotatedSym
16
16
include (" MTKUtils.jl" )
17
17
18
18
"""
19
- VertexModel(sys::ODESystem, inputs, outputs; kwargs...)
19
+ VertexModel(sys::ODESystem, inputs, outputs; ff_to_constraint=true, kwargs...)
20
20
21
21
Create a `VertexModel` object from a given `ODESystem` created with ModelingToolkit.
22
22
You need to provide 2 lists of symbolic names (`Symbol` or `Vector{Symbols}`):
23
23
- `inputs`: names of variables in you equation representing the aggregated edge states
24
24
- `outputs`: names of variables in you equation representing the node output
25
+
26
+ `ff_to_constraint` controlls, whether output transformations `g` which depend on inputs should be
25
27
"""
26
- function VertexModel (sys:: ODESystem , inputs, outputs; verbose= false , name= getname (sys), kwargs... )
28
+ function VertexModel (sys:: ODESystem , inputs, outputs; verbose= false , name= getname (sys), ff_to_constraint = true , kwargs... )
27
29
warn_events (sys)
28
30
inputs = inputs isa AbstractVector ? inputs : [inputs]
29
31
outputs = outputs isa AbstractVector ? outputs : [outputs]
30
- gen = generate_io_function (sys, (inputs,), (outputs,); verbose)
32
+ gen = generate_io_function (sys, (inputs,), (outputs,); verbose, ff_to_constraint )
31
33
32
34
f = gen. f
33
35
g = gen. g
@@ -58,7 +60,7 @@ function VertexModel(sys::ODESystem, inputs, outputs; verbose=false, name=getnam
58
60
end
59
61
60
62
"""
61
- EdgeModel(sys::ODESystem, srcin, dstin, AntiSymmetric(dstout); kwargs...)
63
+ EdgeModel(sys::ODESystem, srcin, dstin, AntiSymmetric(dstout); ff_to_constraint=false, kwargs...)
62
64
63
65
Create a `EdgeModel` object from a given `ODESystem` created with ModelingToolkit.
64
66
@@ -68,20 +70,26 @@ the symbol vector in
68
70
- `AntiSymmetric(dstout)`,
69
71
- `Symmetric(dstout)`, or
70
72
- `Directed(dstout)`.
73
+
74
+ `ff_to_constraint` controlls, whether output transformations `g` which depend on inputs should be
75
+ transformed into constraints.
71
76
"""
72
77
EdgeModel (sys:: ODESystem , srcin, dstin, dstout; kwargs... ) = EdgeModel (sys, srcin, dstin, nothing , dstout; kwargs... )
73
78
74
79
"""
75
- EdgeModel(sys::ODESystem, srcin, srcout, dstin, dstout; kwargs...)
80
+ EdgeModel(sys::ODESystem, srcin, srcout, dstin, dstout; ff_to_constraint=false, kwargs...)
76
81
77
82
Create a `EdgeModel` object from a given `ODESystem` created with ModelingToolkit.
78
83
You need to provide 4 lists of symbolic names (`Symbol` or `Vector{Symbols}`):
79
84
- `srcin`: names of variables in you equation representing the node state at the source
80
85
- `dstin`: names of variables in you equation representing the node state at the destination
81
86
- `srcout`: names of variables in you equation representing the output at the source
82
87
- `dstout`: names of variables in you equation representing the output at the destination
88
+
89
+ `ff_to_constraint` controlls, whether output transformations `g` which depend on inputs should be
90
+ transformed into constraints.
83
91
"""
84
- function EdgeModel (sys:: ODESystem , srcin, dstin, srcout, dstout; verbose= false , name= getname (sys), kwargs... )
92
+ function EdgeModel (sys:: ODESystem , srcin, dstin, srcout, dstout; verbose= false , name= getname (sys), ff_to_constraint = false , kwargs... )
85
93
warn_events (sys)
86
94
srcin = srcin isa AbstractVector ? srcin : [srcin]
87
95
dstin = dstin isa AbstractVector ? dstin : [dstin]
@@ -103,7 +111,7 @@ function EdgeModel(sys::ODESystem, srcin, dstin, srcout, dstout; verbose=false,
103
111
outs = (srcout, dstout)
104
112
end
105
113
106
- gen = generate_io_function (sys, (srcin, dstin), outs; verbose)
114
+ gen = generate_io_function (sys, (srcin, dstin), outs; verbose, ff_to_constraint )
107
115
108
116
f = gen. f
109
117
g = singlesided ? gwrap (gen. g; ff= gen. fftype) : gen. g
@@ -192,7 +200,8 @@ function _get_metadata(sys, name)
192
200
end
193
201
194
202
function generate_io_function (_sys, inputss:: Tuple , outputss:: Tuple ;
195
- expression= Val{false }, verbose= false )
203
+ expression= Val{false }, verbose= false ,
204
+ ff_to_constraint)
196
205
# TODO : scalarize vector symbolics/equations?
197
206
198
207
# f_* may be given in namepsace version or as symbols, resolve to unnamespaced Symbolic
@@ -240,21 +249,6 @@ function generate_io_function(_sys, inputss::Tuple, outputss::Tuple;
240
249
# end
241
250
throw (ArgumentError (String (take! (buf))))
242
251
end
243
- # generate mass matrix (this might change the equations)
244
- mass_matrix = begin
245
- # equations of form o = f(...) have to be transformed to 0 = f(...) - o
246
- for (i, eq) in enumerate (eqs)
247
- if eq_type (eq)[1 ] == :explicit_algebraic
248
- eqs[i] = 0 ~ eq. rhs - eq. lhs
249
- end
250
- end
251
- verbose && @info " Transformed algebraic eqs" eqs
252
-
253
- # create massmatrix, we don't use the method provided by ODESystem because of reordering
254
- mm = generate_massmatrix (eqs)
255
- verbose && @info " Reordered by states and generated mass matrix" mm
256
- mm
257
- end
258
252
259
253
# extract observed equations. They might depend on eachother so resolve them
260
254
obs_subs = Dict (eq. lhs => eq. rhs for eq in observed (sys))
@@ -268,22 +262,64 @@ function generate_io_function(_sys, inputss::Tuple, outputss::Tuple;
268
262
@warn " obs_deps !⊆ parameters ∪ unknowns. Difference: $(setdiff (obs_deps, Set (allparams) ∪ Set (states))) "
269
263
end
270
264
271
- # find the output equations, this might remove the mfrom obseqs!
272
- outeqs = map (Iterators. flatten (outputss)) do out
265
+ # if some states shadow outputs (out ~ state in observed)
266
+ # switch their names. I.e. prioritize use of name `out`
267
+ renamings = Dict ()
268
+ for eq in obseqs
269
+ if eq. lhs ∈ Set (alloutputs) && iscall (eq. rhs) &&
270
+ operation (eq. rhs) isa Symbolics. BasicSymbolic && eq. rhs ∈ Set (states)
271
+ verbose && @info " Encountered trivial equation $eq . Swap out $(eq. lhs) <=> $(eq. rhs) everywhere."
272
+ renamings[eq. lhs] = eq. rhs
273
+ renamings[eq. rhs] = eq. lhs
274
+ end
275
+ end
276
+ if ! isempty (renamings)
277
+ eqs = map (eq -> substitute (eq, renamings), eqs)
278
+ obseqs = map (eq -> substitute (eq, renamings), obseqs)
279
+ states = map (s -> substitute (s, renamings), states)
280
+ verbose && @info " New States:" states
281
+ end
282
+
283
+ # find the output equations, this might remove them from obseqs!
284
+ outeqs = Equation[]
285
+ for out in Iterators. flatten (outputss)
273
286
if out ∈ Set (states)
274
- out ~ out
287
+ push! (outeqs, out ~ out)
275
288
else
276
289
idx = findfirst (eq -> isequal (eq. lhs, out), obseqs)
277
290
if isnothing (idx)
278
291
throw (ArgumentError (" Output $out was neither foundin states nor in observed equations." ))
279
292
end
280
293
eq = obseqs[idx]
281
294
deleteat! (obseqs, idx)
282
- obseqs
283
- eq
295
+
296
+ if ff_to_constraint && ! isempty (get_variables (eq. rhs) ∩ allinputs)
297
+ verbose && @info " Output $out would lead to FF in g, promote to state instead."
298
+ push! (eqs, 0 ~ eq. lhs - eq. rhs)
299
+ push! (states, eq. lhs)
300
+ push! (outeqs, eq. lhs ~ eq. lhs)
301
+ else
302
+ push! (outeqs, eq)
303
+ end
284
304
end
285
305
end
286
306
307
+ # generate mass matrix (this might change the equations)
308
+ mass_matrix = begin
309
+ # equations of form o = f(...) have to be transformed to 0 = f(...) - o
310
+ for (i, eq) in enumerate (eqs)
311
+ if eq_type (eq)[1 ] == :explicit_algebraic
312
+ eqs[i] = 0 ~ eq. rhs - eq. lhs
313
+ end
314
+ end
315
+ verbose && @info " Transformed algebraic eqs" eqs
316
+
317
+ # create massmatrix, we don't use the method provided by ODESystem because of reordering
318
+ mm = generate_massmatrix (eqs)
319
+ verbose && @info " Generated mass matrix" mm
320
+ mm
321
+ end
322
+
287
323
iv = only (independent_variables (sys))
288
324
out_deps = _all_rhs_symbols (outeqs)
289
325
fftype = _determine_fftype (out_deps, states, allinputs, params, iv)
0 commit comments