Skip to content

Commit 2b0998b

Browse files
committed
Rewrite change_independent_variable to handle any derivative order and nonlinear equations
1 parent 10793d2 commit 2b0998b

File tree

3 files changed

+77
-103
lines changed

3 files changed

+77
-103
lines changed

docs/src/tutorials/change_independent_variable.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ There are at least three ways of answering this:
3939
We will demonstrate the last method by changing the independent variable from $t$ to $x$.
4040
This transformation is well-defined for any non-zero horizontal velocity $v$.
4141
```@example changeivar
42-
M2 = change_independent_variable(M1, x; dummies = true)
42+
M2 = change_independent_variable(M1, x)
4343
M2s = structural_simplify(M2; allow_symbolic = true)
4444
# a sanity test on the 10 x/y variables that are accessible to the user # hide
4545
@assert allequal([x, M1s.x]) # hide
@@ -110,7 +110,7 @@ To do this, we will change the independent variable in two stages; from $t$ to $
110110
Notice that $\mathrm{d}a/\mathrm{d}t > 0$ provided that $\Omega > 0$, and $\mathrm{d}b/\mathrm{d}a > 0$, so the transformation is well-defined.
111111
First, we transform from $t$ to $a$:
112112
```@example changeivar
113-
M2 = change_independent_variable(M1, M1.a; dummies = true)
113+
M2 = change_independent_variable(M1, M1.a)
114114
```
115115
Unlike the original, notice that this system is *non-autonomous* because the independent variable $a$ appears explicitly in the equations!
116116
This means that to change the independent variable from $a$ to $b$, we must provide not only the rate of change relation $db(a)/da = \exp(-b)$, but *also* the equation $a(b) = \exp(b)$ so $a$ can be eliminated in favor of $b$:

src/systems/diffeqs/basic_transformations.jl

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

5353
"""
54-
change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, add_old_diff = false, simplify = true, fold = false, kwargs...)
54+
change_independent_variable(sys::AbstractODESystem, iv, eqs = []; 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)``).
57-
An equation in `sys` must define the rate of change of the new independent variable (e.g. ``du(t)/dt``).
58-
This or other additional equations can also be specified through `eqs`.
59-
6057
The transformation is well-defined when the mapping between the new and old independent variables are one-to-one.
6158
This is satisfied if one is a strictly increasing function of the other (e.g. ``du(t)/dt > 0`` or ``du(t)/dt < 0``).
6259
60+
Any extra equations `eqs` involving the new and old independent variables will be taken into account in the transformation.
61+
6362
# Keyword arguments
6463
65-
- `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.
64+
- `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 ``dt/du = 1/(du/dt)``.
6765
- `simplify`: Whether expanded derivative expressions are simplified. This can give a tidier transformation.
6866
- `fold`: Whether internal substitutions will evaluate numerical expressions.
69-
Additional keyword arguments `kwargs...` are forwarded to the constructor that rebuilds `sys`.
7067
7168
# Usage before structural simplification
7269
@@ -77,6 +74,7 @@ Subsequently, consider passing `allow_symbolic = true` to `structural_simplify(s
7774
7875
If `sys` is non-autonomous (i.e. ``t`` appears explicitly in its equations), it is often desirable to also pass an algebraic equation relating the new and old independent variables (e.g. ``t = f(u(t))``).
7976
Otherwise the transformed system will be underdetermined and cannot be structurally simplified without additional changes.
77+
If an algebraic relation is not known, consider using `add_old_diff`.
8078
8179
# Usage with hierarchical systems
8280
@@ -91,16 +89,20 @@ By changing the independent variable, it can be reformulated for vertical positi
9189
```julia
9290
julia> @variables x(t) y(t);
9391
94-
julia> @named M = ODESystem([D(D(y)) ~ -9.81, D(x) ~ 10.0], t);
92+
julia> @named M = ODESystem([D(D(y)) ~ -9.81, D(D(x)) ~ 0.0], t);
93+
94+
julia> M = change_independent_variable(M, x);
9595
96-
julia> M = change_independent_variable(complete(M), x);
96+
julia> M = structural_simplify(M; allow_symbolic = true);
9797
98-
julia> unknowns(M′)
99-
1-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
98+
julia> unknowns(M)
99+
3-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
100+
xˍt(x)
100101
y(x)
102+
yˍx(x)
101103
```
102104
"""
103-
function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, add_old_diff = false, simplify = true, fold = false, kwargs...)
105+
function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; add_old_diff = false, simplify = true, fold = false, kwargs...)
104106
iv2_of_iv1 = unwrap(iv) # e.g. u(t)
105107
iv1 = get_iv(sys) # e.g. t
106108

@@ -115,78 +117,45 @@ function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummi
115117
iv1name = nameof(iv1) # e.g. :t
116118
iv2name = nameof(operation(iv2_of_iv1)) # e.g. :u
117119
iv2, = @independent_variables $iv2name # e.g. u
118-
iv1_of_iv2, = @variables $iv1name(iv2) # inverse in case sys is autonomous; e.g. t(u)
119-
iv1_of_iv2 = GlobalScope(iv1_of_iv2) # do not namespace old independent variable as new dependent variable
120+
iv1_of_iv2, = GlobalScope.(@variables $iv1name(iv2)) # inverse, e.g. t(u), global because iv1 has no namespacing in sys
120121
D1 = Differential(iv1) # e.g. d/d(t)
121-
D2 = Differential(iv2_of_iv1) # e.g. d/d(u(t))
122+
div2_of_iv1 = GlobalScope(default_toterm(D1(iv2_of_iv1))) # e.g. uˍt(t)
123+
div2_of_iv2 = substitute(div2_of_iv1, iv1 => iv2) # e.g. uˍt(u)
124+
div2_of_iv2_of_iv1 = substitute(div2_of_iv2, iv2 => iv2_of_iv1) # e.g. uˍt(u(t))
125+
126+
# If requested, add a differential equation for the old independent variable as a function of the old one
127+
if add_old_diff
128+
eqs = [eqs; Differential(iv2)(iv1_of_iv2) ~ 1 / div2_of_iv2] # e.g. dt(u)/du ~ 1 / uˍt(u) (https://en.wikipedia.org/wiki/Inverse_function_rule)
129+
end
130+
@set! sys.eqs = [get_eqs(sys); eqs] # add extra equations we derived before starting transformation process
131+
@set! sys.unknowns = [get_unknowns(sys); [iv1, div2_of_iv1]] # add new variables, will be transformed to e.g. t(u) and uˍt(u) # add dummy variables and old independent variable as a function of the new one
122132

123-
# 1) Utility that performs the chain rule on an expression, e.g. (d/dt)(f(t)) -> (d/dt)(f(u(t))) -> df(u(t))/du(t) * du(t)/dt
124-
function chain_rule(ex)
133+
# Create a utility that performs the chain rule on an expression, followed by insertion of the new independent variable
134+
# e.g. (d/dt)(f(t)) -> (d/dt)(f(u(t))) -> df(u(t))/du(t) * du(t)/dt -> df(u)/du * uˍt(u)
135+
# Then use it to transform everything in the system!
136+
function transform(ex)
137+
# 1) Replace the argument of every function; e.g. f(t) -> f(u(t))
125138
for var in vars(ex; op = Nothing) # loop over all variables in expression (op = Nothing prevents interpreting "D(f(t))" as one big variable)
126139
is_function_of_iv1 = iscall(var) && isequal(only(arguments(var)), iv1) # is the expression of the form f(t)?
127-
if is_function_of_iv1 && !isequal(var, iv2_of_iv1) # substitute f(t) -> f(u(t)), but not u(t) -> u(u(t))
140+
if is_function_of_iv1 && !isequal(var, iv2_of_iv1) # prevent e.g. u(t) -> u(u(t))
128141
var_of_iv1 = var # e.g. f(t)
129-
var_of_iv2 = substitute(var_of_iv1, iv1 => iv2_of_iv1) # e.g. f(u(t))
130-
ex = substitute(ex, var_of_iv1 => var_of_iv2)
142+
var_of_iv2_of_iv1 = substitute(var_of_iv1, iv1 => iv2_of_iv1) # e.g. f(u(t))
143+
ex = substitute(ex, var_of_iv1 => var_of_iv2_of_iv1; fold)
131144
end
132145
end
133-
ex = expand_derivatives(ex, simplify) # expand chain rule, e.g. (d/dt)(f(u(t)))) -> df(u(t))/du(t) * du(t)/dt
134-
return ex
135-
end
136-
137-
# 2) Find e.g. du/dt in equations, then calculate e.g. d²u/dt², ...
138-
eqs = [eqs; get_eqs(sys)] # all equations (system-defined + user-provided) we may use
139-
idxs = findall(eq -> isequal(eq.lhs, D1(iv2_of_iv1)), eqs)
140-
if length(idxs) != 1
141-
error("Exactly one equation for $D1($iv2_of_iv1) was not specified! Got $(length(idxs)) equations:\n", join(eqs[idxs], '\n'))
142-
end
143-
div2_of_iv1_eq = popat!(eqs, only(idxs)) # get and remove e.g. du/dt = ... (may be added back later as a dummy)
144-
div2_of_iv1 = chain_rule(div2_of_iv1_eq.rhs)
145-
if isequal(div2_of_iv1, 0) # e.g. du/dt ~ 0
146-
error("Independent variable transformation $(div2_of_iv1_eq) is singular!")
147-
end
148-
ddiv2_of_iv1 = chain_rule(D1(div2_of_iv1)) # TODO: implement higher orders (order >= 3) derivatives with a loop
149-
150-
# 3) If requested, insert extra dummy equations for e.g. du/dt, d²u/dt², ...
151-
# Otherwise, replace all these derivatives by their explicit expressions
152-
unks = get_unknowns(sys)
153-
if dummies
154-
div2 = substitute(default_toterm(D1(iv2_of_iv1)), iv1 => iv2) # e.g. uˍt(u)
155-
ddiv2 = substitute(default_toterm(D1(D1(iv2_of_iv1))), iv1 => iv2) # e.g. uˍtt(u)
156-
div2, ddiv2 = GlobalScope.([div2, ddiv2]) # do not namespace dummies in new system
157-
eqs = [[div2 ~ div2_of_iv1, ddiv2 ~ ddiv2_of_iv1]; eqs] # add dummy equations
158-
unks = [unks; [div2, ddiv2]] # add dummy variables
159-
else
160-
div2 = div2_of_iv1
161-
ddiv2 = ddiv2_of_iv1
162-
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
171-
@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
173-
174-
# 5) Transform everything from old to new independent variable, e.g. t -> u.
175-
# Substitution order matters! Must begin with highest order to get D(D(u(t))) -> u_tt(u).
176-
# If we had started with the lowest order, we would get D(D(u(t))) -> D(u_t(u)) -> 0!
177-
iv1_to_iv2_subs = [ # a vector ensures substitution order
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
180-
iv2_of_iv1 => iv2 # order 0, e.g. u(t) -> u
181-
iv1 => iv1_of_iv2 # in case sys was autonomous, e.g. t -> t(u)
182-
]
183-
function transform(ex)
184-
ex = chain_rule(ex)
185-
for sub in iv1_to_iv2_subs
186-
ex = substitute(ex, sub; fold)
146+
# 2) Expand with chain rule until nothing changes anymore
147+
orgex = nothing
148+
while !isequal(ex, orgex)
149+
orgex = ex # save original
150+
ex = expand_derivatives(ex, simplify) # expand chain rule, e.g. (d/dt)(f(u(t)))) -> df(u(t))/du(t) * du(t)/dt
151+
ex = substitute(ex, D1(iv2_of_iv1) => div2_of_iv2_of_iv1; fold) # e.g. du(t)/dt -> uˍt(u(t))
187152
end
153+
# 3) Set new independent variable
154+
ex = substitute(ex, iv2_of_iv1 => iv2; fold) # set e.g. u(t) -> u everywhere
155+
ex = substitute(ex, iv1 => iv1_of_iv2; fold) # set e.g. t -> t(u) everywhere
188156
return ex
189157
end
158+
190159
function transform(sys::AbstractODESystem)
191160
eqs = map(transform, get_eqs(sys))
192161
unknowns = map(transform, get_unknowns(sys))
@@ -203,7 +172,7 @@ function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummi
203172
wascomplete = iscomplete(sys) # save before reconstructing system
204173
sys = typeof(sys)( # recreate system with transformed fields
205174
eqs, iv2, unknowns, ps; observed, initialization_eqs, parameter_dependencies, defaults, guesses,
206-
assertions, name = nameof(sys), description = description(sys), kwargs...
175+
assertions, name = nameof(sys), description = description(sys)
207176
)
208177
systems = map(transform, systems) # recurse through subsystems
209178
sys = compose(sys, systems) # rebuild hierarchical system
@@ -213,5 +182,6 @@ function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummi
213182
end
214183
return sys
215184
end
185+
216186
return transform(sys)
217187
end

test/basic_transformations.jl

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ end
3030
eqs1 = [D(D(x)) ~ D(x) + x, D(y) ~ 1]
3131
M1 = ODESystem(eqs1, t; name = :M)
3232
M2 = change_independent_variable(M1, y)
33-
eqs2 = substitute(equations(M2), M2.y => M1.t) # system should be equivalent when parametrized with y (since D(y) ~ 1), so substitute back ...
34-
@test eqs1[1] == only(eqs2) # ... and check that the equations are unmodified
33+
@variables y x(y) yˍt(y)
34+
Dy = Differential(y)
35+
@test Set(equations(M2)) == Set([yˍt^2*(Dy^2)(x) + yˍt*Dy(yˍt)*Dy(x) ~ x + Dy(x)*yˍt, yˍt ~ 1])
3536
end
3637

3738
@testset "Change independent variable" begin
@@ -44,7 +45,7 @@ end
4445
]
4546
initialization_eqs = [x ~ 1.0, y ~ 1.0, D(y) ~ 0.0]
4647
M1 = ODESystem(eqs, t; initialization_eqs, name = :M)
47-
M2 = change_independent_variable(M1, s; dummies = true)
48+
M2 = change_independent_variable(M1, s)
4849

4950
M1 = structural_simplify(M1; allow_symbolic = true)
5051
M2 = structural_simplify(M2; allow_symbolic = true)
@@ -77,16 +78,15 @@ end
7778
M1 = compose(M1, r, m, Λ)
7879

7980
# Apply in two steps, where derivatives are defined at each step: first t -> a, then a -> b
80-
M2 = change_independent_variable(M1, M1.a; dummies = true)
81+
M2 = change_independent_variable(M1, M1.a)
8182
M2c = complete(M2) # just for the following equation comparison (without namespacing)
82-
a, ȧ, Ω, Ωr, Ωm, ΩΛ, ϕ, aˍt, aˍtt = M2c.a, M2c.ȧ, M2c.Ω, M2c.r.Ω, M2c.m.Ω, M2c.Λ.Ω, M2c.ϕ, M2c.aˍt, M2c.aˍtt
83+
a, ȧ, Ω, Ωr, Ωm, ΩΛ, ϕ, aˍt = M2c.a, M2c.ȧ, M2c.Ω, M2c.r.Ω, M2c.m.Ω, M2c.Λ.Ω, M2c.ϕ, M2c.aˍt
8384
Da = Differential(a)
8485
@test Set(equations(M2)) == Set([
85-
aˍt ~# 1st order dummy equation
86-
aˍtt ~ Da(ȧ) * aˍt # 2nd order dummy equation
86+
aˍt ~# dummy equation
8787
Ω ~ Ωr + Ωm + ΩΛ
8888
~ (Ω) * a^2
89-
aˍtt*Da(ϕ) + aˍt^2*(Da^2)(ϕ) ~ -3*aˍt^2/a*Da(ϕ)
89+
Da(aˍt)*Da(ϕ)*aˍt + aˍt^2*(Da^2)(ϕ) ~ -3*aˍt^2/a*Da(ϕ)
9090
aˍt*Da(Ωr) ~ -4*Ωr*aˍt/a
9191
aˍt*Da(Ωm) ~ -3*Ωm*aˍt/a
9292
aˍt*Da(ΩΛ) ~ 0
@@ -104,13 +104,13 @@ end
104104
@testset "Change independent variable (simple)" begin
105105
@variables x(t) y1(t) # y(t)[1:1] # TODO: use array variables y(t)[1:2] when fixed: https://github.com/JuliaSymbolics/Symbolics.jl/issues/1383
106106
Mt = ODESystem([D(x) ~ 2*x, D(y1) ~ y1], t; name = :M)
107-
Mx = change_independent_variable(Mt, x; dummies = true)
107+
Mx = change_independent_variable(Mt, x)
108108
@variables x xˍt(x) xˍtt(x) y1(x) # y(x)[1:1] # TODO: array variables
109109
Dx = Differential(x)
110-
@test (Set(equations(Mx)) == Set([xˍt ~ 2*x, xˍtt ~ 2*xˍt, xˍt*Dx(y1) ~ y1]))
110+
@test (Set(equations(Mx)) == Set([xˍt ~ 2*x, xˍt*Dx(y1) ~ y1]))
111111
end
112112

113-
@testset "Change independent variable (free fall)" begin
113+
@testset "Change independent variable (free fall with 1st order horizontal equation)" begin
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, ...
@@ -122,6 +122,18 @@ end
122122
@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

125+
@testset "Change independent variable (free fall with 2nd order horizontal equation)" begin
126+
@variables x(t) y(t)
127+
@parameters g # gravitational acceleration
128+
Mt = ODESystem([D(D(y)) ~ -g, D(D(x)) ~ 0], t; name = :M) # gives (x, y) as function of t, ...
129+
Mx = change_independent_variable(Mt, x; add_old_diff = true) # ... but we want y as a function of x
130+
Mx = structural_simplify(Mx; allow_symbolic = true)
131+
Dx = Differential(Mx.x)
132+
prob = ODEProblem(Mx, [Mx.y => 0.0, Dx(Mx.y) => 1.0, Mx.t => 0.0, Mx.xˍt => 10.0], (0.0, 20.0), [g => 9.81]) # 1 = dy/dx = (dy/dt)/(dx/dt) means equal initial horizontal and vertical velocities
133+
sol = solve(prob, Tsit5(); reltol = 1e-5)
134+
@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)
135+
end
136+
125137
@testset "Change independent variable (crazy analytical example)" begin
126138
@independent_variables t
127139
D = Differential(t)
@@ -130,16 +142,15 @@ end
130142
D(D(y)) ~ D(x)^2 + D(y^3) |> expand_derivatives # expand D(y^3) # TODO: make this test 3rd order
131143
D(x) ~ x^4 + y^5 + t^6
132144
], t; name = :M)
133-
M2 = change_independent_variable(M1, x; dummies = true)
145+
M2 = change_independent_variable(M1, x)
134146

135147
# Compare to pen-and-paper result
136148
@independent_variables x
137149
Dx = Differential(x)
138150
@variables xˍt(x) xˍtt(x) y(x) t(x)
139151
@test Set(equations(M2)) == Set([
140-
xˍt^2*(Dx^2)(y) + xˍtt*Dx(y) ~ xˍt^2 + 3*y^2*Dx(y)*xˍt # from D(D(y))
141-
xˍt ~ x^4 + y^5 + t^6 # 1st order dummy equation
142-
xˍtt ~ 4*x^3*xˍt + 5*y^4*Dx(y)*xˍt + 6*t^5 # 2nd order dummy equation
152+
xˍt^2*(Dx^2)(y) + xˍt*Dx(xˍt)*Dx(y) ~ xˍt^2 + 3*y^2*Dx(y)*xˍt # from D(D(y))
153+
xˍt ~ x^4 + y^5 + t^6 # dummy equation
143154
])
144155
end
145156

@@ -157,11 +168,12 @@ end
157168

158169
# Ensure that interpolations are called with the same variables
159170
M2 = change_independent_variable(M1, x, [t ~ (x)])
160-
@variables x y(x) t(x)
171+
@variables x xˍt(x) y(x) t(x)
161172
Dx = Differential(x)
162173
@test Set(equations(M2)) == Set([
163174
t ~ (x)
164-
2*t*Dx(y) ~ 1*fc(t) + 2*fc(x) + 3*fc(y) + 1*callme(f, t) + 2*callme(f, x) + 3*callme(f, y)
175+
xˍt ~ 2*t
176+
xˍt*Dx(y) ~ 1*fc(t) + 2*fc(x) + 3*fc(y) + 1*callme(f, t) + 2*callme(f, x) + 3*callme(f, y)
165177
])
166178

167179
_f = LinearInterpolation([1.0, 1.0], [-100.0, +100.0]) # constant value 1
@@ -173,18 +185,10 @@ end
173185

174186
@testset "Change independent variable (errors)" begin
175187
@variables x(t) y z(y) w(t) v(t)
176-
M = ODESystem([D(x) ~ 0, v ~ x], t; name = :M)
177-
@test_throws "singular" change_independent_variable(M, x)
188+
M = ODESystem([D(x) ~ 1, v ~ x], t; name = :M)
178189
@test_throws "structurally simplified" change_independent_variable(structural_simplify(M), y)
179-
@test_throws "Got 0 equations:" change_independent_variable(M, w)
180-
@test_throws "Got 0 equations:" change_independent_variable(M, v)
181-
M = ODESystem([2 * D(x) ~ 1, v ~ x], t; name = :M) # TODO: allow equations like this
182-
@test_throws "Got 0 equations:" change_independent_variable(M, x)
183-
M = ODESystem([D(x) ~ 1, v ~ 1], t; name = :M)
184-
@test_throws "Got 2 equations:" change_independent_variable(M, x, [D(x) ~ 2])
185190
@test_throws "not a function of the independent variable" change_independent_variable(M, y)
186191
@test_throws "not a function of the independent variable" change_independent_variable(M, z)
187-
M = ODESystem([D(x) ~ 0, v ~ x], t; name = :M)
188192
@variables x(..) # require explicit argument
189193
M = ODESystem([D(x(t)) ~ x(t-1)], t; name = :M)
190194
@test_throws "DDE" change_independent_variable(M, x(t))

0 commit comments

Comments
 (0)