Skip to content

Commit b17fc0f

Browse files
committed
Merge branch 'master' into extend_metadata
2 parents 3c101cf + 2f5c718 commit b17fc0f

File tree

9 files changed

+95
-32
lines changed

9 files changed

+95
-32
lines changed

docs/src/tutorials/nonlinear.md

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,27 @@ We use (unknown) variables for our nonlinear system.
99
```@example nonlinear
1010
using ModelingToolkit, NonlinearSolve
1111
12+
# Define a nonlinear system
1213
@variables x y z
1314
@parameters σ ρ β
15+
eqs = [0 ~ σ * (y - x)
16+
0 ~ x * (ρ - z) - y
17+
0 ~ x * y - β * z]
18+
guesses = [x => 1.0, y => 0.0, z => 0.0]
19+
ps = [σ => 10.0, ρ => 26.0, β => 8 / 3]
20+
@mtkbuild ns = NonlinearSystem(eqs)
1421
15-
# Define a nonlinear system
16-
eqs = [0 ~ σ * (y - x),
17-
0 ~ x * (ρ - z) - y,
18-
0 ~ x * y - β * z]
19-
@mtkbuild ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β])
20-
21-
guess = [x => 1.0,
22-
y => 0.0,
23-
z => 0.0]
24-
25-
ps = [σ => 10.0
26-
ρ => 26.0
27-
β => 8 / 3]
22+
guesses = [x => 1.0, y => 0.0, z => 0.0]
23+
ps = [σ => 10.0, ρ => 26.0, β => 8 / 3]
2824
29-
prob = NonlinearProblem(ns, guess, ps)
25+
prob = NonlinearProblem(ns, guesses, ps)
3026
sol = solve(prob, NewtonRaphson())
3127
```
3228

3329
We can similarly ask to generate the `NonlinearProblem` with the analytical
3430
Jacobian function:
3531

3632
```@example nonlinear
37-
prob = NonlinearProblem(ns, guess, ps, jac = true)
33+
prob = NonlinearProblem(ns, guesses, ps, jac = true)
3834
sol = solve(prob, NewtonRaphson())
3935
```

src/systems/diffeqs/abstractodesystem.jl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -725,10 +725,16 @@ function get_u0(
725725
defs = mergedefaults(defs, parammap, ps)
726726
end
727727

728-
obs = filter!(x -> !(x[1] isa Number),
729-
map(x -> isparameter(x.rhs) ? x.lhs => x.rhs : x.rhs => x.lhs, observed(sys)))
730-
observedmap = isempty(obs) ? Dict() : todict(obs)
731-
defs = mergedefaults(defs, observedmap, u0map, dvs)
728+
# Convert observed equations "lhs ~ rhs" into defaults.
729+
# Use the order "lhs => rhs" by default, but flip it to "rhs => lhs"
730+
# if "lhs" is known by other means (parameter, another default, ...)
731+
# TODO: Is there a better way to determine which equations to flip?
732+
obs = map(x -> x.lhs => x.rhs, observed(sys))
733+
obs = map(x -> x[1] in keys(defs) ? reverse(x) : x, obs)
734+
obs = filter!(x -> !(x[1] isa Number), obs) # exclude e.g. "0 => x^2 + y^2 - 25"
735+
obsmap = isempty(obs) ? Dict() : todict(obs)
736+
737+
defs = mergedefaults(defs, obsmap, u0map, dvs)
732738
if symbolic_u0
733739
u0 = varmap_to_vars(
734740
u0map, dvs; defaults = defs, tofloat = false, use_union = false, toterm)

src/systems/nonlinear/nonlinearsystem.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,12 @@ function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = fal
193193
return cache[1]
194194
end
195195

196-
rhs = [eq.rhs for eq in equations(sys)]
196+
# observed equations may depend on unknowns, so substitute them in first
197+
# TODO: rather keep observed derivatives unexpanded, like "Differential(obs)(expr)"?
198+
obs = Dict(eq.lhs => eq.rhs for eq in observed(sys))
199+
rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys))
197200
vals = [dv for dv in unknowns(sys)]
201+
198202
if sparse
199203
jac = sparsejacobian(rhs, vals, simplify = simplify)
200204
else
@@ -215,7 +219,8 @@ function generate_jacobian(
215219
end
216220

217221
function calculate_hessian(sys::NonlinearSystem; sparse = false, simplify = false)
218-
rhs = [eq.rhs for eq in equations(sys)]
222+
obs = Dict(eq.lhs => eq.rhs for eq in observed(sys))
223+
rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys))
219224
vals = [dv for dv in unknowns(sys)]
220225
if sparse
221226
hess = [sparsehessian(rhs[i], vals, simplify = simplify) for i in 1:length(rhs)]

src/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ function iv_from_nested_derivative(x, op = Differential)
228228
end
229229

230230
hasdefault(v) = hasmetadata(v, Symbolics.VariableDefaultValue)
231-
getdefault(v) = value(getmetadata(v, Symbolics.VariableDefaultValue))
231+
getdefault(v) = value(Symbolics.getdefaultval(v))
232232
function getdefaulttype(v)
233233
def = value(getmetadata(unwrap(v), Symbolics.VariableDefaultValue, nothing))
234234
def === nothing ? Float64 : typeof(def)

test/guess_propagation.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,21 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0])
9090
@variables y(t) = x0
9191
@mtkbuild sys = ODESystem([x ~ x0, D(y) ~ x], t)
9292
prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0])
93-
prob[x] == 1.0
94-
prob[y] == 1.0
93+
@test prob[x] == 1.0
94+
@test prob[y] == 1.0
9595

9696
@parameters x0
9797
@variables x(t)
9898
@variables y(t) = x0
9999
@mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t)
100100
prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0])
101-
prob[x] == 1.0
102-
prob[y] == 1.0
101+
@test prob[x] == 1.0
102+
@test prob[y] == 1.0
103103

104104
@parameters x0
105105
@variables x(t) = x0
106106
@variables y(t) = x
107107
@mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t)
108108
prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0])
109-
prob[x] == 1.0
110-
prob[y] == 1.0
109+
@test prob[x] == 1.0
110+
@test prob[y] == 1.0

test/model_parsing.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ end
263263
@test getdefault(model.cval) == 1
264264
@test isequal(getdefault(model.c), model.cval + model.jval)
265265
@test getdefault(model.d) == 2
266-
@test_throws KeyError getdefault(model.e)
266+
@test_throws ErrorException getdefault(model.e)
267267
@test getdefault(model.f) == 3
268268
@test getdefault(model.i) == 4
269269
@test all(getdefault.(scalarize(model.b2)) .== [1, 3])

test/nonlinearsystem.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,37 @@ sys = structural_simplify(ns)
283283
@test length(equations(sys)) == 0
284284
sys = structural_simplify(ns; conservative = true)
285285
@test length(equations(sys)) == 1
286+
287+
# https://github.com/SciML/ModelingToolkit.jl/issues/2858
288+
@testset "Jacobian/Hessian with observed equations that depend on unknowns" begin
289+
@variables x y z
290+
@parameters σ ρ β
291+
eqs = [0 ~ σ * (y - x)
292+
0 ~ x *- z) - y
293+
0 ~ x * y - β * z]
294+
guesses = [x => 1.0, y => 0.0, z => 0.0]
295+
ps ==> 10.0, ρ => 26.0, β => 8 / 3]
296+
@mtkbuild ns = NonlinearSystem(eqs)
297+
298+
@test isequal(calculate_jacobian(ns), [(-1 - z + ρ)*σ -x*σ
299+
2x*(-z + ρ) -β-(x^2)])
300+
# solve without analytical jacobian
301+
prob = NonlinearProblem(ns, guesses, ps)
302+
sol = solve(prob, NewtonRaphson())
303+
@test sol.retcode == ReturnCode.Success
304+
305+
# solve with analytical jacobian
306+
prob = NonlinearProblem(ns, guesses, ps, jac = true)
307+
sol = solve(prob, NewtonRaphson())
308+
@test sol.retcode == ReturnCode.Success
309+
310+
# system that contains a chain of observed variables when simplified
311+
@variables x y z
312+
eqs = [0 ~ x^2 + 2z + y, z ~ y, y ~ x] # analytical solution x = y = z = 0 or -3
313+
@mtkbuild ns = NonlinearSystem(eqs) # solve for y with observed chain z -> x -> y
314+
@test isequal(expand.(calculate_jacobian(ns)), [3 // 2 + y;;])
315+
@test isequal(calculate_hessian(ns), [[1;;]])
316+
prob = NonlinearProblem(ns, unknowns(ns) .=> -4.0) # give guess < -3 to reach -3
317+
sol = solve(prob, NewtonRaphson())
318+
@test sol[x] sol[y] sol[z] -3
319+
end

test/odesystem.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,3 +1208,23 @@ end
12081208
@test ModelingToolkit.get_metadata(extend(A2, B1)) == A
12091209
@test Set(ModelingToolkit.get_metadata(extend(A2, B2))) == Set(A B)
12101210
end
1211+
1212+
# https://github.com/SciML/ModelingToolkit.jl/issues/2859
1213+
@testset "Initialization with defaults from observed equations (edge case)" begin
1214+
@variables x(t) y(t) z(t)
1215+
eqs = [D(x) ~ 0, y ~ x, D(z) ~ 0]
1216+
defaults = [x => 1, z => y]
1217+
@named sys = ODESystem(eqs, t; defaults)
1218+
ssys = structural_simplify(sys)
1219+
prob = ODEProblem(ssys, [], (0.0, 1.0), [])
1220+
@test prob[x] == prob[y] == prob[z] == 1.0
1221+
1222+
@parameters y0
1223+
@variables x(t) y(t) z(t)
1224+
eqs = [D(x) ~ 0, y ~ y0 / x, D(z) ~ y]
1225+
defaults = [y0 => 1, x => 1, z => y]
1226+
@named sys = ODESystem(eqs, t; defaults)
1227+
ssys = structural_simplify(sys)
1228+
prob = ODEProblem(ssys, [], (0.0, 1.0), [])
1229+
@test prob[x] == prob[y] == prob[z] == 1.0
1230+
end

test/variable_parsing.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,18 @@ end
104104
y = 2, [connect = Flow]
105105
end
106106

107+
@test_throws ErrorException ModelingToolkit.getdefault(x)
107108
@test !hasmetadata(x, VariableDefaultValue)
108109
@test getmetadata(x, VariableConnectType) == Flow
109110
@test getmetadata(x, VariableUnit) == u
110111
@test getmetadata(y, VariableDefaultValue) === 2
111112
@test getmetadata(y, VariableConnectType) == Flow
112113

113114
a = rename(value(x), :a)
114-
@test !hasmetadata(x, VariableDefaultValue)
115-
@test getmetadata(x, VariableConnectType) == Flow
116-
@test getmetadata(x, VariableUnit) == u
115+
@test_throws ErrorException ModelingToolkit.getdefault(a)
116+
@test !hasmetadata(a, VariableDefaultValue)
117+
@test getmetadata(a, VariableConnectType) == Flow
118+
@test getmetadata(a, VariableUnit) == u
117119

118120
@variables t x(t)=1 [connect = Flow, unit = u]
119121

0 commit comments

Comments
 (0)