My PhD was on the topic of delay differential equations (DDEs) and, at the time, I wrote lots of utilities to do things like calculate the stability of linearised equations. All that code has been lost to the mists of time but I still have call for it every now and again (e.g., for real-time dynamic substructuring where I need to model the delays in the control loop).
Here is some code in Julia that calculates the stability of a DDE in the form
$$ x’(t) = Ax(t) + Bx(t-\tau) $$
where $A$ and $B$ can be (square) matrices, i.e., systems of DDEs. If you’ve got a nonlinear model, you just need to linearise around an equilibrium first.
The algorithm implemented is that of Pseudospectral Differencing Methods for Characteristic Roots of Delay Differential Equations; D. Breda, S. Maset, and R. Vermiglio, SIAM Journal on Scientific Computing 2005 27:2, 482-495. It has the property that it approximates the characteristic roots of the DDE closest to the origin best.
using LinearAlgebra
"""
cheb_mesh(T::Type, N::Integer)
cheb_mesh(N::Integer)
Return the Chebyshev nodes of degree `N` on the interval [0, 1].
"""
cheb_mesh(T::Type, N::Integer) = (-cos.((T(π) * (0:N)) ./ N) .+ 1) ./ 2
cheb_mesh(N::Integer) = cheb_mesh(Float64, N)
"""
cheb(T::Type, N::Integer)
cheb(N::Integer)
Return a tuple `(x, D)` where `x` contains the Chebyshev nodes of degree `N` on the interval
[0, 1] using the numerical type `T` and `D` is the Chebyshev differentiation matrix on the
same interval.
## Example - differentiate a sinusoid
julia> (x, D) = cheb(10) ;
julia> D * sinpi.(x) ≈ π * cospi.(x)
true
"""
function cheb(T::Type, N::Integer)
if N == 0
return (ones(T, 1), zeros(T, 1, 1))
else
x = cheb_mesh(T, N)
c = [2; ones(T, N - 1, 1); 2] .* (-1) .^ (0:N)
dx = x .- x'
D = (c * (1 ./ c)') ./ (dx + I)
D[diagind(D)] .-= vec(sum(D; dims=2))
return (x, D)
end
end
cheb(N::Integer) = cheb(Float64, N)
"""
dde_stability(A, B, τ; N=10)
Calculate (a finite number of) the characteristic roots of the linear delay differential
equation
x'(t) = Ax(t) + Bx(t-τ)
where `A` and `B` can be (square) matrices. `N` controls the order of the Chebyshev
polynomial approximation to the infinitesimal generator; higher values of `N` result in a
more accurate approximation. A property of the algorithm used is that characteristic roots
closest to the origin are approximated best.
## Reference
Pseudospectral Differencing Methods for Characteristic Roots of Delay Differential
Equations; D. Breda, S. Maset, and R. Vermiglio, SIAM Journal on Scientific Computing 2005
27:2, 482-495
"""
function dde_stability(A, B, τ; N=10)
@assert size(A) == size(B)
@assert size(A, 1) == size(A, 2)
T = eltype(A) == Int ? Float64 : eltype(A)
n = size(A, 1)
(x, D) = cheb(T, N)
M = kron(-D, Matrix(I / convert(T, τ), n, n))
M[1:n, 1:n] .= A
M[1:n, (n + 1):(end - n)] .= 0
M[1:n, (end - n + 1):end] .= B
return eigen(M)
end
# Tests
# 1. x'(t) = 2x(t) - exp(1)*x(t - 1) has a double root at λ = 1
# 2. x'(t) = -x(t - τ) goes unstable at τ=π/2
# 3. x'(t) = α*A*x(t) + (1-α)*A*x(t-τ)
# A = [
# 50 284 41 23 50 32
# -280 -46 -19 -37 -10 -28
# 35 -1 26 143 35 17
# 5 -31 -139 -22 5 -13
# 20 -16 11 -7 -115 137
# -10 -46 -19 -37 -145 -163
# ]
# with α=0.9 is stable for 3π/(270*(2*0.9 - 1)) ≤ τ ≤ 4π/270
# that is 0.04363323129985824 ≤ τ ≤ 0.046542113386515455