Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ ModelingToolkitStandardLibrary = "2.20"
Moshi = "0.3"
NaNMath = "0.3, 1"
NonlinearSolve = "4.3"
ODEInterfaceDiffEq = "3.13.4"
OffsetArrays = "1"
OrderedCollections = "1"
OrdinaryDiffEq = "6.82.0"
Expand Down Expand Up @@ -184,6 +185,7 @@ LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
ODEInterfaceDiffEq = "09606e27-ecf5-54fc-bb29-004bd9f985bf"
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
OptimizationBase = "bca83a33-5cc9-4baa-983d-23429ab6bcbb"
OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1"
Expand All @@ -206,4 +208,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging", "OptimizationBase", "LinearSolve"]
test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "ODEInterfaceDiffEq", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging", "OptimizationBase", "LinearSolve"]
2 changes: 2 additions & 0 deletions docs/src/API/model_building.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ symbolic analysis.

```@docs
liouville_transform
fractional_to_ordinary
linear_fractional_to_ordinary
change_of_variables
stochastic_integral_transform
Girsanov_transform
Expand Down
3 changes: 2 additions & 1 deletion src/ModelingToolkit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc
subset_tunables
export liouville_transform, change_independent_variable, substitute_component,
add_accumulations, noise_to_brownians, Girsanov_transform, change_of_variables,
respecialize
fractional_to_ordinary, linear_fractional_to_ordinary
export respecialize
export PDESystem
export Differential, expand_derivatives, @derivatives
export Equation, ConstrainedEquation
Expand Down
236 changes: 236 additions & 0 deletions src/systems/diffeqs/basic_transformations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,242 @@ 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)
```
"""
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;
Comment on lines +200 to +209
Copy link
Member

Choose a reason for hiding this comment

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

eqs is just one equation though?

initials = 0, additional_eqs = [], iv = only(@independent_variables t), matrix=false
)
D = Differential(iv)
i = 0
all_eqs = Equation[]
all_def = Pair[]

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[]

if matrix
new_z = Symbol(:ʐ, :_, i)
i += 1
γs = diagm([γ_i(index) for index in M:N-1])
cs = [c_i(index) for index in M:N-1]

if (α < 1)
new_z = only(@variables $new_z(iv)[1:N-M])
new_eq = D(new_z) ~ -γs*new_z .+ sub_eq
rhs = dot(cs, new_z) + initial
push!(def, new_z=>zeros(N-M))
else
new_z = only(@variables $new_z(iv)[1:N-M, 1:m])
new_eq = D(new_z) ~ -γs*new_z + hcat(fill(sub_eq, N-M, 1), collect(new_z[:, 1:m-1]*diagm(1:m-1)))
rhs = coeff*sum(cs[i]*new_z[i, m] for i in 1:N-M)
for (index, value) in enumerate(initial)
rhs += value * iv^(index - 1) / gamma(index)
end
push!(def, new_z=>zeros(N-M, m))
end
push!(new_eqs, new_eq)
else
if (α < 1)
rhs = initial
for index in range(M, N-1; step=1)
new_z = Symbol(:ʐ, :_, i)
i += 1
new_z = ModelingToolkit.unwrap(only(@variables $new_z(iv)))
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 * iv^(index - 1) / gamma(index)
end
for index in range(M, N-1; step=1)
new_z = Symbol(:ʐ, :_, i)
i += 1
γ = γ_i(index)
base = sub_eq
for k in range(1, m; step=1)
new_z = Symbol(:ʐ, :_, index-M, :_, k)
new_z = ModelingToolkit.unwrap(only(@variables $new_z(iv)))
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
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
append!(all_eqs, additional_eqs)
@named sys = System(all_eqs, iv; 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)
```
"""
Copy link
Member

Choose a reason for hiding this comment

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

Docs here too.

function linear_fractional_to_ordinary(
degrees, coeffs, rhs, epsilon, T;
initials = 0, symbol = :x, iv = only(@independent_variables t), matrix=false
)
previous = Symbol(symbol, :_, 0)
previous = ModelingToolkit.unwrap(only(@variables $previous(iv)))
@variables x_0(iv)
D = Differential(iv)
i = 0
all_eqs = Equation[]
all_def = Pair[]

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[]
if matrix
new_z = Symbol(:ʐ, :_, i)
i += 1
γs = diagm([γ_i(index) for index in M:N-1])
cs = [c_i(index) for index in M:N-1]

new_z = only(@variables $new_z(iv)[1:N-M])
new_eq = D(new_z) ~ -γs*new_z .+ sub_eq
sum = dot(cs, new_z)
push!(def, new_z=>zeros(N-M))
push!(new_eqs, new_eq)
else
sum = 0
for index in range(M, N-1; step=1)
new_z = Symbol(:ʐ, :_, i)
i += 1
new_z = ModelingToolkit.unwrap(only(@variables $new_z(iv)))
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
end
return (new_eqs, def, sum)
end

for i in range(1, ceil(Int, degrees[1]); step=1)
new_x = Symbol(symbol, :_, i)
new_x = ModelingToolkit.unwrap(only(@variables $new_x(iv)))
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(symbol, :_, rounded)
new_x = ModelingToolkit.unwrap(only(@variables $new_x(iv)))
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, iv; defaults=all_def)
return mtkcompile(sys)
end

"""
change_independent_variable(
sys::System, iv, eqs = [];
Expand Down
86 changes: 86 additions & 0 deletions test/fractional_to_ordinary.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using ModelingToolkit, OrdinaryDiffEq, ODEInterfaceDiffEq, SpecialFunctions, LinearAlgebra
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)

for time in 0:0.1:1
@test isapprox(expect(time, α), sol(time, idxs=x), atol=1e-7)
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; matrix=true)

prob = ODEProblem(sys, [], tspan)
sol = solve(prob, radau5(), saveat=timepoint, abstol = 1e-10, reltol = 1e-10)

for time in 0:0.1:1
@test isapprox(expect(time, α), sol(time, idxs=x), atol=1e-7)
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)

for time in 0:0.1:1
@test isapprox(expect(time, α), sol(time, idxs=x), atol=1e-7)
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], matrix=true)
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-5)

msys = 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], matrix=true)
mprob = ODEProblem(sys, [], tspan)
msol = solve(prob, radau5(), abstol = 1e-5, reltol = 1e-5)

@test isapprox(expect(5000), msol(5000, idxs=x_0), atol=1e-5)
3 changes: 2 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,10 @@ end
@safetestset "Subsystem replacement" include("substitute_component.jl")
@safetestset "Linearization Tests" include("linearize.jl")
@safetestset "LinearProblem Tests" include("linearproblem.jl")
@safetestset "Fractional Differential Equations Tests" include("fractional_to_ordinary.jl")
end
end

if GROUP == "All" || GROUP == "SymbolicIndexingInterface"
@safetestset "SymbolicIndexingInterface test" include("symbolic_indexing_interface.jl")
@safetestset "SciML Problem Input Test" include("sciml_problem_inputs.jl")
Expand Down
Loading