Skip to content

Commit 865ef02

Browse files
committed
Merge functions for changing independent variable into one
1 parent acd0fab commit 865ef02

File tree

2 files changed

+50
-57
lines changed

2 files changed

+50
-57
lines changed

src/systems/diffeqs/basic_transformations.jl

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -56,52 +56,28 @@ function liouville_transform(sys::AbstractODESystem)
5656
ODESystem(neweqs, t, vars, parameters(sys), checks = false)
5757
end
5858

59-
# TODO: handle case when new iv is a variable already in the system
60-
function change_independent_variable(sys::AbstractODESystem, iv, iv1_of_iv2, iv2_of_iv1; kwargs...)
61-
iv1 = ModelingToolkit.get_iv(sys) # old independent variable
62-
iv2 = iv # new independent variable
63-
64-
name2 = nameof(iv2)
65-
iv2func, = @variables $name2(iv1)
66-
67-
eqs = ModelingToolkit.get_eqs(sys) |> copy # don't modify original system
68-
vars = []
69-
for (i, eq) in enumerate(eqs)
70-
vars = Symbolics.get_variables(eq)
71-
for var1 in vars
72-
if Symbolics.iscall(var1) # skip e.g. constants
73-
name = nameof(operation(var1))
74-
var2, = @variables $name(iv2func)
75-
eq = substitute(eq, var1 => var2; fold = false)
76-
end
77-
end
78-
eq = expand_derivatives(eq) # expand out with chain rule to get d(iv2)/d(iv1)
79-
eq = substitute(eq, Differential(iv1)(iv2func) => Differential(iv1)(iv2_of_iv1)) # substitute in d(iv2)/d(iv1)
80-
eq = expand_derivatives(eq)
81-
eq = substitute(eq, iv2func => iv2) # make iv2 independent
82-
eq = substitute(eq, iv1 => iv1_of_iv2) # substitute any remaining old ivars
83-
eqs[i] = eq
84-
end
85-
86-
sys2 = typeof(sys)(eqs, iv2; name = nameof(sys), description = description(sys), kwargs...)
87-
return sys2
88-
end
89-
90-
function change_independent_variable(sys::AbstractODESystem, iv; kwargs...)
59+
function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; verbose = false, kwargs...)
9160
iv1 = get_iv(sys) # e.g. t
92-
iv2func = iv # e.g. a(t)
93-
iv2name = nameof(operation(unwrap(iv))) # TODO: handle namespacing?
94-
iv2, = @independent_variables $iv2name
95-
96-
# TODO: find iv2func in system and replace it with some dummy variable
97-
# TODO: not just to 1st, but to all orders
61+
iv2name = nameof(iv) # TODO: handle namespacing?
62+
iv2, = @independent_variables $iv2name # e.g. a
63+
iv2func, = @variables $iv2name(iv1) # e.g. a(t)
64+
D1 = Differential(iv1)
65+
D2 = Differential(iv2)
9866

9967
div2name = Symbol(iv2name, :_t)
100-
div2, = @variables $div2name(iv2)
68+
div2, = @variables $div2name(iv2) # e.g. a_t(a)
69+
70+
ddiv2name = Symbol(iv2name, :_tt)
71+
ddiv2, = @variables $ddiv2name(iv2) # e.g. a_tt(a)
10172

10273
eqs = ModelingToolkit.get_eqs(sys) |> copy # don't modify original system
74+
!isnothing(eq) && push!(eqs, eq)
10375
vars = []
76+
div2_div1 = nothing
10477
for (i, eq) in enumerate(eqs)
78+
verbose && println("1. ", eq)
79+
80+
# 1) Substitute f(t₁) => f(t₂(t₁)) in all variables
10581
vars = Symbolics.get_variables(eq)
10682
for var1 in vars
10783
if Symbolics.iscall(var1) && !isequal(var1, iv2func) # && isequal(only(arguments(var1)), iv1) # skip e.g. constants
@@ -110,17 +86,32 @@ function change_independent_variable(sys::AbstractODESystem, iv; kwargs...)
11086
eq = substitute(eq, var1 => var2; fold = false)
11187
end
11288
end
89+
verbose && println("2. ", eq)
90+
91+
# 2) Substitute in dummy variables for dⁿt₂/dt₁ⁿ
11392
eq = expand_derivatives(eq) # expand out with chain rule to get d(iv2)/d(iv1)
114-
#eq = substitute(eq, Differential(iv1)(iv2func) => Differential(iv1)(iv2_of_iv1)) # substitute in d(iv2)/d(iv1)
115-
#eq = expand_derivatives(eq)
116-
eq = substitute(eq, Differential(iv1)(Differential(iv1)(iv2func)) => div2) # e.g. D(a(t)) => a_t(t) # TODO: more orders
117-
eq = substitute(eq, Differential(iv1)(iv2func) => div2) # e.g. D(a(t)) => a_t(t) # TODO: more orders
118-
eq = substitute(eq, iv2func => iv2) # make iv2 independent
119-
#eq = substitute(eq, iv1 => iv1_of_iv2) # substitute any remaining old ivars
93+
verbose && println("3. ", eq)
94+
eq = substitute(eq, D1(D1(iv2func)) => ddiv2) # order 2 # TODO: more orders
95+
eq = substitute(eq, D1(iv2func) => div2) # order 1; e.g. D(a(t)) => a_t(t)
96+
eq = substitute(eq, iv2func => iv2) # order 0; make iv2 independent
97+
verbose && println("4. ", eq)
98+
verbose && println()
99+
120100
eqs[i] = eq
121-
println(eq)
101+
102+
if isequal(eq.lhs, div2)
103+
div2_div1 = eq.rhs
104+
end
122105
end
123106

107+
isnothing(div2_div1) && error("No equation for $D1($iv2func) was specified.")
108+
verbose && println("Found $div2 = $div2_div1")
109+
110+
# 3) Add equations for dummy variables
111+
div1_div2 = 1 / div2_div1
112+
push!(eqs, ddiv2 ~ expand_derivatives(-D2(div1_div2) / div1_div2^3)) # e.g. https://math.stackexchange.com/questions/249253/second-derivative-of-the-inverse-function # TODO: higher orders
113+
114+
# 4) Recreate system with new equations
124115
sys2 = typeof(sys)(eqs, iv2; name = nameof(sys), description = description(sys), kwargs...)
125116
return sys2
126117
end

test/basic_transformations.jl

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,23 @@ sol = solve(prob, Tsit5())
3434
@test sol[end, end] 1.0742818931017244
3535

3636
@testset "Change independent variable" begin
37-
# Autonomous 1st order (t doesn't appear in the equations)
3837
@independent_variables t
39-
@variables x(t) y(t) z(t)
38+
@variables x(t) y(t) z(t) s(t)
4039
eqs = [
4140
D(x) ~ y
4241
D(D(y)) ~ 2 * x * D(y)
4342
z ~ x + D(y)
43+
D(s) ~ 1 / (2*s)
4444
]
4545
@named sys1 = ODESystem(eqs, t)
46+
sys1 = complete(sys1)
4647

4748
@independent_variables s
48-
@named sys2 = ModelingToolkit.change_independent_variable(sys1, s, s^2, (t))
49+
sys2 = ModelingToolkit.change_independent_variable(sys1, s)
4950

50-
sys1 = structural_simplify(sys1)
51-
sys2 = structural_simplify(sys2)
52-
prob1 = ODEProblem(sys1, [sys1.x => 1.0, sys1.y => 1.0, Differential(t)(sys1.y) => 0.0], (1.0, 4.0))
51+
sys1 = structural_simplify(sys1; allow_symbolic = true)
52+
sys2 = structural_simplify(sys2; allow_symbolic = true)
53+
prob1 = ODEProblem(sys1, [sys1.x => 1.0, sys1.y => 1.0, Differential(t)(sys1.y) => 0.0, sys1.s => 1.0], (1.0, 4.0))
5354
prob2 = ODEProblem(sys2, [sys2.x => 1.0, sys2.y => 1.0, Differential(s)(sys2.y) => 0.0], (1.0, 2.0))
5455
sol1 = solve(prob1, Tsit5(); reltol = 1e-10, abstol = 1e-10)
5556
sol2 = solve(prob2, Tsit5(); reltol = 1e-10, abstol = 1e-10)
@@ -59,10 +60,10 @@ sol = solve(prob, Tsit5())
5960
all(isapprox.(sol1(ts, idxs=sys1.y), sol2(ss, idxs=sys2.y); atol = 1e-7))
6061
end
6162

62-
#@testset "Change independent variable (Friedmann equation)" begin
63+
@testset "Change independent variable (Friedmann equation)" begin
6364
@independent_variables t
6465
D = Differential(t)
65-
@variables a(t) ρr(t) ρm(t) ρΛ(t) ρ(t) ϕ(t)
66+
@variables a(t) ρr(t) ρm(t) ρΛ(t) ρ(t) P(t) ϕ(t)
6667
@parameters Ωr0 Ωm0 ΩΛ0
6768
eqs = [
6869
ρr ~ 3/(8*Num(π)) * Ωr0 / a^4
@@ -75,7 +76,8 @@ end
7576
@named M1 = ODESystem(eqs, t)
7677
M1 = complete(M1)
7778

78-
M2 = ModelingToolkit.change_independent_variable(M1, M1.a)
79-
79+
@independent_variables a
80+
M2 = ModelingToolkit.change_independent_variable(M1, a)
8081
M2 = structural_simplify(M2; allow_symbolic = true)
81-
#end
82+
@test length(unknowns(M2)) == 2
83+
end

0 commit comments

Comments
 (0)