Raw File
PeriodicOrbitFD.jl
using BlockArrays, SparseArrays
# This file implements some Finite Differences methods to locate periodic orbits

####################################################################################################
# method using the Trapezoidal rule (Order 2 in time) and discretisation of the periodic orbit. This is not a shooting method!

"""
	pb = PeriodicOrbitTrap(F, J, ϕ, xπ, M::Int, linsolve)
This structure implements Finite Differences based on Trapezoidal rule to locate periodic orbits. The arguements are as follows
- F vector field
- J jacobian of the vector field
- ϕ used for the Poincare section
- xπ used for the Poincare section
- M::Int number of slices in [0,2π]
- linsolve <: LinearSolver  linear solver

You can then call pb(orbitguess) to apply the functional to a guess. Note that orbitguess must be of size M * N + 1 where N is the number of unknowns in the state space and `orbitguess[M*N+1]` is an estimate of the period of the limit cycle.

The scheme is as follows, one look for `T = x[end]` and
 ``x_{i+1} - x_{i} - \\frac{h}{2} \\left(F(x_{i+1}) + F(x_i)\\right) = 0``

where `h = T/M`. Finally, the phase of the periodic orbit is constraint by

 ``\\langle x[1] - x\\pi, \\phi\\rangle.``

"""
@with_kw struct PeriodicOrbitTrap{vectype, S <: LinearSolver} <: PeriodicOrbit
    # Function F(x, p) = 0
    F::Function

    # Jacobian of F wrt x
    J::Function

    # variables to define a Poincare Section
    ϕ::vectype
    xπ::vectype

    # discretisation of the time interval
    M::Int = 100

    linsolve::S
end

"""
This encodes the previous functional for finding periodic orbits based on finite differences using the Trapezoidal rule
"""
function (poPb::PeriodicOrbitTrap{vectype, S})(u0::vectype) where {vectype <: AbstractVector, S}
    M = poPb.M
    N = div(length(u0) - 1, M)
    T = u0[end]
    h = T / M

    u0c = reshape(u0[1:end-1], N, M)
    outc = similar(u0c)
    for ii=2:M
        outc[:, ii] .= (u0c[:, ii] .- u0c[:, ii-1]) .- h/2 .* (poPb.F(u0c[:, ii]) .+ poPb.F(u0c[:, ii-1]))
    end

    # closure condition ensuring a periodic orbit
    outc[:, 1] .= u0c[:, M] .- u0c[:, 1]

    return vcat(vec(outc),
			dot(u0c[:, 1] .- poPb.xπ, poPb.ϕ)) # this is the phase condition
end

"""
Matrix free expression of the Jacobian of the problem for computing periodic obits when evaluated at `u0` and applied to `du`.
"""
function (poPb::PeriodicOrbitTrap{vectype, S})(u0::vectype, du) where {vectype, S}
    M = poPb.M
    N = div(length(u0) - 1, M)
    T = u0[end]
    h = T / M

    u0c = reshape(u0[1:end-1], N, M)
    duc = reshape(du[1:end-1], N, M)
    outc = similar(u0c)

    for ii=2:M
        outc[:, ii] .= (duc[:, ii] .- duc[:, ii-1]) .- h/2 .* (apply(poPb.J(u0c[:, ii]), duc[:, ii]) .+ apply(poPb.J(u0c[:, ii-1]), duc[:, ii-1]) )
    end

    # closure condition
    outc[:, 1] .= duc[:, M] .- duc[:, 1]

    δ = 1e-9
    dTFper = (poPb(vcat(u0[1:end-1], T + δ)) - poPb(u0)) / δ
    return vcat(vec(outc) .+ dTFper[1:end-1] .* du[end],
				dot(duc[:, 1], poPb.ϕ) + dTFper[end] * du[end])
end

"""
Sparse Matrix expression expression of the Jacobian for the periodic problem computed at the space-time guess: `u0`
"""
function JacobianPeriodicFD(poPb::PeriodicOrbitTrap{vectype, S}, u0::vectype, γ = 1.0) where {vectype, S}
	# extraction of various constants
	M = poPb.M
    N = div(length(u0) - 1, M)
    T = u0[end]
    h = T / M

	J = BlockArray(spzeros(M * N, M * N), N * ones(Int64,M),  N * ones(Int64,M))

	In = spdiagm( 0 => ones(N))
	On = spzeros(N, N)

	u0c = reshape(u0[1:end-1], N, M)
    outc = similar(u0c)

	for ii=2:M
		Jn = In - h/2 .* poPb.J(u0c[:, ii])
		setblock!(J, Jn, ii, ii)

		Jn = -In - h/2 .* poPb.J(u0c[:, ii-1])
		setblock!(J, Jn, ii,ii-1)
	end
	setblock!(J, -γ * In, 1, 1)
	setblock!(J,  In, 1, M)
	return J
end

"""
Function waiting to be accepted to BlockArrays.jl
"""
function blockToSparse(J::AbstractBlockArray)
	nl, nc = size(J.blocks)
    # form the first line of blocks
    res = J[Block(1,1)]
    for j=2:nc
        res = hcat(res,J[Block(1,j)])
    end
    # continue with the other lines
    for i=2:nl
        line = J[Block(i,1)]
        for j=2:nc
            line = hcat(line,J[Block(i,j)])
        end
        res = vcat(res,line)
    end
    return res
end

function (poPb::PeriodicOrbitTrap{vectype, S})(u0::vectype, tp::Symbol = :jacsparse) where {vectype, S}
	# extraction of various constants
	M = poPb.M
    N = div(length(u0) - 1, M)
    T = u0[end]
    h = T / M
	J_block = JacobianPeriodicFD(poPb, u0)

	# we now set up the last line / column
	δ = 1e-9
    dTFper = (poPb(vcat(u0[1:end-1], T + δ)) - poPb(u0)) / δ

	# this bad for performance. Get converted to SparseMatrix at the next line
	J = blockToSparse(J_block)
	J = hcat(J, dTFper[1:end-1])
	J = vcat(J, spzeros(1, N*M+1)) # this line is 5% of compute time

	J[N*M+1, 1:N] .=  poPb.ϕ
	J[N*M+1, N*M+1] = dTFper[end]
	return J
end

####################################################################################################
# Computation of Floquet Coefficients
# THIS IS WORK IN PROGRESS, DOES NOT WORK WELL YET
"""
Matrix-Free expression expression of the Monodromy matrix for the periodic problem computed at the space-time guess: `u0`
"""
function FloquetPeriodicFD(poPb::PeriodicOrbitTrap{vectype, S}, u0::vectype, du::vectype) where {vectype, S}
	# extraction of various constants
	M = poPb.M
    N = div(length(u0)-1, M)
    T = u0[end]
    h = T / M

	out = copy(du)

	u0c = reshape(u0[1:end-1], N, M)

	for ii=2:M
		out .= out./h .+ 1/2 .* apply(poPb.J(u0c[:, ii-1]), out)
		res, _ = poPb.linsolve(I/h - 1/2 * poPb.J(u0c[:, ii]), out)
		res .= out
	end
	return out
end

struct FloquetFD <: EigenSolver
	poPb
end

function (fl::FloquetFD)(J, sol, nev)
	@show sol.p
	@show length(sol.u)
	Jmono = x -> FloquetPeriodicFD(fl.poPb(sol.p), sol.u, x)
	n = div(length(sol.u)-1, 50)
	@show n
	vals, vec, info = KrylovKit.eigsolve(Jmono,rand(n),15, :LM)
	return log.(vals), vec, true, info.numops
end
back to top