Skip to content

Commit ee21223

Browse files
committed
Handle autonomous systems and more fields
1 parent 00b2711 commit ee21223

File tree

2 files changed

+49
-38
lines changed

2 files changed

+49
-38
lines changed

src/systems/diffeqs/basic_transformations.jl

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function liouville_transform(sys::AbstractODESystem; kwargs...)
5151
end
5252

5353
"""
54-
function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; dummies = false, simplify = true, verbose = false, kwargs...)
54+
function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, simplify = true, verbose = false, kwargs...)
5555
5656
Transform the independent variable (e.g. ``t``) of the ODE system `sys` to a dependent variable `iv` (e.g. ``f(t)``).
5757
An equation in `sys` must define the rate of change of the new independent variable (e.g. ``df(t)/dt``).
@@ -89,7 +89,7 @@ julia> unknowns(M′)
8989
y(x)
9090
```
9191
"""
92-
function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; dummies = false, simplify = true, verbose = false, kwargs...)
92+
function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, simplify = true, verbose = false, kwargs...)
9393
if !iscomplete(sys)
9494
error("Cannot change independent variable of incomplete system $(nameof(sys))")
9595
elseif isscheduled(sys)
@@ -100,9 +100,10 @@ function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; d
100100

101101
iv = unwrap(iv)
102102
iv1 = get_iv(sys) # e.g. t
103-
104103
if !iscall(iv) || !isequal(only(arguments(iv)), iv1)
105104
error("New independent variable $iv is not a function of the independent variable $iv1 of the system $(nameof(sys))")
105+
elseif !isautonomous(sys) && isempty(findall(eq -> isequal(eq.lhs, iv1), eqs))
106+
error("System $(nameof(sys)) is autonomous in $iv1. An equation of the form $iv1 ~ F($iv) must be provided.")
106107
end
107108

108109
iv2func = iv # e.g. a(t)
@@ -111,50 +112,53 @@ function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; d
111112
D1 = Differential(iv1)
112113
D2 = Differential(iv2)
113114

115+
iv1name = nameof(iv1) # e.g. t
116+
iv1func, = @variables $iv1name(iv2) # e.g. t(a)
117+
114118
div2name = Symbol(iv2name, :_t)
115119
div2, = @variables $div2name(iv2) # e.g. a_t(a)
116120

117121
ddiv2name = Symbol(iv2name, :_tt)
118122
ddiv2, = @variables $ddiv2name(iv2) # e.g. a_tt(a)
119123

120-
eqs = ModelingToolkit.get_eqs(sys) |> copy # don't modify original system
121-
!isnothing(eq) && push!(eqs, eq)
122-
vars = []
123-
div2_div1 = nothing
124-
for (i, eq) in enumerate(eqs)
125-
verbose && println("1. ", eq)
126-
124+
function transform(ex)
127125
# 1) Substitute f(t₁) => f(t₂(t₁)) in all variables
128-
vars = Symbolics.get_variables(eq)
126+
verbose && println("1. ", ex)
127+
vars = Symbolics.get_variables(ex)
129128
for var1 in vars
130129
if Symbolics.iscall(var1) && !isequal(var1, iv2func) # && isequal(only(arguments(var1)), iv1) # skip e.g. constants
131130
name = nameof(operation(var1))
132131
var2, = @variables $name(iv2func)
133-
eq = substitute(eq, var1 => var2; fold = false)
132+
ex = substitute(ex, var1 => var2; fold = false)
134133
end
135134
end
136-
verbose && println("2. ", eq)
137135

138136
# 2) Substitute in dummy variables for dⁿt₂/dt₁ⁿ
139-
eq = expand_derivatives(eq) # expand out with chain rule to get d(iv2)/d(iv1)
140-
verbose && println("3. ", eq)
141-
eq = substitute(eq, D1(D1(iv2func)) => ddiv2) # order 2 # TODO: more orders
142-
eq = substitute(eq, D1(iv2func) => div2) # order 1; e.g. D(a(t)) => a_t(t)
143-
eq = substitute(eq, iv2func => iv2) # order 0; make iv2 independent
144-
verbose && println("4. ", eq)
145-
verbose && println()
146-
147-
eqs[i] = eq
148-
149-
if isequal(eq.lhs, div2)
150-
div2_div1 = eq.rhs
151-
end
137+
verbose && println("2. ", ex)
138+
ex = expand_derivatives(ex) # expand out with chain rule to get d(iv2)/d(iv1)
139+
verbose && println("3. ", ex)
140+
ex = substitute(ex, D1(D1(iv2func)) => ddiv2) # order 2 # TODO: more orders
141+
ex = substitute(ex, D1(iv2func) => div2) # order 1; e.g. D(a(t)) => a_t(t)
142+
ex = substitute(ex, iv2func => iv2) # order 0; make iv2 independent
143+
verbose && println("4. ", ex)
144+
ex = substitute(ex, iv1 => iv1func) # if autonomous
145+
verbose && println("5. ", ex)
146+
return ex
152147
end
153148

154-
verbose && println("Found $div2 = $div2_div1")
155-
if isnothing(div2_div1)
149+
eqs = [get_eqs(sys); eqs]
150+
eqs = map(transform, eqs)
151+
152+
initialization_eqs = map(transform, get_initialization_eqs(sys))
153+
154+
div2_div1_idxs = findall(eq -> isequal(eq.lhs, div2), eqs)
155+
if length(div2_div1_idxs) == 0
156156
error("No equation for $D1($iv2func) was specified.")
157-
elseif isequal(div2_div1, 0)
157+
elseif length(div2_div1_idxs) >= 2
158+
error("Multiple equations for $D1($iv2func) were specified.")
159+
end
160+
div2_div1 = eqs[only(div2_div1_idxs)].rhs
161+
if isequal(div2_div1, 0)
158162
error("Cannot change independent variable from $iv1 to $iv2 with singular transformation $(div2 ~ div2_div1).")
159163
end
160164

@@ -167,14 +171,14 @@ function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; d
167171
push!(eqs, ddiv2 ~ ddiv1_ddiv2) # e.g. https://math.stackexchange.com/questions/249253/second-derivative-of-the-inverse-function # TODO: higher orders
168172

169173
# 4) If requested, instead remove and insert dummy equations
170-
if !dummies
174+
if !dummies # TODO: make dummies = false work with more fields!
171175
dummyidxs = findall(eq -> isequal(eq.lhs, div2) || isequal(eq.lhs, ddiv2), eqs)
172176
dummyeqs = splice!(eqs, dummyidxs) # return and remove dummy equations
173177
dummysubs = Dict(eq.lhs => eq.rhs for eq in dummyeqs)
174178
eqs = substitute.(eqs, Ref(dummysubs)) # don't iterate over dummysubs
175179
end
176180

177181
# 5) Recreate system with new equations
178-
sys2 = typeof(sys)(eqs, iv2; name = nameof(sys), description = description(sys), kwargs...)
182+
sys2 = typeof(sys)(eqs, iv2; initialization_eqs, name = nameof(sys), description = description(sys), kwargs...)
179183
return sys2
180184
end

test/basic_transformations.jl

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ end
4242
z ~ x + D(y)
4343
D(s) ~ 1 / (2*s)
4444
]
45-
M1 = ODESystem(eqs, t; name = :M) |> complete
46-
M2 = change_independent_variable(M1, M1.s)
45+
initialization_eqs = [x ~ 1.0, y ~ 1.0, D(y) ~ 0.0]
46+
M1 = ODESystem(eqs, t; initialization_eqs, name = :M) |> complete
47+
M2 = change_independent_variable(M1, M1.s; dummies = true)
4748

48-
M1 = structural_simplify(M1)
49-
M2 = structural_simplify(M2)
50-
prob1 = ODEProblem(M1, [M1.x => 1.0, M1.y => 1.0, Differential(M1.t)(M1.y) => 0.0, M1.s => 1.0], (1.0, 4.0))
51-
prob2 = ODEProblem(M2, [M2.x => 1.0, M2.y => 1.0, Differential(M2.s)(M2.y) => 0.0], (1.0, 2.0))
49+
M1 = structural_simplify(M1; allow_symbolic = true)
50+
M2 = structural_simplify(M2; allow_symbolic = true)
51+
prob1 = ODEProblem(M1, [M1.s => 1.0], (1.0, 4.0), [])
52+
prob2 = ODEProblem(M2, [], (1.0, 2.0), [])
5253
sol1 = solve(prob1, Tsit5(); reltol = 1e-10, abstol = 1e-10)
5354
sol2 = solve(prob2, Tsit5(); reltol = 1e-10, abstol = 1e-10)
5455
ts = range(0.0, 1.0, length = 50)
@@ -75,7 +76,7 @@ end
7576
# Apply in two steps, where derivatives are defined at each step: first t -> a, then a -> b
7677
M2 = change_independent_variable(M1, M1.a) |> complete #, D(b) ~ D(a)/a; verbose = true)
7778
@variables b(M2.a)
78-
M3 = change_independent_variable(M2, b, Differential(M2.a)(b) ~ exp(-b))
79+
M3 = change_independent_variable(M2, b, [Differential(M2.a)(b) ~ exp(-b), M2.a ~ exp(b)])
7980
M2 = structural_simplify(M2; allow_symbolic = true)
8081
M3 = structural_simplify(M3; allow_symbolic = true)
8182
@test length(unknowns(M2)) == 2 && length(unknowns(M3)) == 2
@@ -100,6 +101,12 @@ end
100101
@test all(isapprox.(sol[Mx.y], sol[Mx.x - g*(Mx.x/v)^2/2]; atol = 1e-10)) # compare to analytical solution (x(t) = v*t, y(t) = v*t - g*t^2/2)
101102
end
102103

104+
@testset "Change independent variable (autonomous system)" begin
105+
M = ODESystem([D(x) ~ t], t; name = :M) |> complete # non-autonomous
106+
@test_throws "t ~ F(x(t)) must be provided" change_independent_variable(M, M.x)
107+
@test_nowarn change_independent_variable(M, M.x, [t ~ 2*x])
108+
end
109+
103110
@testset "Change independent variable (errors)" begin
104111
@variables x(t) y z(y) w(t) v(t)
105112
M = ODESystem([D(x) ~ 0, v ~ x], t; name = :M)

0 commit comments

Comments
 (0)