Skip to content

Commit b6acc1c

Browse files
committed
Export and document change_independent_variable
1 parent d45c53e commit b6acc1c

File tree

4 files changed

+59
-19
lines changed

4 files changed

+59
-19
lines changed

docs/src/systems/ODESystem.md

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ ODESystem
3030
structural_simplify
3131
ode_order_lowering
3232
dae_index_lowering
33+
change_independent_variable
3334
liouville_transform
3435
alias_elimination
3536
tearing

src/ModelingToolkit.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc
255255
tunable_parameters, isirreducible, getdescription, hasdescription,
256256
hasunit, getunit, hasconnect, getconnect,
257257
hasmisc, getmisc, state_priority
258-
export ode_order_lowering, dae_order_lowering, liouville_transform
258+
export ode_order_lowering, dae_order_lowering, liouville_transform, change_independent_variable
259259
export PDESystem
260260
export Differential, expand_derivatives, @derivatives
261261
export Equation, ConstrainedEquation

src/systems/diffeqs/basic_transformations.jl

+40-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,46 @@ function liouville_transform(sys::AbstractODESystem; kwargs...)
5050
ODESystem(neweqs, t, vars, parameters(sys); checks = false, name = nameof(sys), kwargs...)
5151
end
5252

53-
function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; verbose = false, simplify = true, dummies = false, kwargs...)
53+
"""
54+
function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; dummies = false, simplify = true, verbose = false, kwargs...)
55+
56+
Transform the independent variable (e.g. ``t``) of the ODE system `sys` to a dependent variable `iv` (e.g. ``f(t)``).
57+
An equation in `sys` must define the rate of change of the new independent variable (e.g. ``df(t)/dt``).
58+
Alternatively, `eq` can specify such an equation.
59+
60+
The transformation is well-defined when the mapping between the new and old independent variables are one-to-one.
61+
This is satisfied if one is a strictly increasing function of the other (e.g. ``df(t)/dt > 0`` or ``df(t)/dt < 0``).
62+
63+
Keyword arguments
64+
=================
65+
If `dummies`, derivatives of the new independent variable are expressed through dummy equations; otherwise they are explicitly inserted into the equations.
66+
If `simplify`, these dummy expressions are simplified and often give a tidier transformation.
67+
If `verbose`, the function prints intermediate transformations of equations to aid debugging.
68+
Any additional keyword arguments `kwargs...` are forwarded to the constructor that rebuilds the system.
69+
70+
Usage before structural simplification
71+
======================================
72+
The variable change must take place before structural simplification.
73+
Subsequently, consider passing `allow_symbolic = true` to `structural_simplify(sys)` to reduce the number of unknowns, with the understanding that the transformation is well-defined.
74+
75+
Example
76+
=======
77+
Consider a free fall with constant horizontal velocity.
78+
The laws of physics naturally describes position as a function of time.
79+
By changing the independent variable, it can be reformulated for vertical position as a function of horizontal distance:
80+
```julia
81+
julia> @variables x(t) y(t);
82+
83+
julia> @named M = ODESystem([D(D(y)) ~ -9.81, D(x) ~ 10.0], t);
84+
85+
julia> M′ = change_independent_variable(complete(M), x);
86+
87+
julia> unknowns(M′)
88+
1-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
89+
y(x)
90+
```
91+
"""
92+
function change_independent_variable(sys::AbstractODESystem, iv, eq = nothing; dummies = false, simplify = true, verbose = false, kwargs...)
5493
if !iscomplete(sys)
5594
error("Cannot change independent variable of incomplete system $(nameof(sys))")
5695
elseif isscheduled(sys)

test/basic_transformations.jl

+17-17
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ end
2929
@variables x(t) y(t)
3030
eqs1 = [D(D(x)) ~ D(x) + x, D(y) ~ 1]
3131
M1 = ODESystem(eqs1, t; name = :M) |> complete
32-
M2 = ModelingToolkit.change_independent_variable(M1, M1.y)
32+
M2 = change_independent_variable(M1, M1.y)
3333
eqs2 = substitute(equations(M2), M2.y => M1.t) # system should be equivalent when parametrized with y (since D(y) ~ 1), so substitute back ...
3434
@test eqs1[1] == only(eqs2) # ... and check that the equations are unmodified
3535
end
@@ -43,10 +43,10 @@ end
4343
D(s) ~ 1 / (2*s)
4444
]
4545
M1 = ODESystem(eqs, t; name = :M) |> complete
46-
M2 = ModelingToolkit.change_independent_variable(M1, M1.s)
46+
M2 = change_independent_variable(M1, M1.s)
4747

48-
M1 = structural_simplify(M1; allow_symbolic = true)
49-
M2 = structural_simplify(M2; allow_symbolic = true)
48+
M1 = structural_simplify(M1)
49+
M2 = structural_simplify(M2)
5050
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))
5151
prob2 = ODEProblem(M2, [M2.x => 1.0, M2.y => 1.0, Differential(M2.s)(M2.y) => 0.0], (1.0, 2.0))
5252
sol1 = solve(prob1, Tsit5(); reltol = 1e-10, abstol = 1e-10)
@@ -73,26 +73,26 @@ end
7373
M1 = ODESystem(eqs, t; name = :M) |> complete
7474

7575
# Apply in two steps, where derivatives are defined at each step: first t -> a, then a -> b
76-
M2 = ModelingToolkit.change_independent_variable(M1, M1.a) |> complete #, D(b) ~ D(a)/a; verbose = true)
76+
M2 = change_independent_variable(M1, M1.a) |> complete #, D(b) ~ D(a)/a; verbose = true)
7777
@variables b(M2.a)
78-
M3 = ModelingToolkit.change_independent_variable(M2, b, Differential(M2.a)(b) ~ exp(-b))
78+
M3 = change_independent_variable(M2, b, Differential(M2.a)(b) ~ exp(-b))
7979
M2 = structural_simplify(M2; allow_symbolic = true)
8080
M3 = structural_simplify(M3; allow_symbolic = true)
81-
@test length(unknowns(M2)) == 2
81+
@test length(unknowns(M2)) == 2 && length(unknowns(M3)) == 2
8282
end
8383

8484
@testset "Change independent variable (simple)" begin
8585
@variables x(t)
86-
Mt = ODESystem([D(x) ~ 2*x], t; name = :M) |> complete
87-
Mx = ModelingToolkit.change_independent_variable(Mt, Mt.x; dummies = true)
86+
Mt = ODESystem([D(x) ~ 2*x], t; name = :M) |> complete # TODO: avoid complete. can avoid it if passing defined $variable directly to change_independent_variable
87+
Mx = change_independent_variable(Mt, Mt.x; dummies = true)
8888
@test (@variables x x_t(x) x_tt(x); Set(equations(Mx)) == Set([x_t ~ 2x, x_tt ~ 4x]))
8989
end
9090

9191
@testset "Change independent variable (free fall)" begin
9292
@variables x(t) y(t)
9393
@parameters g v # gravitational acceleration and constant horizontal velocity
9494
Mt = ODESystem([D(D(y)) ~ -g, D(x) ~ v], t; name = :M) |> complete # gives (x, y) as function of t, ...
95-
Mx = ModelingToolkit.change_independent_variable(Mt, Mt.x; dummies = false) # ... but we want y as a function of x
95+
Mx = change_independent_variable(Mt, Mt.x; dummies = false) # ... but we want y as a function of x
9696
Mx = structural_simplify(Mx; allow_symbolic = true)
9797
Dx = Differential(Mx.x)
9898
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
@@ -103,13 +103,13 @@ end
103103
@testset "Change independent variable (errors)" begin
104104
@variables x(t) y z(y) w(t) v(t)
105105
M = ODESystem([D(x) ~ 0, v ~ x], t; name = :M)
106-
@test_throws "incomplete" ModelingToolkit.change_independent_variable(M, M.x)
106+
@test_throws "incomplete" change_independent_variable(M, M.x)
107107
M = complete(M)
108-
@test_throws "singular" ModelingToolkit.change_independent_variable(M, M.x)
109-
@test_throws "structurally simplified" ModelingToolkit.change_independent_variable(structural_simplify(M), y)
110-
@test_throws "No equation" ModelingToolkit.change_independent_variable(M, w)
111-
@test_throws "No equation" ModelingToolkit.change_independent_variable(M, v)
112-
@test_throws "not a function of the independent variable" ModelingToolkit.change_independent_variable(M, y)
113-
@test_throws "not a function of the independent variable" ModelingToolkit.change_independent_variable(M, z)
108+
@test_throws "singular" change_independent_variable(M, M.x)
109+
@test_throws "structurally simplified" change_independent_variable(structural_simplify(M), y)
110+
@test_throws "No equation" change_independent_variable(M, w)
111+
@test_throws "No equation" change_independent_variable(M, v)
112+
@test_throws "not a function of the independent variable" change_independent_variable(M, y)
113+
@test_throws "not a function of the independent variable" change_independent_variable(M, z)
114114
M = ODESystem([D(x) ~ t], t; name = :M) |> complete
115115
end

0 commit comments

Comments
 (0)