Skip to content

Commit 82a46d7

Browse files
Merge branch 'master' of github.com:SciML/ModelingToolkit.jl into domain-sets
2 parents 140200f + b8d072b commit 82a46d7

19 files changed

+474
-18
lines changed

Project.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelingToolkit"
22
uuid = "961ee093-0014-501f-94e3-6117800e7a78"
33
authors = ["Chris Rackauckas <[email protected]>"]
4-
version = "5.15.0"
4+
version = "5.17.1"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -16,6 +16,7 @@ Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
1616
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
1717
DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf"
1818
IfElse = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
19+
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
1920
LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800"
2021
Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
2122
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
@@ -48,9 +49,10 @@ DataStructures = "0.17, 0.18"
4849
DiffEqBase = "6.54.0"
4950
DiffEqJump = "6.7.5"
5051
DiffRules = "0.1, 1.0"
51-
Distributions = "0.23, 0.24"
52+
Distributions = "0.23, 0.24, 0.25"
5253
DocStringExtensions = "0.7, 0.8"
5354
IfElse = "0.1"
55+
JuliaFormatter = "0.12, 0.13"
5456
LabelledArrays = "1.3"
5557
Latexify = "0.11, 0.12, 0.13, 0.14, 0.15"
5658
LightGraphs = "1.3"

docs/src/basics/Composition.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,40 @@ done even if the variable `x` is eliminated from the system from
125125
transformations like `alias_elimination` or `tearing`: the variable
126126
will be lazily reconstructed on demand.
127127

128+
### Variable scope and parameter expressions
129+
130+
In some scenarios, it could be useful for model parameters to be expressed
131+
in terms of other parameters, or shared between common subsystems.
132+
To fascilitate this, ModelingToolkit supports sybmolic expressions
133+
in default values, and scoped variables.
134+
135+
With symbolic parameters, it is possible to set the default value of a parameter or initial condition to an expression of other variables.
136+
137+
```julia
138+
# ...
139+
sys = ODESystem(
140+
# ...
141+
# directly in the defauls argument
142+
defaults=Pair{Num, Any}[
143+
x => u,
144+
y => σ,
145+
z => u-0.1,
146+
])
147+
# by assigning to the parameter
148+
sys.y = u*1.1
149+
```
150+
151+
In a hierarchical system, variables of the subsystem get namespaced by the name of the system they are in. This prevents naming clashes, but also enforces that every state and parameter is local to the subsystem it is used in. In some cases it might be desirable to have variables and parameters that are shared between subsystems, or even global. This can be accomplished as follows.
152+
153+
```julia
154+
@variables a b c d
155+
156+
# a is a local variable
157+
b = ParentScope(b) # b is a variable that belongs to one level up in the hierarchy
158+
c = ParentScope(ParentScope(c)) # ParentScope can be nested
159+
d = GlobalScope(d) # global variables will never be namespaced
160+
```
161+
128162
## Structural Simplify
129163

130164
In many cases, the nicest way to build a model may leave a lot of

docs/src/tutorials/ode_modeling.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ equations(fol_simplified) == equations(fol_model)
119119
```
120120

121121
You can extract the equations from a system using `equations` (and, in the same
122-
way, `variables` and `parameters`). The simplified equation is exactly the same
123-
as the original one, so the simulation performence will also be the same.
122+
way, `states` and `parameters`). The simplified equation is exactly the same
123+
as the original one, so the simulation performance will also be the same.
124124
However, there is one difference. MTK does keep track of the eliminated
125125
algebraic variables as "observables" (see
126126
[Observables and Variable Elimination](@ref)).

src/ModelingToolkit.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import SymbolicUtils: istree, arguments, operation, similarterm, promote_symtype
3535
using SymbolicUtils.Code
3636
import SymbolicUtils.Code: toexpr
3737
import SymbolicUtils.Rewriters: Chain, Postwalk, Prewalk, Fixpoint
38+
import JuliaFormatter
3839

3940
using Reexport
4041
@reexport using Symbolics
@@ -129,14 +130,16 @@ include("systems/pde/pdesystem.jl")
129130
include("systems/reaction/reactionsystem.jl")
130131
include("systems/dependency_graphs.jl")
131132

133+
include("systems/discrete_system/discrete_system.jl")
134+
132135
include("systems/systemstructure.jl")
133136
using .SystemStructures
134137

135138
include("systems/alias_elimination.jl")
136139
include("structural_transformation/StructuralTransformations.jl")
137140
@reexport using .StructuralTransformations
138141

139-
export ODESystem, ODEFunction, ODEFunctionExpr, ODEProblemExpr
142+
export ODESystem, ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system
140143
export DAEFunctionExpr, DAEProblemExpr
141144
export SDESystem, SDEFunction, SDEFunctionExpr, SDESystemExpr
142145
export SystemStructure
@@ -161,6 +164,7 @@ export Term, Sym
161164
export SymScope, LocalScope, ParentScope, GlobalScope
162165
export independent_variable, states, parameters, equations, controls, observed, structure
163166
export structural_simplify
167+
export DiscreteSystem, DiscreteProblem
164168

165169
export calculate_jacobian, generate_jacobian, generate_function
166170
export calculate_tgrad, generate_tgrad

src/structural_transformation/codegen.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ function torn_system_jacobian_sparsity(sys)
6868
end
6969
end
7070

71-
dvar2idx(idx) = idx # maps `dvar` to the index of the states
71+
dvrange = diffvars_range(s)
72+
dvar2idx = Dict(v=>i for (i, v) in enumerate(dvrange))
7273
I = Int[]; J = Int[]
7374
eqidx = 0
7475
for ieq in 𝑠vertices(graph)
@@ -77,11 +78,11 @@ function torn_system_jacobian_sparsity(sys)
7778
for ivar in 𝑠neighbors(graph, ieq)
7879
if isdiffvar(s, ivar)
7980
push!(I, eqidx)
80-
push!(J, dvar2idx(ivar))
81+
push!(J, dvar2idx[ivar])
8182
elseif isalgvar(s, ivar)
8283
for dvar in avars2dvars[ivar]
8384
push!(I, eqidx)
84-
push!(J, dvar2idx(dvar))
85+
push!(J, dvar2idx[dvar])
8586
end
8687
end
8788
end

src/systems/abstractsystem.jl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,103 @@ function (f::AbstractSysToExpr)(O)
411411
return build_expr(:call, Any[operation(O); f.(arguments(O))])
412412
end
413413

414+
###
415+
### System utils
416+
###
417+
function push_vars!(stmt, name, typ, vars)
418+
isempty(vars) && return
419+
vars_expr = Expr(:macrocall, typ, nothing)
420+
for s in vars
421+
if istree(s)
422+
f = nameof(operation(s))
423+
args = arguments(s)
424+
ex = :($f($(args...)))
425+
else
426+
ex = nameof(s)
427+
end
428+
push!(vars_expr.args, ex)
429+
end
430+
push!(stmt, :($name = $collect($vars_expr)))
431+
return
432+
end
433+
434+
function round_trip_expr(t, var2name)
435+
name = get(var2name, t, nothing)
436+
name !== nothing && return name
437+
t isa Sym && return nameof(t)
438+
istree(t) || return t
439+
f = round_trip_expr(operation(t), var2name)
440+
args = map(Base.Fix2(round_trip_expr, var2name), arguments(t))
441+
return :($f($(args...)))
442+
end
443+
round_trip_eq(eq, var2name) = Expr(:call, :~, round_trip_expr(eq.lhs, var2name), round_trip_expr(eq.rhs, var2name))
444+
445+
function push_eqs!(stmt, eqs, var2name)
446+
eqs_name = gensym(:eqs)
447+
eqs_expr = Expr(:vcat)
448+
eqs_blk = Expr(:(=), eqs_name, eqs_expr)
449+
for eq in eqs
450+
push!(eqs_expr.args, round_trip_eq(eq, var2name))
451+
end
452+
453+
push!(stmt, eqs_blk)
454+
return eqs_name
455+
end
456+
457+
function push_defaults!(stmt, defs, var2name)
458+
defs_name = gensym(:defs)
459+
defs_expr = Expr(:call, Dict)
460+
defs_blk = Expr(:(=), defs_name, defs_expr)
461+
for d in defs
462+
n = round_trip_expr(d.first, var2name)
463+
v = round_trip_expr(d.second, var2name)
464+
push!(defs_expr.args, :($(=>)($n, $v)))
465+
end
466+
467+
push!(stmt, defs_blk)
468+
return defs_name
469+
end
470+
471+
###
472+
### System I/O
473+
###
474+
function toexpr(sys::AbstractSystem)
475+
sys = flatten(sys)
476+
expr = Expr(:block)
477+
stmt = expr.args
478+
479+
iv = independent_variable(sys)
480+
ivname = gensym(:iv)
481+
if iv !== nothing
482+
push!(stmt, :($ivname = (@variables $(getname(iv)))[1]))
483+
end
484+
485+
stsname = gensym(:sts)
486+
sts = states(sys)
487+
push_vars!(stmt, stsname, Symbol("@variables"), sts)
488+
psname = gensym(:ps)
489+
ps = parameters(sys)
490+
push_vars!(stmt, psname, Symbol("@parameters"), ps)
491+
492+
var2name = Dict{Any,Symbol}()
493+
for v in Iterators.flatten((sts, ps))
494+
var2name[v] = getname(v)
495+
end
496+
497+
eqs_name = push_eqs!(stmt, equations(sys), var2name)
498+
defs_name = push_defaults!(stmt, defaults(sys), var2name)
499+
500+
if sys isa ODESystem
501+
push!(stmt, :($ODESystem($eqs_name, $ivname, $stsname, $psname; defaults=$defs_name)))
502+
elseif sys isa NonlinearSystem
503+
push!(stmt, :($NonlinearSystem($eqs_name, $stsname, $psname; defaults=$defs_name)))
504+
end
505+
506+
striplines(expr) # keeping the line numbers is never helpful
507+
end
508+
509+
Base.write(io::IO, sys::AbstractSystem) = write(io, readable_code(toexpr(sys)))
510+
414511
function Base.show(io::IO, ::MIME"text/plain", sys::AbstractSystem)
415512
eqs = equations(sys)
416513
if eqs isa AbstractArray
@@ -581,6 +678,10 @@ function check_eqs_u0(eqs, dvs, u0)
581678
return nothing
582679
end
583680

681+
###
682+
### Connectors
683+
###
684+
584685
function with_connection_type(expr)
585686
@assert expr isa Expr && (expr.head == :function || (expr.head == :(=) &&
586687
expr.args[1] isa Expr &&

src/systems/diffeqs/abstractodesystem.jl

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ function DiffEqBase.ODEFunction{iip}(sys::AbstractODESystem, dvs = states(sys),
221221
jac = _jac === nothing ? nothing : _jac,
222222
tgrad = _tgrad === nothing ? nothing : _tgrad,
223223
mass_matrix = _M,
224-
jac_prototype = (!isnothing(u0) && sparse) ? (!jac ? similar(jacobian_sparsity(sys),uElType) : similar(get_jac(sys)[],uElType)) : nothing,
224+
jac_prototype = (!isnothing(u0) && sparse) ? (!jac ? similar(jacobian_sparsity(sys),uElType) : SparseArrays.sparse(similar(get_jac(sys)[],uElType))) : nothing,
225225
syms = Symbol.(states(sys)),
226226
indepsym = Symbol(independent_variable(sys)),
227227
observed = observedfun,
@@ -397,16 +397,34 @@ function process_DEProblem(constructor, sys::AbstractODESystem,u0map,parammap;
397397
ps = parameters(sys)
398398
defs = defaults(sys)
399399
iv = independent_variable(sys)
400+
if parammap isa Dict
401+
u0defs = merge(parammap, defs)
402+
elseif eltype(parammap) <: Pair
403+
u0defs = merge(Dict(parammap), defs)
404+
elseif eltype(parammap) <: Number
405+
u0defs = merge(Dict(zip(ps, parammap)), defs)
406+
else
407+
u0defs = defs
408+
end
409+
if u0map isa Dict
410+
pdefs = merge(u0map, defs)
411+
elseif eltype(u0map) <: Pair
412+
pdefs = merge(Dict(u0map), defs)
413+
elseif eltype(u0map) <: Number
414+
pdefs = merge(Dict(zip(dvs, u0map)), defs)
415+
else
416+
pdefs = defs
417+
end
400418

401-
u0 = varmap_to_vars(u0map,dvs; defaults=defs)
419+
u0 = varmap_to_vars(u0map,dvs; defaults=u0defs)
402420
if implicit_dae && du0map !== nothing
403421
ddvs = map(Differential(iv), dvs)
404422
du0 = varmap_to_vars(du0map, ddvs; defaults=defaults, toterm=identity)
405423
else
406424
du0 = nothing
407425
ddvs = nothing
408426
end
409-
p = varmap_to_vars(parammap,ps; defaults=defs)
427+
p = varmap_to_vars(parammap,ps; defaults=pdefs)
410428

411429
check_eqs_u0(eqs, dvs, u0)
412430

src/systems/diffeqs/modelingtoolkitize.jl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ function modelingtoolkitize(prob::DiffEqBase.ODEProblem)
1717
has_p = !(p isa Union{DiffEqBase.NullParameters,Nothing})
1818

1919
var(x, i) = Num(Sym{FnType{Tuple{symtype(t)}, Real}}(nameof(Variable(x, i))))
20-
vars = ArrayInterface.restructure(prob.u0,[var(:x, i)(ModelingToolkit.value(t)) for i in eachindex(prob.u0)])
21-
params = has_p ? reshape([Num(toparam(Sym{Real}(nameof(Variable(, i))))) for i in eachindex(p)],size(p)) : []
20+
_vars = [var(:x, i)(ModelingToolkit.value(t)) for i in eachindex(prob.u0)]
21+
vars = prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0,_vars)
22+
params = if has_p
23+
_params = [Num(toparam(Sym{Real}(nameof(Variable(, i))))) for i in eachindex(p)]
24+
p isa Number ? _params[1] : reshape(_params,size(p))
25+
else
26+
[]
27+
end
28+
2229
var_set = Set(vars)
2330

2431
D = Differential(t)
@@ -64,8 +71,6 @@ function modelingtoolkitize(prob::DiffEqBase.ODEProblem)
6471
de
6572
end
6673

67-
68-
6974
"""
7075
$(TYPEDSIGNATURES)
7176

src/systems/diffeqs/odesystem.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,42 @@ function collect_differential_variables(sys::ODESystem)
300300
end
301301
return diffvars
302302
end
303+
304+
# We have a stand-alone function to convert a `NonlinearSystem` or `ODESystem`
305+
# to an `ODESystem` to connect systems, and we later can reply on
306+
# `structural_simplify` to convert `ODESystem`s to `NonlinearSystem`s.
307+
"""
308+
$(TYPEDSIGNATURES)
309+
310+
Convert a `NonlinearSystem` to an `ODESystem` or converts an `ODESystem` to a
311+
new `ODESystem` with a different independent variable.
312+
"""
313+
function convert_system(::Type{<:ODESystem}, sys, t; name=nameof(sys))
314+
isempty(observed(sys)) || throw(ArgumentError("`convert_system` cannot handle reduced model (i.e. observed(sys) is non-empty)."))
315+
t = value(t)
316+
varmap = Dict()
317+
sts = states(sys)
318+
newsts = similar(sts, Any)
319+
for (i, s) in enumerate(sts)
320+
if istree(s)
321+
args = arguments(s)
322+
length(args) == 1 || throw(InvalidSystemException("Illegal state: $s. The state can have at most one argument like `x(t)`."))
323+
arg = args[1]
324+
if isequal(arg, t)
325+
newsts[i] = s
326+
continue
327+
end
328+
ns = operation(s)(t)
329+
newsts[i] = ns
330+
varmap[s] = ns
331+
else
332+
ns = indepvar2depvar(s, t)
333+
newsts[i] = ns
334+
varmap[s] = ns
335+
end
336+
end
337+
sub = Base.Fix2(substitute, varmap)
338+
neweqs = map(sub, equations(sys))
339+
defs = Dict(sub(k) => sub(v) for (k, v) in defaults(sys))
340+
return ODESystem(neweqs, t, newsts, parameters(sys); defaults=defs, name=name)
341+
end

0 commit comments

Comments
 (0)