Skip to content

Commit c10d206

Browse files
Merge pull request #2382 from MarcBerliner/initial_conditions
Custom initial condition equations for an `ODESystem`
2 parents d0d67de + 8415e58 commit c10d206

File tree

6 files changed

+162
-1
lines changed

6 files changed

+162
-1
lines changed

docs/src/basics/Variable_metadata.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ hasbounds(u)
8383
getbounds(u)
8484
```
8585

86+
## Guess
87+
88+
Specify an initial guess for custom initial conditions of an `ODESystem`.
89+
90+
```@example metadata
91+
@variables u [guess = 1]
92+
hasguess(u)
93+
```
94+
95+
```@example metadata
96+
getguess(u)
97+
```
98+
8699
## Mark input as a disturbance
87100

88101
Indicate that an input is not available for control, i.e., it's a disturbance input.

src/ModelingToolkit.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ include("systems/jumps/jumpsystem.jl")
142142

143143
include("systems/nonlinear/nonlinearsystem.jl")
144144
include("systems/nonlinear/modelingtoolkitize.jl")
145+
include("systems/nonlinear/initializesystem.jl")
145146

146147
include("systems/optimization/constraints_system.jl")
147148
include("systems/optimization/optimizationsystem.jl")
@@ -201,7 +202,8 @@ export NonlinearSystem, OptimizationSystem, ConstraintsSystem
201202
export alias_elimination, flatten
202203
export connect, domain_connect, @connector, Connection, Flow, Stream, instream
203204
export @component, @mtkmodel, @mtkbuild
204-
export isinput, isoutput, getbounds, hasbounds, isdisturbance, istunable, getdist, hasdist,
205+
export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbance,
206+
istunable, getdist, hasdist,
205207
tunable_parameters, isirreducible, getdescription, hasdescription, isbinaryvar,
206208
isintegervar
207209
export ode_order_lowering, dae_order_lowering, liouville_transform
@@ -236,6 +238,7 @@ export toexpr, get_variables
236238
export simplify, substitute
237239
export build_function
238240
export modelingtoolkitize
241+
export initializesystem
239242

240243
export @variables, @parameters, @constants, @brownian
241244
export @named, @nonamespace, @namespace, extend, compose, complete
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
$(TYPEDSIGNATURES)
3+
4+
Generate `NonlinearSystem` which initializes an ODE problem from specified initial conditions of an `ODESystem`.
5+
"""
6+
function initializesystem(sys::ODESystem; name = nameof(sys), kwargs...)
7+
if has_parent(sys) && (parent = get_parent(sys); parent !== nothing)
8+
sys = parent
9+
end
10+
sts, eqs = states(sys), equations(sys)
11+
12+
idxs_diff = isdiffeq.(eqs)
13+
idxs_alge = .!idxs_diff
14+
15+
# Algebraic equations and initial guesses are unchanged
16+
eqs_ics = similar(eqs)
17+
u0 = Vector{Any}(undef, length(sts))
18+
19+
eqs_ics[idxs_alge] .= eqs[idxs_alge]
20+
u0[idxs_alge] .= getmetadata.(unwrap.(sts[idxs_alge]),
21+
Symbolics.VariableDefaultValue,
22+
nothing)
23+
24+
for idx in findall(idxs_diff)
25+
st = sts[idx]
26+
if !hasdefault(st)
27+
error("Invalid setup: unknown $(st) has no default value or equation.")
28+
end
29+
30+
def = getdefault(st)
31+
if def isa Equation
32+
if !hasguess(st)
33+
error("Invalid setup: unknown $(st) has an initial condition equation with no guess.")
34+
end
35+
guess = getguess(st)
36+
eqs_ics[idx] = def
37+
38+
u0[idx] = guess
39+
else
40+
eqs_ics[idx] = st ~ def
41+
42+
u0[idx] = def
43+
end
44+
end
45+
46+
pars = parameters(sys)
47+
sys_nl = NonlinearSystem(eqs_ics,
48+
sts,
49+
pars;
50+
defaults = Dict(sts .=> u0),
51+
name,
52+
kwargs...)
53+
54+
return sys_nl
55+
end

src/variables.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,3 +425,42 @@ macro brownian(xs...)
425425
xs,
426426
tobrownian) |> esc
427427
end
428+
429+
## Guess ======================================================================
430+
struct VariableGuess end
431+
Symbolics.option_to_metadata_type(::Val{:guess}) = VariableGuess
432+
getguess(x::Num) = getguess(Symbolics.unwrap(x))
433+
434+
"""
435+
getguess(x)
436+
437+
Get the guess for the initial value associated with symbolic variable `x`.
438+
Create variables with a guess like this
439+
440+
```
441+
@variables x [guess=1]
442+
```
443+
"""
444+
function getguess(x)
445+
p = Symbolics.getparent(x, nothing)
446+
p === nothing || (x = p)
447+
Symbolics.getmetadata(x, VariableGuess, nothing)
448+
end
449+
450+
"""
451+
hasguess(x)
452+
453+
Determine whether symbolic variable `x` has a guess associated with it.
454+
See also [`getguess`](@ref).
455+
"""
456+
function hasguess(x)
457+
getguess(x) !== nothing
458+
end
459+
460+
function get_default_or_guess(x)
461+
if hasdefault(x) && !((def = getdefault(x)) isa Equation)
462+
return def
463+
else
464+
return getguess(x)
465+
end
466+
end

test/nonlinearsystem.jl

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ using DiffEqBase, SparseArrays
44
using Test
55
using NonlinearSolve
66
using ModelingToolkit: value
7+
using ModelingToolkit: get_default_or_guess
78

89
canonequal(a, b) = isequal(simplify(a), simplify(b))
910

@@ -232,3 +233,45 @@ testdict = Dict([:test => 1])
232233
@test prob_.u0 == [1.0, 2.0, 1.0]
233234
@test prob_.p == [2.0, 1.0, 1.0]
234235
end
236+
237+
@testset "Initialization System" begin
238+
# Define the Lotka Volterra system which begins at steady state
239+
@parameters t
240+
pars = @parameters a=1.5 b=1.0 c=3.0 d=1.0 dx_ss = 1e-5
241+
242+
vars = @variables begin
243+
dx(t),
244+
dy(t),
245+
(x(t) = dx ~ dx_ss), [guess = 0.5]
246+
(y(t) = dy ~ 0), [guess = -0.5]
247+
end
248+
249+
D = Differential(t)
250+
251+
eqs = [dx ~ a * x - b * x * y
252+
dy ~ -c * y + d * x * y
253+
D(x) ~ dx
254+
D(y) ~ dy]
255+
256+
@named sys = ODESystem(eqs, t, vars, pars)
257+
258+
sys_simple = structural_simplify(sys)
259+
260+
# Set up the initialization system
261+
sys_init = initializesystem(sys_simple)
262+
263+
sys_init_simple = structural_simplify(sys_init)
264+
265+
prob = NonlinearProblem(sys_init_simple, get_default_or_guess.(states(sys_init_simple)))
266+
267+
@test prob.u0 == [0.5, -0.5]
268+
269+
sol = solve(prob)
270+
@test sol.retcode == SciMLBase.ReturnCode.Success
271+
272+
# Confirm for all the states of the non-simplified system
273+
@test all(.≈(sol[states(sys)], [1e-5, 0, 1e-5 / 1.5, 0]; atol = 1e-8))
274+
275+
# Confirm for all the states of the simplified system
276+
@test all(.≈(sol[states(sys_simple)], [1e-5 / 1.5, 0]; atol = 1e-8))
277+
end

test/test_variable_metadata.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ using ModelingToolkit
88
@variables y
99
@test !hasbounds(y)
1010

11+
# Guess
12+
@variables y [guess = 0]
13+
@test getguess(y) === 0
14+
@test hasguess(y) === true
15+
16+
@variables y
17+
@test hasguess(y) === false
18+
1119
# Disturbance
1220
@variables u [disturbance = true]
1321
@test isdisturbance(u)

0 commit comments

Comments
 (0)