Skip to content

Commit aa96020

Browse files
Bye bye ₊, use var symbols with .
This is something that was iteratively chipped away at during the v9 changes. The last thing left here was the handling of FuncAffect, which needed to have a funky reindexer to handle the fact that u.resisitor.v is done in two getproperty calls, and so we have to lazily merge the symbols. This is non-breaking because we've been very specific to not rely on any of the symbol names in v9 for a very good reason, and this is that reason. I'm sure someone out there did hardcode ₊ stuff against all recommendations though... I need to fix the printing in SymbolicUtils.jl to really finalize this change.
1 parent 9050e70 commit aa96020

14 files changed

+92
-70
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
88
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
99
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
1010
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
11+
ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
1112
ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
1213
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
1314
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
@@ -67,6 +68,7 @@ ArrayInterface = "6, 7"
6768
BifurcationKit = "0.3"
6869
Combinatorics = "1"
6970
Compat = "3.42, 4"
71+
ComponentArrays = "0.15"
7072
ConstructionBase = "1"
7173
DataStructures = "0.17, 0.18"
7274
DeepDiffs = "1"

docs/src/basics/Composition.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ connected = compose(
3737
equations(connected)
3838
3939
#4-element Vector{Equation}:
40-
# Differential(t)(decay1f(t)) ~ 0
41-
# decay2f(t) ~ decay1x(t)
42-
# Differential(t)(decay1x(t)) ~ decay1f(t) - (decay1a*(decay1x(t)))
43-
# Differential(t)(decay2x(t)) ~ decay2f(t) - (decay2a*(decay2x(t)))
40+
# Differential(t)(decay1.f(t)) ~ 0
41+
# decay2.f(t) ~ decay1.x(t)
42+
# Differential(t)(decay1.x(t)) ~ decay1.f(t) - (decay1.a*(decay1.x(t)))
43+
# Differential(t)(decay2.x(t)) ~ decay2.f(t) - (decay2.a*(decay2.x(t)))
4444
4545
simplified_sys = structural_simplify(connected)
4646
@@ -149,27 +149,27 @@ p = [a, b, c, d, e, f]
149149
level0 = ODESystem(Equation[], t, [], p; name = :level0)
150150
level1 = ODESystem(Equation[], t, [], []; name = :level1) level0
151151
parameters(level1)
152-
#level0a
152+
#level0.a
153153
#b
154154
#c
155-
#level0d
156-
#level0e
155+
#level0.d
156+
#level0.e
157157
#f
158158
level2 = ODESystem(Equation[], t, [], []; name = :level2) level1
159159
parameters(level2)
160-
#level1level0a
161-
#level1b
160+
#level1.level0.a
161+
#level1.b
162162
#c
163-
#level0d
164-
#level1level0e
163+
#level0.d
164+
#level1.level0.e
165165
#f
166166
level3 = ODESystem(Equation[], t, [], []; name = :level3) level2
167167
parameters(level3)
168-
#level2level1level0a
169-
#level2level1b
170-
#level2c
171-
#level2level0d
172-
#level1level0e
168+
#level2.level1.level0.a
169+
#level2.level1.b
170+
#level2.c
171+
#level2.level0.d
172+
#level1.level0.e
173173
#f
174174
```
175175

docs/src/basics/Events.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,10 @@ When accessing variables of a sub-system, it can be useful to rename them
172172
(alternatively, an affect function may be reused in different contexts):
173173

174174
```julia
175-
[x ~ 0] => (affect!, [resistorv => :v, x], [p, q => :p2], [], ctx)
175+
[x ~ 0] => (affect!, [resistor.v => :v, x], [p, q => :p2], [], ctx)
176176
```
177177

178-
Here, the symbolic variable `resistorv` is passed as `v` while the symbolic
178+
Here, the symbolic variable `resistor.v` is passed as `v` while the symbolic
179179
parameter `q` has been renamed `p2`.
180180

181181
As an example, here is the bouncing ball example from above using the functional

docs/src/tutorials/domain_connections.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ end
115115
nothing #hide
116116
```
117117

118-
To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `structural_simplify()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluidρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluidρ`. Therefore, there is now only 1 defined density value `fluidρ` which sets the density for the connected network.
118+
To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `structural_simplify()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid.ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid.ρ`. Therefore, there is now only 1 defined density value `fluid.ρ` which sets the density for the connected network.
119119

120120
```@repl domain
121121
sys = structural_simplify(odesys)
@@ -181,7 +181,7 @@ end
181181
nothing #hide
182182
```
183183

184-
After running `structural_simplify()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_aρ` and `act.port_b.ρ` points to `fluid_bρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected.
184+
After running `structural_simplify()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a.ρ` and `act.port_b.ρ` points to `fluid_b.ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected.
185185

186186
```@example domain
187187
@component function ActuatorSystem1(; name)

src/ModelingToolkit.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ using DiffEqCallbacks
2222
using Graphs
2323
import ExprTools: splitdef, combinedef
2424
import OrderedCollections
25+
import ComponentArrays
2526

2627
using SymbolicIndexingInterface
2728
using LinearAlgebra, SparseArrays, LabelledArrays

src/inputoutput.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ function is_bound(sys, u, stack = [])
7171
In the following scenario
7272
julia> observed(syss)
7373
2-element Vector{Equation}:
74-
sysy(tv) ~ sysx(tv)
75-
y(tv) ~ sysx(tv)
76-
sysy(t) is bound to the outer y(t) through the variable sysx(t) and should thus return is_bound(sysy(t)) = true.
77-
When asking is_bound(sysy(t)), we know that we are looking through observed equations and can thus ask
78-
if var is bound, if it is, then sysy(t) is also bound. This can lead to an infinite recursion, so we maintain a stack of variables we have previously asked about to be able to break cycles
74+
sys.y(tv) ~ sys.x(tv)
75+
y(tv) ~ sys.x(tv)
76+
sys.y(t) is bound to the outer y(t) through the variable sys.x(t) and should thus return is_bound(sys.y(t)) = true.
77+
When asking is_bound(sys.y(t)), we know that we are looking through observed equations and can thus ask
78+
if var is bound, if it is, then sys.y(t) is also bound. This can lead to an infinite recursion, so we maintain a stack of variables we have previously asked about to be able to break cycles
7979
=#
8080
u Set(stack) && return false # Cycle detected
8181
eqs = equations(sys)
@@ -119,17 +119,17 @@ function same_or_inner_namespace(u, var)
119119
nv = get_namespace(var)
120120
nu == nv || # namespaces are the same
121121
startswith(nv, nu) || # or nv starts with nu, i.e., nv is an inner namespace to nu
122-
occursin('', string(getname(var))) &&
123-
!occursin('', string(getname(u))) # or u is top level but var is internal
122+
occursin('.', string(getname(var))) &&
123+
!occursin('.', string(getname(u))) # or u is top level but var is internal
124124
end
125125

126126
function inner_namespace(u, var)
127127
nu = get_namespace(u)
128128
nv = get_namespace(var)
129129
nu == nv && return false
130130
startswith(nv, nu) || # or nv starts with nu, i.e., nv is an inner namespace to nu
131-
occursin('', string(getname(var))) &&
132-
!occursin('', string(getname(u))) # or u is top level but var is internal
131+
occursin('.', string(getname(var))) &&
132+
!occursin('.', string(getname(u))) # or u is top level but var is internal
133133
end
134134

135135
"""
@@ -139,11 +139,11 @@ Return the namespace of a variable as a string. If the variable is not namespace
139139
"""
140140
function get_namespace(x)
141141
sname = string(getname(x))
142-
parts = split(sname, '')
142+
parts = split(sname, '.')
143143
if length(parts) == 1
144144
return ""
145145
end
146-
join(parts[1:(end - 1)], '')
146+
join(parts[1:(end - 1)], '.')
147147
end
148148

149149
"""

src/systems/abstractsystem.jl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,8 @@ function SymbolicIndexingInterface.is_variable(sys::AbstractSystem, sym::Symbol)
365365
return is_variable(ic, sym)
366366
end
367367
return any(isequal(sym), getname.(variable_symbols(sys))) ||
368-
count('', string(sym)) == 1 &&
369-
count(isequal(sym), Symbol.(nameof(sys), :, getname.(variable_symbols(sys)))) ==
368+
count('.', string(sym)) == 1 &&
369+
count(isequal(sym), Symbol.(nameof(sys), :., getname.(variable_symbols(sys)))) ==
370370
1
371371
end
372372

@@ -399,9 +399,9 @@ function SymbolicIndexingInterface.variable_index(sys::AbstractSystem, sym::Symb
399399
idx = findfirst(isequal(sym), getname.(variable_symbols(sys)))
400400
if idx !== nothing
401401
return idx
402-
elseif count('', string(sym)) == 1
402+
elseif count('.', string(sym)) == 1
403403
return findfirst(isequal(sym),
404-
Symbol.(nameof(sys), :, getname.(variable_symbols(sys))))
404+
Symbol.(nameof(sys), :., getname.(variable_symbols(sys))))
405405
end
406406
return nothing
407407
end
@@ -431,9 +431,9 @@ function SymbolicIndexingInterface.is_parameter(sys::AbstractSystem, sym::Symbol
431431
return is_parameter(ic, sym)
432432
end
433433
return any(isequal(sym), getname.(parameter_symbols(sys))) ||
434-
count('', string(sym)) == 1 &&
434+
count('.', string(sym)) == 1 &&
435435
count(isequal(sym),
436-
Symbol.(nameof(sys), :, getname.(parameter_symbols(sys)))) == 1
436+
Symbol.(nameof(sys), :., getname.(parameter_symbols(sys)))) == 1
437437
end
438438

439439
function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym)
@@ -466,9 +466,9 @@ function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym::Sym
466466
idx = findfirst(isequal(sym), getname.(parameter_symbols(sys)))
467467
if idx !== nothing
468468
return idx
469-
elseif count('', string(sym)) == 1
469+
elseif count('.', string(sym)) == 1
470470
return findfirst(isequal(sym),
471-
Symbol.(nameof(sys), :, getname.(parameter_symbols(sys))))
471+
Symbol.(nameof(sys), :., getname.(parameter_symbols(sys))))
472472
end
473473
return nothing
474474
end
@@ -889,7 +889,7 @@ function renamespace(sys, x)
889889
elseif x isa AbstractSystem
890890
rename(x, renamespace(sys, nameof(x)))
891891
else
892-
Symbol(getname(sys), :, x)
892+
Symbol(getname(sys), :., x)
893893
end
894894
end
895895

@@ -1248,7 +1248,7 @@ function round_trip_eq(eq::Equation, var2name)
12481248
syss = get_systems(eq.rhs)
12491249
call = Expr(:call, connect)
12501250
for sys in syss
1251-
strs = split(string(nameof(sys)), "")
1251+
strs = split(string(nameof(sys)), ".")
12521252
s = Symbol(strs[1])
12531253
for st in strs[2:end]
12541254
s = Expr(:., s, Meta.quot(Symbol(st)))

src/systems/callbacks.jl

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,24 @@ function generate_rootfinding_callback(cbs, sys::AbstractODESystem, dvs = unknow
512512
end
513513
end
514514

515+
# Put a wrapper on NamedTuple so that u.resistor.v indexes like u.var"resistor.v"
516+
# Required for hierarchical, but a hack that should be fixed in the future
517+
struct NamedTupleSymbolFix{T}
518+
x::T
519+
sym::Symbol
520+
521+
end
522+
NamedTupleSymbolFix(x) = NamedTupleSymbolFix(x, Symbol(""))
523+
function Base.getproperty(u::NamedTupleSymbolFix, s::Symbol)
524+
newsym = getfield(u,:sym) == Symbol("") ? s : Symbol(getfield(u,:sym), ".", s)
525+
x = getfield(u,:x)
526+
if newsym in keys(x)
527+
getproperty(x, newsym)
528+
else
529+
NamedTupleSymbolFix(x, newsym)
530+
end
531+
end
532+
515533
function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...)
516534
dvs_ind = Dict(reverse(en) for en in enumerate(dvs))
517535
v_inds = map(sym -> dvs_ind[sym], unknowns(affect))
@@ -526,9 +544,10 @@ function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...)
526544
# HACK: filter out eliminated symbols. Not clear this is the right thing to do
527545
# (MTK should keep these symbols)
528546
u = filter(x -> !isnothing(x[2]), collect(zip(unknowns_syms(affect), v_inds))) |>
529-
NamedTuple
547+
NamedTuple |> NamedTupleSymbolFix
548+
530549
p = filter(x -> !isnothing(x[2]), collect(zip(parameters_syms(affect), p_inds))) |>
531-
NamedTuple
550+
NamedTuple |> NamedTupleSymbolFix
532551

533552
let u = u, p = p, user_affect = func(affect), ctx = context(affect)
534553
function (integ)

src/systems/connectors.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ function generate_isouter(sys::AbstractSystem)
129129
function isouter(sys)::Bool
130130
s = string(nameof(sys))
131131
isconnector(sys) || error("$s is not a connector!")
132-
idx = findfirst(isequal(''), s)
132+
idx = findfirst(isequal('.'), s)
133133
parent_name = Symbol(idx === nothing ? s : s[1:prevind(s, idx)])
134134
parent_name in outer_connectors
135135
end

test/funcaffect.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ i8 = findfirst(==(8.0), sol[:t])
123123
ctx = [0]
124124
function affect4!(integ, u, p, ctx)
125125
ctx[1] += 1
126-
@test u.resistorv == 1
126+
@test u.resistor.v == 1
127127
end
128128
s1 = compose(
129129
ODESystem(Equation[], t, [], [], name = :s1,
@@ -137,7 +137,7 @@ sol = solve(prob, Tsit5())
137137
include("../examples/rc_model.jl")
138138

139139
function affect5!(integ, u, p, ctx)
140-
@test integ.u[u.capacitorv] 0.3
140+
@test integ.u[u.capacitor.v] 0.3
141141
integ.ps[p.C] *= 200
142142
end
143143

test/input_output_handling.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ end
3434
@test get_namespace(x) == ""
3535
@test get_namespace(sys.x) == "sys"
3636
@test get_namespace(sys2.x) == "sys2"
37-
@test get_namespace(sys2.sys.x) == "sys2sys"
38-
@test get_namespace(sys21.sys1.v) == "sys21sys1"
37+
@test get_namespace(sys2.sys.x) == "sys2.sys"
38+
@test get_namespace(sys21.sys1.v) == "sys21.sys1"
3939

4040
@test !is_bound(sys, u)
4141
@test !is_bound(sys, x)

test/inputoutput.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ connected = ODESystem(Equation[], t, [], [], observed = connections,
1919

2020
sys = connected
2121

22-
@variables lorenz1F lorenz2F
23-
@test pins(connected) == Variable[lorenz1F, lorenz2F]
22+
@variables lorenz1.F lorenz2.F
23+
@test pins(connected) == Variable[lorenz1.F, lorenz2.F]
2424
@test isequal(observed(connected),
2525
[connections...,
2626
lorenz1.u ~ lorenz1.x + lorenz1.y - lorenz1.z,
@@ -40,7 +40,7 @@ simplifyeqs(eqs) = Equation.((x -> x.lhs).(eqs), simplify.((x -> x.rhs).(eqs)))
4040

4141
@test isequal(simplifyeqs(equations(connected)), simplifyeqs(collapsed_eqs))
4242

43-
# Variables indicated to be input/output
43+
# Variables indicated to be input/output
4444
@variables x [input = true]
4545
@test hasmetadata(x, Symbolics.option_to_metadata_type(Val(:input)))
4646
@test getmetadata(x, Symbolics.option_to_metadata_type(Val(:input))) == true

test/odesystem.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,9 +1109,9 @@ function RealExpressionSystem(; name)
11091109
vars = @variables begin
11101110
x(t)
11111111
z(t)[1:1]
1112-
end # doing a collect on z doesn't work either.
1113-
@named e1 = RealExpression(y = x) # This works perfectly.
1114-
@named e2 = RealExpression(y = z[1]) # This bugs. However, `full_equations(e2)` works as expected.
1112+
end # doing a collect on z doesn't work either.
1113+
@named e1 = RealExpression(y = x) # This works perfectly.
1114+
@named e2 = RealExpression(y = z[1]) # This bugs. However, `full_equations(e2)` works as expected.
11151115
systems = [e1, e2]
11161116
ODESystem(Equation[], t, Iterators.flatten(vars), []; systems, name)
11171117
end
@@ -1166,7 +1166,7 @@ end
11661166
# Namespacing of array variables
11671167
@variables x(t)[1:2]
11681168
@named sys = ODESystem(Equation[], t)
1169-
@test getname(unknowns(sys, x)) == :sys₊x
1169+
@test getname(unknowns(sys, x)) == Symbol("sys.x")
11701170
@test size(unknowns(sys, x)) == size(x)
11711171

11721172
# Issue#2667

0 commit comments

Comments
 (0)