Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f9f5257
Created FDE to ODE for one equation with alpha < 1
Jun 25, 2025
80091f1
Merge branch 'SciML:master' into iss3707
fchen121 Jun 25, 2025
6fd7d31
Implement FDE to ODE for single equation with alpha > 1
Jun 26, 2025
8a1101d
Update FDE_to_ODE to handle multiple FDEs
Jun 26, 2025
8d21234
Fix bug in FDE to ODE for alpha > 1
Jun 26, 2025
b936c8b
Create test case for FDE to ODE
Jun 27, 2025
06d5699
Merge branch 'SciML:master' into iss3707
fchen121 Jun 27, 2025
90534fa
Add docstring to fractional_to_ordinary
Jun 27, 2025
ee6a7e5
Merge branch 'SciML:master' into iss3707
fchen121 Jul 3, 2025
17f978b
Add new function to deal with multiple term problem
Jul 3, 2025
970b404
Merge branch 'SciML:master' into iss3707
fchen121 Jul 7, 2025
4efd06f
Improve test cases
Jul 9, 2025
4f0753c
Update docstring
Jul 10, 2025
9237e47
Merge branch 'SciML:master' into iss3707
fchen121 Jul 17, 2025
4293364
Merge branch 'SciML:master' into iss3707
fchen121 Jul 23, 2025
9480748
Implemented matrix ver. and changed test cases
Aug 7, 2025
8cf8960
Merge branch 'SciML:master' into iss3707
fchen121 Aug 7, 2025
c64979c
Combined matrix ver. with regular
Aug 8, 2025
a01857f
Added matrix form for linear FDE to ODE and new test case
Aug 14, 2025
b094647
Fix merge issue
Aug 14, 2025
68a9807
Merge branch 'SciML:master' into iss3707
fchen121 Aug 14, 2025
1b6ea2d
Fix function format
Aug 14, 2025
0506d04
Add new function to doc and fix export issue
Aug 14, 2025
bdd88c9
Update src/systems/diffeqs/basic_transformations.jl
ChrisRackauckas Sep 9, 2025
5a0f8f8
Update Project.toml
ChrisRackauckas Sep 9, 2025
8d8d057
Update runtests.jl
ChrisRackauckas Sep 9, 2025
9b0222f
Merge branch 'master' into iss3707
ChrisRackauckas Sep 9, 2025
90be611
Update src/ModelingToolkit.jl
ChrisRackauckas Sep 9, 2025
2c3dfc9
Update fractional_to_ordinary.jl
ChrisRackauckas Sep 9, 2025
0411e45
Update test/fractional_to_ordinary.jl
ChrisRackauckas Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the Plots dep

PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Expand Down Expand Up @@ -142,6 +143,7 @@ OrdinaryDiffEq = "6.82.0"
OrdinaryDiffEqCore = "1.15.0"
OrdinaryDiffEqDefault = "1.2"
OrdinaryDiffEqNonlinearSolve = "1.5.0"
Plots = "1.40.15"
PrecompileTools = "1"
Pyomo = "0.1.0"
REPL = "1"
Expand Down
5 changes: 3 additions & 2 deletions src/ModelingToolkit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,9 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc
tunable_parameters, isirreducible, getdescription, hasdescription,
hasunit, getunit, hasconnect, getconnect,
hasmisc, getmisc, state_priority
export liouville_transform, change_independent_variable, substitute_component,
add_accumulations, noise_to_brownians, Girsanov_transform, change_of_variables
export liouville_transform, change_independent_variable, substitute_component, add_accumulations,
noise_to_brownians, Girsanov_transform, change_of_variables,
fractional_to_ordinary, linear_fractional_to_ordinary
export PDESystem
export Differential, expand_derivatives, @derivatives
export Equation, ConstrainedEquation
Expand Down
179 changes: 179 additions & 0 deletions src/systems/diffeqs/basic_transformations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,185 @@ 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
```
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation of arguments and such. See the Function Template in https://github.com/SciML/SciMLStyle.

function fractional_to_ordinary(eqs, variables, alphas, epsilon, T; initials = 0)
@independent_variables t
Copy link
Member

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?

Copy link
Member

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).

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gamma isn't defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gamma should be the gamma function from SpecialFunctions.

Copy link
Member

Choose a reason for hiding this comment

The 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)
Copy link
Member

Choose a reason for hiding this comment

The 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

function linear_fractional_to_ordinary(degrees, coeffs, rhs, epsilon, T; initials = 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this differ from just defining D.(u, alpha) ~ A*u and calling the first one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 = [];
Expand Down
68 changes: 68 additions & 0 deletions test/fractional_to_ordinary.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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.)

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(), abstol = 1e-10, reltol = 1e-10)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sol = solve(prob, radau5(), abstol = 1e-10, reltol = 1e-10)
sol = solve(prob, radau5(), saveat=time, 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(), 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.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(), 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

# 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^-6, 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-3)
@test isapprox(2.1581264031, sol(220, idxs=y), atol=1e-3)
Loading