-
-
Notifications
You must be signed in to change notification settings - Fork 228
Transformation function to turn FDEs into ODEs #3776
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
f9f5257
80091f1
6fd7d31
8a1101d
8d21234
b936c8b
06d5699
90534fa
ee6a7e5
17f978b
970b404
4efd06f
4f0753c
9237e47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -185,6 +185,205 @@ function change_of_variables( | |
return new_sys | ||
end | ||
|
||
""" | ||
Generates the system of ODEs to find solution to FDEs. | ||
|
||
Example: | ||
|
||
```julia | ||
@independent_variables t | ||
@variables x(t) | ||
D = Differential(t) | ||
tspan = (0., 1.) | ||
|
||
α = 0.5 | ||
eqs = (9*gamma(1 + α)/4) - (3*t^(4 - α/2)*gamma(5 + α/2)/gamma(5 - α/2)) | ||
eqs += (gamma(9)*t^(8 - α)/gamma(9 - α)) + (3/2*t^(α/2)-t^4)^3 - x^(3/2) | ||
sys = fractional_to_ordinary(eqs, x, α, 10^-7, 1) | ||
|
||
prob = ODEProblem(sys, [], tspan) | ||
sol = solve(prob, radau5(), abstol = 1e-10, reltol = 1e-10) | ||
|
||
time = 0 | ||
while(time <= 1) | ||
@test isapprox((3/2*time^(α/2) - time^4)^2, sol(time, idxs=x), atol=1e-3) | ||
time += 0.1 | ||
end | ||
``` | ||
""" | ||
function fractional_to_ordinary(eqs, variables, alphas, epsilon, T; initials = 0) | ||
@independent_variables t | ||
D = Differential(t) | ||
i = 0 | ||
all_eqs = Equation[] | ||
all_def = Pair{Num, Int64}[] | ||
|
||
function fto_helper(sub_eq, sub_var, α; initial=0) | ||
alpha_0 = α | ||
if (α > 1) | ||
coeff = 1/(α - 1) | ||
m = 2 | ||
while (α - m > 0) | ||
coeff /= α - m | ||
m += 1 | ||
end | ||
alpha_0 = α - m + 1 | ||
end | ||
|
||
δ = (gamma(alpha_0+1) * epsilon)^(1/alpha_0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gamma isn't defined? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gamma should be the gamma function from SpecialFunctions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤦 ahhh makes sense. |
||
a = pi/2*(1-(1-alpha_0)/((2-alpha_0) * log(epsilon^-1))) | ||
h = 2*pi*a / log(1 + (2/epsilon * (cos(a))^(alpha_0 - 1))) | ||
|
||
x_sub = (gamma(2-alpha_0) * epsilon)^(1/(1-alpha_0)) | ||
x_sup = -log(gamma(1-alpha_0) * epsilon) | ||
M = floor(Int, log(x_sub / T) / h) | ||
N = ceil(Int, log(x_sup / δ) / h) | ||
|
||
function c_i(index) | ||
h * sin(pi * alpha_0) / pi * exp((1-alpha_0)*h*index) | ||
end | ||
|
||
function γ_i(index) | ||
exp(h * index) | ||
end | ||
|
||
new_eqs = Equation[] | ||
def = Pair{Num, Int64}[] | ||
|
||
if (α < 1) | ||
rhs = initial | ||
for index in range(M, N-1; step=1) | ||
new_z = Symbol(:z, :_, i) | ||
i += 1 | ||
new_z = ModelingToolkit.unwrap(only(@variables $new_z(t))) | ||
new_eq = D(new_z) ~ sub_eq - γ_i(index)*new_z | ||
push!(new_eqs, new_eq) | ||
push!(def, new_z=>0) | ||
rhs += c_i(index)*new_z | ||
end | ||
else | ||
rhs = 0 | ||
for (index, value) in enumerate(initial) | ||
rhs += value * t^(index - 1) / gamma(index) | ||
end | ||
for index in range(M, N-1; step=1) | ||
new_z = Symbol(:z, :_, i) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a unicode piece so this doesn't clash with a user chosen name |
||
i += 1 | ||
γ = γ_i(index) | ||
base = sub_eq | ||
for k in range(1, m; step=1) | ||
new_z = Symbol(:z, :_, index-M, :_, k) | ||
new_z = ModelingToolkit.unwrap(only(@variables $new_z(t))) | ||
new_eq = D(new_z) ~ base - γ*new_z | ||
base = k * new_z | ||
push!(new_eqs, new_eq) | ||
push!(def, new_z=>0) | ||
end | ||
rhs += coeff*c_i(index)*new_z | ||
end | ||
end | ||
push!(new_eqs, sub_var ~ rhs) | ||
return (new_eqs, def) | ||
end | ||
|
||
for (eq, cur_var, alpha, init) in zip(eqs, variables, alphas, initials) | ||
(new_eqs, def) = fto_helper(eq, cur_var, alpha; initial=init) | ||
append!(all_eqs, new_eqs) | ||
append!(all_def, def) | ||
end | ||
@named sys = System(all_eqs, t; defaults=all_def) | ||
return mtkcompile(sys) | ||
end | ||
|
||
""" | ||
Generates the system of ODEs to find solution to FDEs. | ||
|
||
Example: | ||
|
||
```julia | ||
@independent_variables t | ||
@variables x_0(t) | ||
D = Differential(t) | ||
tspan = (0., 5000.) | ||
|
||
function expect(t) | ||
return sqrt(2) * sin(t + pi/4) | ||
end | ||
|
||
sys = linear_fractional_to_ordinary([3, 2.5, 2, 1, .5, 0], [1, 1, 1, 4, 1, 4], 6*cos(t), 10^-5, 5000; initials=[1, 1, -1]) | ||
prob = ODEProblem(sys, [], tspan) | ||
sol = solve(prob, radau5(), abstol = 1e-5, reltol = 1e-5) | ||
``` | ||
""" | ||
function linear_fractional_to_ordinary(degrees, coeffs, rhs, epsilon, T; initials = 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how does this differ from just defining There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is made for FDE with multiple fractional terms in a single equation, which the first one can't handle. On the other hand, this one doesn't work for non-linear DE. |
||
@independent_variables t | ||
@variables x_0(t) | ||
D = Differential(t) | ||
i = 0 | ||
all_eqs = Equation[] | ||
all_def = Pair{Num, Int64}[] | ||
|
||
function fto_helper(sub_eq, α) | ||
δ = (gamma(α+1) * epsilon)^(1/α) | ||
a = pi/2*(1-(1-α)/((2-α) * log(epsilon^-1))) | ||
h = 2*pi*a / log(1 + (2/epsilon * (cos(a))^(α - 1))) | ||
|
||
x_sub = (gamma(2-α) * epsilon)^(1/(1-α)) | ||
x_sup = -log(gamma(1-α) * epsilon) | ||
M = floor(Int, log(x_sub / T) / h) | ||
N = ceil(Int, log(x_sup / δ) / h) | ||
|
||
function c_i(index) | ||
h * sin(pi * α) / pi * exp((1-α)*h*index) | ||
end | ||
|
||
function γ_i(index) | ||
exp(h * index) | ||
end | ||
|
||
new_eqs = Equation[] | ||
def = Pair{Num, Int64}[] | ||
sum = 0 | ||
for index in range(M, N-1; step=1) | ||
new_z = Symbol(:z, :_, i) | ||
i += 1 | ||
new_z = ModelingToolkit.unwrap(only(@variables $new_z(t))) | ||
new_eq = D(new_z) ~ sub_eq - γ_i(index)*new_z | ||
push!(new_eqs, new_eq) | ||
push!(def, new_z=>0) | ||
sum += c_i(index)*new_z | ||
end | ||
return (new_eqs, def, sum) | ||
end | ||
|
||
previous = x_0 | ||
for i in range(1, ceil(Int, degrees[1]); step=1) | ||
new_x = Symbol(:x, :_, i) | ||
new_x = ModelingToolkit.unwrap(only(@variables $new_x(t))) | ||
push!(all_eqs, D(previous) ~ new_x) | ||
push!(all_def, previous => initials[i]) | ||
previous = new_x | ||
end | ||
|
||
new_rhs = -rhs | ||
for (degree, coeff) in zip(degrees, coeffs) | ||
rounded = ceil(Int, degree) | ||
new_x = Symbol(:x, :_, rounded) | ||
new_x = ModelingToolkit.unwrap(only(@variables $new_x(t))) | ||
if isinteger(degree) | ||
new_rhs += coeff * new_x | ||
else | ||
(new_eqs, def, sum) = fto_helper(new_x, rounded - degree) | ||
append!(all_eqs, new_eqs) | ||
append!(all_def, def) | ||
new_rhs += coeff * sum | ||
end | ||
end | ||
push!(all_eqs, 0 ~ new_rhs) | ||
@named sys = System(all_eqs, t; defaults=all_def) | ||
return mtkcompile(sys) | ||
end | ||
|
||
""" | ||
change_independent_variable( | ||
sys::System, iv, eqs = []; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
using ModelingToolkit, OrdinaryDiffEq, ODEInterfaceDiffEq, SpecialFunctions | ||
using Test | ||
|
||
# Testing for α < 1 | ||
# Uses example 1 from Section 7 of https://arxiv.org/pdf/2506.04188 | ||
@independent_variables t | ||
@variables x(t) | ||
D = Differential(t) | ||
tspan = (0., 1.) | ||
timepoint = [0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.] | ||
|
||
function expect(t, α) | ||
return (3/2*t^(α/2) - t^4)^2 | ||
end | ||
|
||
α = 0.5 | ||
eqs = (9*gamma(1 + α)/4) - (3*t^(4 - α/2)*gamma(5 + α/2)/gamma(5 - α/2)) | ||
eqs += (gamma(9)*t^(8 - α)/gamma(9 - α)) + (3/2*t^(α/2)-t^4)^3 - x^(3/2) | ||
sys = fractional_to_ordinary(eqs, x, α, 10^-7, 1) | ||
|
||
prob = ODEProblem(sys, [], tspan) | ||
sol = solve(prob, radau5(), saveat=timepoint, abstol = 1e-10, reltol = 1e-10) | ||
|
||
time = 0 | ||
while(time <= 1) | ||
@test isapprox(expect(time, α), sol(time, idxs=x), atol=1e-3) | ||
time += 0.1 | ||
end | ||
|
||
α = 0.3 | ||
eqs = (9*gamma(1 + α)/4) - (3*t^(4 - α/2)*gamma(5 + α/2)/gamma(5 - α/2)) | ||
eqs += (gamma(9)*t^(8 - α)/gamma(9 - α)) + (3/2*t^(α/2)-t^4)^3 - x^(3/2) | ||
sys = fractional_to_ordinary(eqs, x, α, 10^-7, 1) | ||
|
||
prob = ODEProblem(sys, [], tspan) | ||
sol = solve(prob, radau5(), saveat=timepoint, abstol = 1e-10, reltol = 1e-10) | ||
|
||
time = 0 | ||
while(time <= 1) | ||
@test isapprox(expect(time, α), sol(time, idxs=x), atol=1e-7) | ||
time += 0.1 | ||
end | ||
|
||
α = 0.9 | ||
eqs = (9*gamma(1 + α)/4) - (3*t^(4 - α/2)*gamma(5 + α/2)/gamma(5 - α/2)) | ||
eqs += (gamma(9)*t^(8 - α)/gamma(9 - α)) + (3/2*t^(α/2)-t^4)^3 - x^(3/2) | ||
sys = fractional_to_ordinary(eqs, x, α, 10^-7, 1) | ||
|
||
prob = ODEProblem(sys, [], tspan) | ||
sol = solve(prob, radau5(), saveat=timepoint, abstol = 1e-10, reltol = 1e-10) | ||
|
||
time = 0 | ||
while(time <= 1) | ||
@test isapprox(expect(time, α), sol(time, idxs=x), atol=1e-7) | ||
time += 0.1 | ||
end | ||
|
||
# Testing for example 2 of Section 7 | ||
@independent_variables t | ||
@variables x(t) y(t) | ||
D = Differential(t) | ||
tspan = (0., 220.) | ||
|
||
sys = fractional_to_ordinary([1 - 4*x + x^2 * y, 3*x - x^2 * y], [x, y], [1.3, 0.8], 10^-8, 220; initials=[[1.2, 1], 2.8]) | ||
prob = ODEProblem(sys, [], tspan) | ||
sol = solve(prob, radau5(), abstol = 1e-8, reltol = 1e-8) | ||
|
||
@test isapprox(1.0097684171, sol(220, idxs=x), atol=1e-5) | ||
@test isapprox(2.1581264031, sol(220, idxs=y), atol=1e-5) | ||
|
||
#Testing for example 3 of Section 7 | ||
@independent_variables t | ||
@variables x_0(t) | ||
D = Differential(t) | ||
tspan = (0., 5000.) | ||
|
||
function expect(t) | ||
return sqrt(2) * sin(t + pi/4) | ||
end | ||
|
||
sys = linear_fractional_to_ordinary([3, 2.5, 2, 1, .5, 0], [1, 1, 1, 4, 1, 4], 6*cos(t), 10^-5, 5000; initials=[1, 1, -1]) | ||
prob = ODEProblem(sys, [], tspan) | ||
sol = solve(prob, radau5(), abstol = 1e-5, reltol = 1e-5) | ||
|
||
@test isapprox(expect(5000), sol(5000, idxs=x_0), atol=1e-6) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should just be the same as the main
t
. @AayushSabharwal what's the quick way to check the eqs all match?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That they have the same indepvar?
check_equations(eqs, iv)
.