How to set up your own pathtracking algorithm

How to set up your own pathtracking algorithm

We want to illustrate how to setup your own pathtracking algorithm by the already implemented AffinePredictorCorrector.

First you have to define a subtype of AbstractPathtrackingAlgorithm. This is the user facing part.

struct AffinePredictorCorrector <: AbstractPathtrackingAlgorithm
end

Note that you could also allow the user to set certain options, e.g. maybe you want to give him/her the choice between an explicit Euler method and a Runge-Kutta method.

You also have to clarify whether the algorithm will work in the projective or affine space. Here we want to work in affine space.

is_projective(::AffinePredictorCorrector) = false

Now you have to define a struct which is subtype of AbstractPathtrackerCache. This is used for the internal dispatch and also serves as an cache to avoid memory allocations. We will need a working matrix and a vector. Thus we define the following

struct AffineCache{T} <: AbstractPathtrackerCache{T}
    A::Matrix{T}
    b::Vector{T}
end

Then you have to define a new method for alg_cache(algorithm, homotopy, x) which will create our AffineCache:

function alg_cache(alg::AffinePredictorCorrector, H::AbstractHomotopy, x::AbstractVector{T}) where T
    n = length(x)
    A = zeros(T, n, n)
    b = zeros(T, n)
    AffineCache(A, b)
end

We are already half way done! Now comes the interesting part. We have to define two methods. The first one is a correction method. For us this is a simple newton iteration.

function correct!(x, # the startvalue
    t, # current 'time'
    H, # the homotopy itself
    cfg, # An AbstractHomotopyConfiguration for efficient evaluation
    abstol::Float64, # the target accuracy
    maxiters::Int, # the maximal number of iterations
    cache::AffineCache{Complex{T}} # our defined Cache
    ) where T
    @unpack A, b = cache
    m = size(A,2)
    k = 0
    while true
        k += 1
        evaluate!(b, H, x, t, cfg)

        if norm(b, Inf) < abstol
            return true
        elseif k > maxiters
            return false
        end

        # put jacobian in A
        jacobian!(A, H, x, t, cfg, true)

        # this computes A x = b and stores the result x in b
        LU = lufact!(A)
        # there is a bug in v0.6.0 see patches.jl
        my_A_ldiv_B!(LU, b)
        x .= x .- b
    end
end

This method will be used for the refinement of the final solutions. But we can also use it for the next method. We now want to define the method which will actually be used during the pathtracking! For this we have to define the method perform_step!(pathtracker, values, cache::AffineCache). The Pathtracker will invoke this function at each iteration.

function perform_step!(tracker, values::PathtrackerPrecisionValues{T}, cache::AffineCache{Complex{T}}) where T
    @unpack s, ds = tracker # s is our current 'time', ds the step length
    @unpack H, cfg, x, xnext = values
    @unpack A, b = cache

    m = size(A,2)

    # PREDICT
    # put jacobian in A
    jacobian!(A, H, x, s, cfg)
    # put Hdt in b
    dt!(b, H, x, s, cfg, true)

    # this computes A x = b and stores the result x in b
    LU = lufact!(A)
    # there is a bug in v0.6.0 see patches.jl
    my_A_ldiv_B!(LU, b)

    xnext .= x .- ds .* b

    # CORRECT
    @unpack abstol, corrector_maxiters = tracker.options
    tracker.step_sucessfull = correct!(xnext, s + ds, H, cfg, abstol, corrector_maxiters, cache)
    nothing
end

With this in place you are ready to go! Now you can simply solve a system using your own pathracking algorithm, e.g. using solve(F, AffinePredictorCorrector()).