Skip to content

Commit 10793d2

Browse files
committed
Optionally add differential equation for old independent variable
1 parent d660269 commit 10793d2

File tree

3 files changed

+25
-9
lines changed

3 files changed

+25
-9
lines changed

docs/src/tutorials/change_independent_variable.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ sol = solve(prob, Tsit5())
6464
plot(sol; idxs = M2.y) # must index by M2.y = y(x); not M1.y = y(t)!
6565
```
6666

67+
!!! tip "Usage tips"
68+
Look up the documentation of [`change_independent_variable`](@ref) for tips on how to use it.
69+
70+
For example, if you also need $t(x)$, you can tell it to add a differential equation for the old independent variable in terms of the new one using the [inverse function rule](https://en.wikipedia.org/wiki/Inverse_function_rule) (here $\mathrm{d}t/\mathrm{d}x = 1 / (\mathrm{d}x/\mathrm{d}t)$). If you know an analytical expression between the independent variables (here $t = x/v$), you can also pass it directly to the function to avoid the extra differential equation.
71+
6772
## 2. Alleviating stiffness by changing to logarithmic time
6873

6974
In cosmology, the [Friedmann equations](https://en.wikipedia.org/wiki/Friedmann_equations) describe the expansion of the universe.

src/systems/diffeqs/basic_transformations.jl

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

5353
"""
54-
change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, simplify = true, fold = false, kwargs...)
54+
change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, add_old_diff = false, simplify = true, fold = false, kwargs...)
5555
5656
Transform the independent variable (e.g. ``t``) of the ODE system `sys` to a dependent variable `iv` (e.g. ``u(t)``).
5757
An equation in `sys` must define the rate of change of the new independent variable (e.g. ``du(t)/dt``).
@@ -63,6 +63,7 @@ This is satisfied if one is a strictly increasing function of the other (e.g. ``
6363
# Keyword arguments
6464
6565
- `dummies`: Whether derivatives of the new independent variable with respect to the old one are expressed through dummy equations or explicitly inserted into the equations.
66+
- `add_old_diff`: Whether to add a differential equation for the old independent variable in terms of the new one using the inverse function rule.
6667
- `simplify`: Whether expanded derivative expressions are simplified. This can give a tidier transformation.
6768
- `fold`: Whether internal substitutions will evaluate numerical expressions.
6869
Additional keyword arguments `kwargs...` are forwarded to the constructor that rebuilds `sys`.
@@ -99,7 +100,7 @@ julia> unknowns(M′)
99100
y(x)
100101
```
101102
"""
102-
function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, simplify = true, fold = false, kwargs...)
103+
function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, add_old_diff = false, simplify = true, fold = false, kwargs...)
103104
iv2_of_iv1 = unwrap(iv) # e.g. u(t)
104105
iv1 = get_iv(sys) # e.g. t
105106

@@ -148,24 +149,34 @@ function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummi
148149

149150
# 3) If requested, insert extra dummy equations for e.g. du/dt, d²u/dt², ...
150151
# Otherwise, replace all these derivatives by their explicit expressions
152+
unks = get_unknowns(sys)
151153
if dummies
152154
div2 = substitute(default_toterm(D1(iv2_of_iv1)), iv1 => iv2) # e.g. uˍt(u)
153155
ddiv2 = substitute(default_toterm(D1(D1(iv2_of_iv1))), iv1 => iv2) # e.g. uˍtt(u)
154156
div2, ddiv2 = GlobalScope.([div2, ddiv2]) # do not namespace dummies in new system
155157
eqs = [[div2 ~ div2_of_iv1, ddiv2 ~ ddiv2_of_iv1]; eqs] # add dummy equations
156-
@set! sys.unknowns = [get_unknowns(sys); [div2, ddiv2]] # add dummy variables
158+
unks = [unks; [div2, ddiv2]] # add dummy variables
157159
else
158160
div2 = div2_of_iv1
159161
ddiv2 = ddiv2_of_iv1
160162
end
163+
164+
# 4) If requested, add a differential equation for the old independent variable as a function of the old one
165+
if add_old_diff
166+
div1lhs = substitute(D2(iv1_of_iv2), iv2 => iv2_of_iv1) # e.g. (d/du(t))(t(u(t))); will be transformed to (d/du)(t(u)) by chain rule
167+
div1rhs = 1 / div2 # du/dt = 1/(dt/du) (https://en.wikipedia.org/wiki/Inverse_function_rule)
168+
eqs = [eqs; div1lhs ~ div1rhs]
169+
unks = [unks; iv1_of_iv2]
170+
end
161171
@set! sys.eqs = eqs # add extra equations we derived before starting transformation process
172+
@set! sys.unknowns = unks # add dummy variables and old independent variable as a function of the new one
162173

163-
# 4) Transform everything from old to new independent variable, e.g. t -> u.
174+
# 5) Transform everything from old to new independent variable, e.g. t -> u.
164175
# Substitution order matters! Must begin with highest order to get D(D(u(t))) -> u_tt(u).
165176
# If we had started with the lowest order, we would get D(D(u(t))) -> D(u_t(u)) -> 0!
166177
iv1_to_iv2_subs = [ # a vector ensures substitution order
167-
D1(D1(iv2_of_iv1)) => ddiv2 # order 2, e.g. D(D(u(t))) -> u_tt(u)
168-
D1(iv2_of_iv1) => div2 # order 1, e.g. D(u(t)) -> u_t(u)
178+
D1(D1(iv2_of_iv1)) => ddiv2 # order 2, e.g. D(D(u(t))) -> u_tt(u) or explicit expression
179+
D1(iv2_of_iv1) => div2 # order 1, e.g. D(u(t)) -> u_t(u) or explicit expression
169180
iv2_of_iv1 => iv2 # order 0, e.g. u(t) -> u
170181
iv1 => iv1_of_iv2 # in case sys was autonomous, e.g. t -> t(u)
171182
]

test/basic_transformations.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,12 @@ end
114114
@variables x(t) y(t)
115115
@parameters g v # gravitational acceleration and constant horizontal velocity
116116
Mt = ODESystem([D(D(y)) ~ -g, D(x) ~ v], t; name = :M) # gives (x, y) as function of t, ...
117-
Mx = change_independent_variable(Mt, x; dummies = false) # ... but we want y as a function of x
117+
Mx = change_independent_variable(Mt, x; add_old_diff = true) # ... but we want y as a function of x
118118
Mx = structural_simplify(Mx; allow_symbolic = true)
119119
Dx = Differential(Mx.x)
120-
prob = ODEProblem(Mx, [Mx.y => 0.0, Dx(Mx.y) => 1.0], (0.0, 20.0), [g => 9.81, v => 10.0]) # 1 = dy/dx = (dy/dt)/(dx/dt) means equal initial horizontal and vertical velocities
120+
prob = ODEProblem(Mx, [Mx.y => 0.0, Dx(Mx.y) => 1.0, Mx.t => 0.0], (0.0, 20.0), [g => 9.81, v => 10.0]) # 1 = dy/dx = (dy/dt)/(dx/dt) means equal initial horizontal and vertical velocities
121121
sol = solve(prob, Tsit5(); reltol = 1e-5)
122-
@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)
122+
@test all(isapprox.(sol[Mx.y], sol[Mx.x - g*(Mx.t)^2/2]; atol = 1e-10)) # compare to analytical solution (x(t) = v*t, y(t) = v*t - g*t^2/2)
123123
end
124124

125125
@testset "Change independent variable (crazy analytical example)" begin

0 commit comments

Comments
 (0)