Skip to content

Commit 0cc516e

Browse files
Merge pull request #2863 from hersle/nl_jac_obs_dep
Capture observed dependence on unknowns in simplified nonlinear systems
2 parents f5378df + 3088c80 commit 0cc516e

File tree

3 files changed

+52
-17
lines changed

3 files changed

+52
-17
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/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)]

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

0 commit comments

Comments
 (0)