Skip to content

Commit 01bf879

Browse files
authored
Merge branch 'SciML:master' into func-affect
2 parents f5571ea + fd22563 commit 01bf879

File tree

12 files changed

+146
-84
lines changed

12 files changed

+146
-84
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
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"

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/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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import SymbolicUtils.Rewriters: Chain, Postwalk, Prewalk, Fixpoint
4141
import JuliaFormatter
4242

4343
using Reexport
44+
using Symbolics: degree
4445
@reexport using Symbolics
4546
export @derivatives
4647
using Symbolics: _parse_vars, value, @derivatives, get_variables,
@@ -172,7 +173,7 @@ export NonlinearSystem, OptimizationSystem
172173
export alias_elimination, flatten
173174
export connect, @connector, Connection, Flow, Stream, instream
174175
export isinput, isoutput, getbounds, hasbounds, isdisturbance, istunable, getdist, hasdist,
175-
tunable_parameters, isirreducible
176+
tunable_parameters, isirreducible, getdescription, hasdescription
176177
export ode_order_lowering, dae_order_lowering, liouville_transform
177178
export PDESystem
178179
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: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@ 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) ||
8-
isparameter(operation(x))
15+
isparameter(operation(x))
916
elseif istree(x) && operation(x) == (getindex)
1017
isparameter(arguments(x)[1])
1118
elseif x isa Symbolic

src/systems/abstractsystem.jl

Lines changed: 47 additions & 35 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")
@@ -948,10 +956,17 @@ function will be applied during the tearing process. It also takes kwargs
948956
`allow_symbolic=false` and `allow_parameter=true` which limits the coefficient
949957
types during tearing.
950958
"""
951-
function structural_simplify(sys::AbstractSystem; simplify = false, kwargs...)
959+
function structural_simplify(sys::AbstractSystem, args...; kwargs...)
952960
sys = expand_connections(sys)
953961
state = TearingState(sys)
954-
state, = inputs_to_parameters!(state)
962+
sys, input_idxs = _structural_simplify(sys, state, args...; kwargs...)
963+
sys
964+
end
965+
966+
function _structural_simplify(sys::AbstractSystem, state; simplify = false,
967+
check_bound = true,
968+
kwargs...)
969+
state, input_idxs = inputs_to_parameters!(state, check_bound)
955970
sys = alias_elimination!(state)
956971
state = TearingState(sys)
957972
check_consistency(state)
@@ -964,7 +979,31 @@ function structural_simplify(sys::AbstractSystem; simplify = false, kwargs...)
964979
fullstates = [map(eq -> eq.lhs, observed(sys)); states(sys)]
965980
@set! sys.observed = topsort_equations(observed(sys), fullstates)
966981
invalidate_cache!(sys)
967-
return sys
982+
return sys, input_idxs
983+
end
984+
985+
function io_preprocessing(sys::AbstractSystem, inputs,
986+
outputs; simplify = false, kwargs...)
987+
sys = expand_connections(sys)
988+
state = TearingState(sys)
989+
markio!(state, inputs, outputs)
990+
sys, input_idxs = _structural_simplify(sys, state; simplify, check_bound = false,
991+
kwargs...)
992+
993+
eqs = equations(sys)
994+
check_operator_variables(eqs, Differential)
995+
# Sort equations and states such that diff.eqs. match differential states and the rest are algebraic
996+
diffstates = collect_operator_variables(sys, Differential)
997+
eqs = sort(eqs, by = e -> !isoperator(e.lhs, Differential),
998+
alg = Base.Sort.DEFAULT_STABLE)
999+
@set! sys.eqs = eqs
1000+
diffstates = [arguments(e.lhs)[1] for e in eqs[1:length(diffstates)]]
1001+
sts = [diffstates; setdiff(states(sys), diffstates)]
1002+
@set! sys.states = sts
1003+
diff_idxs = 1:length(diffstates)
1004+
alge_idxs = (length(diffstates) + 1):length(sts)
1005+
1006+
sys, diff_idxs, alge_idxs, input_idxs
9681007
end
9691008

9701009
"""
@@ -994,36 +1033,9 @@ See also [`linearize`](@ref) which provides a higher-level interface.
9941033
function linearization_function(sys::AbstractSystem, inputs,
9951034
outputs; simplify = false,
9961035
kwargs...)
997-
sys = expand_connections(sys)
998-
state = TearingState(sys)
999-
markio!(state, inputs, outputs)
1000-
state, input_idxs = inputs_to_parameters!(state, false)
1001-
sys = alias_elimination!(state)
1002-
state = TearingState(sys)
1003-
check_consistency(state)
1004-
if sys isa ODESystem
1005-
sys = dae_order_lowering(dummy_derivative(sys, state))
1006-
end
1007-
state = TearingState(sys)
1008-
find_solvables!(state; kwargs...)
1009-
sys = tearing_reassemble(state, tearing(state), simplify = simplify)
1010-
fullstates = [map(eq -> eq.lhs, observed(sys)); states(sys)]
1011-
@set! sys.observed = topsort_equations(observed(sys), fullstates)
1012-
invalidate_cache!(sys)
1013-
1014-
eqs = equations(sys)
1015-
check_operator_variables(eqs, Differential)
1016-
# Sort equations and states such that diff.eqs. match differential states and the rest are algebraic
1017-
diffstates = collect_operator_variables(sys, Differential)
1018-
eqs = sort(eqs, by = e -> !isoperator(e.lhs, Differential),
1019-
alg = Base.Sort.DEFAULT_STABLE)
1020-
@set! sys.eqs = eqs
1021-
diffstates = [arguments(e.lhs)[1] for e in eqs[1:length(diffstates)]]
1022-
sts = [diffstates; setdiff(states(sys), diffstates)]
1023-
@set! sys.states = sts
1024-
1025-
diff_idxs = 1:length(diffstates)
1026-
alge_idxs = (length(diffstates) + 1):length(sts)
1036+
sys, diff_idxs, alge_idxs, input_idxs = io_preprocessing(sys, inputs, outputs; simplify,
1037+
kwargs...)
1038+
sts = states(sys)
10271039
fun = ODEFunction(sys)
10281040
lin_fun = let fun = fun,
10291041
h = ModelingToolkit.build_explicit_observed_function(sys, outputs)
@@ -1210,11 +1222,11 @@ function linearize(sys, lin_fun; t = 0.0, op = Dict(), allow_input_derivatives =
12101222
C = [
12111223
h_x h_z
12121224
]
1213-
Bs = -(gz \ (f_x * f_u + g_u))
1225+
Bs = -(gz \ g_u) # This equation differ from the cited paper, the paper is likely wrong since their equaiton leads to a dimension mismatch.
12141226
if !iszero(Bs)
12151227
if !allow_input_derivatives
12161228
der_inds = findall(vec(any(!=(0), Bs, dims = 1)))
1217-
error("Input derivatives appeared in expressions (-g_z\\(f_x*f_u + g_u) != 0), the following inputs appeared differentiated: $(inputs(sys)[der_inds]). Call `linear_staespace` with keyword argument `allow_input_derivatives = true` to allow this and have the returned `B` matrix be of double width ($(2nu)), where the last $nu inputs are the derivatives of the first $nu inputs.")
1229+
error("Input derivatives appeared in expressions (-g_z\\g_u != 0), the following inputs appeared differentiated: $(inputs(sys)[der_inds]). Call `linear_staespace` with keyword argument `allow_input_derivatives = true` to allow this and have the returned `B` matrix be of double width ($(2nu)), where the last $nu inputs are the derivatives of the first $nu inputs.")
12181230
end
12191231
B = [B Bs]
12201232
end

src/utils.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ function iv_from_nested_derivative(x, op = Differential)
223223
if istree(x) && operation(x) == getindex
224224
iv_from_nested_derivative(arguments(x)[1], op)
225225
elseif istree(x)
226-
operation(x) isa op ? iv_from_nested_derivative(arguments(x)[1], op) : arguments(x)[1]
226+
operation(x) isa op ? iv_from_nested_derivative(arguments(x)[1], op) :
227+
arguments(x)[1]
227228
elseif issym(x)
228229
x
229230
else

src/variables.jl

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
struct VariableUnit end
22
struct VariableConnectType end
33
struct VariableNoiseType end
4-
struct VariableDescriptionType end
54
struct VariableInput end
65
struct VariableOutput end
76
struct VariableIrreducible end
87
Symbolics.option_to_metadata_type(::Val{:unit}) = VariableUnit
98
Symbolics.option_to_metadata_type(::Val{:connect}) = VariableConnectType
109
Symbolics.option_to_metadata_type(::Val{:noise}) = VariableNoiseType
11-
Symbolics.option_to_metadata_type(::Val{:description}) = VariableDescriptionType
1210
Symbolics.option_to_metadata_type(::Val{:input}) = VariableInput
1311
Symbolics.option_to_metadata_type(::Val{:output}) = VariableOutput
1412
Symbolics.option_to_metadata_type(::Val{:irreducible}) = VariableIrreducible
@@ -267,3 +265,24 @@ function getbounds(p::AbstractVector)
267265
ub = last.(bounds)
268266
(; lb, ub)
269267
end
268+
269+
## Description =================================================================
270+
struct VariableDescription end
271+
Symbolics.option_to_metadata_type(::Val{:description}) = VariableDescription
272+
273+
getdescription(x::Num) = getdescription(Symbolics.unwrap(x))
274+
275+
"""
276+
getdescription(x)
277+
278+
Return any description attached to variables `x`. If no description is attached, an empty string is returned.
279+
"""
280+
function getdescription(x)
281+
p = Symbolics.getparent(x, nothing)
282+
p === nothing || (x = p)
283+
Symbolics.getmetadata(x, VariableDescription, "")
284+
end
285+
286+
function hasdescription(x)
287+
getdescription(x) != ""
288+
end

test/input_output_handling.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ eqs = [
108108
]
109109

110110
@named sys = ODESystem(eqs)
111-
f, dvs, ps = ModelingToolkit.generate_control_function(sys, expression = Val{false},
112-
simplify = true)
111+
f, dvs, ps = ModelingToolkit.generate_control_function(sys, simplify = true)
113112

114113
@test isequal(dvs[], x)
115114
@test isempty(ps)
@@ -170,8 +169,7 @@ eqs = [connect_sd(sd, mass1, mass2)
170169
@named _model = ODESystem(eqs, t)
171170
@named model = compose(_model, mass1, mass2, sd);
172171

173-
f, dvs, ps = ModelingToolkit.generate_control_function(model, expression = Val{false},
174-
simplify = true)
172+
f, dvs, ps = ModelingToolkit.generate_control_function(model, simplify = true)
175173
@test length(dvs) == 4
176174
@test length(ps) == length(parameters(model))
177175
p = ModelingToolkit.varmap_to_vars(ModelingToolkit.defaults(model), ps)

0 commit comments

Comments
 (0)