Skip to content

Commit 4f0b564

Browse files
committed
Merge branch 'master' into myb/strong_alias
2 parents 931c442 + b77c4aa commit 4f0b564

File tree

14 files changed

+357
-63
lines changed

14 files changed

+357
-63
lines changed

Project.toml

Lines changed: 3 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 = "8.15.2"
4+
version = "8.16.0"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -91,11 +91,12 @@ OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
9191
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
9292
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
9393
ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf"
94+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
9495
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
9596
SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f"
9697
StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
9798
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
9899
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
99100

100101
[targets]
101-
test = ["AmplNLWriter", "BenchmarkTools", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "Random", "ReferenceTests", "SafeTestsets", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials"]
102+
test = ["AmplNLWriter", "BenchmarkTools", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "Random", "ReferenceTests", "SafeTestsets", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials"]

docs/src/basics/FAQ.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,58 @@ ERROR: TypeError: non-boolean (Num) used in boolean context
4141

4242
then it's likely you are trying to trace through a function which cannot be
4343
directly represented in Julia symbols. The techniques to handle this problem,
44-
such as `@register_symbolic`, are described in detail
44+
such as `@register_symbolic`, are described in detail
4545
[in the Symbolics.jl documentation](https://symbolics.juliasymbolics.org/dev/manual/faq/#Transforming-my-function-to-a-symbolic-equation-has-failed.-What-do-I-do?-1).
46+
47+
## Using ModelingToolkit with Optimization / Automatic Differentiation
48+
49+
If you are using ModelingToolkit inside of a loss function and are having issues with
50+
mixing MTK with automatic differentiation, getting performance, etc... don't! Instead, use
51+
MTK outside of the loss function to generate the code, and then use the generated code
52+
inside of the loss function.
53+
54+
For example, let's say you were building ODEProblems in the loss function like:
55+
56+
```julia
57+
function loss(p)
58+
prob = ODEProblem(sys, [], [p1 => p[1], p2 => p[2]])
59+
sol = solve(prob, Tsit5())
60+
sum(abs2,sol)
61+
end
62+
```
63+
64+
Since `ODEProblem` on a MTK `sys` will have to generate code, this will be slower than
65+
caching the generated code, and will required automatic differentiation to go through the
66+
code generation process itself. All of this is unnecessary. Instead, generate the problem
67+
once outside of the loss function, and remake the prob inside of the loss function:
68+
69+
```julia
70+
prob = ODEProblem(sys, [], [p1 => p[1], p2 => p[2]])
71+
function loss(p)
72+
remake(prob,p = ...)
73+
sol = solve(prob, Tsit5())
74+
sum(abs2,sol)
75+
end
76+
```
77+
78+
Now, one has to be careful with `remake` to ensure that the parameters are in the right
79+
order. One can use the previously mentioned indexing functionality to generate index
80+
maps for reordering `p` like:
81+
82+
```julia
83+
p = @parameters x y z
84+
idxs = ModelingToolkit.varmap_to_vars([p[1] => 1, p[2] => 2, p[3] => 3], p)
85+
p[idxs]
86+
```
87+
88+
Using this, the fixed index map can be used in the loss function. This would look like:
89+
90+
```julia
91+
prob = ODEProblem(sys, [], [p1 => p[1], p2 => p[2]])
92+
idxs = Int.(ModelingToolkit.varmap_to_vars([p1 => 1, p2 => 2], p))
93+
function loss(p)
94+
remake(prob,p = p[idxs])
95+
sol = solve(prob, Tsit5())
96+
sum(abs2,sol)
97+
end
98+
```

docs/src/basics/Variable_metadata.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
# Symbolic metadata
2-
It is possible to add metadata to symbolic variables. The following
3-
information can be added (note, it's possible to extend this to user-defined metadata as well)
2+
It is possible to add metadata to symbolic variables, the metadata will be displayed when calling help on a variable.
3+
4+
The following information can be added (note, it's possible to extend this to user-defined metadata as well)
5+
6+
## Variable descriptions
7+
Descriptive strings can be attached to variables using the `[description = "descriptive string"]` syntax:
8+
```@example metadata
9+
using ModelingToolkit
10+
@variables u [description = "This is my input"]
11+
getdescription(u)
12+
```
13+
14+
When variables with descriptions are present in systems, they will be printed when the system is shown in the terminal:
15+
```@example metadata
16+
@parameters t
17+
@variables u(t) [description = "A short description of u"]
18+
@parameters p [description = "A description of p"]
19+
@named sys = ODESystem([u ~ p], t)
20+
show(stdout, "text/plain", sys) # hide
21+
```
22+
23+
Calling help on the variable `u` displays the description, alongside other metadata:
24+
```julia
25+
help?> u
26+
27+
A variable of type Symbolics.Num (Num wraps anything in a type that is a subtype of Real)
28+
29+
Metadata
30+
≡≡≡≡≡≡≡≡≡≡
31+
32+
ModelingToolkit.VariableDescription: This is my input
33+
34+
Symbolics.VariableSource: (:variables, :u)
35+
```
436

537
## Input or output
638
Designate a variable as either an input or an output using the following
@@ -34,7 +66,7 @@ isdisturbance(u)
3466
```
3567

3668
## Mark parameter as tunable
37-
Indicate that a parameter can be automatically tuned by automatic control tuning apps.
69+
Indicate that a parameter can be automatically tuned by parameter optimization or automatic control tuning apps.
3870

3971
```@example metadata
4072
@parameters Kp [tunable=true]

docs/src/systems/SDESystem.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ sde = SDESystem(ode, noiseeqs)
2424
```@docs
2525
structural_simplify
2626
alias_elimination
27+
Girsanov_transform
2728
```
2829

2930
## Analyses

docs/src/tutorials/ode_modeling.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ algebraic variables as "observables" (see
125125
That means, MTK still knows how to calculate them out of the information available
126126
in a simulation result. The intermediate variable `RHS` therefore can be plotted
127127
along with the state variable. Note that this has to be requested explicitly,
128-
though:
128+
through:
129129

130130
```julia
131131
prob = ODEProblem(fol_simplified, [x => 0.0], (0.0,10.0), [τ => 3.0])

src/ModelingToolkit.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export NonlinearSystem, OptimizationSystem
173173
export alias_elimination, flatten
174174
export connect, @connector, Connection, Flow, Stream, instream
175175
export isinput, isoutput, getbounds, hasbounds, isdisturbance, istunable, getdist, hasdist,
176-
tunable_parameters, isirreducible
176+
tunable_parameters, isirreducible, getdescription, hasdescription
177177
export ode_order_lowering, dae_order_lowering, liouville_transform
178178
export PDESystem
179179
export Differential, expand_derivatives, @derivatives

src/inputoutput.jl

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -170,38 +170,28 @@ The return values also include the remaining states and parameters, in the order
170170
# Example
171171
```
172172
using ModelingToolkit: generate_control_function, varmap_to_vars, defaults
173-
f, dvs, ps = generate_control_function(sys, expression=Val{false}, simplify=true)
173+
f, dvs, ps = generate_control_function(sys, expression=Val{false}, simplify=false)
174174
p = varmap_to_vars(defaults(sys), ps)
175175
x = varmap_to_vars(defaults(sys), dvs)
176176
t = 0
177177
f[1](x, inputs, p, t)
178178
```
179179
"""
180-
function generate_control_function(sys::AbstractODESystem;
180+
function generate_control_function(sys::AbstractODESystem, inputs = unbound_inputs(sys);
181181
implicit_dae = false,
182-
has_difference = false,
183-
simplify = true,
182+
simplify = false,
184183
kwargs...)
185-
ctrls = unbound_inputs(sys)
186-
if isempty(ctrls)
184+
if isempty(inputs)
187185
error("No unbound inputs were found in system.")
188186
end
189187

190-
# One can either connect unbound inputs to new parameters and allow structural_simplify, but then the unbound inputs appear as states :( .
191-
# One can also just remove them from the states and parameters for the purposes of code generation, but then structural_simplify fails :(
192-
# To have the best of both worlds, all unbound inputs must be converted to `@parameters` in which case structural_simplify handles them correctly :)
193-
sys = toparam(sys, ctrls)
194-
195-
if simplify
196-
sys = structural_simplify(sys)
197-
end
188+
sys, diff_idxs, alge_idxs = io_preprocessing(sys, inputs, []; simplify,
189+
check_bound = false, kwargs...)
198190

199191
dvs = states(sys)
200192
ps = parameters(sys)
201-
202-
dvs = setdiff(dvs, ctrls)
203-
ps = setdiff(ps, ctrls)
204-
inputs = map(x -> time_varying_as_func(value(x), sys), ctrls)
193+
ps = setdiff(ps, inputs)
194+
inputs = map(x -> time_varying_as_func(value(x), sys), inputs)
205195

206196
eqs = [eq for eq in equations(sys) if !isdifferenceeq(eq)]
207197
check_operator_variables(eqs, Differential)
@@ -223,24 +213,10 @@ function generate_control_function(sys::AbstractODESystem;
223213
end
224214
pre, sol_states = get_substitutions_and_solved_states(sys)
225215
f = build_function(rhss, args...; postprocess_fbody = pre, states = sol_states,
226-
kwargs...)
216+
expression = Val{false}, kwargs...)
227217
f, dvs, ps
228218
end
229219

230-
"""
231-
toparam(sys, ctrls::AbstractVector)
232-
233-
Transform all instances of `@varibales` in `ctrls` appearing as states and in equations of `sys` with similarly named `@parameters`. This allows [`structural_simplify`](@ref)(sys) in the presence unbound inputs.
234-
"""
235-
function toparam(sys, ctrls::AbstractVector)
236-
eqs = equations(sys)
237-
subs = Dict(ctrls .=> toparam.(ctrls))
238-
eqs = map(eqs) do eq
239-
substitute(eq.lhs, subs) ~ substitute(eq.rhs, subs)
240-
end
241-
ODESystem(eqs, name = nameof(sys))
242-
end
243-
244220
function inputs_to_parameters!(state::TransformationState, check_bound = true)
245221
@unpack structure, fullvars, sys = state
246222
@unpack var_to_diff, graph, solvable_graph = structure

src/parameters.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@ struct MTKParameterCtx end
33

44
function isparameter(x)
55
x = unwrap(x)
6-
if istree(x) && operation(x) isa Symbolic
6+
7+
#TODO: Delete this branch
8+
if x isa Symbolic && Symbolics.getparent(x, false) !== false
9+
p = Symbolics.getparent(x)
10+
isparameter(p) ||
11+
(hasmetadata(p, Symbolics.VariableSource) &&
12+
getmetadata(p, Symbolics.VariableSource)[1] == :parameters)
13+
elseif istree(x) && operation(x) isa Symbolic
714
getmetadata(x, MTKParameterCtx, false) ||
815
isparameter(operation(x))
916
elseif istree(x) && operation(x) == (getindex)

src/systems/abstractsystem.jl

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,10 @@ function Base.show(io::IO, mime::MIME"text/plain", sys::AbstractSystem)
752752
:displaysize => (1, displaysize(io)[2])), val)
753753
print(io, "]")
754754
end
755+
description = getdescription(s)
756+
if description !== nothing && description != ""
757+
print(io, ": ", description)
758+
end
755759
end
756760
end
757761
limited && print(io, "\n")
@@ -774,6 +778,10 @@ function Base.show(io::IO, mime::MIME"text/plain", sys::AbstractSystem)
774778
:displaysize => (1, displaysize(io)[2])), val)
775779
print(io, "]")
776780
end
781+
description = getdescription(s)
782+
if description !== nothing && description != ""
783+
print(io, ": ", description)
784+
end
777785
end
778786
end
779787
limited && print(io, "\n")
@@ -965,6 +973,21 @@ function structural_simplify(sys::AbstractSystem, io = nothing; simplify = false
965973
return has_io ? (sys, input_idxs) : sys
966974
end
967975

976+
function io_preprocessing(sys::AbstractSystem, inputs,
977+
outputs; simplify = false, kwargs...)
978+
sys, input_idxs = structural_simplify(sys, (inputs, outputs); simplify, kwargs...)
979+
980+
eqs = equations(sys)
981+
alg_start_idx = findfirst(!isdiffeq, eqs)
982+
if alg_start_idx === nothing
983+
alg_start_idx = length(eqs) + 1
984+
end
985+
diff_idxs = 1:alg_start_idx - 1
986+
alge_idxs = alg_start_idx:length(eqs)
987+
988+
sys, diff_idxs, alge_idxs, input_idxs
989+
end
990+
968991
"""
969992
lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; simplify = false, kwargs...)
970993
@@ -990,21 +1013,11 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ
9901013
See also [`linearize`](@ref) which provides a higher-level interface.
9911014
"""
9921015
function linearization_function(sys::AbstractSystem, inputs,
993-
outputs; kwargs...)
994-
sys, input_idxs = structural_simplify(sys, (inputs, outputs); kwargs...)
995-
eqs = equations(sys)
996-
check_operator_variables(eqs, Differential)
997-
# Sort equations and states such that diff.eqs. match differential states and the rest are algebraic
998-
diffstates = collect_operator_variables(sys, Differential)
999-
eqs = sort(eqs, by = e -> !isoperator(e.lhs, Differential),
1000-
alg = Base.Sort.DEFAULT_STABLE)
1001-
@set! sys.eqs = eqs
1002-
diffstates = [arguments(e.lhs)[1] for e in eqs[1:length(diffstates)]]
1003-
sts = [diffstates; setdiff(states(sys), diffstates)]
1004-
@set! sys.states = sts
1005-
1006-
diff_idxs = 1:length(diffstates)
1007-
alge_idxs = (length(diffstates) + 1):length(sts)
1016+
outputs; simplify = false,
1017+
kwargs...)
1018+
sys, diff_idxs, alge_idxs, input_idxs = io_preprocessing(sys, inputs, outputs; simplify,
1019+
kwargs...)
1020+
sts = states(sys)
10081021
fun = ODEFunction(sys)
10091022
lin_fun = let fun = fun,
10101023
h = ModelingToolkit.build_explicit_observed_function(sys, outputs)

0 commit comments

Comments
 (0)