Skip to content

Commit 6b0f03c

Browse files
author
fchen121
committed
Created change of variable for SDE
1 parent a8ed1c8 commit 6b0f03c

File tree

3 files changed

+211
-1
lines changed

3 files changed

+211
-1
lines changed

src/ModelingToolkit.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc
296296
hasunit, getunit, hasconnect, getconnect,
297297
hasmisc, getmisc, state_priority
298298
export liouville_transform, change_independent_variable, substitute_component,
299-
add_accumulations, noise_to_brownians
299+
add_accumulations, noise_to_brownians, changeofvariables, change_of_variable_SDE
300300
export PDESystem
301301
export Differential, expand_derivatives, @derivatives
302302
export Equation, ConstrainedEquation

src/systems/diffeqs/basic_transformations.jl

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,120 @@ function liouville_transform(sys::System; kwargs...)
5353
)
5454
end
5555

56+
"""
57+
$(TYPEDSIGNATURES)
58+
59+
Generates the set of ODEs after change of variables.
60+
61+
62+
Example:
63+
64+
```julia
65+
using ModelingToolkit, OrdinaryDiffEq, Test
66+
67+
# Change of variables: z = log(x)
68+
# (this implies that x = exp(z) is automatically non-negative)
69+
70+
@parameters t α
71+
@variables x(t)
72+
D = Differential(t)
73+
eqs = [D(x) ~ α*x]
74+
75+
tspan = (0., 1.)
76+
u0 = [x => 1.0]
77+
p = [α => -0.5]
78+
79+
@named sys = ODESystem(eqs; defaults=u0)
80+
prob = ODEProblem(sys, [], tspan, p)
81+
sol = solve(prob, Tsit5())
82+
83+
@variables z(t)
84+
forward_subs = [log(x) => z]
85+
backward_subs = [x => exp(z)]
86+
87+
@named new_sys = changeofvariables(sys, forward_subs, backward_subs)
88+
@test equations(new_sys)[1] == (D(z) ~ α)
89+
90+
new_prob = ODEProblem(new_sys, [], tspan, p)
91+
new_sol = solve(new_prob, Tsit5())
92+
93+
@test isapprox(new_sol[x][end], sol[x][end], atol=1e-4)
94+
```
95+
96+
"""
97+
function changeofvariables(sys::System, forward_subs, backward_subs; simplify=false, t0=missing)
98+
t = independent_variable(sys)
99+
100+
old_vars = first.(backward_subs)
101+
new_vars = last.(forward_subs)
102+
kept_vars = setdiff(states(sys), old_vars)
103+
rhs = [eq.rhs for eq in equations(sys)]
104+
105+
# use: dz/dt = ∂z/∂x dx/dt + ∂z/∂t
106+
dzdt = Symbolics.derivative( first.(forward_subs), t )
107+
new_eqs = Equation[]
108+
for (new_var, ex) in zip(new_vars, dzdt)
109+
for ode_eq in equations(sys)
110+
ex = substitute(ex, ode_eq.lhs => ode_eq.rhs)
111+
end
112+
ex = substitute(ex, Dict(forward_subs))
113+
ex = substitute(ex, Dict(backward_subs))
114+
if simplify
115+
ex = Symbolics.simplify(ex, expand=true)
116+
end
117+
push!(new_eqs, Differential(t)(new_var) ~ ex)
118+
end
119+
120+
defs = get_defaults(sys)
121+
new_defs = Dict()
122+
for f_sub in forward_subs
123+
#TODO call value(...)?
124+
ex = substitute(first(f_sub), defs)
125+
if !ismissing(t0)
126+
ex = substitute(ex, t => t0)
127+
end
128+
new_defs[last(f_sub)] = ex
129+
end
130+
return ODESystem(new_eqs;
131+
defaults=new_defs,
132+
observed=vcat(observed(sys),first.(backward_subs) .~ last.(backward_subs))
133+
)
134+
end
135+
136+
function change_of_variable_SDE(sys::System, forward_subs, backward_subs, iv; simplify=false, t0=missing)
137+
t = independent_variable(sys)
138+
139+
old_vars = first.(backward_subs)
140+
new_vars = last.(forward_subs)
141+
142+
# use: f = Y(t, X)
143+
# use: dY = (∂f/∂t + μ∂f/∂x + (1/2)*σ^2*∂2f/∂x2)dt + σ∂f/∂xdW
144+
old_eqs = equations(sys)
145+
old_noise = get_noiseeqs(sys)
146+
∂f∂t = Symbolics.derivative( first.(forward_subs), t )
147+
∂f∂x = Symbolics.derivative( first.(forward_subs), old_vars )
148+
∂2f∂x2 = Symbolics.derivative( ∂f∂x, old_vars )
149+
new_eqs = Equation[]
150+
151+
for (new_var, eq, noise, first, second, third) in zip(new_vars, old_eqs, old_noise, ∂f∂t, ∂f∂x, ∂2f∂x2)
152+
ex = first + eq.rhs * second + 1/2 * noise^2 * third
153+
for eqs in old_eqs
154+
ex = substitute(ex, eqs.lhs => eqs.rhs)
155+
end
156+
ex = substitute(ex, Dict(forward_subs))
157+
ex = substitute(ex, Dict(backward_subs))
158+
if simplify
159+
ex = Symbolics.simplify(ex, expand=true)
160+
end
161+
push!(new_eqs, Differential(t)(new_var) ~ ex)
162+
end
163+
new_noise = [noise * div for (noise, div) in zip(old_noise, ∂f∂x)]
164+
165+
return SDESystem(new_eqs, new_noise;
166+
observed=vcat(observed(sys),first.(backward_subs) .~ last.(backward_subs))
167+
)
168+
end
169+
56170
"""
57171
change_independent_variable(
58172
sys::System, iv, eqs = [];

test/changeofvariables.jl

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using ModelingToolkit, OrdinaryDiffEq
2+
using Test, LinearAlgebra
3+
4+
5+
# Change of variables: z = log(x)
6+
# (this implies that x = exp(z) is automatically non-negative)
7+
8+
@parameters t α
9+
@variables x(t)
10+
D = Differential(t)
11+
eqs = [D(x) ~ α*x]
12+
13+
tspan = (0., 1.)
14+
u0 = [x => 1.0]
15+
p ==> -0.5]
16+
17+
sys = ODESystem(eqs; defaults=u0)
18+
prob = ODEProblem(sys, [], tspan, p)
19+
sol = solve(prob, Tsit5())
20+
21+
@variables z(t)
22+
forward_subs = [log(x) => z]
23+
backward_subs = [x => exp(z)]
24+
new_sys = changeofvariables(sys, forward_subs, backward_subs)
25+
@test equations(new_sys)[1] == (D(z) ~ α)
26+
27+
new_prob = ODEProblem(new_sys, [], tspan, p)
28+
new_sol = solve(new_prob, Tsit5())
29+
30+
@test isapprox(new_sol[x][end], sol[x][end], atol=1e-4)
31+
32+
33+
34+
# Riccati equation
35+
@parameters t α
36+
@variables x(t)
37+
D = Differential(t)
38+
eqs = [D(x) ~ t^2 + α - x^2]
39+
sys = ODESystem(eqs, defaults=[x=>1.])
40+
41+
@variables z(t)
42+
forward_subs = [t + α/(x+t) => z ]
43+
backward_subs = [ x => α/(z-t) - t]
44+
45+
new_sys = changeofvariables(sys, forward_subs, backward_subs; simplify=true, t0=0.)
46+
# output should be equivalent to
47+
# t^2 + α - z^2 + 2 (but this simplification is not found automatically)
48+
49+
tspan = (0., 1.)
50+
p ==> 1.]
51+
prob = ODEProblem(sys,[],tspan,p)
52+
new_prob = ODEProblem(new_sys,[],tspan,p)
53+
54+
sol = solve(prob, Tsit5())
55+
new_sol = solve(new_prob, Tsit5())
56+
57+
@test isapprox(sol[x][end], new_sol[x][end], rtol=1e-4)
58+
59+
60+
# Linear transformation to diagonal system
61+
@parameters t
62+
@variables x[1:3](t)
63+
D = Differential(t)
64+
A = [0. -1. 0.; -0.5 0.5 0.; 0. 0. -1.]
65+
eqs = D.(x) .~ A*x
66+
67+
tspan = (0., 10.)
68+
u0 = x .=> [1.0, 2.0, -1.0]
69+
70+
sys = ODESystem(eqs; defaults=u0)
71+
prob = ODEProblem(sys,[],tspan)
72+
sol = solve(prob, Tsit5())
73+
74+
T = eigen(A).vectors
75+
76+
@variables z[1:3](t)
77+
forward_subs = T \ x .=> z
78+
backward_subs = x .=> T*z
79+
80+
new_sys = changeofvariables(sys, forward_subs, backward_subs; simplify=true)
81+
82+
new_prob = ODEProblem(new_sys, [], tspan, p)
83+
new_sol = solve(new_prob, Tsit5())
84+
85+
# test RHS
86+
new_rhs = [eq.rhs for eq in equations(new_sys)]
87+
new_A = Symbolics.value.(Symbolics.jacobian(new_rhs, z))
88+
@test isapprox(diagm(eigen(A).values), new_A, rtol = 1e-10)
89+
@test isapprox( new_sol[x[1],end], sol[x[1],end], rtol=1e-4)
90+
91+
# Change of variables for sde
92+
@Browian B
93+
@parameters μ σ
94+
@variables x(t) y(t)
95+
96+

0 commit comments

Comments
 (0)