Skip to content

Commit 0dc0f5d

Browse files
Merge pull request #2617 from AayushSabharwal/as/better-missing-subsystem-error
feat: improve error messages for missing variables and subsystems
2 parents f299353 + a28c12b commit 0dc0f5d

File tree

8 files changed

+139
-5
lines changed

8 files changed

+139
-5
lines changed

src/systems/diffeqs/odesystem.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ struct ODESystem <: AbstractODESystem
188188
check_parameters(ps, iv)
189189
check_equations(deqs, iv)
190190
check_equations(equations(cevents), iv)
191+
check_namespacing([deqs; equations(cevents)], dvs, ps, iv; systems)
191192
end
192193
if checks == true || (checks & CheckUnits) > 0
193194
u = __get_unit_type(dvs, ps, iv)

src/systems/diffeqs/sdesystem.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ struct SDESystem <: AbstractODESystem
141141
check_parameters(ps, iv)
142142
check_equations(deqs, iv)
143143
check_equations(equations(cevents), iv)
144+
check_namespacing(
145+
[deqs; equations(cevents); vec(unwrap.(neqs))], dvs, ps, iv; systems)
144146
end
145147
if checks == true || (checks & CheckUnits) > 0
146148
u = __get_unit_type(dvs, ps, iv)

src/systems/discrete_system/discrete_system.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ struct DiscreteSystem <: AbstractTimeDependentSystem
103103
if checks == true || (checks & CheckComponents) > 0
104104
check_variables(dvs, iv)
105105
check_parameters(ps, iv)
106+
check_namespacing(discreteEqs, dvs, ps, iv; systems)
106107
end
107108
if checks == true || (checks & CheckUnits) > 0
108109
u = __get_unit_type(dvs, ps, iv)

src/systems/nonlinear/nonlinearsystem.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct NonlinearSystem <: AbstractTimeIndependentSystem
9595
if checks == true || (checks & CheckUnits) > 0
9696
u = __get_unit_type(unknowns, ps)
9797
check_units(u, eqs)
98+
check_namespacing(eqs, unknowns, ps, nothing; systems)
9899
end
99100
new(tag, eqs, unknowns, ps, var_to_name, observed, jac, name, systems, defaults,
100101
connector_type, metadata, gui_metadata, tearing_state, substitutions, complete,

src/systems/optimization/constraints_system.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ struct ConstraintsSystem <: AbstractTimeIndependentSystem
8888
if checks == true || (checks & CheckUnits) > 0
8989
u = __get_unit_type(unknowns, ps)
9090
check_units(u, constraints)
91+
check_namespacing(constraints, unknowns, ps, nothing; systems)
9192
end
9293
new(tag, constraints, unknowns, ps, var_to_name, observed, jac, name, systems,
9394
defaults,

src/systems/optimization/optimizationsystem.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ struct OptimizationSystem <: AbstractOptimizationSystem
7474
unwrap(op) isa Symbolic && check_units(u, op)
7575
check_units(u, observed)
7676
check_units(u, constraints)
77+
check_namespacing([op; constraints], unknowns, ps, nothing; systems)
7778
end
7879
new(tag, op, unknowns, ps, var_to_name, observed,
7980
constraints, name, systems, defaults, metadata, gui_metadata, complete,

src/utils.jl

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,35 @@ function check_equations(eqs, iv)
182182
throw(ArgumentError("Differential w.r.t. variable ($single_iv) other than the independent variable ($iv) are not allowed."))
183183
end
184184
end
185+
186+
function check_namespacing(eqs, dvs, ps, iv; systems = [])
187+
eqsyms = vars(eqs; op = Nothing)
188+
syssyms = Set{Symbol}()
189+
foldl(Iterators.flatten((dvs, ps)); init = syssyms) do prev, sym
190+
sym = unwrap(sym)
191+
while istree(sym) && operation(sym) isa Operator
192+
sym = only(arguments(sym))
193+
end
194+
push!(prev, getname(sym))
195+
prev
196+
end
197+
subsysnames = get_name.(systems)
198+
if iv !== nothing
199+
push!(syssyms, getname(iv))
200+
end
201+
for sym in eqsyms
202+
symname = getname(sym)
203+
strname = String(symname)
204+
if occursin('', strname)
205+
subsysname = Symbol(first(split(strname, '')))
206+
subsysname in subsysnames && continue
207+
error("Unexpected variable $sym. Expected system to have subsystem with name $subsysname.")
208+
end
209+
symname in syssyms && continue
210+
error("Symbol $sym does not occur in the system.")
211+
end
212+
end
213+
185214
"""
186215
Get all the independent variables with respect to which differentials are taken.
187216
"""
@@ -348,8 +377,8 @@ end
348377
vars(exprs::Num; op = Differential) = vars(unwrap(exprs); op)
349378
vars(exprs::Symbolics.Arr; op = Differential) = vars(unwrap(exprs); op)
350379
vars(exprs; op = Differential) = foldl((x, y) -> vars!(x, y; op = op), exprs; init = Set())
351-
vars(eq::Equation; op = Differential) = vars!(Set(), eq; op = op)
352-
function vars!(vars, eq::Equation; op = Differential)
380+
vars(eq::Union{Equation, Inequality}; op = Differential) = vars!(Set(), eq; op = op)
381+
function vars!(vars, eq::Union{Equation, Inequality}; op = Differential)
353382
(vars!(vars, eq.lhs; op = op); vars!(vars, eq.rhs; op = op); vars)
354383
end
355384
function vars!(vars, O; op = Differential)

test/variable_scope.jl

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
using ModelingToolkit
2-
using ModelingToolkit: SymScope
2+
using ModelingToolkit: SymScope, t_nounits as t, D_nounits as D
33
using Symbolics: arguments, value
44
using Test
55

6-
@parameters t
76
@variables a b(t) c d e(t)
87

98
b = ParentScope(b)
@@ -52,7 +51,7 @@ end
5251
@test renamed([:foo :bar :baz], c) == Symbol("foo₊c")
5352
@test renamed([:foo :bar :baz], d) == :d
5453

55-
@parameters t a b c d e f
54+
@parameters a b c d e f
5655
p = [a
5756
ParentScope(b)
5857
ParentScope(ParentScope(c))
@@ -73,3 +72,102 @@ ps = ModelingToolkit.getname.(parameters(level3))
7372
@test isequal(ps[4], :level2₊level0₊d)
7473
@test isequal(ps[5], :level1₊level0₊e)
7574
@test isequal(ps[6], :f)
75+
76+
@variables x(t) y(t)[1:2]
77+
@parameters p q[1:2]
78+
79+
@test_throws ["Symbol", "x(t)", "does not occur"] ODESystem(
80+
[D(x) ~ p], t, [], [p]; name = :foo)
81+
@test_nowarn ODESystem([D(x) ~ p], t, [x], [p]; name = :foo)
82+
@test_throws ["Symbol", "y(t)", "does not occur"] ODESystem(
83+
D(y) ~ q, t, [], [q]; name = :foo)
84+
@test_nowarn ODESystem(D(y) ~ q, t, [y], [q]; name = :foo)
85+
@test_throws ["Symbol", "y(t)", "[1]", "does not occur"] ODESystem(
86+
D(y[1]) ~ x, t, [x], []; name = :foo)
87+
@test_nowarn ODESystem(D(y[1]) ~ x, t, [x, y], []; name = :foo)
88+
@test_throws ["Symbol", "p", "does not occur"] ODESystem(D(x) ~ p, t, [x], []; name = :foo)
89+
@test_nowarn ODESystem(D(x) ~ p, t, [x], [p]; name = :foo)
90+
@test_throws ["Symbol", "q", "does not occur"] ODESystem(D(y) ~ q, t, [y], []; name = :foo)
91+
@test_nowarn ODESystem(D(y) ~ q, t, [y], [q]; name = :foo)
92+
@test_throws ["Symbol", "q", "[1]", "does not occur"] ODESystem(
93+
D(y[1]) ~ q[1], t, [y], []; name = :foo)
94+
@test_nowarn ODESystem(D(y[1]) ~ q[1], t, [y], [q]; name = :foo)
95+
@test_throws ["Symbol", "x(t)", "does not occur"] ODESystem(
96+
Equation[], t, [], [p]; name = :foo, continuous_events = [[x ~ 0.0] => [p ~ 1.0]])
97+
@test_nowarn ODESystem(
98+
Equation[], t, [x], [p]; name = :foo, continuous_events = [[x ~ 0.0] => [p ~ 1.0]])
99+
100+
@named sys1 = ODESystem(Equation[], t, [x, y], [p, q])
101+
@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] ODESystem(
102+
[D(x) ~ sys1.x], t; name = :sys2)
103+
@test_nowarn ODESystem([D(x) ~ sys1.x], t; name = :sys2, systems = [sys1])
104+
@test_throws ["Unexpected", "sys1₊y(t)", "subsystem with name sys1"] ODESystem(
105+
[D(x) ~ sum(sys1.y)], t; name = :sys2)
106+
@test_nowarn ODESystem([D(x) ~ sum(sys1.y)], t; name = :sys2, systems = [sys1])
107+
@test_throws ["Unexpected", "sys1₊y(t)", "[1]", "subsystem with name sys1"] ODESystem(
108+
D(x) ~ sys1.y[1], t; name = :sys2)
109+
@test_nowarn ODESystem(D(x) ~ sys1.y[1], t; name = :sys2, systems = [sys1])
110+
@test_throws ["Unexpected", "sys1₊p", "subsystem with name sys1"] ODESystem(
111+
D(x) ~ sys1.p, t; name = :sys2)
112+
@test_nowarn ODESystem(D(x) ~ sys1.p, t; name = :sys2, systems = [sys1])
113+
@test_throws ["Unexpected", "sys1₊q", "subsystem with name sys1"] ODESystem(
114+
D(y) ~ sys1.q, t; name = :sys2)
115+
@test_nowarn ODESystem(D(y) ~ sys1.q, t; name = :sys2, systems = [sys1])
116+
@test_throws ["Unexpected", "sys1₊q", "[1]", "subsystem with name sys1"] ODESystem(
117+
D(x) ~ sys1.q[1], t; name = :sys2)
118+
@test_nowarn ODESystem(D(x) ~ sys1.q[1], t; name = :sys2, systems = [sys1])
119+
@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] ODESystem(
120+
Equation[], t, [], [p]; name = :sys2, continuous_events = [[sys1.x ~ 0] => [p ~ 1.0]])
121+
@test_nowarn ODESystem(Equation[], t, [], [p]; name = :sys2,
122+
continuous_events = [[sys1.x ~ 0] => [p ~ 1.0]], systems = [sys1])
123+
124+
# Ensure SDESystem checks noise eqs as well
125+
@test_throws ["Symbol", "x(t)", "does not occur"] SDESystem(
126+
Equation[], [0.1x], t, [], []; name = :foo)
127+
@test_nowarn SDESystem(Equation[], [0.1x], t, [x], []; name = :foo)
128+
@named sys1 = SDESystem(Equation[], [], t, [x], [])
129+
@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] SDESystem(
130+
Equation[], [0.1sys1.x], t, [], []; name = :foo)
131+
@test_nowarn SDESystem(Equation[], [0.1sys1.x], t, [], []; name = :foo, systems = [sys1])
132+
133+
# Ensure DiscreteSystem checks work
134+
k = ShiftIndex(t)
135+
@test_throws ["Symbol", "x(t)", "does not occur"] DiscreteSystem(
136+
[x ~ x(k - 1) + x(k - 2)], t, [], []; name = :foo)
137+
@test_nowarn DiscreteSystem([x ~ x(k - 1) + x(k - 2)], t; name = :foo)
138+
@named sys1 = DiscreteSystem(Equation[], t, [x], [])
139+
@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] DiscreteSystem(
140+
[x ~ x(k - 1) + sys1.x(k - 2)], t, [x], []; name = :sys2)
141+
@test_nowarn DiscreteSystem(
142+
[x ~ x(k - 1) + sys1.x(k - 2)], t, [x], []; name = :sys2, systems = [sys1])
143+
144+
# Ensure NonlinearSystem checks work
145+
@variables x
146+
@test_throws ["Symbol", "x", "does not occur"] NonlinearSystem(
147+
[0 ~ 2x + 3], [], []; name = :foo)
148+
@test_nowarn NonlinearSystem([0 ~ 2x + 3], [x], []; name = :foo)
149+
@named sys1 = NonlinearSystem(Equation[], [x], [])
150+
@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] NonlinearSystem(
151+
[0 ~ sys1.x + 3], [], []; name = :foo)
152+
@test_nowarn NonlinearSystem([0 ~ sys1.x + 3], [], []; name = :foo, systems = [sys1])
153+
154+
# Ensure ConstraintsSystem checks work
155+
@test_throws ["Symbol", "x", "does not occur"] ConstraintsSystem(
156+
[0 ~ x^2 - 3], [], []; name = :foo)
157+
@test_nowarn ConstraintsSystem([0 ~ x^2 - 3], [x], []; name = :foo)
158+
@test_throws ["Symbol", "x", "does not occur"] ConstraintsSystem(
159+
[Inequality(x^2, 3, <)], [], []; name = :foo)
160+
@test_nowarn ConstraintsSystem([Inequality(x^2, 3, <)], [x], []; name = :foo)
161+
@named sys1 = ConstraintsSystem(Equation[], [x], [])
162+
@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] ConstraintsSystem(
163+
[0 ~ sys1.x^2 - 2], [], []; name = :sys2)
164+
@test_nowarn ConstraintsSystem([0 ~ sys1.x^2 - 2], [], []; name = :sys2, systems = [sys1])
165+
166+
# Ensure OptimizationSystem checks work
167+
@test_throws ["Symbol", "x", "does not occur"] OptimizationSystem(
168+
y[1], [y[1]], []; constraints = [x ~ 3], name = :foo)
169+
@test_nowarn OptimizationSystem(y[1], [y[1], x], []; constraints = [x ~ 3], name = :foo)
170+
@named sys1 = OptimizationSystem(x, [x], [])
171+
@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] OptimizationSystem(
172+
sys1.x^2 - 2, [], []; name = :sys2)
173+
@test_nowarn OptimizationSystem(sys1.x^2 - 2, [], []; name = :sys2, systems = [sys1])

0 commit comments

Comments
 (0)