diff --git a/README.md b/README.md index ed3a98e1..1d508144 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ # FractionalDiffEq.jl - -
- -
- - -FractionalDiffEq.jl provides FDE solvers to [DifferentialEquations.jl](https://diffeq.sciml.ai/dev/) ecosystem, including FODE(Fractional Ordianry Differential Equations), FDDE(Fractional Delay Differential Equations) and many more. There are many performant solvers available, capable of solving many kinds of fractional differential equations. +FractionalDiffEq.jl provides FDE solvers to [DifferentialEquations.jl](https://diffeq.sciml.ai/dev/) ecosystem, including FODE(Fractional Ordinary Differential Equations), FDDE(Fractional Delay Differential Equations) and many more. There are many performant solvers available, capable of solving many kinds of fractional differential equations. # Installation -If you have already installed Julia, you can install FractionalDiffEq.jl in REPL using Julia package manager: +If you have already installed Julia, you can install FractionalDiffEq.jl in REPL using the Julia package manager: ```julia pkg> add FractionalDiffEq @@ -95,7 +89,7 @@ Or use the [example file](https://github.com/SciFracX/FractionalDiffEq.jl/blob/m ### System of Fractional Differential Equations: -FractionalDiffEq.jl is a powerful tool to solve system of fractional differential equations, if you are familiar with [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl), it would be just like out of the box. +FractionalDiffEq.jl is a powerful tool to solve a system of fractional differential equations, if you are familiar with [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl), it would be just like out of the box. Let's see if we have a Chua chaos system: diff --git a/docs/src/fdde.md b/docs/src/fdde.md index 78435ba1..09cc8a0f 100644 --- a/docs/src/fdde.md +++ b/docs/src/fdde.md @@ -83,7 +83,7 @@ we can pass ```α``` as a ```Number``` or a ```Function``` to tell FractionalDif ## System of FDDE -Time delay [Chen system](https://en.wikipedia.org/wiki/Multiscroll_attractor) as a famous chaotic system with time delay, has important applications in many fields. As for the simulation of time delay FDDE sysstem, FractionalDiffEq.jl is also a powerful tool to do the simulation, we would illustrate the usage via code below: +Time delay [Chen system](https://en.wikipedia.org/wiki/Multiscroll_attractor) as a famous chaotic system with time delay, has important applications in many fields. As for the simulation of time delay FDDE system, FractionalDiffEq.jl is also a powerful tool to do the simulation, we would illustrate the usage via code below: ```math \begin{cases} diff --git a/src/FractionalDiffEq.jl b/src/FractionalDiffEq.jl index efa52605..be5fc07f 100644 --- a/src/FractionalDiffEq.jl +++ b/src/FractionalDiffEq.jl @@ -77,6 +77,11 @@ function __solve(prob::FODEProblem, alg::FODESystemAlgorithm, args...; kwargs... return solve!(cache) end +function __solve(prob::FDDEProblem, alg::FDDEAlgorithm, args...; kwargs...) + cache = init(prob, alg, args...; kwargs...) + return solve!(cache) +end + # General types export FDEProblem @@ -108,7 +113,7 @@ export AtanganaSedaCF export AtanganaSeda # FDDE solvers -export DelayPECE, DelayABM, MatrixForm +export DelayPIEX, DelayPECE, DelayABM, MatrixForm # DODE solvers export DOMatrixDiscrete diff --git a/src/delay/DelayABM.jl b/src/delay/DelayABM.jl index 69bab5e6..5b7f7c14 100644 --- a/src/delay/DelayABM.jl +++ b/src/delay/DelayABM.jl @@ -1,3 +1,24 @@ +@concrete mutable struct ABMCache{iip, T} + prob + alg + mesh + u0 + order + constant_algs + p + + x + x0 + x1 + N + Ndelay + + dt + kwargs +end + +Base.eltype(::ABMCache{iip, T}) where {iip, T} = T + """ solve(FDDE::FDDEProblem, dt, DelayABM()) @@ -16,59 +37,91 @@ Use the Adams-Bashforth-Moulton method to solve fractional delayed differential struct DelayABM <: FDDEAlgorithm end #FIXME: There are still some improvments about initial condition #FIXME: Fix DelayABM method for FDDESystem : https://www.researchgate.net/publication/245538900_A_Predictor-Corrector_Scheme_For_Solving_Nonlinear_Delay_Differential_Equations_Of_Fractional_Order -#FIXME: Also the problem definition f(t, h, y) or f(t, y, h)? -function solve(FDDE::FDDEProblem, dt, ::DelayABM) - @unpack f, h, order, constant_lags, tspan, p = FDDE +#TODO: Need more works +function SciMLBase.__init(prob::FDDEProblem, alg::DelayABM; dt=0.0, kwargs...) + @unpack f, order, u0, h, tspan, p, constant_lags = prob τ = constant_lags[1] - N::Int = round(Int, tspan[2]/dt) - Ndelay::Int = round(Int, τ/dt) - x1 = zeros(Float64, Ndelay+N+1) - x = zeros(Float64, Ndelay+N+1) + T = eltype(u0) + l = length(u0) + iip = SciMLBase.isinplace(prob) + t0 = tspan[1]; tfinal = tspan[2] + N = round(Int, (tfinal-t0)/dt) + mesh = collect(T, t0:dt:tfinal) + Ndelay = round(Int, τ/dt) + #x1 = zeros(Float64, Ndelay+N+1) + #x = zeros(Float64, Ndelay+N+1) + #x1 = [zeros(T, l) for i=1:(Ndelay+N+1)] + #x = [zeros(T, l) for i=1:(Ndelay+N+1)] + x1 = _generate_similar_array(u0, Ndelay+N+1, u0) + x = _generate_similar_array(u0, Ndelay+N+1, u0) #x1[Ndelay+N+1] = 0 #x[Ndelay+N+1] = 0 # History function handling #FIXME: When the value of history function h is different with the initial value? - if typeof(h) <: Number - x[1:Ndelay] = h*ones(Ndelay) - elseif typeof(h) <: Function - x[Ndelay] = h(p, 0) - x[1:Ndelay-1] .= h(p, collect(Float64, -dt*(Ndelay-1):dt:(-dt))) + println(x[1]) + x[Ndelay] = length(u0) == 1 ? ([h(p, 0)]) : (h(p, 0)) + for i=1:Ndelay + x[i] .= h(p, collect(Float64, -dt*(Ndelay-1):dt:(-dt))) end x0 = copy(x[Ndelay]) - - x1[Ndelay+1] = x0 + dt^order*f(x[1], x0, p, 0)/(gamma(order)*order) + if iip + tmp1 = zeros(T, l) + tmp2 = zeros(T, l) + f(tmp1, x0, x[1], p, 0) + f(tmp2, x[Ndelay+1], x[1], p, 0) + @. x1[Ndelay+1] = x0 + dt^order*tmp1/(gamma(order)*order) + @. x[Ndelay+1] = x0 + dt^order*(tmp2 + order*tmp1)/gamma(order+2) + else + @. x1[Ndelay+1] = x0 + dt^order*f(x0, x[1], p, 0)/(gamma(order)*order) + @. x[Ndelay+1] = x0 + dt^order*(f(x[Ndelay+1], x[1], p, 0) + order*f(x0, x[1], p, 0))/gamma(order+2) + end - x[Ndelay+1] = x0 + dt^order*(f(x[1], x[Ndelay+1], p, 0) + order*f(x[1], x0, p, 0))/gamma(order+2) + return ABMCache{iip, T}(prob, alg, mesh, u0, order, constant_lags, p, x, x0, x1, N, Ndelay, dt, kwargs) - @fastmath @inbounds @simd for n=1:N - M1=(n^(order+1)-(n-order)*(n+1)^order)*f(x[1], x0, p, 0) - - N1=((n+1)^order-n^order)*f(x[1], x0, p, 0) +end - @fastmath @inbounds @simd for j=1:n - M1 = M1+((n-j+2)^(order+1)+(n-j)^(order+1)-2*(n-j+1)^(order+1))*f(x[j], x[Ndelay+j], p, 0) - N1 = N1+((n-j+1)^order-(n-j)^order)*f(x[j], x[Ndelay+j], p, 0) +function SciMLBase.solve!(cache::ABMCache{iip, T}) where {iip, T} + @unpack prob, alg, mesh, u0, order, constant_algs, p, x, x0, x1, N, Ndelay, dt, kwargs = cache + l = length(u0) + if iip + @fastmath @inbounds @simd for n=1:N + tmp1 = zeros(T, l) + prob.f(tmp1, x0, x[1], p, 0) + M1 = @. (n^(order+1)-(n-order)*(n+1)^order)*tmp1 + N1 = @. ((n+1)^order-n^order)*tmp1 + + @fastmath @inbounds @simd for j=1:n + tmp2 = zeros(T, l) + prob.f(tmp2, x[Ndelay+j], x[j], p, 0) + M1 = @. M1+((n-j+2)^(order+1)+(n-j)^(order+1)-2*(n-j+1)^(order+1))*tmp2 + N1 = @. N1+((n-j+1)^order-(n-j)^order)*tmp2 + end + @. x1[Ndelay+n+1] = x0+dt^order*N1/(gamma(order)*order) + tmp3 = zeros(T, l) + prob.f(tmp3, x[Ndelay+n+1], x[n+1], p, 0) + @. x[Ndelay+n+1] = x0+dt^order*(tmp3 + M1)/gamma(order+2) + end + else + @fastmath @inbounds @simd for n=1:N + @. M1=(n^(order+1)-(n-order)*(n+1)^order)*prob.f(x0, x[1], p, 0) + @. N1=((n+1)^order-n^order)*prob.f(x0, x[1], p, 0) + + @fastmath @inbounds @simd for j=1:n + @. M1 = M1+((n-j+2)^(order+1)+(n-j)^(order+1)-2*(n-j+1)^(order+1))*prob.f(x[Ndelay+j], x[j], p, 0) + @. N1 = N1+((n-j+1)^order-(n-j)^order)*prob.f(x[Ndelay+j], x[j], p, 0) + end + @. x1[Ndelay+n+1] = x0+dt^order*N1/(gamma(order)*order) + @. x[Ndelay+n+1] = x0+dt^order*(prob.f(x[Ndelay+n+1], x[n+1], p, 0)+M1)/gamma(order+2) end - x1[Ndelay+n+1] = x0+dt^order*N1/(gamma(order)*order) - x[Ndelay+n+1] = x0+dt^order*(f(x[n+1], x[Ndelay+n+1], p, 0)+M1)/gamma(order+2) end - xresult = zeros(N-Ndelay+1) - yresult = zeros(N-Ndelay+1) - - xresult[N-Ndelay+1]=0 - yresult[N-Ndelay+1]=0 - - @fastmath @inbounds @simd for n=2*Ndelay+1:N+Ndelay+1 - xresult[n-2*Ndelay] = x[n] - yresult[n-2*Ndelay] = x[n-Ndelay] - end + u = x[Ndelay+1:end] - return xresult, yresult + return DiffEqBase.build_solution(prob, alg, mesh, u) end diff --git a/src/delay/DelayPECE.jl b/src/delay/DelayPECE.jl index 1b34f663..f34470ea 100644 --- a/src/delay/DelayPECE.jl +++ b/src/delay/DelayPECE.jl @@ -1,122 +1,143 @@ -""" -# Usage - - solve(FDDE::FDDEProblem, step_size, DelayPECE()) - -Using the delayed predictor-corrector method to solve the delayed fractional differential equation problem in the Caputo sense. - -Capable of solving both single term FDDE and multiple FDDE, support time varying lags of course😋. - -### References - -```tex -@article{Wang2013ANM, - title={A Numerical Method for Delayed Fractional-Order Differential Equations}, - author={Zhen Wang}, - journal={J. Appl. Math.}, - year={2013}, - volume={2013}, - pages={256071:1-256071:7} -} - -@inproceedings{Nagy2014NUMERICALSF, - title={NUMERICAL SIMULATIONS FOR VARIABLE-ORDER FRACTIONAL NONLINEAR DELAY DIFFERENTIAL EQUATIONS}, - author={Abdelhameed M. Nagy and Taghreed Abdul Rahman Assiri}, - year={2014} -} - -@inproceedings{Abdelmalek2019APM, - title={A Predictor-Corrector Method for Fractional Delay-Differential System with Multiple Lags}, - author={Salem Abdelmalek and Redouane Douaifia}, - year={2019} -} -``` -""" -struct DelayPECE <: FDDEAlgorithm end +@concrete mutable struct DelayPECECache{iip, T} + prob + alg + mesh + u0 + order + constant_lags + p + y + yp + + dt + kwargs +end +#= #FIXME: What if we have an FDDE with both variable order and time varying lag?? -function solve(FDDE::FDDEProblem, step_size, ::DelayPECE) +function solve(FDDE::FDDEProblem, dt, ::DelayPECE) # If the delays are time varying, we need to specify single delay and multiple delay if FDDE.constant_lags[1] isa Function # Here is the PECE solver for single time varying lag - solve_fdde_with_single_lag(FDDE, step_size) + solve_fdde_with_single_lag(FDDE, dt) elseif FDDE.constant_lags[1] isa AbstractArray{Function} # Here is the PECE solver for multiple time varying lags - solve_fdde_with_multiple_lags(FDDE, step_size) #TODO: implement this + solve_fdde_with_multiple_lags(FDDE, dt) #TODO: implement this # Varying order fractional delay differential equations elseif FDDE.order[1] isa Function if length(FDDE.constant_lags[1]) == 1 # Here is the PECE solver for single lag with variable order - solve_fdde_with_single_lag_and_variable_order(FDDE, step_size) + solve_fdde_with_single_lag_and_variable_order(FDDE, dt) else # Here is the PECE solver for multiple lags with variable order - solve_fdde_with_multiple_lags_and_variable_order(FDDE, step_size) + solve_fdde_with_multiple_lags_and_variable_order(FDDE, dt) end else # If the delays are constant if length(FDDE.constant_lags[1]) == 1 # Call the DelayPECE solver for single lag FDDE - solve_fdde_with_single_lag(FDDE, step_size) + solve_fdde_with_single_lag(FDDE, dt) else # Call the DelayPECE solver for multiple lags FDDE - solve_fdde_with_multiple_lags(FDDE, step_size) + solve_fdde_with_multiple_lags(FDDE, dt) end end end +=# -function solve_fdde_with_single_lag(FDDE::FDDEProblem, step_size) - @unpack f, h, order, constant_lags, p, tspan = FDDE +function SciMLBase.__init(prob::FDDEProblem, alg::DelayPECE; dt=0.0, kwargs...) + dt ≤ 0 ? throw(ArgumentError("dt must be positive")) : nothing + @unpack f, h, order, u0, constant_lags, p, tspan = prob τ = constant_lags[1] - iip = SciMLBase.isinplace(FDDE) - T = tspan[2] - t = collect(0:step_size:T) - maxn = size(t, 1) - yp = collect(Float64, 0:step_size:T+step_size) - y = copy(t) - y[1] = h(p, 0) + iip = SciMLBase.isinplace(prob) + t0 = tspan[1]; tfinal = tspan[2] + T = eltype(u0) + mesh = collect(Float64, t0:dt:tfinal) + size(mesh, 1) + yp = collect(Float64, t0:dt:tfinal+dt) + N = length(t0:dt:tfinal+dt) + yp = _generate_similar_array(u0, N, u0) + y = _generate_similar_array(u0, N-1, u0) + y[1] = _generate_similar_array(u0, 1, h(p, 0)) + + return DelayPECECache{iip, T}(prob, alg, mesh, u0, order[1], τ, p, y, yp, dt, kwargs) +end + +@inline function _generate_similar_array(u0, N, src) + if N≠1 + if length(u0) == 1 + return [similar([src]) for i=1:N] + else + return [similar(src) for i=1:N] + end + else + return [src] + end +end + +@inline function OrderWrapper(order, t) + if order isa Function + return order(t) + else + return order + end +end + +function SciMLBase.solve!(cache::DelayPECECache{iip, T}) where {iip, T} + @unpack prob, alg, mesh, u0, order, p, dt = cache + maxn = length(mesh) + l = length(u0) + initial = _generate_similar_array(u0, 1, prob.h(p, 0)) for n in 1:maxn-1 - yp[n+1] = 0 + order = OrderWrapper(order, mesh[n+1]) + cache.yp[n+1] = zeros(T, l) for j = 1:n if iip - tmp = zeros(length(yp[1])) - f(tmp, y[j], v(h, j, τ, step_size, y, yp, t, p), p, t[j]) - yp[n+1] = yp[n+1]+generalized_binomials(j-1, n-1, order, step_size)*tmp + tmp = zeros(length(cache.yp[1])) + prob.f(tmp, cache.y[j], v(cache, j), p, mesh[j]) + cache.yp[n+1] = cache.yp[n+1]+generalized_binomials(j-1, n-1, order, dt)*tmp else - yp[n+1] = yp[n+1]+generalized_binomials(j-1, n-1, order, step_size)*f(y[j], v(h, j, τ, step_size, y, yp, t, p), p, t[j]) + length(u0) == 1 ? (tmp = prob.f(cache.y[j][1], v(cache, j)[1], p, mesh[j])) : (tmp = prob.f(cache.y[j], v(cache, j), p, mesh[j])) + @. cache.yp[n+1] = cache.yp[n+1] + generalized_binomials(j-1, n-1, order, dt)*tmp end end - yp[n+1] = yp[n+1]/gamma(order)+h(p, 0) - - y[n+1] = 0 + cache.yp[n+1] = cache.yp[n+1]/gamma(order) + initial + cache.y[n+1] = zeros(T, l) + @fastmath @inbounds @simd for j=1:n if iip - tmp = zeros(length(y[1])) - f(tmp, y[j], v(h, j, τ, step_size, y, yp, t, p), p, t[j]) - y[n+1] = y[n+1]+a(j-1, n-1, order, step_size)*tmp + tmp = zeros(T, length(cache.y[1])) + prob.f(tmp, cache.y[j], v(cache, j), p, mesh[j]) + cache.y[n+1] = cache.y[n+1]+a(j-1, n-1, order, dt)*tmp else - y[n+1] = y[n+1]+a(j-1, n-1, order, step_size)*f(y[j], v(h, j, τ, step_size, y, yp, t, p), p, t[j]) + length(u0) == 1 ? (tmp=prob.f(cache.y[j][1], v(cache, j)[1], p, mesh[j])) : (tmp=prob.f(cache.y[j], v(cache, j), p, mesh[j])) + @. cache.y[n+1] = cache.y[n+1]+a(j-1, n-1, order, dt)*tmp end end + if iip - tmp = zeros(y[1]) - f(tmp, yp[n+1], v(h, n+1, τ, step_size, y, yp, t, p), p, t[n+1]) - y[n+1] = y[n+1]/gamma(order)+step_size^order*tmp/gamma(order+2)+h(p, 0) + tmp = zeros(T, l) + prob.f(tmp, cache.yp[n+1], v(cache, n+1), p, mesh[n+1]) + cache.y[n+1] = cache.y[n+1]/gamma(order)+dt^order*tmp/gamma(order+2) + initial else - y[n+1] = y[n+1]/gamma(order)+step_size^order*f(yp[n+1], v(h, n+1, τ, step_size, y, yp, t, p), p, t[n+1])/gamma(order+2)+h(p, 0) + length(u0) == 1 ? (tmp = prob.f(cache.yp[n+1][1], v(cache, n+1)[1], p, mesh[n+1])) : (tmp = prob.f(cache.yp[n+1], v(cache, n+1), p, mesh[n+1])) + @. cache.y[n+1] = cache.y[n+1]/gamma(order) + dt^order*tmp/gamma(order+2) + initial + end end - - V = copy(t) +#= + V = copy(mesh) @fastmath @inbounds @simd for n = 1:maxn-1 # The delay term - V[n] = v(h, n, τ, step_size, y, yp, t, p) + V[n] = v(cache, n) end - return V, y + =# + return DiffEqBase.build_solution(prob, alg, mesh, cache.y) end -function a(j::Int, n::Int, order, step_size) +function a(j::Int, n::Int, order, dt) if j == n+1 result = 1 elseif j == 0 @@ -124,70 +145,74 @@ function a(j::Int, n::Int, order, step_size) else result = (n-j+2)^(order+1) + (n-j)^(order+1) - 2*(n-j+1)^(order+1) end - return result*step_size^order / (order*(order + 1)) + return result*dt^order / (order*(order + 1)) end -#generalized_binomials(j, n, order::Number, step_size) = @. step_size^order/order*((n-j+1)^order - (n-j)^order) -generalized_binomials(j, n, order, step_size) = @. step_size^order/order*((n-j+1)^order - (n-j)^order) +generalized_binomials(j, n, order, dt) = @. dt^order/order*((n-j+1)^order - (n-j)^order) -function v(h, n, τ, step_size, y, yp, t, p) +function v(cache::DelayPECECache{iip, T}, n) where {iip, T} + @unpack prob, mesh, dt, constant_lags, p = cache + τ = constant_lags if typeof(τ) <: Function - m = floor.(Int, τ.(t)/step_size) - δ = m.-τ.(t)./step_size - if τ(t[n]) >= (n-1)*step_size - return h(p, (n-1)*step_size-τ(t[n])) + m = floor.(Int, τ.(mesh)/dt) + δ = m.-τ.(mesh)./dt + if τ(mesh[n]) >= (n-1)*dt + return prob.h(p, (n-1)*dt-τ(mesh[n])) else if m[n] > 1 - return δ[n]*y[n-m[n]+2] + (1-δ[n])*y[n-m[n]+1] + return δ[n]*cache.y[n-m[n]+2] + (1-δ[n])*cache.y[n-m[n]+1] elseif m[n] == 1 - return δ[n]*yp[n+1] + (1-δ[n])*y[n] + return δ[n]*cache.yp[n+1] + (1-δ[n])*cache.y[n] end end else - if τ >= (n-1)*step_size - return h(p, (n-1)*step_size-τ) + if τ >= (n-1)*dt + if length(prob.u0) == 1 + return [prob.h(p, (n-1)*dt-τ)] + else + return prob.h(p, (n-1)*dt-τ) + end else - m = floor(Int, τ/step_size) - δ = m-τ/step_size - + m = floor(Int, τ/dt) + δ = m-τ/dt if m>1 - return δ*y[n-m+2] + (1-δ)*y[n-m+1] + return δ*cache.y[n-m+2] + (1-δ)*cache.y[n-m+1] elseif m == 1 - return δ*yp[n+1] + (1-δ)*y[n] + return δ*cache.yp[n+1] + (1-δ)*cache.y[n] end end end end -function solve_fdde_with_multiple_lags(FDDE::FDDEProblem, step_size) +function solve_fdde_with_multiple_lags(FDDE::FDDEProblem, dt) @unpack f, h, order, constant_lags, p, tspan = FDDE τ = constant_lags[1] - t = collect(0:step_size:tspan[2]) - maxn = length(t) + mesh = collect(0:dt:tspan[2]) + maxn = length(mesh) yp = zeros(Float64, maxn) - y = copy(t) + y = copy(mesh) y[1] = h(p, 0) for n in 1:maxn-1 yp[n+1] = 0 for j = 1:n - yp[n+1] = yp[n+1]+generalized_binomials(j-1, n-1, order, step_size)*f(t[j], y[j], multiv(h, j, τ, step_size, y, yp, p)...) + yp[n+1] = yp[n+1]+generalized_binomials(j-1, n-1, order, dt)*f(mesh[j], y[j], multiv(h, j, τ, dt, y, yp, p)...) end yp[n+1] = yp[n+1]/gamma(order)+h(p, 0) y[n+1] = 0 for j=1:n - y[n+1] = y[n+1]+multia(j-1, n-1, order, step_size)*f(t[j], y[j], multiv(h, j, τ, step_size, y, yp, p)...) + y[n+1] = y[n+1]+multia(j-1, n-1, order, dt)*f(mesh[j], y[j], multiv(h, j, τ, dt, y, yp, p)...) end - y[n+1] = y[n+1]/gamma(order)+step_size^order*f(t[n+1], yp[n+1], multiv(h, n+1, τ, step_size, y, yp, p)...)/gamma(order+2) + h(p, 0) + y[n+1] = y[n+1]/gamma(order)+dt^order*f(mesh[n+1], yp[n+1], multiv(h, n+1, τ, dt, y, yp, p)...)/gamma(order+2) + h(p, 0) end V = [] for n = 1:maxn - push!(V, multiv(h, n, τ, step_size, y, yp, p)) + push!(V, multiv(h, n, τ, dt, y, yp, p)) end delayed = zeros(length(τ), length(V)) @@ -197,7 +222,7 @@ function solve_fdde_with_multiple_lags(FDDE::FDDEProblem, step_size) return delayed, y end -function multia(j::Int, n::Int, order, step_size) +function multia(j::Int, n::Int, order, dt) if j == n+1 result = 1 elseif j == 0 @@ -207,15 +232,15 @@ function multia(j::Int, n::Int, order, step_size) else result = (n-j+2)^(order+1) + (n-j)^(order+1) - 2*(n-j+1)^(order+1) end - return result*step_size^order/(order*(order + 1)) + return result*dt^order/(order*(order + 1)) end -function multiv(h, n, τ, step_size, y, yp, p) - if maximum(τ) > n*step_size - return h.(p, (n-1)*step_size.-τ) +function multiv(h, n, τ, dt, y, yp, p) + if maximum(τ) > n*dt + return h.(p, (n-1)*dt.-τ) else - m = floor.(Int, τ./step_size) - δ = m.-τ./step_size + m = floor.(Int, τ./dt) + δ = m.-τ./dt function judge(m) temp1 = findall(x->x>1, m) @@ -235,16 +260,16 @@ end #########################For variable order FDDE########################### -function solve_fdde_with_single_lag_and_variable_order(FDDE::FDDEProblem, step_size) +function solve_fdde_with_single_lag_and_variable_order(FDDE::FDDEProblem, dt) @unpack f, order, h, constant_lags, p, tspan = FDDE iip = SciMLBase.isinplace(FDDE) order = order[1] τ = constant_lags[1] - T = tspan[2] - t = collect(0:step_size:T) - maxn = size(t, 1) - yp = collect(0:step_size:T+step_size) - y = copy(t) + tfinal = tspan[2] + mesh = collect(0:dt:tfinal) + maxn = size(mesh, 1) + yp = collect(0:dt:tfinal+dt) + y = copy(mesh) y[1] = h(p, 0) for n in 1:maxn-1 @@ -252,71 +277,71 @@ function solve_fdde_with_single_lag_and_variable_order(FDDE::FDDEProblem, step_s for j = 1:n if iip tmp = zeros(length(yp[1])) - f(tmp, y[j], v(h, j, τ, step_size, y, yp, t, p), p, t[j]) - yp[n+1] = yp[n+1] + generalized_binomials(j-1, n-1, order(t[n+1]), step_size)*tmp + f(tmp, y[j], v(h, j, τ, dt, y, yp, mesh, p), p, mesh[j]) + yp[n+1] = yp[n+1] + generalized_binomials(j-1, n-1, order(mesh[n+1]), dt)*tmp else - yp[n+1] = yp[n+1] + generalized_binomials(j-1, n-1, order(t[n+1]), step_size)*f(y[j], v(h, j, τ, step_size, y, yp, t, p), p, t[j]) + yp[n+1] = yp[n+1] + generalized_binomials(j-1, n-1, order(mesh[n+1]), dt)*f(y[j], v(h, j, τ, dt, y, yp, mesh, p), p, mesh[j]) end end - yp[n+1] = yp[n+1]/gamma(order(t[n+1]))+h(p, 0) + yp[n+1] = yp[n+1]/gamma(order(mesh[n+1]))+h(p, 0) y[n+1] = 0 @fastmath @inbounds @simd for j=1:n if iip tmp = zeros(length(y[1])) - f(tmp, y[j], v(h, j, τ, step_size, y, yp, t, p), p, t[j]) - y[n+1] = y[n+1]+a(j-1, n-1, order(t[n+1]), step_size)*tmp + f(tmp, y[j], v(h, j, τ, dt, y, yp, mesh, p), p, mesh[j]) + y[n+1] = y[n+1]+a(j-1, n-1, order(mesh[n+1]), dt)*tmp else - y[n+1] = y[n+1]+a(j-1, n-1, order(t[n+1]), step_size)*f(y[j], v(h, j, τ, step_size, y, yp, t, p), p, t[j]) + y[n+1] = y[n+1]+a(j-1, n-1, order(mesh[n+1]), dt)*f(y[j], v(h, j, τ, dt, y, yp, mesh, p), p, mesh[j]) end end if iip tmp = zeros(length(y[1])) - f(tmp, yp[n+1], v(h, n+1, τ, step_size, y, yp, t, p), p, t[n+1]) - y[n+1] = y[n+1]/gamma(order(t[n+1]))+step_size^order(t[n+1])*tmp/gamma(order(t[n+1])+2)+h(p, 0) + f(tmp, yp[n+1], v(h, n+1, τ, dt, y, yp, mesh, p), p, mesh[n+1]) + y[n+1] = y[n+1]/gamma(order(mesh[n+1]))+dt^order(mesh[n+1])*tmp/gamma(order(mesh[n+1])+2)+h(p, 0) else - y[n+1] = y[n+1]/gamma(order(t[n+1]))+step_size^order(t[n+1])*f(yp[n+1], v(h, n+1, τ, step_size, y, yp, t, p), p, t[n+1])/gamma(order(t[n+1])+2)+h(p, 0) + y[n+1] = y[n+1]/gamma(order(mesh[n+1]))+dt^order(mesh[n+1])*f(yp[n+1], v(h, n+1, τ, dt, y, yp, mesh, p), p, mesh[n+1])/gamma(order(mesh[n+1])+2)+h(p, 0) end end - V = copy(t) + V = copy(mesh) @fastmath @inbounds @simd for n = 1:maxn-1 - V[n] = v(h, n, τ, step_size, y, yp, t, p) + V[n] = v(h, n, τ, dt, y, yp, mesh, p) end return V, y end -function solve_fdde_with_multiple_lags_and_variable_order(FDDE::FDDEProblem, step_size) +function solve_fdde_with_multiple_lags_and_variable_order(FDDE::FDDEProblem, dt) @unpack f, h, order, constant_lags, p, tspan = FDDE τ = constant_lags[1] - t = collect(0:step_size:tspan[2]) - maxn = length(t) + mesh = collect(0:dt:tspan[2]) + maxn = length(mesh) yp = zeros(maxn) - y = copy(t) + y = copy(mesh) y[1] = h(p, 0) for n in 1:maxn-1 yp[n+1] = 0 for j = 1:n - yp[n+1] = yp[n+1]+generalized_binomials(j-1, n-1, order(t[n+1]), step_size)*f(t[j], y[j], multiv(h, j, τ, step_size, y, yp, p)...) + yp[n+1] = yp[n+1]+generalized_binomials(j-1, n-1, order(mesh[n+1]), dt)*f(mesh[j], y[j], multiv(h, j, τ, dt, y, yp, p)...) end - yp[n+1] = yp[n+1]/gamma(order(t[n+1]))+h(p, 0) + yp[n+1] = yp[n+1]/gamma(order(mesh[n+1]))+h(p, 0) y[n+1] = 0 for j=1:n - y[n+1] = y[n+1]+multia(j-1, n-1, order(t[n+1]), step_size)*f(t[j], y[j], multiv(h, j, τ, step_size, y, yp, p)...) + y[n+1] = y[n+1]+multia(j-1, n-1, order(mesh[n+1]), dt)*f(mesh[j], y[j], multiv(h, j, τ, dt, y, yp, p)...) end - y[n+1] = y[n+1]/gamma(order(t[n+1]))+step_size^order(t[n+1])*f(t[n+1], yp[n+1], multiv(h, n+1, τ, step_size, y, yp, p)...)/gamma(order(t[n+1])+2) + h(p, 0) + y[n+1] = y[n+1]/gamma(order(mesh[n+1]))+dt^order(mesh[n+1])*f(mesh[n+1], yp[n+1], multiv(h, n+1, τ, dt, y, yp, p)...)/gamma(order(mesh[n+1])+2) + h(p, 0) end V = [] for n = 1:maxn - push!(V, multiv(h, n, τ, step_size, y, yp, p)) + push!(V, multiv(h, n, τ, dt, y, yp, p)) end delayed = zeros(Float, length(τ), length(V)) diff --git a/src/delay/product_integral.jl b/src/delay/product_integral.jl index 7dba4617..e886db0c 100644 --- a/src/delay/product_integral.jl +++ b/src/delay/product_integral.jl @@ -19,37 +19,67 @@ Use explicit rectangular product integration algorithm to solve an FDDE problem. } ``` =# +@concrete mutable struct DelayPIEXCache{iip, T} + prob + alg + mesh + u0 + order + constant_lags + p -function solve(FDDE::FDDEProblem, dt, ::PIEX) - @unpack f, order, h, tspan, p, constant_lags = FDDE + N + y0 + y + g + b + + dt + kwargs +end + +Base.eltype(::DelayPIEXCache{iip, T}) where {iip, T} = T + +function SciMLBase.__init(prob::FDDEProblem, alg::DelayPIEX; dt=0.0, kwargs...) + dt ≤ 0 ? throw(ArgumentError("dt must be positive")) : nothing + @unpack f, order, u0, h, tspan, p, constant_lags = prob τ = constant_lags[1] - iip = SciMLBase.isinplace(FDDE) - t0 = tspan[1]; T = tspan[2] - N::Int = ceil(Int, (T-t0)/dt) - t = t0 .+ dt*collect(0:N) + iip = SciMLBase.isinplace(prob) + T = eltype(u0) + t0 = tspan[1]; tfinal = tspan[2] + N = ceil(Int, (tfinal-t0)/dt) + mesh = t0 .+ dt*collect(0:N) - nn_al = collect(Float64, 0:N).^order + order = order[1] # Only for commensurate order FDDE + nn_al = collect(T, 0:N).^order[1] b = [0; nn_al[2:end].-nn_al[1:end-1]]/gamma(order+1) - h_al = dt^order y0 = h(p, t0) - y = zeros(Float64, N+1) + length(y0) == 1 ? (y=[similar([y0]) for i=1:N+1]) : (y=[similar(y0) for i=1:N+1]) - g = zeros(Float64, N+1) - y[1] = y0 + length(y0) == 1 ? (g=[similar([y0]) for i=1:N+1]) : (g=[similar(y0) for i=1:N+1]) + length(y0) == 1 ? (y[1] = [y0]) : (y[1] = y0) + return DelayPIEXCache{iip, T}(prob, alg, mesh, u0, order, τ, p, N, y0, y, g, b, dt, kwargs) +end + +function SciMLBase.solve!(cache::DelayPIEXCache{iip, T}) where {iip, T} + @unpack prob, alg, mesh, u0, order, constant_lags, p, N, y0, y, g, b, dt, kwargs = cache + h_al = dt^order[1] + τ = constant_lags + l = length(u0) for n = 1:N - tnm1 = t[n] + tnm1 = mesh[n] if tnm1 <= τ - y_nm1_tau = h(p, tnm1-τ) + y_nm1_tau = prob.h(p, tnm1-τ) else nm1_tau1 = floor(Int, (tnm1-τ)/dt) nm1_tau2 = ceil(Int, (tnm1-τ)/dt) if nm1_tau1 == nm1_tau2 y_nm1_tau = y[nm1_tau1+1] else - tt0 = t[nm1_tau1+1] - tt1 = t[nm1_tau1+2] + tt0 = mesh[nm1_tau1+1] + tt1 = mesh[nm1_tau1+2] yy0 = y[nm1_tau1+1] yy1 = y[nm1_tau1+2] y_nm1_tau = ((tnm1-τ)-tt0)/(tt1-tt0)*yy1 + ((tnm1-τ)-tt1)/(tt0-tt1)*yy0 @@ -58,16 +88,18 @@ function solve(FDDE::FDDEProblem, dt, ::PIEX) if iip tmp = zeros(length(g[1])) - f(tmp, y[n], y_nm1_tau, tnm1, p) + prob.f(tmp, y[n], y_nm1_tau, p, tnm1) g[n] = tmp else - g[n] = f(y[n], y_nm1_tau, tnm1, p) + length(u0) == 1 ? (tmp = prob.f(y[n], y_nm1_tau[1], p, tnm1)) : (tmp = prob.f(y[n], y_nm1_tau, p, tnm1)) + g[n] = tmp end - f_mem = 0 + f_mem = zeros(T, l) for j = 0:n-1 - f_mem = f_mem + g[j+1]*b[n-j+1] + @. f_mem = f_mem + g[j+1]*b[n-j+1] end - y[n+1] = y0 + h_al*f_mem + @. y[n+1] = y0 + h_al*f_mem end - return y + + return DiffEqBase.build_solution(prob, alg, mesh, y) end \ No newline at end of file diff --git a/src/types/algorithms.jl b/src/types/algorithms.jl index d6614c18..02605f2e 100644 --- a/src/types/algorithms.jl +++ b/src/types/algorithms.jl @@ -171,6 +171,41 @@ Predictor-Correct scheme. """ struct PECE <: FODESystemAlgorithm end +""" +# Usage + + solve(FDDE::FDDEProblem, dt, DelayPECE()) + +Using the delayed predictor-corrector method to solve the delayed fractional differential equation problem in the Caputo sense. + +Capable of solving both single term FDDE and multiple FDDE, support time varying lags of course😋. + +### References + +```tex +@article{Wang2013ANM, + title={A Numerical Method for Delayed Fractional-Order Differential Equations}, + author={Zhen Wang}, + journal={J. Appl. Math.}, + year={2013}, + volume={2013}, + pages={256071:1-256071:7} +} + +@inproceedings{Nagy2014NUMERICALSF, + title={NUMERICAL SIMULATIONS FOR VARIABLE-ORDER FRACTIONAL NONLINEAR DELAY DIFFERENTIAL EQUATIONS}, + author={Abdelhameed M. Nagy and Taghreed Abdul Rahman Assiri}, + year={2014} +} + +@inproceedings{Abdelmalek2019APM, + title={A Predictor-Corrector Method for Fractional Delay-Differential System with Multiple Lags}, + author={Salem Abdelmalek and Redouane Douaifia}, + year={2019} +} +``` +""" +struct DelayPECE <: FDDEAlgorithm end """ The classical Euler method extended for fractional order differential equations. @@ -182,6 +217,11 @@ Explicit product integral method for initial value problems of fractional order """ struct PIEX <: FODESystemAlgorithm end +""" +Explicit product integral method for initial value problems of fractional order differential equations. +""" +struct DelayPIEX <: FDDEAlgorithm end + """ Explicit product integral method for initial value problems of fractional order differential equations. @@ -189,6 +229,7 @@ Explicit product integral method for initial value problems of fractional order struct MTPIEX <: MultiTermsFODEAlgorithm end + """ solve(prob::FODESystem, h, AtanganaSedaAB()) diff --git a/test/fdde.jl b/test/fdde.jl index 5cbe5989..8b9bed22 100644 --- a/test/fdde.jl +++ b/test/fdde.jl @@ -9,16 +9,20 @@ f(y, ϕ, p, t) = 3.5*y*(1-ϕ/19) - dt = 0.5 + dt = 0.2 α = 0.97 τ = [0.8] u0 = 19.00001 tspan = (0.0, 1.0) fddeprob = FDDEProblem(f, α, u0, h, constant_lags = τ, tspan) - V, y = solve(fddeprob, dt, DelayPECE()) + sol = solve(fddeprob, DelayPECE(), dt=dt) - @test V≈[19.0, 19.0, 1.0] - @test y≈[19.00001, 19.00001, 37.274176448220274] + @test isapprox(test_sol(sol)', [19.00001 + 19.00001 + 19.00001 + 19.00001 + 19.000006224428567 + 18.999998984089164]; atol=1e-7) end @testset "Test DelayPECE method with single constant lag with variable order" begin @@ -30,17 +34,26 @@ end end end - f(y, ϕ, p, t) = 3.5*y*(1-ϕ/19) - dt = 0.5 + f(y, ϕ, p, t) = 3.5*y*(1-ϕ/19) + dt = 0.01 alpha(t) = 0.99-(0.01/100)*t τ = [0.8] tspan = (0.0, 1.0) u0 = 19.00001 fddeprob = FDDEProblem(f, [alpha], u0, h, constant_lags = τ, tspan) - V, y = solve(fddeprob, dt, DelayPECE()) + sol = solve(fddeprob, DelayPECE(), dt=dt) - @test V≈[19.0, 19.0, 1.0] - @test y≈[19.00001, 19.00001, 36.698681913021375] + @test isapprox(test_sol(sol)'[end-10:end], [19.000006225431903 + 19.00000586970618 + 19.000005514290276 + 19.000005159159087 + 19.000004804291258 + 19.000004449668406 + 19.00000409527454 + 19.000003741095618 + 19.00000338711922 + 19.000003033334277 + 19.000002679730862]; atol=1e-7) end @testset "Test DelayPECE method with single time varying lag" begin @@ -56,16 +69,25 @@ end return 3.5*y*(1-ϕ/19) end - dt = 0.5 + dt = 0.01 alpha = 0.97 u0 = 19.00001 τ(t) = 0.8 tspan = (0.0, 1.0) fddeprob = FDDEProblem(f, alpha, u0, h, constant_lags = [τ], tspan) - V, y = solve(fddeprob, dt, DelayPECE()) + sol = solve(fddeprob, DelayPECE(), dt=dt) - @test V≈[19.0, 19.0, 1.0] - @test y≈[19.00001, 19.00001, 37.274176448220274] + @test isapprox(test_sol(sol)'[end-10:end], [19.000006018940965 + 19.000005651668022 + 19.000005285353794 + 19.000004919919107 + 19.000004555296744 + 19.000004191428914 + 19.00000382826542 + 19.000003465762248 + 19.000003103880502 + 19.00000274258556 + 19.00000238184641]; atol=1e-8) end @testset "Test Product Integral method" begin @@ -85,36 +107,40 @@ end tspan = (0.0, 2.0) dt = 0.5 prob = FDDEProblem(f, order, u0, ϕ, constant_lags = τ, tspan) - result = solve(prob, dt, PIEX()) - @test result≈[19.00001, 19.00001, 19.00001, 18.99999190949352, 18.99997456359874] + sol = solve(prob, DelayPIEX(), dt=dt) + @test isapprox(test_sol(sol)', [19.00001, 19.00001, 19.00001, 18.99999190949352, 18.99997456359874]; atol=1e-3) end - +#DelayABM todo +#= @testset "Test DelayABM method" begin dt=0.5 tspan = (0.0, 5.0) α=0.97 - τ=[2] - h = 0.5 + τ=[2.0] u0 = 0.5 - delayabmfun(ϕ, y, p, t) = 2*ϕ/(1+ϕ^9.65)-y + h(p, t) = 0.5 + delayabmfun(y, ϕ, p, t) = 2*ϕ/(1+ϕ^9.65)-y prob = FDDEProblem(delayabmfun, α, u0, h, constant_lags=τ, tspan) - x, y=solve(prob, dt, DelayABM()) + x, y=solve(prob, DelayABM(), dt=dt) @test isapprox(x, [1.078559863692747, 1.175963999045738, 1.1661317460354588, 1.128481756921719, 1.0016061526083417, 0.7724564325042358, 0.5974978685646778]; atol=1e-3) @test isapprox(y, [0.8889787467894421, 0.9404487875504524, 0.9667449499617093, 0.9803311436135411, 1.078559863692747, 1.175963999045738, 1.1661317460354588]; atol=1e-3) end @testset "Test DelayABM for FDDESystem" begin - function EnzymeKinetics!(dy, y, ϕ, t) + function EnzymeKinetics!(dy, y, ϕ, p, t) dy[1] = 10.5-y[1]/(1+0.0005*ϕ[4]^3) dy[2] = y[1]/(1+0.0005*ϕ[4]^3)-y[2] dy[3] = y[2]-y[3] dy[4] = y[3]-0.5*y[4] end - q = [60, 10, 10, 20]; α = [0.95, 0.95, 0.95, 0.95] - prob = FDDESystem(EnzymeKinetics!, q, α, 4, 6) - #sold, sol = solve(prob, 0.1, DelayABM()) - sol = solve(prob, 0.1, DelayABM()) + function test_phi(p, t) + return [60.0, 10.0, 10.0, 20.0] + end + tspan = (0.0, 6.0) + q = [60.0, 10.0, 10.0, 20.0]; α = [0.95, 0.95, 0.95, 0.95] + prob = FDDEProblem(EnzymeKinetics!, α, q, test_phi, constant_lags=[4], tspan) + sol = solve(prob, DelayABM(), dt=0.1) #= @test isapprox(sold, [ 56.3 11.3272 11.4284 22.4025 56.3229 11.2293 11.4106 22.4194 @@ -204,4 +230,5 @@ end @test isapprox(delayed, [0.5 0.5 0.5 0.5 0.5 1.12259 0.62302 1.28025 0.818417 2.53596 -1.65415 0.5 0.5 0.5 0.5 0.5 0.605999 1.2225 0.491575 1.37261 0.47491 3.37398]; atol=1e-3) end +=# =# \ No newline at end of file