Skip to content

Commit 4c7a4aa

Browse files
committed
Merge branch 'master' of https://github.com/SciML/ModelingToolkit.jl into sy/difference_ODESys
2 parents 376b216 + d3f46aa commit 4c7a4aa

26 files changed

+187
-59
lines changed

.github/workflows/Downstream.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ jobs:
2121
- {user: SciML, repo: CellMLToolkit.jl, group: All}
2222
- {user: SciML, repo: NeuralPDE.jl, group: NNPDE}
2323
- {user: SciML, repo: DataDrivenDiffEq.jl, group: Standard}
24-
24+
- {user: SciML, repo: StructuralIdentifiability.jl, group: All}
25+
2526
steps:
2627
- uses: actions/checkout@v2
2728
- uses: julia-actions/setup-julia@v1

Project.toml

Lines changed: 2 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.22.0"
4+
version = "5.23.0"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -56,7 +56,7 @@ Distributions = "0.23, 0.24, 0.25"
5656
DocStringExtensions = "0.7, 0.8"
5757
DomainSets = "0.5"
5858
IfElse = "0.1"
59-
JuliaFormatter = "0.12, 0.13, 0.14"
59+
JuliaFormatter = "0.12, 0.13, 0.14, 0.15"
6060
LabelledArrays = "1.3"
6161
Latexify = "0.11, 0.12, 0.13, 0.14, 0.15"
6262
LightGraphs = "1.3"

docs/src/basics/ContextualVariables.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,36 @@ All modeling projects have some form of parameters. `@parameters` marks a variab
2020
as being the parameter of some system, which allows automatic detection algorithms
2121
to ignore such variables when attempting to find the states of a system.
2222

23-
## Flow Variables (TODO)
23+
## Variable metadata [Experimental/TODO]
2424

2525
In many engineering systems some variables act like "flows" while others do not.
2626
For example, in circuit models you have current which flows, and the related
2727
voltage which does not. Or in thermal models you have heat flows. In these cases,
2828
the `connect` statement enforces conservation of flow between all of the connected
2929
components.
3030

31-
For example, the following specifies that `x` is a 2x2 matrix of flow variables:
31+
For example, the following specifies that `x` is a 2x2 matrix of flow variables
32+
with the unit m^3/s:
3233

3334
```julia
34-
@variables x[1:2,1:2] type=flow
35+
@variables x[1:2,1:2] [connect = Flow; unit = u"m^3/s"]
3536
```
3637

37-
## Stream Variables
38+
ModelingToolkit defines `connect`, `unit`, `noise`, and `description` keys for
39+
the metadata. One can get and set metadata by
3840

39-
TODO
41+
```julia
42+
julia> @variables x [unit = u"m^3/s"];
43+
44+
julia> hasmetadata(x, Symbolics.option_to_metadata_type(Val(:unit)))
45+
true
4046

41-
## Brownian Variables
47+
julia> getmetadata(x, Symbolics.option_to_metadata_type(Val(:unit)))
48+
m³ s⁻¹
4249

43-
TODO
50+
julia> x = setmetadata(x, Symbolics.option_to_metadata_type(Val(:unit)), u"m/s")
51+
x
52+
53+
julia> getmetadata(x, Symbolics.option_to_metadata_type(Val(:unit)))
54+
m s⁻¹
55+
```

docs/src/tutorials/acausal_components.md

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,11 @@ rc_eqs = [
9595
connect(capacitor.n, source.n, ground.g)
9696
]
9797

98-
@named rc_model = ODESystem(rc_eqs, t,
99-
systems=[resistor, capacitor, source, ground])
98+
@named rc_model = compose(ODESystem(rc_eqs, t),
99+
[resistor, capacitor, source, ground])
100100
sys = structural_simplify(rc_model)
101101
u0 = [
102102
capacitor.v => 0.0
103-
capacitor.p.i => 0.0
104103
]
105104
prob = ODAEProblem(sys, u0, (0, 10.0))
106105
sol = solve(prob, Tsit5())
@@ -289,15 +288,15 @@ rc_eqs = [
289288
Finally we build our four component model with these connection rules:
290289
291290
```julia
292-
@named rc_model = ODESystem(rc_eqs, t,
293-
systems=[resistor, capacitor, source, ground])
291+
@named rc_model = compose(ODESystem(rc_eqs, t)
292+
[resistor, capacitor, source, ground])
294293
```
295294
296-
Notice that this model is acasual because we have not specified anything about
297-
the causality of the model. We have simply specified what is true about each
298-
of the variables. This forms a system of differential-algebraic equations
299-
(DAEs) which define the evolution of each state of the system. The
300-
equations are:
295+
Note that we can also specify the subsystems in a vector. This model is acasual
296+
because we have not specified anything about the causality of the model. We have
297+
simply specified what is true about each of the variables. This forms a system
298+
of differential-algebraic equations (DAEs) which define the evolution of each
299+
state of the system. The equations are:
301300
302301
```julia
303302
equations(rc_model)
@@ -369,9 +368,10 @@ parameters(rc_model)
369368
This system could be solved directly as a DAE using [one of the DAE solvers
370369
from DifferentialEquations.jl](https://diffeq.sciml.ai/stable/solvers/dae_solve/).
371370
However, let's take a second to symbolically simplify the system before doing the
372-
solve. The function `structural_simplify` looks for all of the equalities and
373-
eliminates unnecessary variables to build the leanest numerical representation
374-
of the system. Let's see what it does here:
371+
solve. Although we can use ODE solvers that handles mass matrices to solve the
372+
above system directly, we want to run the `structural_simplify` function first,
373+
as it eliminates many unnecessary variables to build the leanest numerical
374+
representation of the system. Let's see what it does here:
375375
376376
```julia
377377
sys = structural_simplify(rc_model)
@@ -410,15 +410,13 @@ plot(sol)
410410
411411
![](https://user-images.githubusercontent.com/1814174/109416295-55184100-798b-11eb-96d1-5bb7e40135ba.png)
412412
413-
However, we can also choose to use the "torn nonlinear system" to remove all
414-
of the algebraic variables from the solution of the system. Note that this
415-
requires having done `structural_simplify`. This is done by using `ODAEProblem`
416-
like:
413+
Since we have run `structural_simplify`, MTK can numerically solve all the
414+
unreduced algebraic equations numerically using the `ODAEProblem` (note the
415+
letter `A`):
417416
418417
```julia
419418
u0 = [
420419
capacitor.v => 0.0
421-
capacitor.p.i => 0.0
422420
]
423421
prob = ODAEProblem(sys, u0, (0, 10.0))
424422
sol = solve(prob, Rodas4())

docs/src/tutorials/tearing_parallelism.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ function rc_model(i; name, source, ground, R, C)
126126
connect_heat(resistor.h, heat_capacitor.h)
127127
]
128128

129-
rc_model = ODESystem(rc_eqs, t, systems=[resistor, capacitor, source, ground, heat_capacitor], name=Symbol(name, i))
129+
rc_model = compose(ODESystem(rc_eqs, t, name=Symbol(name, i)),
130+
[resistor, capacitor, source, ground, heat_capacitor])
130131
end
131132
```
132133

@@ -150,7 +151,7 @@ end;
150151
eqs = [
151152
D(E) ~ sum(((i, sys),)->getproperty(sys, Symbol(:resistor, i)).h.Q_flow, enumerate(rc_systems))
152153
]
153-
big_rc = compose([ODESystem(eqs, t, [E], []); rc_systems])
154+
big_rc = compose(ODESystem(eqs, t, [E], []), rc_systems)
154155
```
155156

156157
Now let's say we want to expose a bit more parallelism via running tearing.

examples/rc_model.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ rc_eqs = [
1414
connect(capacitor.n, source.n, ground.g)
1515
]
1616

17-
@named rc_model = compose(ODESystem(rc_eqs, t), resistor, capacitor, source, ground)
17+
@named rc_model = compose(ODESystem(rc_eqs, t), [resistor, capacitor, source, ground])

src/ModelingToolkit.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import Symbolics: rename, get_variables!, _solve, hessian_sparsity,
5555

5656
import DiffEqBase: @add_kwonly
5757

58-
import LightGraphs: SimpleDiGraph, add_edge!
58+
import LightGraphs: SimpleDiGraph, add_edge!, incidence_matrix
5959

6060
using Requires
6161

@@ -146,6 +146,8 @@ for S in subtypes(ModelingToolkit.AbstractSystem)
146146
@eval convert_system(::Type{<:$S}, sys::$S) = sys
147147
end
148148

149+
struct Flow end
150+
149151
export ODESystem, ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system
150152
export DAEFunctionExpr, DAEProblemExpr
151153
export SDESystem, SDEFunction, SDEFunctionExpr, SDESystemExpr
@@ -191,7 +193,7 @@ export toexpr, get_variables
191193
export simplify, substitute
192194
export build_function
193195
export modelingtoolkitize
194-
export @variables, @parameters
196+
export @variables, @parameters, Flow
195197
export @named, @nonamespace, @namespace, extend, compose
196198

197199
end # module

src/bipartite_graph.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ module BipartiteGraphs
22

33
export BipartiteEdge, BipartiteGraph
44

5-
using Reexport
65
export 𝑠vertices, 𝑑vertices, has_𝑠vertex, has_𝑑vertex, 𝑠neighbors, 𝑑neighbors,
76
𝑠edges, 𝑑edges, nsrcs, ndsts, SRC, DST
87

98
using DocStringExtensions
109
using UnPack
1110
using SparseArrays
12-
@reexport using LightGraphs
11+
using LightGraphs
1312
using Setfield
1413

1514
###

src/structural_transformation/StructuralTransformations.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ using ModelingToolkit: ODESystem, AbstractSystem, var_from_nested_derivative,
2121
ExtraVariablesSystemException
2222

2323
using ModelingToolkit.BipartiteGraphs
24+
using LightGraphs
2425
using ModelingToolkit.SystemStructures
2526

2627
using ModelingToolkit.DiffEqBase

src/systems/abstractsystem.jl

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -597,13 +597,16 @@ function Base.show(io::IO, ::MIME"text/plain", sys::AbstractSystem)
597597
return nothing
598598
end
599599

600-
function _named(expr)
600+
function split_assign(expr)
601601
if !(expr isa Expr && expr.head === :(=) && expr.args[2].head === :call)
602602
throw(ArgumentError("expression should be of the form `sys = foo(a, b)`"))
603603
end
604604
name, call = expr.args
605+
end
605606

607+
function _named(name, call, runtime=false)
606608
has_kw = false
609+
call isa Expr || throw(Meta.ParseError("The rhs must be an Expr. Got $call."))
607610
if length(call.args) >= 2 && call.args[2] isa Expr
608611
# canonicalize to use `:parameters`
609612
if call.args[2].head === :kw
@@ -626,18 +629,78 @@ function _named(expr)
626629
kws = call.args[2].args
627630

628631
if !any(kw->(kw isa Symbol ? kw : kw.args[1]) == :name, kws) # don't overwrite `name` kwarg
629-
pushfirst!(kws, Expr(:kw, :name, Meta.quot(name)))
632+
pushfirst!(kws, Expr(:kw, :name, runtime ? name : Meta.quot(name)))
633+
end
634+
call
635+
end
636+
637+
function _named_idxs(name::Symbol, idxs, call)
638+
if call.head !== :->
639+
throw(ArgumentError("Not an anonymous function"))
640+
end
641+
if !isa(call.args[1], Symbol)
642+
throw(ArgumentError("not a single-argument anonymous function"))
630643
end
631-
:($name = $call)
644+
sym, ex = call.args
645+
ex = Base.Cartesian.poplinenum(ex)
646+
ex = _named(:(Symbol($(Meta.quot(name)), :_, $sym)), ex, true)
647+
ex = Base.Cartesian.poplinenum(ex)
648+
:($name = $map($sym->$ex, $idxs))
632649
end
633650

651+
check_name(name) = name isa Symbol || throw(Meta.ParseError("The lhs must be a symbol (a) or a ref (a[1:10]). Got $name."))
652+
634653
"""
635-
$(SIGNATURES)
654+
@named y = foo(x)
655+
@named y[1:10] = foo(x)
656+
@named y 1:10 i -> foo(x*i)
636657
637658
Rewrite `@named y = foo(x)` to `y = foo(x; name=:y)`.
659+
660+
Rewrite `@named y[1:10] = foo(x)` to `y = map(i′->foo(x; name=Symbol(:y_, i′)), 1:10)`.
661+
662+
Rewrite `@named y 1:10 i -> foo(x*i)` to `y = map(i->foo(x*i; name=Symbol(:y_, i)), 1:10)`.
663+
664+
Examples:
665+
```julia
666+
julia> using ModelingToolkit
667+
668+
julia> foo(i; name) = i, name
669+
foo (generic function with 1 method)
670+
671+
julia> x = 41
672+
41
673+
674+
julia> @named y = foo(x)
675+
(41, :y)
676+
677+
julia> @named y[1:3] = foo(x)
678+
3-element Vector{Tuple{Int64, Symbol}}:
679+
(41, :y_1)
680+
(41, :y_2)
681+
(41, :y_3)
682+
683+
julia> @named y 1:3 i -> foo(x*i)
684+
3-element Vector{Tuple{Int64, Symbol}}:
685+
(41, :y_1)
686+
(82, :y_2)
687+
(123, :y_3)
688+
```
638689
"""
639690
macro named(expr)
640-
esc(_named(expr))
691+
name, call = split_assign(expr)
692+
if Meta.isexpr(name, :ref)
693+
name, idxs = name.args
694+
check_name(name)
695+
esc(_named_idxs(name, idxs, :($(gensym()) -> $call)))
696+
else
697+
check_name(name)
698+
esc(:($name = $(_named(name, call))))
699+
end
700+
end
701+
702+
macro named(name::Symbol, idxs, call)
703+
esc(_named_idxs(name, idxs, call))
641704
end
642705

643706
function _config(expr, namespace)
@@ -826,7 +889,12 @@ by default.
826889
function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol=nameof(sys))
827890
T = SciMLBase.parameterless_type(basesys)
828891
iv = independent_variable(basesys)
829-
sys = convert_system(T, sys, iv)
892+
if iv === nothing
893+
sys = convert_system(T, sys)
894+
else
895+
sys = convert_system(T, sys, iv)
896+
end
897+
830898
eqs = union(equations(basesys), equations(sys))
831899
sts = union(states(basesys), states(sys))
832900
ps = union(parameters(basesys), parameters(sys))
@@ -849,15 +917,14 @@ Base.:(&)(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol=nameof(sys)
849917
compose multiple systems together. The resulting system would inherit the first
850918
system's name.
851919
"""
852-
compose(syss::AbstractSystem...; name=nameof(first(syss))) = compose(collect(syss); name=name)
853-
function compose(syss::AbstractArray{<:AbstractSystem}; name=nameof(first(syss)))
854-
nsys = length(syss)
855-
nsys >= 2 || throw(ArgumentError("There must be at least 2 systems. Got $nsys systems."))
856-
sys = first(syss)
920+
function compose(sys::AbstractSystem, systems::AbstractArray{<:AbstractSystem}; name=nameof(sys))
921+
nsys = length(systems)
922+
nsys >= 1 || throw(ArgumentError("There must be at least 1 subsystem. Got $nsys subsystems."))
857923
@set! sys.name = name
858-
@set! sys.systems = syss[2:end]
924+
@set! sys.systems = systems
859925
return sys
860926
end
927+
compose(syss::AbstractSystem...; name=nameof(first(syss))) = compose(first(syss), collect(syss[2:end]); name=name)
861928
Base.:()(sys1::AbstractSystem, sys2::AbstractSystem) = compose(sys1, sys2)
862929

863930
UnPack.unpack(sys::ModelingToolkit.AbstractSystem, ::Val{p}) where p = getproperty(sys, p; namespace=false)

0 commit comments

Comments
 (0)