Skip to content

Commit e9667fe

Browse files
committed
Merge branch 'master' into myb/dd
2 parents 6954970 + c0d1112 commit e9667fe

34 files changed

+801
-143
lines changed

Project.toml

Lines changed: 9 additions & 5 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 = ["Yingbo Ma <[email protected]>", "Chris Rackauckas <[email protected]> and contributors"]
4-
version = "9.39.1"
4+
version = "9.41.0"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -24,6 +24,7 @@ ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
2424
Expronicon = "6b7a57c9-7cc1-4fdf-b7f5-e857abae3636"
2525
FindFirstFunctions = "64ca27bc-2ba2-4a57-88aa-44e436879224"
2626
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
27+
FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e"
2728
FunctionWrappersWrappers = "77dc65aa-8811-40c2-897b-53d922fa7daf"
2829
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
2930
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
@@ -76,10 +77,11 @@ ChainRulesCore = "1"
7677
Combinatorics = "1"
7778
Compat = "3.42, 4"
7879
ConstructionBase = "1"
80+
DataInterpolations = "6.4"
7981
DataStructures = "0.17, 0.18"
8082
DeepDiffs = "1"
8183
DiffEqBase = "6.103.0"
82-
DiffEqCallbacks = "2.16, 3"
84+
DiffEqCallbacks = "2.16, 3, 4"
8385
DiffEqNoiseProcess = "5"
8486
DiffRules = "0.1, 1.0"
8587
Distributed = "1"
@@ -91,6 +93,7 @@ ExprTools = "0.1.10"
9193
Expronicon = "0.8"
9294
FindFirstFunctions = "1"
9395
ForwardDiff = "0.10.3"
96+
FunctionWrappers = "1.1"
9497
FunctionWrappersWrappers = "0.1"
9598
Graphs = "1.5.2"
9699
InteractiveUtils = "1"
@@ -118,8 +121,8 @@ SparseArrays = "1"
118121
SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2"
119122
StaticArrays = "0.10, 0.11, 0.12, 1.0"
120123
SymbolicIndexingInterface = "0.3.29"
121-
SymbolicUtils = "3.2"
122-
Symbolics = "6.3"
124+
SymbolicUtils = "3.7"
125+
Symbolics = "6.12"
123126
URIs = "1"
124127
UnPack = "0.1, 1.0"
125128
Unitful = "1.1"
@@ -129,6 +132,7 @@ julia = "1.9"
129132
AmplNLWriter = "7c4d4715-977e-5154-bfe0-e096adeac482"
130133
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
131134
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"
135+
DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0"
132136
DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
133137
DelayDiffEq = "bcd4f6db-9728-5f36-b5f7-82caef46ccdb"
134138
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
@@ -154,4 +158,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
154158
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
155159

156160
[targets]
157-
test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET"]
161+
test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET"]

docs/src/basics/FAQ.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,62 @@ D = Differential(x)
244244
@variables y(x)
245245
@named sys = ODESystem([D(y) ~ x], x)
246246
```
247+
248+
## Ordering of tunable parameters
249+
250+
Tunable parameters are floating point parameters, not used in callbacks and not marked with `tunable = false` in their metadata. These are expected to be used with AD
251+
and optimization libraries. As such, they are stored together in one `Vector{T}`. To obtain the ordering of tunable parameters in this buffer, use:
252+
253+
```@docs
254+
tunable_parameters
255+
```
256+
257+
If you have an array in which a particular dimension is in the order of tunable parameters (e.g. the jacobian with respect to tunables) then that dimension of the
258+
array can be reordered into the required permutation using the symbolic variables:
259+
260+
```@docs
261+
reorder_dimension_by_tunables!
262+
reorder_dimension_by_tunables
263+
```
264+
265+
For example:
266+
267+
```@example reorder
268+
using ModelingToolkit
269+
270+
@parameters p q[1:3] r[1:2, 1:2]
271+
272+
@named sys = ODESystem(Equation[], ModelingToolkit.t_nounits, [], [p, q, r])
273+
sys = complete(sys)
274+
```
275+
276+
The canonicalized tunables portion of `MTKParameters` will be in the order of tunables:
277+
278+
```@example reorder
279+
using SciMLStructures: canonicalize, Tunable
280+
281+
ps = MTKParameters(sys, [p => 1.0, q => [2.0, 3.0, 4.0], r => [5.0 6.0; 7.0 8.0]])
282+
arr = canonicalize(Tunable(), ps)[1]
283+
```
284+
285+
We can reorder this to contain the value for `p`, then all values for `q`, then for `r` using:
286+
287+
```@example reorder
288+
reorder_dimension_by_tunables(sys, arr, [p, q, r])
289+
```
290+
291+
This also works with interleaved subarrays of symbolics:
292+
293+
```@example reorder
294+
reorder_dimension_by_tunables(sys, arr, [q[1], r[1, :], q[2], r[2, :], q[3], p])
295+
```
296+
297+
And arbitrary dimensions of higher dimensional arrays:
298+
299+
```@example reorder
300+
highdimarr = stack([i * arr for i in 1:5]; dims = 1)
301+
```
302+
303+
```@example reorder
304+
reorder_dimension_by_tunables(sys, highdimarr, [q[1:2], r[1, :], q[3], r[2, :], p]; dim = 2)
305+
```

docs/src/basics/InputOutput.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ We can inspect the state realization chosen by MTK
5555
x_sym
5656
```
5757

58-
as expected, `x` is chosen as the state variable.
5958
as expected, `x` is chosen as the state variable.
6059

6160
```@example inputoutput

docs/src/basics/MTKLanguage.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,14 @@ end
6363
@structural_parameters begin
6464
f = sin
6565
N = 2
66+
M = 3
6667
end
6768
begin
6869
v_var = 1.0
6970
end
7071
@variables begin
7172
v(t) = v_var
72-
v_array(t)[1:2, 1:3]
73+
v_array(t)[1:N, 1:M]
7374
v_for_defaults(t)
7475
end
7576
@extend ModelB(; p1)
@@ -310,10 +311,10 @@ end
310311
- `:defaults`: Dictionary of variables and default values specified in the `@defaults`.
311312
- `:extend`: The list of extended unknowns, name given to the base system, and name of the base system.
312313
- `:structural_parameters`: Dictionary of structural parameters mapped to their metadata.
313-
- `:parameters`: Dictionary of symbolic parameters mapped to their metadata. For
314-
parameter arrays, length is added to the metadata as `:size`.
315-
- `:variables`: Dictionary of symbolic variables mapped to their metadata. For
316-
variable arrays, length is added to the metadata as `:size`.
314+
- `:parameters`: Dictionary of symbolic parameters mapped to their metadata. Metadata of
315+
the parameter arrays is, for now, omitted.
316+
- `:variables`: Dictionary of symbolic variables mapped to their metadata. Metadata of
317+
the variable arrays is, for now, omitted.
317318
- `:kwargs`: Dictionary of keyword arguments mapped to their metadata.
318319
- `:independent_variable`: Independent variable, which is added while generating the Model.
319320
- `:equations`: List of equations (represented as strings).
@@ -324,10 +325,10 @@ For example, the structure of `ModelC` is:
324325
julia> ModelC.structure
325326
Dict{Symbol, Any} with 10 entries:
326327
:components => Any[Union{Expr, Symbol}[:model_a, :ModelA], Union{Expr, Symbol}[:model_array_a, :ModelA, :(1:N)], Union{Expr, Symbol}[:model_array_b, :ModelA, :(1:N)]]
327-
:variables => Dict{Symbol, Dict{Symbol, Any}}(:v=>Dict(:default=>:v_var, :type=>Real), :v_array=>Dict(:type=>Real, :size=>(2, 3)), :v_for_defaults=>Dict(:type=>Real))
328+
:variables => Dict{Symbol, Dict{Symbol, Any}}(:v=>Dict(:default=>:v_var, :type=>Real), :v_for_defaults=>Dict(:type=>Real))
328329
:icon => URI("https://github.com/SciML/SciMLDocs/blob/main/docs/src/assets/logo.png")
329-
:kwargs => Dict{Symbol, Dict}(:f=>Dict(:value=>:sin), :N=>Dict(:value=>2), :v=>Dict{Symbol, Any}(:value=>:v_var, :type=>Real), :v_array=>Dict{Symbol, Union{Nothing, UnionAll}}(:value=>nothing, :type=>AbstractArray{Real}), :v_for_defaults=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real), :p1=>Dict(:value=>nothing))
330-
:structural_parameters => Dict{Symbol, Dict}(:f=>Dict(:value=>:sin), :N=>Dict(:value=>2))
330+
:kwargs => Dict{Symbol, Dict}(:f => Dict(:value => :sin), :N => Dict(:value => 2), :M => Dict(:value => 3), :v => Dict{Symbol, Any}(:value => :v_var, :type => Real), :v_for_defaults => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real), :p1 => Dict(:value => nothing)),
331+
:structural_parameters => Dict{Symbol, Dict}(:f => Dict(:value => :sin), :N => Dict(:value => 2), :M => Dict(:value => 3))
331332
:independent_variable => t
332333
:constants => Dict{Symbol, Dict}(:c=>Dict{Symbol, Any}(:value=>1, :type=>Int64, :description=>"Example constant."))
333334
:extend => Any[[:p2, :p1], Symbol("#mtkmodel__anonymous__ModelB"), :ModelB]

ext/MTKChainRulesCoreExt.jl

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,99 @@ module MTKChainRulesCoreExt
22

33
import ModelingToolkit as MTK
44
import ChainRulesCore
5-
import ChainRulesCore: NoTangent
5+
import ChainRulesCore: Tangent, ZeroTangent, NoTangent, zero_tangent, unthunk
66

77
function ChainRulesCore.rrule(::Type{MTK.MTKParameters}, tunables, args...)
88
function mtp_pullback(dt)
9+
dt = unthunk(dt)
910
(NoTangent(), dt.tunable[1:length(tunables)],
1011
ntuple(_ -> NoTangent(), length(args))...)
1112
end
1213
MTK.MTKParameters(tunables, args...), mtp_pullback
1314
end
1415

16+
function subset_idxs(idxs, portion, template)
17+
ntuple(Val(length(template))) do subi
18+
[Base.tail(idx.idx) for idx in idxs if idx.portion == portion && idx.idx[1] == subi]
19+
end
20+
end
21+
22+
selected_tangents(::NoTangent, _) = ()
23+
selected_tangents(::ZeroTangent, _) = ZeroTangent()
24+
function selected_tangents(
25+
tangents::AbstractArray{T}, idxs::Vector{Tuple{Int}}) where {T <: Number}
26+
selected_tangents(tangents, map(only, idxs))
27+
end
28+
function selected_tangents(tangents::AbstractArray{T}, idxs...) where {T <: Number}
29+
newtangents = copy(tangents)
30+
view(newtangents, idxs...) .= zero(T)
31+
newtangents
32+
end
33+
function selected_tangents(
34+
tangents::AbstractVector{T}, idxs) where {S <: Number, T <: AbstractArray{S}}
35+
newtangents = copy(tangents)
36+
for i in idxs
37+
j, k... = i
38+
if k == ()
39+
newtangents[j] = zero(newtangents[j])
40+
else
41+
newtangents[j] = selected_tangents(newtangents[j], k...)
42+
end
43+
end
44+
newtangents
45+
end
46+
function selected_tangents(tangents::AbstractVector{T}, idxs) where {T <: AbstractArray}
47+
newtangents = similar(tangents, Union{T, NoTangent})
48+
copyto!(newtangents, tangents)
49+
for i in idxs
50+
j, k... = i
51+
if k == ()
52+
newtangents[j] = NoTangent()
53+
else
54+
newtangents[j] = selected_tangents(newtangents[j], k...)
55+
end
56+
end
57+
newtangents
58+
end
59+
function selected_tangents(
60+
tangents::Union{Tangent{<:Tuple}, Tangent{T, <:Tuple}}, idxs) where {T}
61+
ntuple(Val(length(tangents))) do i
62+
selected_tangents(tangents[i], idxs[i])
63+
end
64+
end
65+
66+
function ChainRulesCore.rrule(
67+
::typeof(MTK.remake_buffer), indp, oldbuf::MTK.MTKParameters, idxs, vals)
68+
if idxs isa AbstractSet
69+
idxs = collect(idxs)
70+
end
71+
idxs = map(idxs) do i
72+
i isa MTK.ParameterIndex ? i : MTK.parameter_index(indp, i)
73+
end
74+
newbuf = MTK.remake_buffer(indp, oldbuf, idxs, vals)
75+
tunable_idxs = reduce(
76+
vcat, (idx.idx for idx in idxs if idx.portion isa MTK.SciMLStructures.Tunable))
77+
disc_idxs = subset_idxs(idxs, MTK.SciMLStructures.Discrete(), oldbuf.discrete)
78+
const_idxs = subset_idxs(idxs, MTK.SciMLStructures.Constants(), oldbuf.constant)
79+
nn_idxs = subset_idxs(idxs, MTK.NONNUMERIC_PORTION, oldbuf.nonnumeric)
80+
81+
pullback = let idxs = idxs
82+
function remake_buffer_pullback(buf′)
83+
buf′ = unthunk(buf′)
84+
f′ = NoTangent()
85+
indp′ = NoTangent()
86+
87+
tunable = selected_tangents(buf′.tunable, tunable_idxs)
88+
discrete = selected_tangents(buf′.discrete, disc_idxs)
89+
constant = selected_tangents(buf′.constant, const_idxs)
90+
nonnumeric = selected_tangents(buf′.nonnumeric, nn_idxs)
91+
oldbuf′ = Tangent{typeof(oldbuf)}(; tunable, discrete, constant, nonnumeric)
92+
idxs′ = NoTangent()
93+
vals′ = map(i -> MTK._ducktyped_parameter_values(buf′, i), idxs)
94+
return f′, indp′, oldbuf′, idxs′, vals′
95+
end
96+
end
97+
newbuf, pullback
98+
end
99+
15100
end

src/ModelingToolkit.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ using Base: RefValue
3838
using Combinatorics
3939
import Distributions
4040
import FunctionWrappersWrappers
41+
import FunctionWrappers: FunctionWrapper
4142
using URIs: URI
4243
using SciMLStructures
4344
using Compat
@@ -63,7 +64,8 @@ using Symbolics: _parse_vars, value, @derivatives, get_variables,
6364
VariableSource, getname, variable, Connection, connect,
6465
NAMESPACE_SEPARATOR, set_scalar_metadata, setdefaultval,
6566
initial_state, transition, activeState, entry, hasnode,
66-
ticksInState, timeInState, fixpoint_sub, fast_substitute
67+
ticksInState, timeInState, fixpoint_sub, fast_substitute,
68+
CallWithMetadata, CallWithParent
6769
const NAMESPACE_SEPARATOR_SYMBOL = Symbol(NAMESPACE_SEPARATOR)
6870
import Symbolics: rename, get_variables!, _solve, hessian_sparsity,
6971
jacobian_sparsity, isaffine, islinear, _iszero, _isone,
@@ -276,6 +278,6 @@ export debug_system
276278
export Sample, Hold, Shift, ShiftIndex, sampletime, SampleTime
277279
export Clock, SolverStepClock, TimeDomain
278280

279-
export MTKParameters
281+
export MTKParameters, reorder_dimension_by_tunables!, reorder_dimension_by_tunables
280282

281283
end # module

src/parameters.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ function isparameter(x)
2626
end
2727
end
2828

29+
function iscalledparameter(x)
30+
x = unwrap(x)
31+
return isparameter(getmetadata(x, CallWithParent, nothing))
32+
end
33+
34+
function getcalledparameter(x)
35+
x = unwrap(x)
36+
return getmetadata(x, CallWithParent)
37+
end
38+
2939
"""
3040
toparam(s)
3141

src/systems/abstractsystem.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,44 @@ namespace its subsystems or variables, i.e. `isequal(complete(sys).v.i, v.i)`.
882882
function complete(sys::AbstractSystem; split = true)
883883
if split && has_index_cache(sys)
884884
@set! sys.index_cache = IndexCache(sys)
885+
all_ps = parameters(sys)
886+
if !isempty(all_ps)
887+
# reorder parameters by portions
888+
ps_split = reorder_parameters(sys, all_ps)
889+
# if there are no tunables, vcat them
890+
if isempty(get_index_cache(sys).tunable_idx)
891+
ordered_ps = reduce(vcat, ps_split)
892+
else
893+
# if there are tunables, they will all be in `ps_split[1]`
894+
# and the arrays will have been scalarized
895+
ordered_ps = eltype(all_ps)[]
896+
i = 1
897+
# go through all the tunables
898+
while i <= length(ps_split[1])
899+
sym = ps_split[1][i]
900+
# if the sym is not a scalarized array symbolic OR it was already scalarized,
901+
# just push it as-is
902+
if !iscall(sym) || operation(sym) != getindex ||
903+
any(isequal(sym), all_ps)
904+
push!(ordered_ps, sym)
905+
i += 1
906+
continue
907+
end
908+
# the next `length(sym)` symbols should be scalarized versions of the same
909+
# array symbolic
910+
if !allequal(first(arguments(x))
911+
for x in view(ps_split[1], i:(i + length(sym) - 1)))
912+
error("This should not be possible. Please open an issue in ModelingToolkit.jl with an MWE and stacktrace.")
913+
end
914+
arrsym = first(arguments(sym))
915+
push!(ordered_ps, arrsym)
916+
i += length(arrsym)
917+
end
918+
ordered_ps = vcat(
919+
ordered_ps, reduce(vcat, ps_split[2:end]; init = eltype(ordered_ps)[]))
920+
end
921+
@set! sys.ps = ordered_ps
922+
end
885923
end
886924
if isdefined(sys, :initializesystem) && get_initializesystem(sys) !== nothing
887925
@set! sys.initializesystem = complete(get_initializesystem(sys); split)

src/systems/diffeqs/abstractodesystem.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,11 +817,12 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap;
817817
ModelingToolkit.get_tearing_state(sys) !== nothing) ||
818818
!isempty(initialization_equations(sys))) && t !== nothing
819819
if eltype(u0map) <: Number
820-
u0map = unknowns(sys) .=> u0map
820+
u0map = unknowns(sys) .=> vec(u0map)
821821
end
822-
if isempty(u0map)
822+
if u0map === nothing || isempty(u0map)
823823
u0map = Dict()
824824
end
825+
825826
initializeprob = ModelingToolkit.InitializationProblem(
826827
sys, t, u0map, parammap; guesses, warn_initialize_determined,
827828
initialization_eqs, eval_expression, eval_module, fully_determined, check_units)

src/systems/discrete_system/discrete_system.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,13 @@ function process_DiscreteProblem(constructor, sys::DiscreteSystem, u0map, paramm
247247
dvs = unknowns(sys)
248248
ps = parameters(sys)
249249

250+
if eltype(u0map) <: Number
251+
u0map = unknowns(sys) .=> vec(u0map)
252+
end
253+
if u0map === nothing || isempty(u0map)
254+
u0map = Dict()
255+
end
256+
250257
trueu0map = Dict()
251258
for (k, v) in u0map
252259
k = unwrap(k)

0 commit comments

Comments
 (0)