Skip to content

Commit 4e681e8

Browse files
committed
Merge remote-tracking branch 'origin/master' into s/array-fixes
2 parents 6d67c37 + 8cae6cf commit 4e681e8

17 files changed

+333
-46
lines changed

Project.toml

Lines changed: 3 additions & 3 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.16.0"
4+
version = "5.17.3"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -48,10 +48,10 @@ DataStructures = "0.17, 0.18"
4848
DiffEqBase = "6.54.0"
4949
DiffEqJump = "6.7.5"
5050
DiffRules = "0.1, 1.0"
51-
Distributions = "0.23, 0.24"
51+
Distributions = "0.23, 0.24, 0.25"
5252
DocStringExtensions = "0.7, 0.8"
5353
IfElse = "0.1"
54-
JuliaFormatter = "0.13"
54+
JuliaFormatter = "0.12, 0.13"
5555
LabelledArrays = "1.3"
5656
Latexify = "0.11, 0.12, 0.13, 0.14, 0.15"
5757
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ include("systems/pde/pdesystem.jl")
130130
include("systems/reaction/reactionsystem.jl")
131131
include("systems/dependency_graphs.jl")
132132

133+
include("systems/discrete_system/discrete_system.jl")
134+
133135
include("systems/systemstructure.jl")
134136
using .SystemStructures
135137

@@ -162,6 +164,7 @@ export Term, Sym
162164
export SymScope, LocalScope, ParentScope, GlobalScope
163165
export independent_variable, states, parameters, equations, controls, observed, structure
164166
export structural_simplify
167+
export DiscreteSystem, DiscreteProblem
165168

166169
export calculate_jacobian, generate_jacobian, generate_function
167170
export calculate_tgrad, generate_tgrad

src/systems/abstractsystem.jl

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -665,15 +665,17 @@ AbstractTrees.children(sys::ModelingToolkit.AbstractSystem) = ModelingToolkit.ge
665665
AbstractTrees.printnode(io::IO, sys::ModelingToolkit.AbstractSystem) = print(io, nameof(sys))
666666
AbstractTrees.nodetype(::ModelingToolkit.AbstractSystem) = ModelingToolkit.AbstractSystem
667667

668-
function check_eqs_u0(eqs, dvs, u0)
668+
function check_eqs_u0(eqs, dvs, u0; check_length=true, kwargs...)
669669
if u0 !== nothing
670-
if !(length(eqs) == length(dvs) == length(u0))
671-
throw(ArgumentError("Equations ($(length(eqs))), states ($(length(dvs))), and initial conditions ($(length(u0))) are of different lengths."))
672-
end
673-
else
674-
if !(length(eqs) == length(dvs))
675-
throw(ArgumentError("Equations ($(length(eqs))), states ($(length(dvs))) are of different lengths."))
670+
if check_length
671+
if !(length(eqs) == length(dvs) == length(u0))
672+
throw(ArgumentError("Equations ($(length(eqs))), states ($(length(dvs))), and initial conditions ($(length(u0))) are of different lengths. To allow a different number of equations than states use kwarg check_length=false."))
673+
end
674+
elseif length(dvs) != length(u0)
675+
throw(ArgumentError("States ($(length(dvs))) and initial conditions ($(length(u0))) are of different lengths."))
676676
end
677+
elseif check_length && (length(eqs) != length(dvs))
678+
throw(ArgumentError("Equations ($(length(eqs))) and states ($(length(dvs))) are of different lengths. To allow these to differ use kwarg check_length=false."))
677679
end
678680
return nothing
679681
end
@@ -713,7 +715,7 @@ end
713715

714716
promote_connect_rule(::Type{T}, ::Type{S}) where {T, S} = Union{}
715717
promote_connect_rule(::Type{T}, ::Type{T}) where {T} = T
716-
promote_connect_type(t1::Type, t2::Type, ts::Type...) = promote_connect_rule(promote_connect_rule(t1, t2), ts...)
718+
promote_connect_type(t1::Type, t2::Type, ts::Type...) = promote_connect_type(promote_connect_rule(t1, t2), ts...)
717719
@inline function promote_connect_type(::Type{T}, ::Type{S}) where {T,S}
718720
promote_connect_result(
719721
T,

src/systems/diffeqs/abstractodesystem.jl

Lines changed: 22 additions & 4 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,18 +397,36 @@ 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

411-
check_eqs_u0(eqs, dvs, u0)
429+
check_eqs_u0(eqs, dvs, u0; kwargs...)
412430

413431
f = constructor(sys,dvs,ps,u0;ddvs=ddvs,tgrad=tgrad,jac=jac,checkbounds=checkbounds,
414432
linenumbers=linenumbers,parallel=parallel,simplify=simplify,

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
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
$(TYPEDEF)
3+
4+
A system of difference equations.
5+
6+
# Fields
7+
$(FIELDS)
8+
9+
# Example
10+
11+
```
12+
using ModelingToolkit
13+
14+
@parameters t σ ρ β
15+
@variables x(t) y(t) z(t) next_x(t) next_y(t) next_z(t)
16+
17+
eqs = [next_x ~ σ*(y-x),
18+
next_y ~ x*(ρ-z)-y,
19+
next_z ~ x*y - β*z]
20+
21+
de = DiscreteSystem(eqs,t,[x,y,z],[σ,ρ,β])
22+
```
23+
"""
24+
struct DiscreteSystem <: AbstractSystem
25+
"""The differential equations defining the discrete system."""
26+
eqs::Vector{Equation}
27+
"""Independent variable."""
28+
iv::Sym
29+
"""Dependent (state) variables."""
30+
states::Vector
31+
"""Parameter variables."""
32+
ps::Vector
33+
observed::Vector{Equation}
34+
"""
35+
Name: the name of the system
36+
"""
37+
name::Symbol
38+
"""
39+
systems: The internal systems. These are required to have unique names.
40+
"""
41+
systems::Vector{DiscreteSystem}
42+
"""
43+
default_u0: The default initial conditions to use when initial conditions
44+
are not supplied in `DiscreteSystem`.
45+
"""
46+
default_u0::Dict
47+
"""
48+
default_p: The default parameters to use when parameters are not supplied
49+
in `DiscreteSystem`.
50+
"""
51+
default_p::Dict
52+
end
53+
54+
"""
55+
$(TYPEDSIGNATURES)
56+
57+
Constructs a DiscreteSystem.
58+
"""
59+
function DiscreteSystem(
60+
discreteEqs::AbstractVector{<:Equation}, iv, dvs, ps;
61+
observed = Num[],
62+
systems = DiscreteSystem[],
63+
name=gensym(:DiscreteSystem),
64+
default_u0=Dict(),
65+
default_p=Dict(),
66+
)
67+
iv′ = value(iv)
68+
dvs′ = value.(dvs)
69+
ps′ = value.(ps)
70+
71+
default_u0 isa Dict || (default_u0 = Dict(default_u0))
72+
default_p isa Dict || (default_p = Dict(default_p))
73+
default_u0 = Dict(value(k) => value(default_u0[k]) for k in keys(default_u0))
74+
default_p = Dict(value(k) => value(default_p[k]) for k in keys(default_p))
75+
76+
sysnames = nameof.(systems)
77+
if length(unique(sysnames)) != length(sysnames)
78+
throw(ArgumentError("System names must be unique."))
79+
end
80+
DiscreteSystem(discreteEqs, iv′, dvs′, ps′, observed, name, systems, default_u0, default_p)
81+
end
82+
83+
"""
84+
$(TYPEDSIGNATURES)
85+
86+
Generates an DiscreteProblem from an DiscreteSystem.
87+
"""
88+
function DiffEqBase.DiscreteProblem(sys::DiscreteSystem,u0map,tspan,
89+
parammap=DiffEqBase.NullParameters();
90+
eval_module = @__MODULE__,
91+
eval_expression = true,
92+
kwargs...)
93+
dvs = states(sys)
94+
ps = parameters(sys)
95+
eqs = equations(sys)
96+
# defs = defaults(sys)
97+
t = get_iv(sys)
98+
u0 = varmap_to_vars(u0map,dvs)
99+
rhss = [eq.rhs for eq in eqs]
100+
u = dvs
101+
p = varmap_to_vars(parammap,ps)
102+
103+
f_gen = build_function(rhss, dvs, ps, t; expression=Val{eval_expression}, expression_module=eval_module)
104+
f_oop,f_iip = (@RuntimeGeneratedFunction(eval_module, ex) for ex in f_gen)
105+
f(u,p,t) = f_oop(u,p,t)
106+
DiscreteProblem(f,u0,tspan,p;kwargs...)
107+
end

src/systems/nonlinear/nonlinearsystem.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem,u0map,paramm
220220
u0 = varmap_to_vars(u0map,dvs; defaults=defs)
221221
p = varmap_to_vars(parammap,ps; defaults=defs)
222222

223-
check_eqs_u0(eqs, dvs, u0)
223+
check_eqs_u0(eqs, dvs, u0; kwargs...)
224224

225225
f = constructor(sys,dvs,ps,u0;jac=jac,checkbounds=checkbounds,
226226
linenumbers=linenumbers,parallel=parallel,simplify=simplify,

0 commit comments

Comments
 (0)