function bpart(btype::A,i::Integer,j::Integer,bfc,bfn,k,bccenter,cfield) where {A<:BoundaryCondition}
    if A<:Neumman
        return flux(btype)
    elseif A<:Dirichlet
        return flux_dirichlet(btype,bfc[i],bfn[i],k[i],bccenter[i],cfield[j])
    end
end

# Method B
function laplacian_mB!(out,cfield,k,bf2c,bt,bcond,bfn,bfc,bccenter,bcv,f2c,fn,ccenter,cv,floops::Face2CellLoop)
    NBF = nbfaces(floops)
    @inbounds for i=1:NBF
        j = bf2c[i][1]
        qf = bpart(bcond[bt[i]],i,j,bfc,bfn,k,bccenter,cfield)
        out[j] += qf*bcv[i]
    end

    NF = nfaces(floops)
    @inbounds for i=1:NF
        el = f2c[i]
        j1 = el[1]
        j2 = el[2]
        To = cfield[j1]
        Ta = cfield[j2]
        n = fn[i]
        c = ccenter[i]
        AfoLac = (n⋅n)/(n⋅(c[2] - c[1]))
        qf = k[i]*(Ta-To)*AfoLac

        v = cv[i]
        out[j1] += qf*v[1]
        out[j2] -= qf*v[2]
    end
end

@inline laplacian_mB!(out,cfield,k,bcond,floops) = laplacian_mB!(out,cfield,k,
    floops.bf2c,floops.bft,bcond,floops.bfn,floops.bfc,floops.bccenter,floops.bcv,
    floops.f2c,floops.fn,floops.ccenter,floops.cv,
floops)

function fillbvalues!(Tbf,bcond,Tc,bf2c,bt,bfc,bccenter,bfn,floops::Face2CellLoop{NBF,NF,VT}) where {NBF,NF,VT}
    @inbounds for i=1:NBF
        j = bf2c[i][1]
        a = bcond[bt[i]]
        Tbf[i] = typeof(a) <: Dirichlet ? val(a) : Tc[j] + ((bfc[i] - bccenter[i])⋅bfn[i])*flux(a)/norm(bfn[i])
    end
end

# Gauss Method
function laplacian_Gauss!(out, ∇Tc, Tc, Tf, Tbf, k,bf2c,bt,bcond,bfn,bfc,bccenter,bcv,f2c,fn,fc,ccenter,cv,floops::Face2CellLoop{NBF,NF,VT}) where {NBF,NF,VT}

    #Qf at bfaces
    @inbounds for i=1:NBF
        j = bf2c[i][1]
        qf = bpart(bcond[bt[i]],i,j,bfc,bfn,k,bccenter,Tc)
        out[j] += qf*bcv[i]
    end

    #first Tf
    if !(typeof(Tf)<:FaceSimpleInterpolation)
        @inbounds for i=1:NF
            j1,j2 = f2c[i].ind
            Tf[i] = 0.5*(Tc[j1] + Tc[j2])
        end
    end

    gradient!(∇Tc, Tf, Tbf,floops)

    @inbounds for i=1:NF
        j1, j2 = f2c[i]
        n = fn[i]
        v1, v2 = cv[i]
        qf = 0.5 * k[i] * (n ⋅ (∇Tc[j1] + ∇Tc[j2]))
        out[j1] += qf*v1
        out[j2] -= qf*v2
    end

end

@inline laplacian_Gauss!(out, ∇Tc, Tc, Tf, Tbf,k,bcond,floops) = laplacian_Gauss!(out, ∇Tc, Tc, Tf, Tbf,k,
    floops.bf2c,floops.bft,bcond,floops.bfn,floops.bfc,floops.bccenter,floops.bcv,
    floops.f2c,floops.fn,floops.fc,floops.ccenter,floops.cv,
floops)

# Corrected Gauss Method
function laplacian_CGauss!(out, ∇Tc, Tc, Tf::AbstractVector, Tbf, k,bf2c,bt,bcond,bfn,bfc,bccenter,bcv,f2c,fn,fc,ccenter,cv,floops::Face2CellLoop{NBF,NF,VT}) where {NBF,NF,VT}

    #Qf at bfaces
    @inbounds for i=1:NBF
        j = bf2c[i][1]
        qf = bpart(bcond[bt[i]],i,j,bfc,bfn,k,bccenter,Tc)
        out[j] += qf*bcv[i]
    end

    #first Tf
    if !(typeof(Tf)<:FaceSimpleInterpolation)
        @inbounds for i=1:NF
            j1,j2 = f2c[i].ind
            Tf[i] = 0.5*(Tc[j1] + Tc[j2])
        end
    end
    gradient!(∇Tc, Tf, Tbf,floops)

    @inbounds for i=1:NF
        j1, j2 = f2c[i]
        n = fn[i]
        v1, v2 = cv[i]
        ∇T̂f = 0.5*(∇Tc[j1] + ∇Tc[j2])
        Lac = ccenter[i][2] - ccenter[i][1]
        L2 = Lac⋅Lac
        qf = k[i] * ((Tc[j2] - Tc[j1])*(Lac⋅n)/L2 + ∇T̂f⋅n - (∇T̂f⋅Lac)*(Lac⋅n)/L2)
        out[j1] += qf*v1
        out[j2] -= qf*v2
    end

end

@inline laplacian_CGauss!(out, ∇Tc, Tc, Tf, Tbf,k,bcond,floops) = laplacian_CGauss!(out, ∇Tc, Tc, Tf, Tbf,k,
    floops.bf2c,floops.bft,bcond,floops.bfn,floops.bfc,floops.bccenter,floops.bcv,
    floops.f2c,floops.fn,floops.fc,floops.ccenter,floops.cv,
floops)

abstract type AbstractLaplacian end

struct CorrectedGauss{ArrayType} <: AbstractLaplacian
    ff::ArrayType
end

CorrectedGauss(Tc,mesh) = CorrectedGauss(FaceSimpleInterpolation(Tc,mesh))

function (l::CorrectedGauss)(rhs,p::CellProblemAdvecTemp)
    laplacian_CGauss!(rhs, p.∇Tc, p.Tc, l.ff, p.Tbf, p.k, p.bcond, p.mesh.f2cloops)
end

function (l::CorrectedGauss)(rhs::Vec2DArray,p::Union{StokesProblem,NSProblem})
    laplacian_CGauss!(rhs, p.∇u, p.u, l.ff, p.ubf, p.ν, p.bcond, p.mesh.f2cloops)
end

function (l::CorrectedGauss)(rhs::AbstractArray{T,1},p::NSProblem) where T<:Number
    laplacian_CGauss!(rhs, p.∇Tc, p.Tc, l.ff, p.Tbf, p.k, p.Tbcond, p.mesh.f2cloops)
end

struct Gauss{ArrayType} <: AbstractLaplacian
    Tf::ArrayType
end

Gauss(Tc,mesh) = Gauss(FaceSimpleInterpolation(Tc,mesh))

function (l::Gauss)(rhs,p)
    laplacian_Gauss!(rhs, p.∇Tc, p.Tc, l.Tf, p.Tbf, p.k, p.bcond, p.mesh.f2cloops)
end

struct MethodB <: AbstractLaplacian
end

function (l::MethodB)(rhs,p::CellProblemAdvecTemp)
    laplacian_mB!(rhs, p.Tc, p.k, p.bcond, p.mesh.f2cloops)
end

function (l::MethodB)(rhs::Vec2DArray,p::Union{StokesProblem,NSProblem})
    laplacian_mB!(rhs, p.u, p.ν, p.bcond, p.mesh.f2cloops)
end

function (l::MethodB)(rhs::AbstractArray{T,1},p::Union{StokesProblem,NSProblem}) where {T<:Number}
    laplacian_mB!(rhs, p.Tc, p.k, p.Tbcond, p.mesh.f2cloops)
end

struct CellMimetic{M<:AbstractMatrixLike,P,ArrayT} <: AbstractLaplacian
    A::M
    pcg::P
    b::ArrayT
    x::ArrayT
end

function CellMimetic(k,bcond,m)
    nbf = nbfaces(m)
    nf = nfaces(m)
    nt = nbf+nf
    x = FaceVector{eltype(bcond[1]),nf,nbf,nt}(zeros(eltype(bcond[1]),nt))
    b = similar(x)
    pcg = PCG(x)
    A = MimeticGrad(k,bcond,m)
    return CellMimetic{typeof(A),typeof(pcg),typeof(b)}(A,pcg,b,x)
end

function (l::CellMimetic)(rhs,p::CellProblemAdvecTemp)
    set_x_and_b!(l,p,p.Tc,p.Tbf,p.bcond,p.k,p.mesh.f2cloops)
    solve!(l.pcg,l.A,l.x,l.b,true)
    laplacian_mimetic!(rhs, l.x, p.bcond, p.Tc, p.∇Tc, l.A.rcf, l.A.brcf, p.k, p.mesh.f2cloops)
end

function (l::CellMimetic)(rhs,p::StokesProblem)
    set_x_and_b!(l,p,p.u,p.ubf,p.bcond,p.ν,p.mesh.f2cloops)
    solve!(l.pcg,l.A,l.x,l.b,true)
    laplacian_mimetic!(rhs, l.x, p.bcond, p.u, p.∇u, l.A.rcf, l.A.brcf, p.ν, p.mesh.f2cloops)
end

function set_x_and_b!(l,p,Tc,Tbf,bcond,k,f2cl::Face2CellLoop)

    nbf = nbfaces(f2cl)
    nf = nfaces(f2cl)
    b = l.b
    x = l.x
    bf2c = f2cl.bf2c

    bfc = f2cl.bfc
    bfn = f2cl.bfn
    bccenter = f2cl.bccenter
    bt = f2cl.bft
    bcv = f2cl.bcv
    @inbounds for i = 1:nbf
        j = bf2c[i][1]
        b[:b,i] = Tbf[i] - Tc[j]
        x[:b,i] = bpart(bcond[bt[i]],i,j,bfc,bfn,k,bccenter,Tc)
    end

    f2c = f2cl.f2c

    @inbounds for i = 1:nf
        j1,j2 = f2c[i]
        b[i] = Tc[j2] - Tc[j1]
    end

end

# The gradient calculation is not right. It seems to be off by some multiplication factor.
function laplacian_mimetic!(rhs,x,bcond,Tc,∇Tc,rcf,brcf,k,f2cl::Face2CellLoop)
    nbf = nbfaces(f2cl)
    nf = nfaces(f2cl)

    bf2c = f2cl.bf2c

    bt = f2cl.bft
    bcv = f2cl.bcv
    @inbounds for i=1:nbf
        j = bf2c[i][1]
        bch = bcond[bt[i]]
        if typeof(bch) <: Neumman
            rhs[j] += flux(bch)*bcv[i]
            ∇Tc[j] += flux(bch)*brcf[i]/k[i]
        else
            rhs[j] += x[:b,i]*bcv[i]
            ∇Tc[j] += x[:b,i]*brcf[i]*bcv[i]/k[i]
        end
    end

    f2c = f2cl.f2c
    cv = f2cl.cv
    @inbounds for i=1:nf
        j1, j2 = f2c[i]
        cv1,cv2 = cv[i]
        qf = x[i]
        rhs[j1] += qf*cv1
        rhs[j2] -= qf*cv2

        rc1,rc2 = rcf[i]
        ∇Tc[j1] += qf*rc1*cv1/k[i]
        ∇Tc[j2] -= qf*rc2*cv2/k[i]
    end

end

@inline is_implicit(a::AbstractLaplacian) = false
abstract type AbstractImplicitLaplacian <: AbstractLaplacian end
@inline is_implicit(a::AbstractImplicitLaplacian) = true

struct ImplicitDiff{M<:AbstractMatrixLike,P,T<:Number} <: AbstractImplicitLaplacian
    A::M
    pcg::P
    b::Vector{T}
end

function ImplicitDiff(k,bcond,mesh,d)
    dtrhoc = d[:dt]*d[Symbol("rho*C")]
    A = ImplicitMethodB{typeof(mesh),typeof(bcond),typeof(k),typeof(dtrhoc)}(mesh,bcond,k,dtrhoc)
    pcg = PCG(zeros(length(mesh.cells)),KrylovCtrl("tPCG.set"))
    b = zeros(eltype(k),length(mesh.cells))
    return ImplicitDiff{typeof(A),typeof(pcg),eltype(k)}(A,pcg,b)
end

function (l::ImplicitDiff)(rhs,p)
    set_x_and_b!(l,l.b,rhs,p)
    solve!(l.pcg,l.A,rhs,l.b,true)
    return nothing
end

function set_x_and_b!(l::ImplicitDiff,b,rhs,p)
    A_mul_B!(b,l.A,p.Tc)
    Tc = p.Tc
    dtrhoc = l.A.dtrhoc
    @inbounds @simd for i in linearindices(b)
        b[i] = Tc[i] + dtrhoc*rhs[i] - b[i]
    end
    return nothing
end


@inline function _AudzeEglaisDist(LHC)
    n,d = size(LHC)
    dist = 0.0
    dist_tmp = 0.0

    # Squared l-2 norm of distances between all (unique) points
    for i = 2:n
        for j = 1:i-1
            for k = 1:d
                @inbounds dist_tmp += (LHC[i,k]-LHC[j,k])^2
            end
            @inbounds dist += 1/dist_tmp
            dist_tmp = 0.0
        end
    end
    output = 1/dist
    return output
end

function _AudzeEglaisObjective(dim::Continuous,LHC)
    output = _AudzeEglaisDist(LHC)
    return output
end

function _AudzeEglaisObjective(dim::Categorical,LHC)
    output = _AudzeEglaisDist(LHC)
    output == Inf ? 0 : output
end

"""
    function AudzeEglaisObjective!(LHC::T) where T <: AbstractArray
Return the scalar which should be maximized when using the Audze-Eglais
distance as the objective function. Note this is the inverse of the typical
Audze-Eglais distance which normally is minimized.
"""
function AudzeEglaisObjective(LHC::T; dims::Array{V,1} =[Continuous() for i in 1:size(LHC,2)],
                                            interSampleWeight::Float64=1.0,
                                            ) where T <: AbstractArray where V <: LHCDimension

    out = 0.0

    #Compute the objective function among all points
    out += _AudzeEglaisObjective(Continuous(),LHC)*interSampleWeight

    #Compute the objective function within each categorical dimension
    categoricalDimInds = findall(x->typeof(x)==Categorical,dims)
    for i in categoricalDimInds
        for j = 1:dims[i].levels
            subLHC = @view LHC[LHC[:,i] .== j,:]
            out += _AudzeEglaisObjective(dims[i],subLHC)*dims[i].weight
        end
    end

    return out
end

# Remove depwarning in release 2.x.x
function AudzeEglaisObjective!(dist,LHC::T; dims::Array{V,1} =[Continuous() for i in 1:size(LHC,2)],
                                            interSampleWeight::Float64=1.0,
                                            ) where T <: AbstractArray where V <: LHCDimension
    @warn "AudzeEglaisObjective!(dist,LHC) is deprecated and does not differ from AudzeEglaisObjective(LHC)"
    AudzeEglaisObjective(LHC; dims = dims, interSampleWeight = interSampleWeight)
end


# - Julia online documentation
# - Collections and Data Structures
# - Iteration

using Base.Test
# ============================================= #
# -
# - Base.start(collection) -> iterator
# -
# - I personally do not like the terminology
# - used in Julia documentation.
# -
@testset "Base.start" begin
# --------------------------------------------- #
c = 1:5
# println(c) -> 1:5
@test length(c) == 5

it = Base.start(c)
@test it == 1::Int64
# println(typeof(state)) -> Int64

# --------------------------------------------- #
c = [1;2;3]
@test typeof(c) == Array{Int64, 1}
# println(c) -> [1, 2, 3] # Vector
@test length(c) == 3
@test ndims(c) == 1
@test size(c) == (3,) # tailing comma makes it a tuple

it = Base.start(c)
@test it == 1::Int64

# --------------------------------------------- #
c = [1 2 3]
@test typeof(c) == Array{Int64, 2}
# println(c) -> [1 2 3] # 1x3 matrix
@test length(c) == 3
@test ndims(c) == 2
@test size(c) ==(1,3)
@test size(c,1) == 1
@test size(c,2) == 3

it = Base.start(c)
@test it == 1::Int64

# --------------------------------------------- #
c = [3;2;1]
@test typeof(c) == Array{Int64, 1}
# println(c) -> [1, 2, 3] # Vector
@test length(c) == 3
@test ndims(c) == 1
@test size(c) == (3,) # tailing comma makes it a tuple

it = Base.start(c)
@test it == 1::Int64
end
# ============================================= #
# -
# - Base.done(collection, iterator) -> Bool
# -
@testset "Base.done" begin
c::Vector{Int64} = 1:5

# - done() returns true when
# - iterator is 1 pass the last index
it = 6::Int64
@test Base.done(c, it) == true

# - 2 pass the end is false
it = 7::Int64
@test Base.done(c, it) == false

# - Before the last index is definitely false
it = 3::Int64
@test Base.done(c, it) == false

it = 5::Int64
@test Base.done(c, it) == false
end
# ============================================= #
# - Base.next(collection, iterator) ->
# -   (item pointed by iterator, next_iterator)
@testset "Base.next" begin
c::Vector{Int64} = 5:-1:1
it = Base.start(c)
@test it == 1

item, it = Base.next(c, it)
@test item == 5
@test it == 2

# assign to a tuple
t = Base.next(c, it)
@test t[1] == 4 # item
@test t[2] == 3 # next iterator
end
# ============================================= #
@testset "Base iterator" begin
c::Vector{Int64} = 5:-1:1
@test Base.iteratorsize(c) == Base.HasShape()

@test Base.iteratoreltype(c) == Base.HasEltype()
end

# ============================================= #
# --------------------------------------------- #


using OctreeBH
using StaticArrays

const BOXSIZE = 1.0

const N = 3 #spatial dimension
const T = Float64

Npart = 100000 #number of particles

#desired number of neighbor particles (ngbs) within a search radius (hsml)
Nngb0 = 32

#searching radius that should contain roughly Nngb0 neighboring particles
hsml0 = BOXSIZE * (Nngb0/(4*pi/3*Npart))^(1/N)

@testset "ngbs" begin

#randomly distributing particles
X = [@SVector rand(N) for _ in 1:Npart]

topnode_length = @SVector(ones(N)) * BOXSIZE  #actual length of tree

center = topnode_length .* 0.5

#individual smoothing length (only relevant for scatter ngbs)
δ = 0.0 #add a small perturbation
hsml = ones(Npart) .* hsml0 .* ( 1 .+ δ .* (rand(Npart) .- 0.5) )

mass = ones(Npart) #particle mass

part = [Data{N,T}(SVector(X[i]), i, hsml[i], mass[i]) for i in eachindex(X)]

#build the tree
tree = buildtree(part, center, topnode_length);

#search center
x = @SVector rand(N)

boxsizes = @SVector(ones(N)) * BOXSIZE  #for periodic B.C.

#get indices of the gather neighbors
idx_ngbs_g = get_gather_ngb_tree(x, hsml0, tree, boxsizes)

#get indices of the scatter neighbors
idx_ngbs_s = get_scatter_ngb_tree(x, tree, boxsizes)

#for constant hsml, the two ngbs are identical
@test all(sort(idx_ngbs_g) .== sort(idx_ngbs_s))

end


function ueberpruefekeineLoesung(A1,b1,A3,b3,blick1,blick2)

if size(A3,1) == 3 && size(A3,2) == 2 && size(b3,1) == 3 && size(b3,2) == 1
    y1 = inv(transpose(A1)*A1)*transpose(A1)*b1;
    if norm(transpose(A3[3,:])*y1-b3[3]) < 0.0001
        println("Die Größe eures Gleichungssystems stimmt zwar, allerdings lässt es sich immernoch eindeutig lösen.")
    else
        println("Ihr habt den Vektor korrekt verändert. Das führt dazu, dass es keine Lösung mehr hat.");
        println("Das Problem ist, dass egal, welchen Vektor man als x bei A*x von euch einsetzt, bekommt man nicht euren b-Vektor heraus. Veranschaulicht könnt ihr das in der folgenden Graphik sehen. Alle möglichen b-Vektoren für unterschiedliche x liegen in der roten Ebene. Euer Punkt jedoch liegt außerhalb.");
        println("Ändert solange die Werte für blick1 und blick2, so dass man erkennt, dass der Punkt neben der Ebene liegt.")
        U2,S2,V2 = svd(A3)
        u = U2[:,1];
        v = U2[:,2];
        s = -20*u-20*v;
        a = b3[1];
        b = b3[2];
        c = b3[3];
        plt = surface([(s[1],s[2],s[3]),(30*u[1],30*u[2],30*u[3]),(30*v[1],30*v[2],30*v[3])],alpha=.1, fillalpha=.1)
        plot3d!([a],[b],[c],marker=2,xlim=(-14,14),ylim=(-14,14),zlim=(-14,14), camera = (blick1,blick2),cbar= :none, leg = false,xlabel = "x_1",ylabel = "x_2",zlabel = "x_3")

        plt
    end
else
    println("Ihr habt noch einen Fehler bei der Anzahl der Einträge in der Matrix oder dem Vektor. Stellt noch einmal sicher, dass ihr euer lineares Gleichungssystem aus a) um eine Zeile erweitert habt. Das heißt, dass sowohl der Vektor, als auch die Matrix, um eins in die Länge wachsen muss.")
end
display(plt);
return u,v;
end


using ConservationLawsParticles
using SpecialFunctions, RecursiveArrayTools, DifferentialEquations, Plots

# model
V(t, x) = 0.0 # 2 + 0.5sin(2x) + 0.3sin(2sqrt(2)*x) + 0.2cos(2sqrt(7)*x)
W(t, x) = 0.0 # x^2/2 - abs(x)
W′(t, x) = 0.0 # x - sign(x)
mob(ρ) = 0.0 # max((ρ-1)^2 * (2ρ+1), 0)
smodel = DiffusiveSampledModel((V,), ((W′,),), (mob,), (Diffusion(1),))
imodel = DiffusiveIntegratedModel((V,), ((W,),), (mob,), (Diffusion(1),))

# initial condition
n = 20
x0 = ArrayPartition(vcat(
    range(-3, -2; length=2n),
    range(-1, 0; length=n),
    range(1, 2; length=3n)
    ))

boxsol(t, x; a, b) = (erf((x-a)/2sqrt(t)) - erf((x-b)/2sqrt(t))) / (2*(b-a))
refsol(t, x) = (
        boxsol(t, x; a=-3, b=-2) * (2n-1) +
        boxsol(t, x; a=-1, b=0) * (n-1) +
        boxsol(t, x; a=1, b=2) * (3n-1) +
        boxsol(t, x; a=-2, b=-1) + boxsol(t, x; a=0, b=1)
    ) / (6n-1)

# time span
tspan = (0., 1.0)

# ODE system for the particles
sprob = ODEProblem(velocities_diff!, x0, tspan, smodel)
# iprob = ODEProblem(velocities_diff!, x0, tspan, imodel)

abstol = reltol = 2e-8

@time ssol = solve(sprob, BS5(); abstol=abstol, reltol=reltol);

# plot the particle trajectories
plot(legend=false, title="Trajectories", xlabel="time", ylabel="position")
plot!(ssol, vars=eachindex(x0)[1:5:end]; color=:blue)
savefig("diffusion3.png")

# plot an animation of the density
anim = @animate for t in range(tspan...; length=101)
    plot(title="Density", xlabel="position", ylabel="density",
        xlims=(-7,7), ylims=(0,1/sqrt(pi)))
    plot!(x -> refsol(t, x); color=:blue, label="analytic")
    densityplot!(ssol(t); color=:red, label="particles")
end
gif(anim, "diffusion3.gif")


import Combinatorics: permutations

function parsedata(raw_data)
    data = []
    for line in raw_data
        line = split(line, "\t")
        line = parse.(Int, line)
        push!(data, line)
    end
    data
end

function solve_a(data)
    total = 0

    for line in data
        total += maximum(line) - minimum(line)
    end
    total
end

function solve_b(data)
    total = 0
    for line in data
        for (i, j) in permutations(line,2)
            if i % j == 0
                total += i ÷ j
            end
        end
    end
    total
end

raw_data = readlines("input.txt")
data = parsedata(raw_data)
sol_a = solve_a(data)
println("sol a: $(sol_a)")

sol_b = solve_b(data)
println("sol b: $(sol_b)")


# ----------------------------------------
# FUNCTIONS
# ----------------------------------------

function distance_matrix(left, right; deletion = 1, insertion = 1, substitution = 1)
    function complete(left, right)
        distance = zeros(Int64, length(left) + 1, length(right) + 1)

        for l ∈ 1:length(left)
            @inbounds distance[l + 1, 1] = l * deletion
        end

        for r ∈ 1:length(right)
            @inbounds distance[1, r + 1] = r * insertion
        end

        for l ∈ 1:length(left)
            for r ∈ 1:length(right)
                @inbounds distance[l + 1, r + 1] = min(distance[l, r + 1] + deletion,
                                                       distance[l + 1, r] + insertion,
                                                       distance[l, r] + (left[l] == right[r] ? 0 : substitution))
            end
        end

        return distance
    end

    if length(left) == 0
        return length(right)

    elseif length(right) == 0
        return length(left)

    else
        return complete(left, right)[end]
    end
end


"""
    FixedPoint.jl
exports `afps` function, for further help type `?afps` in the REPL.
"""
module FixedPoint
"""
    afps(f, x; iters::Int = 5000, vel::Float64 = 0.9, ep::Float64 = 0.01, tol::Float64 = 1e-12, grad_norm=x->maximum(abs,x))

solve equation `f(x) = x` according to:

    `f` : function to find fixed point for

    `x` : initial condition, ideally it should be close to the final solution

    `vel` : amount of Nesterov acceleration in [0,1]

    `ep` : learning rate, typically in ]0,1[

    `tol` : absolute tolerance on |f(x)-x|

    `grad_norm` : function to evaluate the norm for |f(x)-x|

returns a named tuple (x, error, iters) where:

    `x` : is the solution found for f(x)=x

    `error` : is the norm of f(x)-x at the solution point

    `iters` : total number of iterations performed
"""
function afps(
    f::Fun,
    x::Mat;
    iters::Int = 5000,
    vel::T = 0.9,
    ep::T = 0.01,
    tol::T = 1e-12,
    grad_norm = x -> maximum(abs, x),
) where {T <: Number, Mat<:Union{AbstractArray{T},T}, Fun <: Function}
    x_n = identity.(x)
    v_n = zero(x_n)
    β = vel
    ϵ = ep
    runs = 0
    for _ = 1:iters
        trial = x_n + β * v_n
        g = f(trial) - trial
        v_n = β * v_n + ϵ * g
        x_n = x_n + v_n
        runs += 1
        if grad_norm(g) < tol
            break
        end
    end
    (x = x_n, error = grad_norm(f(x_n) - x_n), iters = runs)
end

"""
    afps!(f!, x; iters::Int = 5000, vel::Float64 = 0.9, ep::Float64 = 0.01, tol::Float64 = 1e-12, grad_norm=x->maximum(abs,x))

solve equation `f(x) = x` according to:

    `f!` : inplace version of function to find fixed point for, calling `f!(out,x)` should amount to writing `out = f(x)`

    `x` : initial condition, ideally it should be close to the final solution

    `vel` : amount of Nesterov acceleration in [0,1]

    `ep` : learning rate, typically in ]0,1[

    `tol` : absolute tolerance on |f(x)-x|

    `grad_norm` : function to evaluate the norm for |f(x)-x|

returns a named tuple (x, error, iters) where:

    `x` : is the solution found for f(x)=x

    `error` : is the norm of f(x)-x at the solution point

    `iters` : total number of iterations performed
"""
function afps!(
    f!::Fun,
    x_n::Mat;
    iters::Int = 5000,
    vel::T = 0.9,
    ep::T = 0.01,
    tol::T = 1e-12,
    grad_norm = x -> maximum(abs, x),
) where {T <: Number, Mat<:Union{AbstractArray{T},T}, Fun <: Function}
    v_n = zero(x_n)
    trial = zero(x_n)
    g = zero(x_n)
    β = vel
    ϵ = ep
    runs = 0
    for _ = 1:iters
        #prediction using velocity
        x_n .+= β .* v_n
        #eval gradient
        f!(g,x_n)
        g .-= x_n
        #correction
        x_n .+= ϵ .* g
        #update velocity
        v_n .= β .* v_n .+ ϵ .* g
        runs += 1
        if grad_norm(g) < tol
            break
        end
    end
    (x = x_n, error = grad_norm(g), iters = runs)
end

export afps,afps!
end


export ZDT, ZDT1, ZDT2, ZDT3, ZDT4, ZDT6;

zdt_ref_string = "Zitzler et al. - 2000 - Comparison of Multiobjective Evolutionary Algorithms";
@doc """
    abstract type ZDT <: MOP end

Abstract super type for the class of ZDT problems as defined in
$(zdt_ref_string)

These are biobjective problems with a variable number of decision variables
`n_vars >= 2`.
They share a common structure
```math
\\begin{aligned}
    f_1(\\mathbf{x}) &= f( x_1 ),\\\\
    f_2(\\mathbf{x}) &= g( x_2, …, x_n ) · h( f_1( x_1 ), g( x_2, …, x_n ) )
\\end{aligned}
```
"""
abstract type ZDT <: MOP end;

# some ipmlementations that all problems share
num_vars( mop :: Z where Z<:ZDT ) = mop.n_vars; # make sure below that every implementation has field n_vars
num_objectives( mop :: Z where Z<:ZDT ) = 2;

get_pareto_set( mop :: Z where Z<:ZDT ) = SamplingFunction( mop, :ParetoSet, [:regular, :random] );
get_pareto_front( mop :: Z where Z<:ZDT ) = SamplingFunction( mop, :ParetoFront, [:regular, :random] );

function get_objectives( mop :: Z where Z<:ZDT )
    f1, g, h = zdt_helper_functions( mop )
    f2(x :: Vector{R} where R<:Real) = g(x) * h(f1(x), g(x))
    return [f1; f2]
end

########## ZDT 1 ##############
@doc """
    ZDT1( n_vars :: Int )

Bi-Objective problem ZDT1 with convex Pareto Front as given in
$(zdt_ref_string).

The objectives are given as
```math
\\begin{aligned}
    f_1( x_1 ) &= x_1,\\\\
    f_2( x_1, …, x_n) &= g( x_2, …, x_n ) · h( f_1( x_1 ), g( x_2, …, x_n ) ), \\\\
    g( x_2, …, x_n ) &= 1 + (9/(n-1)) · Σ_i x_i, \\\\
    h( f1, g ) &= 1 - \\sqrt{f_1/g}.
\\end{aligned}
```
"""
@with_kw struct ZDT1 <: ZDT
    n_vars :: Int = 2;
end

constraints( mop :: ZDT1 ) = Box( zeros(mop.n_vars), ones( mop.n_vars) );

function zdt_helper_functions( mop :: ZDT1 )
    h(f1,g) = 1 - sqrt( f1 / g );
    g(x) = 1 + 9 / (num_vars(mop) - 1) * sum(x[2:end]);
    f1(x :: Vector{R} where R<:Real) = x[1]
    return [f1, g, h]
end

function get_ideal_point(mop::ZDT1)
    # min of f1 = x is mop.lb[1] = 0
    # min of g is 1, min of h is 1 - sqrt(ub[1]) = 0
    return zeros(2);
end

########## ZDT 2 ##############
@doc """
    ZDT2( n_vars :: Int )

Bi-Objective problem ZDT2 with **non**convex Pareto Front as given in
$(zdt_ref_string).

The objectives are given as
```math
\\begin{aligned}
    f_1( x_1 ) &= x_1,\\\\
    f_2( x_1, …, x_n) &= g( x_2, …, x_n ) · h( f_1( x_1 ), g( x_2, …, x_n ) ), \\\\
    g( x_2, …, x_n ) &= 1 + (9/(n-1)) · Σ_i x_i, \\\\
    h( f1, g ) &= 1 - (f_1/g)².
\\end{aligned}
```
"""
@with_kw struct ZDT2 <: ZDT
    n_vars :: Int = 2;
end

constraints( mop :: ZDT2 ) = Box( zeros(mop.n_vars), ones( mop.n_vars) );

function zdt_helper_functions(mop::ZDT2)
    h(f1,g) = 1 - ( f1 / g )^2;
    g(x) = 1 + 9 / (num_vars(mop) - 1) * sum(x[2:end]);
    f1(x :: Vector{R} where R<:Real) = x[1]
    return [f1, g, h]
end

get_ideal_point(mop::ZDT2) = zeros(2);

########## ZDT 3 ##############
@doc """
    ZDT3( n_vars :: Int )

Bi-Objective problem ZDT3 with discrete Pareto Front as given in
$(zdt_ref_string).

The objectives are given as
```math
\\begin{aligned}
    f_1( x_1 ) &= x_1,\\\\
    f_2( x_1, …, x_n) &= g( x_2, …, x_n ) · h( f_1( x_1 ), g( x_2, …, x_n ) ), \\\\
    g( x_2, …, x_n ) &= 1 + (9/(n-1)) · Σ_i x_i, \\\\
    h( f1, g ) &= 1 - \\sqrt{f_1/g}- (f_1/g)·\\sin( 10 π f_1 )
\\end{aligned}
```
"""
@with_kw struct ZDT3 <: ZDT
    n_vars :: Int = 2;
end

constraints( mop :: ZDT3 ) = Box( zeros(mop.n_vars), ones( mop.n_vars) );

function zdt_helper_functions(mop::ZDT3)
    h(f1,g) = 1 - sqrt( f1 / g ) - (f1 / g) * sin(10 * pi * f1);
    g(x) = 1 + 9 / (num_vars(mop) - 1) * sum(x[2:end]);
    f1(x :: Vector{R} where R<:Real) = x[1]

    return [f1, g, h]
end
get_ideal_point(mop::ZDT3) = zeros(2);

########## ZDT 4 ##############
@doc """
    ZDT4( n_vars :: Int )

Bi-Objective problem ZDT4 with many local Pareto Fronts as given in
$(zdt_ref_string).

The multimodality is due to ``g`` being a version of Rastrigin's function.

The objectives are given as
```math
\\begin{aligned}
    f_1( x_1 ) &= x_1,\\\\
    f_2( x_1, …, x_n) &= g( x_2, …, x_n ) · h( f_1( x_1 ), g( x_2, …, x_n ) ), \\\\
    g( x_2, …, x_n ) &= 1 + (9/(n-1)) · Σ_i ( x_i^2 - \\cos( 4 π x_i ) ), \\\\
    h( f1, g ) &= 1 - \\sqrt{f_1/g}
\\end{aligned}
```
"""
@with_kw struct ZDT4 <: ZDT
    n_vars :: Int = 2;
end

constraints( mop :: ZDT4 ) = Box(
    [ 0; fill(-5.0, mop.n_vars - 1) ],
    [ 1.0; fill(5.0, mop.n_vars - 1) ]
);

function zdt_helper_functions(mop::ZDT4)
    h(f1,g) = 1 - sqrt( f1 / g )
    g(x) = 1 + 10 * ( num_vars(mop) -1 ) + sum( x[2:end].^2 -10*cos.( 4*pi .* x[2:end]) )
    f1(x :: Vector{R} where R<:Real) = x[1]

    return [f1, g, h]
end
get_ideal_point(mop::ZDT4) = zeros(2);

get_critical_set( mop :: Z where Z<:ZDT ) = SamplingFunction( mop, :ParetoCriticalSet, [:regular, :random] );

########## ZDT 5 ##############
# not implemented yet due to it having binary variables

########## ZDT 6 ##############
@doc """
    ZDT6( n_vars :: Int )

Bi-Objective problem ZDT6 with biased Pareto Front as given in
$(zdt_ref_string).

The objectives are given as
```math
\\begin{aligned}
    f_1( x_1 ) &= 1 - \\exp(-4 x_1 ) \\sin^6( 6 π x_1) ,\\\\
    f_2( x_1, …, x_n) &= g( x_2, …, x_n ) · h( f_1( x_1 ), g( x_2, …, x_n ) ), \\\\
    g( x_2, …, x_n ) &= 1 + 9 · Σ_i ( x_i / (n-1) )^{1/4}, \\\\
    h( f1, g ) &= 1 - (f_1/g)²
\\end{aligned}
```
"""
@with_kw struct ZDT6 <: ZDT
    n_vars :: Int = 2;
end

constraints( mop :: ZDT6 ) = Box( zeros(mop.n_vars), ones( mop.n_vars) );

function zdt_helper_functions(mop::ZDT6)
    h(f1,g) = 1 - ( f1 / g )^2;
    g(x) = 1 + 9 * ( sum(x[2:end])/(num_vars(mop)-1) )^0.25;
    f1(x :: Vector{R} where R<:Real) = x[1] - exp( -4*x[1] ) * (sin(6*pi*x[1]))^6;

    return [f1, g, h]
end
get_ideal_point( mop::ZDT6 ) = zeros(2);

####### Point Sampling Functions #########################

#helper
function zdt_x_range(mop :: Z where Z<:ZDT, method :: Symbol, num_points::Int,
    x1 :: Real, x2 :: Union{Nothing,Vector{R} where R<:Real} = nothing )

    if isnothing(x2)
        x2 = zeros( num_vars(mop) -1 );
    end

    @assert length(x2) == num_vars(mop) - 1;

    if method == :regular
        x_range = num_points == 1 ? [x1,] : range(x1, 1.0; length = num_points );
    elseif method == :random
        x_range = [ x1 + (1-x1) * rand() for i = 1 : num_points ]
    end
    return [ [x; x2] for x in x_range ]
end

@doc """
    get_points( sample_func, zdt, Val(:ParetoSet), N; method = :regular )

Return a list of points ``x ∈ ℝⁿ`` where ``x₁ = … = xₙ = 0``.
Methods are `:regular` or `:random`.
"""
function get_points( sample_func :: SamplingFunction, mop :: Union{ZDT1,ZDT2,ZDT4},
        ::Val{:ParetoSet}, num_points :: Int; method = :regular )
    zdt_x_range(mop, method, num_points, 0.0)
end

function get_points( sample_func :: SamplingFunction, mop :: Union{ZDT6},
        ::Val{:ParetoSet}, num_points :: Int; method = :regular )
    # see
    # https://sop.tik.ee.ethz.ch/download/supplementary/testproblems/zdt6/index.php
    x1 = .2807753191;
    zdt_x_range( mop, method, num_points, x1)
end

# ZDT 3 is a special snowflake …
function sample_from_range( arr, N, ::Val{:regular} )
    [ arr[1] + (arr[2] - arr[1]) * x for x in range(0, 1;length = N) ]
end

function sample_from_range( arr, N, ::Val{:random} )
    [ arr[1] + (arr[2] - arr[1]) * rand() for i = 1 : N ]
end

function get_points( :: SamplingFunction, mop :: ZDT3, ::Val{:ParetoSet},
        num_points :: Int; method = :regular )
    # see
    # https://sop.tik.ee.ethz.ch/download/supplementary/testproblems/zdt3/index.php

    chunk_size = num_points ÷ 5;

    x_ranges = [
        [0, 0.0830015349],
            [[1e-10, 0.0] .+ v
            for
            v ∈ [
                [0.1822287280, 0.2577623634],
                [0.4093136748, 0.4538821041],
                [0.6183967944, 0.6525117038],
                [0.8233317983, 0.8518328654],
            ]]...
    ];

    x_range = Float64[];
    for i = 1 : 5
        if i == 1
            N = chunk_size + (num_points % chunk_size)
        else
            N = chunk_size
        end
        push!( x_range, sample_from_range( x_ranges[i], N, Val(method) )... )
    end
    return [ [x; zeros( num_vars(mop) -1 )] for x in x_range ]
end

function get_points( :: SamplingFunction, mop :: ZDT4, ::Val{:ParetoCriticalSet},
        num_points :: Int; x0 = nothing, method = :regular )
    #@eval using NLopt;

    if isnothing(x0)
        x̂ = zeros(mop.n_vars - 1) # global optimum;
    else
        if length(x0) == mop.n_vars
            x0 = x0[2:end];
        end

        if mop.n_vars == 2 && isa( x0, Real )
            x0 = [x0,]
        end

        _, g, _ = zdt_helper_functions( mop );
        # gradient via autodiff
        # g does not depend on x1 = 0.0
        ∇g = x -> ForwardDiff.gradient( g, [0.0; x] )[2:end];

        opt_func = function( x::Vector, grad::Vector)
            if length(grad) > 0
                grad[:] = ∇g(x)
            end
            return g(x)
        end

        box = constraints( mop );

        opt = NLopt.Opt( :LD_MMA, length(x0) );
        opt.lower_bounds = box.lb[2:end];
        opt.upper_bounds = box.ub[2:end];

        opt.xtol_rel = 1e-5;
        opt.maxeval = 200 * length(x0);

        println("Starting search for local minimum of g for ZDT4 with x0:")

        opt.min_objective = opt_func;
        _, x̂, ret =  NLopt.optimize( opt, x0 );
        #@show x̂
        println("Finished with $(opt.numevals) eval(s).")
    end

    return zdt_x_range( mop, method, num_points, 0.0, x̂ );
end

get_critical_front( mop :: Z where Z<:ZDT ) = SamplingFunction( mop, :ParetoCriticalFront, [:regular, :random] );


module Quadratures

import GaussQuadrature
import StaticArrays: SVector, SMatrix, MMatrix
import LinearAlgebra: Diagonal

export QuadratureStyle,
    GLL, GL, polynomial_degree, degrees_of_freedom, quadrature_points

"""
   QuadratureStyle

Quadrature style supertype. See sub-types:
 - [`GLL`](@ref)
 - [`GL`](@ref)
 - [`Uniform`](@ref)
"""
abstract type QuadratureStyle end

"""
    polynomial_degree(QuadratureStyle) -> Int

Returns the polynomial degree of the `QuadratureStyle` concrete type
"""
function polynomial_degree end

"""
    degrees_of_freedom(QuadratureStyle) -> Int

Returns the degrees_of_freedom of the `QuadratureStyle` concrete type
"""
function degrees_of_freedom end

"""
    points, weights = quadrature_points(::Type{FT}, quadrature_style)

The points and weights of the quadrature rule in floating point type `FT`.
"""
function quadrature_points end

"""
    GLL{Nq}()

Gauss-Legendre-Lobatto quadrature using `Nq` quadrature points.
"""
struct GLL{Nq} <: QuadratureStyle end

polynomial_degree(::GLL{Nq}) where {Nq} = Nq - 1
degrees_of_freedom(::GLL{Nq}) where {Nq} = Nq

@generated function quadrature_points(::Type{FT}, ::GLL{Nq}) where {FT, Nq}
    points, weights = GaussQuadrature.legendre(FT, Nq, GaussQuadrature.both)
    :($(SVector{Nq}(points)), $(SVector{Nq}(weights)))
end

"""
    GL{Nq}()

Gauss-Legendre quadrature using `Nq` quadrature points.
"""
struct GL{Nq} <: QuadratureStyle end

polynomial_degree(::GL{Nq}) where {Nq} = Nq - 1
degrees_of_freedom(::GL{Nq}) where {Nq} = Nq

@generated function quadrature_points(::Type{FT}, ::GL{Nq}) where {FT, Nq}
    points, weights = GaussQuadrature.legendre(FT, Nq, GaussQuadrature.neither)
    :($(SVector{Nq}(points)), $(SVector{Nq}(weights)))
end

"""
    Uniform{Nq}()

Uniformly-spaced quadrature.
"""
struct Uniform{Nq} <: QuadratureStyle end

@generated function quadrature_points(::Type{FT}, ::Uniform{Nq}) where {FT, Nq}
    points = SVector{Nq}(range(-1 + 1 / Nq, step = 2 / Nq, length = Nq))
    weights = SVector{Nq}(ntuple(i -> 2 / Nq, Nq))
    :($points, $weights)
end

"""
    barycentric_weights(x::AbstractVector)

The barycentric weights associated with the array of point locations `x`:

```math
w[i] = \\frac{1}{\\prod_{j \\ne i} (x[i] - x[j])
```
Reference:
  - [Berrut2004](@cite) equation 3.2
"""
function barycentric_weights(r::SVector{Nq, T}) where {Nq, T}
    SVector{Nq}(ntuple(Nq) do i
        w = one(T)
        for j in 1:Nq
            if j != i
                w *= (r[j] - r[i])
            end
        end
        inv(w)
    end)
end
@generated function barycentric_weights(
    ::Type{FT},
    quadstyle::QuadratureStyle,
) where {FT}
    barycentric_weights(quadrature_points(FT, quadstyle())[1])
end

function interpolation_matrix(
    points_to::SVector{Nto},
    points_from::SVector{Nfrom},
) where {Nto, Nfrom}
    T = eltype(points_to)
    bw = barycentric_weights(points_from)
    M = zeros(MMatrix{Nto, Nfrom, T, Nto * Nfrom})
    for i in 1:Nto
        x_to = points_to[i]
        skip_row = false
        for j in 1:Nfrom
            if x_to == points_from[j]
                # assign to one to avoid singularity condition
                M[i, j] = one(T)
                # skip over the equal boundry condition
                skip_row = true
            end
            skip_row && break
        end
        skip_row && continue
        w = bw ./ (x_to .- points_from)
        M[i, :] .= w ./ sum(w)
    end
    return SMatrix(M)
end

@generated function interpolation_matrix(
    ::Type{FT},
    quadto::QuadratureStyle,
    quadfrom::QuadratureStyle,
) where {FT}
    interpolation_matrix(
        quadrature_points(FT, quadto())[1],
        quadrature_points(FT, quadfrom())[1],
    )
end

"""
    V = orthonormal_poly(points, quad)

`V[i,j]` contains the `j-1`th Legendre polynomial evaluated at `points[i]`.
i.e. it is the mapping from the modal to the nodal representation.
"""
function orthonormal_poly(
    points::SVector{Np, FT},
    quad::GLL{Nq},
) where {FT, Np, Nq}
    N = Nq - 1
    a, b = GaussQuadrature.legendre_coefs(FT, N)
    if N == 0
        return SMatrix{Np, 1}(ntuple(x -> b[1], Np))
    end
    return SMatrix{Np, Nq}(GaussQuadrature.orthonormal_poly(points, a, b))
end

function spectral_filter_matrix(
    quad::GLL{Nq},
    Σ::SVector{Nq, FT},
) where {Nq, FT}
    points, _ = quadrature_points(FT, quad)
    V = orthonormal_poly(points, quad)
    return V * Diagonal(Σ) / V
end

function cutoff_filter_matrix(
    ::Type{FT},
    quad::GLL{Nq},
    Nc::Integer,
) where {FT, Nq}
    Σ = SVector(ntuple(i -> i <= Nc ? FT(1) : FT(0), Nq))
    return spectral_filter_matrix(quad, Σ)
end

"""
    differentiation_matrix(r::SVector{Nq, T},
                           wb=barycentric_weights(r)::SVector{Nq,T}) where {Nq, T}

The spectral differentiation matrix for a polynomial of degree `Nq` - 1 defined on the
points `r` with associated barycentric weights `wb`.

```math
D_{i,j} = \\begin{cases}
    \\frac{w_j / w_i}{x_i - x_j} \\text{ if } i \\ne j \\\\
    -\\sum_{k \\ne j} D_{k,j} \\text{ if } i = j
\\end{cases}
```

Reference:
 - [Berrut2004](@cite),#https://people.maths.ox.ac.uk/trefethen/barycentric.pdf
"""
function differentiation_matrix(
    r::SVector{Nq, T},
    wb = barycentric_weights(r)::SVector{Nq, T},
) where {Nq, T}
    SMatrix{Nq, Nq, T, Nq * Nq}(
        begin
            if i == j
                D = zero(T)
                for l in 1:Nq
                    if l != i
                        D += one(T) / (r[i] - r[l])
                    end
                end
                D
            else
                (wb[i] / wb[j]) / (r[j] - r[i])
            end
        end for j in 1:Nq, i in 1:Nq
    )
end
@generated function differentiation_matrix(
    ::Type{FT},
    quadstyle::QuadratureStyle,
) where {FT}
    differentiation_matrix(quadrature_points(FT, quadstyle())[1])
end

end # module


"""
    inflation_factor(m::LinearMixedModel, blups=ranef(m), resids=residuals(m)))

Compute how much the standard deviation of the BLUPs/conditional modes and residuals
needs to be inflated in order to match the (restricted) maximum-likelihood estimate.

Due to the shrinkage associated with the random effects in a mixed model, the
variance of the conditional modes / BLUPs / random intercepts and slopes is less
than the variance estimated by the model and displayed in the model summary or
via `MixedModels.VarCorr`. This shrinkage also impacts the observational level
residuals. To compensate for this, the resampled residuals and groups need to be
scale-inflated so that their standard deviation matches that of the estimates in
the original model.

The factor for scale inflation is returned as a `Vector` of inflation factors
for each of the random-effect strata (blocking variables) and the observation-level
variability. The factor is on the standard deviation scale (lower Cholesky factor
in the case of vector-valued random effects).
"""
function inflation_factor(m::LinearMixedModel, blups=ranef(m), resids=residuals(m))
# FIXME I'm not sure this is correct
#       the nonparametric bootstrap underestimates variance components
#       compared to the parametricbootstrap

    σ = sdest(m)
    σres = std(resids; corrected=false)
    inflation = map(zip(m.reterms, blups)) do (trm, re)
        # inflation
        λmle =  trm.λ * σ                               # L_R in CGR
        λemp = cholesky(cov(re'; corrected=false)).L    # L_S in CGR
        # no transpose because the RE are transposed relativ to CGR
        λmle \ λemp
    end

    return [inflation; σ / σres]
end


mutable struct MvTuner{F <: Real}
  current_mean::Vector{F}
  current_cov::Matrix{F}
  beta::F
  iter::Int
  d::Int
end

MvTuner(d::Integer, T::Type=Float64) = MvTuner(zeros(T, d), eye(T, d)*0.01/d,
                                               T(.05), 1, d)

function MvTuner(v::AbstractVector{<:Real})
  d = length(v)
  return MvTuner(v, eye(d)*0.01/d, .05, 1, d)
end

function update!(x::AbstractVector{<:Real}, tuner::MvTuner{<:Real})
  tuner.iter += 1
  update_mean!(tuner.current_mean, x, tuner.iter)
  update_cov!(tuner.current_cov, tuner.current_mean, x, tuner.iter)
end


export interaction_matrix, PowerLaw

abstract type Interaction end

"""
    interaction_matrix(interaction, geometry, positions)
    interaction_matrix(interaction, distances)

Compute the interaction matrix from given distances or geometry and positions.
The latter is only supported for anistrope interactions as these do only depend on the distance.
"""
function interaction_matrix end

abstract type AnisotropeInteraction <: Interaction end

interaction_matrix(interaction::AnisotropeInteraction, geometry::Geometry, positions) = interaction_matrix(interaction, distance_matrix(geometry, positions))

interaction_matrix(interaction::AnisotropeInteraction, distances) = _interaction_strength.(Ref(interaction), distances)

"""
    _interaction_strength(<:AnistropeInteraction, distance)
Compute interaction strength given the distance.
"""
function _interaction_strength end

"""
    PowerLaw{T}

Interaction strength scales proportional to distance to the power -α.

# Fields
- `α::T`: Exponent
"""
struct PowerLaw <: AnisotropeInteraction
    α::Float64
    PowerLaw(α) = new(convert(Float64, α))
end

_interaction_strength(int::PowerLaw, distance::Float64) = float(distance > 0 ? distance^-int.α : 0)


using Base.Test

rqa = Rqa.rqadata(collect("rqa"))
@test_approx_eq Rqa.RR(rqa) 0
@test Rqa.DET(rqa) |> isnan
@test Rqa.LMEAN(rqa) |> isnan
@test Rqa.LENT(rqa) |> isnan

alabama = Rqa.rqadata(collect("alabama"))
@test_approx_eq Rqa.RR(alabama) 6/21
@test_approx_eq Rqa.DET(alabama) 0
@test Rqa.LMEAN(alabama) |> isnan
@test Rqa.LENT(alabama) |> isnan

zzz = Rqa.rqadata(collect("zzz"))
@test_approx_eq Rqa.RR(zzz) 1
@test_approx_eq Rqa.DET(zzz) 2/3
@test_approx_eq Rqa.LMEAN(zzz) 2
@test_approx_eq Rqa.LENT(zzz) 0

w7 = Rqa.crqadata(collect("www"), collect("wwww"))
@test_approx_eq Rqa.RR(w7) 1
@test_approx_eq Rqa.DET(w7) 10/12
@test_approx_eq Rqa.LMEAN(w7) 2.5
@test_approx_eq Rqa.LENT(w7) 1

taran = Rqa.crqadata(collect("ratatar"), collect("taran"))
@test_approx_eq Rqa.RR(taran) 10/35
@test_approx_eq Rqa.DET(taran) 7/10
@test_approx_eq Rqa.LMEAN(taran) 7/3
@test_approx_eq Rqa.LENT(taran) (-(1/3) * log2(1/3) - (2/3) * log2(2/3))
@test_approx_eq Rqa.DET(taran, 3) 3/10
@test_approx_eq Rqa.LMEAN(taran, 3) 3
@test_approx_eq Rqa.LENT(taran, 3) 0
@test_approx_eq Rqa.DET(taran, 4) 0
@test Rqa.LMEAN(taran, 4) |> isnan
@test Rqa.LENT(taran, 4) |> isnan


abstract type AbstractStepwiseRegression{T} end
######################### Forward Regression #############################
struct ForwardRegression{T, AT<:AbstractMatOrFac{T}, B<:AbstractVector{T},
                        V<:AbstractVector{T}, QT<:AbstractMatOrFac{T}, FT} <: AbstractMatchingPursuit{T}
    A::AT # matrix
    b::B # target
    r::V # residual
    QA::QT # temporary storage
    AiQR::FT # updatable QR factorization of Ai
    δ²::V # decrease in SQUARED residual norm
    rescaling::V # OLS rescaling
end
const FR = ForwardRegression
const OrthogonalLeastSquares = ForwardRegression # a.k.a.
const OLS = OrthogonalLeastSquares
const OOMP = OLS # a.k.a. Optimal OMP
const ORMP = OLS # a.k.a order-recursive matching pursuit
const StepwiseRegression = FR

FR(A::AbstractMatOrFac, b::AbstractVector, x::SparseVector) = FR(A, b, x.nzind)
function FR(A::AbstractMatOrFac, b::AbstractVector, nzind::AbstractVector{Int} = ones(Int, 0))
    size(A, 1) == length(b) || throw(DimensionMismatch("size(A, 1) = $(size(A, 1)) ≠ $(length(b)) = length(b)"))
    n, m = size(A)
    T = eltype(A)
    r, Ar = zeros(T, n), zeros(T, m)
    QA = zeros(T, (n, m))
    AiQR = UpdatableQR(A[:, nzind])
    rescaling = colnorms(A)
    δ² = fill(-Inf, m)
    FR(A, b, r, QA, AiQR, δ², rescaling)
end

function fr(A::AbstractMatOrFac, b::AbstractVector;
     max_residual::Real = 0., min_decrease::Real = 0., sparsity::Int = size(A, 2))
     fr(A, b, max_residual, min_decrease, sparsity)
end

# calculates a solution to a potentially underdetermined
# linear system Ax = b with the forward greedy algorithm
# max_ε is the residual norm tolerance
# min_δ is the largest marginal increase in residual norm before the algorithm terminates
# k is the desired sparsity of the solution
# whichever criterion is hit first
function fr(A::AbstractMatOrFac, b::AbstractVector, max_ε::Real, min_δ::Real,
            k::Int = size(A, 1), x = spzeros(size(A, 2)))
    P = FR(A, b)
    for i in 1:k
        forward_step!(P, x, max_ε, min_δ) || break
    end
    return x
end
const ols = fr
const oomp = fr
const ormp = fr
# returns true if foward step was successful, updates x in place
function forward_step!(P::FR, x::SparseVector, max_ε::Real, min_δ::Real)
    nnz(x) < size(P.A, 1) || return false
    residual!(P.r, P.A, x, P.b)
    normr = norm(P.r)
    normr > max_ε || return false
    δ² = forward_δ!(P, x)
    max_δ², i = findmax(δ²)
    if min_δ^2 < max_δ²
        a = @view P.A[:, i]
        addindex!(x, P.AiQR, a, i) # i is index into x.nzval, NOT into x
        ldiv!!(x.nzval, P.AiQR, P.b, P.r)
        return true
    else
        ldiv!!(x.nzval, P.AiQR, P.b, P.r)
        return false
    end
end

function forward_δ!(P::FR, x::AbstractVector)
    residual!(P, x)
    mul!(P.δ², P.A', P.r) # δ = Ar = q
    rescaling = ols_rescaling!(P, x)
    @. P.δ² = P.δ²^2 / rescaling
    @. P.δ²[x.nzind] = 0
    return P.δ²
end

function acquisition_index!(P::FR, x::AbstractVector)
    argmax(forward_δ!(P, x))
end

function update!(P::FR, x::AbstractVector)
    nnz(x) < size(P.A, 1) || return false
    i = acquisition_index!(P, x)
    a = @view P.A[:, i]
    addindex!(x, P.AiQR, a, i)
    ldiv!!(x.nzval, P.AiQR, P.b, P.r) # least-squares solve for active atoms, reuses P.r for intermediate result
    return x
end

###############################################################################
# calculates squared energetic norm of all passive atoms
# |x|^2 - x' * P_I * x where P_I is the projection onto active column set of A
# Q is assumed to be part of QR-factorization of active set
function ols_rescaling!(P::OLS, x::SparseVector)
    Q = P.AiQR.Q
    # @. P.QA = P.A
    # lmul!(Q, P.QA)
    mul!(P.QA, Q', P.A)
    sum!(abs2, P.rescaling', P.A) # unnecessary if P.A is normalized
    QA = @view P.QA[1:nnz(x), :]
    k, m = size(QA)
    for j in 1:m
        @simd for i in 1:k
            @inbounds P.rescaling[j] -= QA[i, j]^2
        end
    end
    return P.rescaling
end


function opamp_nondet(; X0 = Interval(0.0, 0.0),
                        R₁ = 2., R₂ = 6., C = 1.e-3,
                        Ein = Interval(1.9, 2.1))

    α = hcat(-1/(R₂ * C))
    β = hcat(-1/(R₁ * C))

    # continuous evolution
    invariant = Universe(1) # no state constraint
    sys = @system(eout' = α*eout + β*ein, ein ∈ Ein, eout ∈ invariant)

    # initial-value problem
    return @ivp(sys, x(0) ∈ X0)
end

sol_nondet = solve(opamp_nondet(), T=0.1, alg=INT(δ=1e-4));

fig = plot()
plot!(fig, sol_nondet, vars=(0, 1), xlab=L"t", ylab=L"e_{out}", title="Solution for non-deterministic input", lw=0.2)

fig = plot(xlab=L"t", ylab=L"e_{out}(t)", title="Solution for non-deterministic input")

Δt = 0 .. 0.04

sol = solve(opamp_nondet(X0=Interval(-1.0, 1.0)), T=0.1, alg=INT(δ=1e-4));
plot!(fig, sol(Δt), vars=(0, 1), lab="X0 = -1 .. 1", lw=0.2)

sol = solve(opamp_nondet(X0=Interval(-0.5, 0.5)), T=0.1, alg=INT(δ=1e-4));
plot!(fig, sol(Δt), vars=(0, 1), lab="X0 = -0.5 .. 0.5", lw=0.2)

sol = solve(opamp_nondet(), T=0.1, alg=INT(δ=1e-4));
plot!(fig, sol(Δt), vars=(0, 1), lab="X0 = 0", lw=0.2)

fig

using Symbolics

function opamp_with_saturation(; X0 = BallInf(zeros(2), 0.0),
                                 R₁ = 2., R₂ = 6., C = 1.e-3,
                                 γ = 100., δ = 100., Es = 2.)
    var = @variables eₒ ein
    α = hcat(-1/(R₂ * C))
    β = hcat(-1/(R₁ * C))

    # transition graph
    automaton = LightAutomaton(2)
    add_transition!(automaton, 1, 2, 1)

    # modes
    A = [α β; 0. γ]
    b = [0.; δ]
    invariant = HalfSpace(ein <= Es, var)
    mode1 = @system(x' = Ax + b, x ∈ invariant)
    A = [α β; 0. 0.];
    mode2 = @system(x' = Ax, x ∈ Universe(2))
    modes = [mode1, mode2]

    # transition mode1 -> mode2 (saturation)
    guard = Hyperplane(ein == Es, var)
    t1 = @map(x -> x, dim:2, x ∈ guard)
    resetmaps = [t1]

    H = HybridSystem(automaton, modes, resetmaps, [AutonomousSwitching()])

    # initial condition is at the origin in mode 1
    initial_condition = [(1, X0)]
    return IVP(H, initial_condition)
end

X0 = Hyperrectangle([0., 1.5], [0.0, 0.1]);

prob_const = opamp_with_saturation(X0=X0, γ = 0., δ = 0., Es = 3.)
sol_const = solve(prob_const, T=0.1, alg=BOX(δ=1e-4));

plot(sol_const, vars=(0, 2), xlab=L"t", ylab=L"e_{in}(t)", title="Constant input signal", lw=0.2)
plot!(x->x, x->1.5, 0., 0.1, line=2, color="red", linestyle=:dash, legend=nothing)

plot(sol_const, vars=(0, 1), xlab=L"t", ylab=L"e_{out}(t)", title="Solution for constant input", lw=0.2)

X0 = Singleton(zeros(2));

# linearly increasing input signal
prob_lin = opamp_with_saturation(X0=X0, γ=0., δ=100., Es=1.);
sol_lin = solve(prob_lin, T=0.1, alg=BOX(δ=1e-4));

# exponentially increasing input signal
prob_exp = opamp_with_saturation(X0=X0, γ=-100., δ=100., Es=3.);
sol_exp = solve(prob_exp, T=0.1, alg=BOX(δ=1e-4));

plot(sol_lin, vars=(0, 2), xlab=L"t", ylab=L"e_{in}(t)", title="Linear input w/saturation", lw=0.2)

plot(sol_exp, vars=(0, 2), xlab=L"t", ylab=L"e_{in}(t)", title="Exponential input", lw=0.2)

plot(sol_lin, vars=(0, 1), xlab=L"t", ylab=L"e_{out}(t)", title="Solution for linear input", lw=0.2)

plot(sol_exp, vars=(0, 1), xlab=L"t", ylab=L"e_{out}(t)", title="Solution for exponential input", lw=0.2)


using Lorenz63
using Test
using Statistics

@testset "Equilibrium" begin
    XYZ = L63(xyz=[0.,0.,0.])
    @test all(XYZ[:,end] == [0.,0.,0.,])
end

@testset "Scaling up" begin
    XYZ1 = L63(s=1.0)
    XYZ10 = L63(s=10.0)

    # simulations are actually different
    @test XYZ1[:,end] != XYZ10[:,end]

    # means are approximately same
    @test isapprox(mean(XYZ1[1,:]),mean(XYZ10[1,:]),rtol=1e-6)
    @test isapprox(mean(XYZ1[2,:]),mean(XYZ10[2,:]),rtol=1e-6)
    @test isapprox(mean(XYZ1[3,:]),mean(XYZ10[3,:]),rtol=1e-6)

    # variances are approximately same
    @test isapprox(var(XYZ1[1,:]),var(XYZ10[1,:]),rtol=1e-6)
    @test isapprox(var(XYZ1[2,:]),var(XYZ10[2,:]),rtol=1e-6)
    @test isapprox(var(XYZ1[3,:]),var(XYZ10[3,:]),rtol=1e-6)
end

@testset "Scaling down" begin
    XYZ1 = L63(s=1.0)
    XYZ10 = L63(s=1/10)

    # simulations are actually different
    @test XYZ1[:,end] != XYZ10[:,end]

    # means are approximately same
    @test isapprox(mean(XYZ1[1,:]),mean(XYZ10[1,:]),rtol=1e-6)
    @test isapprox(mean(XYZ1[2,:]),mean(XYZ10[2,:]),rtol=1e-6)
    @test isapprox(mean(XYZ1[3,:]),mean(XYZ10[3,:]),rtol=1e-6)

    # variances are approximately same
    @test isapprox(var(XYZ1[1,:]),var(XYZ10[1,:]),rtol=1e-6)
    @test isapprox(var(XYZ1[2,:]),var(XYZ10[2,:]),rtol=1e-6)
    @test isapprox(var(XYZ1[3,:]),var(XYZ10[3,:]),rtol=1e-6)
end

@testset "Bounded" begin
    XYZ = L63()
    @test all(abs.(XYZ) .< 100.0)
end

@testset "Type-flexbile" begin
    XYZ32 = L63(Float32)
    XYZ16 = L63(Float16)

    @test XYZ32[:,1] == XYZ16[:,1]
    @test XYZ32[:,2] != XYZ16[:,2]
end


function myfunction(n)
    for i = 1:n
        A = randn(100,100,20)
        m = maximum(A)
        Am = mapslices(sum, A; dims=2)
        B = A[:,:,5]
        Bsort = mapslices(sort, B; dims=1)
        b = rand(100)
        C = B.*b
    end
end

# # Do the following in the Juno IDE
# using Profile
# Profile.clear()
# myfunction(1) # trigger compilation
# @profile myfunction(10)
# # up to here, everything is the same as for using ProfileView.jl

# # Now comes the cool part. Visualize your profile results:
# Juno.profiler()
# # or simply
# @profiler myfunction(10)

2 Tips and tricks/2_profiling/myprogram.jl


using Graphs
using LightGraphs
using Metis
using Compat
using Base.Test

# ==========================================================
# Initialize test graphs
# ==========================================================

"""
```
laplacian2d(nx, ny) -> L
```

Construct a 2D discrete Laplacian operator on an `nx` x `ny` grid. `L`
is a sparse matrix.
"""
function laplacian2d{T<:Integer}(nx::T,ny::T)

    # Initialize storage for graph
    n = nx*ny
    nzest = 5*n
    I = @compat sizehint!(Int32[],nzest)
    J = @compat sizehint!(Int32[],nzest)
    V = @compat sizehint!(Float64[],nzest)

    # Add edge to graph
    function addEdge(i,j,v)
        push!(I,i)
        push!(J,j)
        push!(V,v)
    end

    # Construct discrete Laplacian
    for y in 1:ny
        for x in 1:nx
            s = x + (y-1)*nx
            addEdge(s,s,4)
            x > 1  && addEdge(s-1,s,-1)
            x < nx && addEdge(s+1,s,-1)
            y > 1  && addEdge(s-nx,s,-1)
            y < ny && addEdge(s+nx,s,-1)
        end
    end
    return sparse(I,J,V,n,n)
end

# Construct SparseMatrixCSC examples
srand(12321)
const a = convert(SparseMatrixCSC{Float64,Int32},
                  sprand(1000,100,0.01))
const ata = a'a
const laplace = laplacian2d(123,89)
const path_CSC = spdiagm((ones(Int,7),ones(Int,7)), (-1,1), 8, 8)
const wheel = sparse([0   100 100 1   1   1   1   1  ;
                      100 0   100 0   0   0   0   1  ;
                      100 100 0   1   0   0   0   0  ;
                      1   0   1   0   100 0   0   0  ;
                      1   0   0   100 0   100 0   0  ;
                      1   0   0   0   100 0   1   0  ;
                      1   0   0   0   0   1   0   100;
                      1   1   0   0   0   0   100 0  ])

# Construct Graphs.GenericAdjacencyList examples
const copter2     = Metis.testgraph("copter2");
const mdual       = Metis.testgraph("mdual")
const path_Graphs = Metis.testgraph("path")

# Construct LightGraphs.Graph examples
const tutte            = LightGraphs.Datasets.TutteGraph()
const path_LightGraphs = LightGraphs.Graph(LightGraphs.PathDiGraph(8))

# Vertex weights for path graph and wheel graph
const vwgt_path  = [4,3,4,3,4,1,1,1]
const vwgt_wheel = [1,1,1,1,1,1,1,2]

# ==========================================================
# Tests with nodeND
# ==========================================================

# copter2
perm, iperm = nodeND(copter2)
@test all(invperm(perm) .== iperm)

# tutte
perm, iperm = nodeND(tutte)
@test all(invperm(perm) .== iperm)

# ata
perm, iperm = nodeND(ata)
@test all(invperm(perm) .== iperm)

# ==========================================================
# Tests with partGraphKway
# ==========================================================

"""
```
interface(g, part) -> Int
```

Compute the number of vertices that lie on partition interfaces. `g`
can be a Graphs package adjacency list or a LightGraphs package graph
and `part` is a partition vector.
"""
# Compute interface size on Graphs.GenericAdjacencyList
function interface(g::Graphs.GenericAdjacencyList, part::Vector)
    n = length(part)
    (n==length(g.vertices)) || error("partition length != # of vertices")
    interfaceSize = 0
    for i in 1:n
        if any(j -> (part[j]!=part[i]), g.adjlist[i])
            interfaceSize += 1
        end
    end
    return interfaceSize
end

# Compute interface size on LightGraphs.Graph
function interface(g::LightGraphs.Graph, part::Vector)
    n = nv(g)
    (n==length(part)) || error("partition length != # of vertices")
    interfaceSize = 0
    for i in 1:n
        if any(j -> (part[j]!=part[i]), map(dst,g.finclist[i]))
            interfaceSize += 1
        end
    end
    return interfaceSize
end

# copter2
objval, part = partGraphKway(copter2, 6)
@test extrema(part) == (1,6)
@test all(i->findfirst(part,i) != 0,1:6)
@test interface(copter2,part) < 6000    # 5907 on an Ubuntu system

# mdual
objval, part = partGraphKway(mdual, 10)
@test extrema(part) == (1,10)
@test all(i->findfirst(part,i) != 0,1:10)
@test interface(mdual,part) < 19000     # 18263 on an Ubuntu system

# tutte
objval, part = partGraphKway(tutte, 6)
@test extrema(part) == (1,6)
@test length(part) == LightGraphs.nv(tutte)
@test all(i-> findfirst(part,i) != 0,1:6)

# path_CSC
objval, part = partGraphKway(path_CSC, 3, vwgt=vwgt_path)
@test objval == 2
@test ((part.==1) == [1,1,0,0,0,0,0,0]
       || (part.==1) == [0,0,1,1,0,0,0,0]
       || (part.==1) == [0,0,0,0,1,1,1,1])
@test ((part.==2) == [1,1,0,0,0,0,0,0]
       || (part.==2) == [0,0,1,1,0,0,0,0]
       || (part.==2) == [0,0,0,0,1,1,1,1])
@test ((part.==3) == [1,1,0,0,0,0,0,0]
       || (part.==3) == [0,0,1,1,0,0,0,0]
       || (part.==3) == [0,0,0,0,1,1,1,1])

# path_Graphs
objval, part = partGraphKway(path_Graphs, 3, vwgt=vwgt_path)
@test objval == 2
@test ((part.==1) == [1,1,0,0,0,0,0,0]
       || (part.==1) == [0,0,1,1,0,0,0,0]
       || (part.==1) == [0,0,0,0,1,1,1,1])
@test ((part.==2) == [1,1,0,0,0,0,0,0]
       || (part.==2) == [0,0,1,1,0,0,0,0]
       || (part.==2) == [0,0,0,0,1,1,1,1])
@test ((part.==3) == [1,1,0,0,0,0,0,0]
       || (part.==3) == [0,0,1,1,0,0,0,0]
       || (part.==3) == [0,0,0,0,1,1,1,1])

# path_LightGraphs
objval, part = partGraphKway(path_LightGraphs, 3, vwgt=vwgt_path)
@test objval == 2
@test ((part.==1) == [1,1,0,0,0,0,0,0]
       || (part.==1) == [0,0,1,1,0,0,0,0]
       || (part.==1) == [0,0,0,0,1,1,1,1])
@test ((part.==2) == [1,1,0,0,0,0,0,0]
       || (part.==2) == [0,0,1,1,0,0,0,0]
       || (part.==2) == [0,0,0,0,1,1,1,1])
@test ((part.==3) == [1,1,0,0,0,0,0,0]
       || (part.==3) == [0,0,1,1,0,0,0,0]
       || (part.==3) == [0,0,0,0,1,1,1,1])

# wheel
objval, part = partGraphKway(wheel, 3, adjwgt=true, vwgt=vwgt_wheel)
@test objval == 8
@test ((part.==1) == [1,1,1,0,0,0,0,0]
       || (part.==1) == [0,0,0,1,1,1,0,0]
       || (part.==1) == [0,0,0,0,0,0,1,1])
@test ((part.==2) == [1,1,1,0,0,0,0,0]
       || (part.==2) == [0,0,0,1,1,1,0,0]
       || (part.==2) == [0,0,0,0,0,0,1,1])
@test ((part.==3) == [1,1,1,0,0,0,0,0]
       || (part.==3) == [0,0,0,1,1,1,0,0]
       || (part.==3) == [0,0,0,0,0,0,1,1])

# ==========================================================
# Tests with partGraphRecursive
# ==========================================================

# copter2
objval, part = partGraphRecursive(copter2,6)
@test extrema(part) == (1,6)
@test all(i->findfirst(part,i) != 0,1:6)
@test interface(copter2,part) < 6000    # 5907 on an Ubuntu system

# mdual
objval, part = partGraphKway(mdual, 10)
@test extrema(part) == (1,10)
@test all(i->findfirst(part,i) != 0,1:10)
@test interface(mdual,part) < 19000     # 18263 on an Ubuntu system

# tutte
objval, part = partGraphKway(tutte, 6)
@test extrema(part) == (1,6)
@test length(part) == LightGraphs.nv(tutte)
@test all(i-> findfirst(part,i) != 0,1:6)

# ==========================================================
# Tests with vertexSep
# ==========================================================

"""
```
testVertexSep(g, part) -> Bool
```

Test whether a graph partition describes a vertex separator. `g` can
be a Graphs package adjacency list, LightGraphs graph, or adjacency
matrix in CSC format. `part` is a partition vector with three
parts. The third part is being evaluated as a vertex separator between
the first two parts.
"""
# Test a vertex separator on Graphs.GenericAdjacencyList
function testVertexSep(g::Graphs.GenericAdjacencyList, part::Vector)
    validPart = true
    for i in 1:length(part)
        partVal = part[i]
        if partVal == 0
            for j in g.adjlist[i]
                if part[j] == 1
                    println("Edge ($i,$j) connects sets 0 and 1")
                    validPart = false
                end
            end
        elseif partVal == 1
            for j in g.adjlist[i]
                if part[j] == 0
                    println("Edge ($i,$j) connects sets 1 and 0")
                    validPart = false
                end
            end
        elseif partVal == 2
            continue
        else
            println("Vertex $i assigned to set $partVal")
            validPart = false
        end
    end
    return validPart
end

# Test a vertex separator on LightGraphs.Graph
function testVertexSep(g::LightGraphs.Graph, part::Vector)
    validPart = true
    for i in 1:length(part)
        partVal = part[i]
        if partVal == 0
            for j in g.fadjlist[i]
                if part[j] == 1
                    println("Edge ($i,$j) connects sets 0 and 1")
                    validPart = false
                end
            end
        elseif partVal == 1
            for j in g.fadjlist[i]
                if part[j] == 0
                    println("Edge ($i,$j) connects sets 1 and 0")
                    validPart = false
                end
            end
        elseif partVal == 2
            continue
        else
            println("Vertex $i assigned to set $partVal")
            validPart = false
        end
    end
    return validPart
end

# Test a vertex separator on SparseMatrixCSC
function testVertexSep(m::SparseMatrixCSC, part::Vector)
    validPart = true
    for i in 1:length(part)
        partVal = part[i]
        if partVal == 0
            for k in m.colptr[i]:m.colptr[i+1]-1
                j = m.rowval[k]
                if part[j] == 1
                    println("Edge ($i,$j) connects sets 0 and 1")
                    validPart = false
                end
            end
        elseif partVal == 1
            for k in m.colptr[i]:m.colptr[i+1]-1
                j = m.rowval[k]
                if part[j] == 0
                    println("Edge ($i,$j) connects sets 1 and 0")
                    validPart = false
                end
            end
        elseif partVal == 2
            continue
        else
            println("Vertex $i assigned to set $partVal")
            validPart = false
        end
    end
    return validPart
end

# copter2
sizes, part = vertexSep(copter2)
@test sizes[1]!=0 && sizes[2]!=0
@test testVertexSep(copter2,part)

# tutte
sizes, part = vertexSep(tutte)
@test sizes[1]!=0 && sizes[2]!=0
@test testVertexSep(tutte,part)

# laplace
sizes, part = vertexSep(laplace)
@test sizes[1]!=0 && sizes[2]!=0
@test testVertexSep(laplace,part)


function solve_find_invalid(input, n)
	preamble = input[1:n]
	sums = zeros(Int64, n, n)
	for i in 1:n
		for j in i + 1:n
			sums[i,j] = preamble[i] + preamble[j]
		end
	end
	ret_i = n + 1
	while !isnothing(findfirst(x -> x == input[ret_i], sums))
		i = ret_i % n != 0 ? ret_i % n : n
		preamble[i] = input[ret_i]
		for j in filter(x -> x != i, 1:n)
			sums[i, j] = preamble[i] + preamble[j]
		end
		ret_i += 1
	end
	input[ret_i]
end

function solve_find_weakness(input, target)
	i = 2
	arr = input[1:2]
	while sum(arr) != target
		if sum(arr) < target
			i += 1
			push!(arr, input[i])
		else
			popfirst!(arr)
		end
	end
	minimum(arr) + maximum(arr)
end

@assert solve_find_invalid(parse.(Int64, readlines("input/test/test_day9.txt")), 5) == 127 ["test case 1 failed"]
@assert solve_find_weakness(parse.(Int64, readlines("input/test/test_day9.txt")), 127) == 62 ["test case 2 failed"]

invalid_number = solve_find_invalid(parse.(Int64, readlines("input/day9.txt")), 25)
println("First star: " * string(invalid_number))
println("Second star: " * string(solve_find_weakness(parse.(Int64, readlines("input/day9.txt")), invalid_number)))


# Original implementations by @nantonel
# https://github.com/maxmouchet/HMMBase.jl/pull/6

# In-place forward pass, where α and c are allocated beforehand.
function forwardlog!(
    α::AbstractMatrix,
    c::AbstractVector,
    a::AbstractVector,
    A::AbstractMatrix,
    LL::AbstractMatrix,
)
    @argcheck size(α, 1) == size(LL, 1) == size(c, 1)
    @argcheck size(α, 2) == size(LL, 2) == size(a, 1) == size(A, 1) == size(A, 2)

    T, K = size(LL)
    (T == 0) && return

    fill!(α, 0.0)
    fill!(c, 0.0)

    m = vec_maximum(view(LL, 1, :))

    for j in OneTo(K)
        α[1, j] = a[j] * exp(LL[1, j] - m)
        c[1] += α[1, j]
    end

    for j in OneTo(K)
        α[1, j] /= c[1]
    end

    c[1] = log(c[1]) + m

    @inbounds for t = 2:T
        m = vec_maximum(view(LL, t, :))

        for j in OneTo(K)
            for i in OneTo(K)
                α[t, j] += α[t-1, i] * A[i, j]
            end
            α[t, j] *= exp(LL[t, j] - m)
            c[t] += α[t, j]
        end

        for j in OneTo(K)
            α[t, j] /= c[t]
        end

        c[t] = log(c[t]) + m
    end
end

# In-place backward pass, where β and c are allocated beforehand.
function backwardlog!(
    β::AbstractMatrix,
    c::AbstractVector,
    a::AbstractVector,
    A::AbstractMatrix,
    LL::AbstractMatrix,
)
    @argcheck size(β, 1) == size(LL, 1) == size(c, 1)
    @argcheck size(β, 2) == size(LL, 2) == size(a, 1) == size(A, 1) == size(A, 2)

    T, K = size(LL)
    L = zeros(K)
    (T == 0) && return

    fill!(β, 0.0)
    fill!(c, 0.0)

    for j in OneTo(K)
        β[end, j] = 1.0
    end

    @inbounds for t = T-1:-1:1
        m = vec_maximum(view(LL, t + 1, :))

        for i in OneTo(K)
            L[i] = exp(LL[t+1, i] - m)
        end

        for j in OneTo(K)
            for i in OneTo(K)
                β[t, j] += β[t+1, i] * A[j, i] * L[i]
            end
            c[t+1] += β[t, j]
        end

        for j in OneTo(K)
            β[t, j] /= c[t+1]
        end

        c[t+1] = log(c[t+1]) + m
    end

    m = vec_maximum(view(LL, 1, :))

    for j in OneTo(K)
        c[1] += a[j] * exp(LL[1, j] - m) * β[1, j]
    end

    c[1] = log(c[1]) + m
end

# In-place posterior computation, where γ is allocated beforehand.
function posteriors!(γ::AbstractMatrix, α::AbstractMatrix, β::AbstractMatrix)
    @argcheck size(γ) == size(α) == size(β)
    T, K = size(α)
    for t in OneTo(T)
        c = 0.0
        for i in OneTo(K)
            γ[t, i] = α[t, i] * β[t, i]
            c += γ[t, i]
        end

        for i in OneTo(K)
            γ[t, i] /= c
        end
    end
end

"""
    forward(a, A, LL) -> (Vector, Float)

Compute forward probabilities using samples likelihoods.
See [Forward-backward algorithm](https://en.wikipedia.org/wiki/Forward–backward_algorithm).

**Output**
- `Vector{Float64}`: forward probabilities.
- `Float64`: log-likelihood of the observed sequence.
"""
function forward(a::AbstractVector, A::AbstractMatrix, LL::AbstractMatrix; logl = nothing)
    (logl !== nothing) && deprecate_kwargs("logl")
    m = Matrix{Float64}(undef, size(LL))
    c = Vector{Float64}(undef, size(LL, 1))
    forwardlog!(m, c, a, A, LL)
    m, sum(c)
end

"""
    backward(a, A, LL) -> (Vector, Float)

Compute backward probabilities using samples likelihoods.
See [Forward-backward algorithm](https://en.wikipedia.org/wiki/Forward–backward_algorithm).

**Output**
- `Vector{Float64}`: backward probabilities.
- `Float64`: log-likelihood of the observed sequence.
"""
function backward(a::AbstractVector, A::AbstractMatrix, LL::AbstractMatrix; logl = nothing)
    (logl !== nothing) && deprecate_kwargs("logl")
    m = Matrix{Float64}(undef, size(LL))
    c = Vector{Float64}(undef, size(LL, 1))
    backwardlog!(m, c, a, A, LL)
    m, sum(c)
end

"""
    forward(hmm, observations; robust) -> (Vector, Float)

Compute forward probabilities of the `observations` given the `hmm` model.

**Output**
- `Vector{Float64}`: forward probabilities.
- `Float64`: log-likelihood of the observed sequence.

**Example**
```julia
using Distributions, HMMBase
hmm = HMM([0.9 0.1; 0.1 0.9], [Normal(0,1), Normal(10,1)])
y = rand(hmm, 1000)
probs, tot = forward(hmm, y)
```
"""
function forward(hmm::AbstractHMM, observations; logl = nothing, robust = false)
    (logl !== nothing) && deprecate_kwargs("logl")
    LL = loglikelihoods(hmm, observations; robust = robust)
    forward(hmm.a, hmm.A, LL)
end

"""
    backward(hmm, observations; robust) -> (Vector, Float)

Compute forward probabilities of the `observations` given the `hmm` model.

**Output**
- `Vector{Float64}`: backward probabilities.
- `Float64`: log-likelihood of the observed sequence.

**Example**
```julia
using Distributions, HMMBase
hmm = HMM([0.9 0.1; 0.1 0.9], [Normal(0,1), Normal(10,1)])
y = rand(hmm, 1000)
probs, tot = forward(hmm, y)
```
"""
function backward(hmm::AbstractHMM, observations; logl = nothing, robust = false)
    (logl !== nothing) && deprecate_kwargs("logl")
    LL = loglikelihoods(hmm, observations; robust = robust)
    backward(hmm.a, hmm.A, LL)
end

"""
    posteriors(α, β) -> Vector

Compute posterior probabilities from `α` and `β`.

**Arguments**
- `α::AbstractVector`: forward probabilities.
- `β::AbstractVector`: backward probabilities.
"""
function posteriors(α::AbstractMatrix, β::AbstractMatrix)
    γ = Matrix{Float64}(undef, size(α))
    posteriors!(γ, α, β)
    γ
end

"""
    posteriors(a, A, LL; kwargs...) -> Vector

Compute posterior probabilities using samples likelihoods.
"""
function posteriors(a::AbstractVector, A::AbstractMatrix, LL::AbstractMatrix; kwargs...)
    α, _ = forward(a, A, LL; kwargs...)
    β, _ = backward(a, A, LL; kwargs...)
    posteriors(α, β)
end

"""
    posteriors(hmm, observations; robust) -> Vector

Compute posterior probabilities using samples likelihoods.

**Example**
```julia
using Distributions, HMMBase
hmm = HMM([0.9 0.1; 0.1 0.9], [Normal(0,1), Normal(10,1)])
y = rand(hmm, 1000)
γ = posteriors(hmm, y)
```
"""
function posteriors(hmm::AbstractHMM, observations; logl = nothing, robust = false)
    (logl !== nothing) && deprecate_kwargs("logl")
    LL = loglikelihoods(hmm, observations; robust = robust)
    posteriors(hmm.a, hmm.A, LL)
end

"""
    loglikelihood(hmm, observations; robust) -> Float64

Compute the log-likelihood of the observations under the model.
This is defined as the sum of the log of the normalization coefficients in the forward filter.

**Output**
- `Float64`: log-likelihood of the observations sequence under the model.

**Example**
```jldoctest
using Distributions, HMMBase
hmm = HMM([0.9 0.1; 0.1 0.9], [Normal(0,1), Normal(10,1)])
loglikelihood(hmm, [0.15, 0.10, 1.35])
# output
-4.588183811489616
```
"""
function loglikelihood(hmm::AbstractHMM, observations; logl = nothing, robust = false)
    (logl !== nothing) && deprecate_kwargs("logl")
    forward(hmm, observations, robust = robust)[2]
end


module Day3
export solvePart1, solvePart2

function parseClaim(s)
    split(s, r"[^0-9]") |>
    parts -> filter(!isempty, parts) |>
    nums -> map(n -> parse(Int64, n), nums)
end

function getAreaIterator(x, y, w, h)
    Iterators.product(x:x+w-1, y:y+h-1)
end

function getInput()
    open("day3input.txt") do f
        readlines(f) |>
        collect |>
        lines -> map(line -> parseClaim(line), lines)
    end
end

function addPointToDict!(dict, xy)
    dict[(xy[1], xy[2])] = get(dict, (xy[1], xy[2]), 0) + 1
end

function addClaimToDict!(dict, claim)
    getAreaIterator(claim[2:end]...) |>
    iter -> foreach(xy -> addPointToDict!(dict, xy), iter)
end

function solvePart1()
    claims = getInput()
    coverage = Dict{Tuple{Int, Int}, Int}()
    for claim in claims
        addClaimToDict!(coverage, claim)
    end
    overlaps = 0
    for (k, v) in coverage
        if v > 1
            overlaps += 1
        end
    end
    println("There are " * string(overlaps) * " square inches of overlap.")
    return overlaps
end

function noOverlappingSquares(dict, claim)
    squaresInClaim = getAreaIterator(claim[2:end]...)
    for square in squaresInClaim
        if dict[square] > 1
            return false
        end
    end
    return true
end

function solvePart2()
    claims = getInput()
    overlappingSquareDict = Dict()
    for claim in claims
        addClaimToDict!(overlappingSquareDict, claim)
    end

    claim = filter(claim -> noOverlappingSquares(overlappingSquareDict, claim), claims)[1][1]
    print("Claim #" * string(claim) * " has no overlapping squares.")
end

end


# Correlation
# TODO: Implement `CorrelationTest` for two arguments

using Statistics: clampcor

export CorrelationTest

"""
    CorrelationTest(x, y)

Perform a t-test for the hypothesis that ``\\text{Cor}(x,y) = 0``, i.e. the correlation
of vectors `x` and `y` is zero.

    CorrelationTest(x, y, Z)

Perform a t-test for the hypothesis that ``\\text{Cor}(x,y|Z=z) = 0``, i.e. the partial
correlation of vectors `x` and `y` given the matrix `Z` is zero.

Implements `pvalue` for the t-test.
Implements `confint` using an approximate confidence interval based on Fisher's
``z``-transform.

See also `partialcor` from StatsBase.

# External resources

* [Partial correlation on Wikipedia](https://en.wikipedia.org/wiki/Partial_correlation#As_conditional_independence_test)
  (for the construction of the confidence interval)
* [Section testing using Student's t-distribution from Pearson correlation coefficient on Wikipedia](https://en.wikipedia.org/wiki/Pearson_correlation_coefficient#Testing_using_Student's_t-distribution)
* [K.J. Levy and S.C. Narula (1978): Testing Hypotheses concerning Partial Correlations: Some Methods and Discussion. International Statistical Review 46(2).](https://www.jstor.org/stable/1402814)
"""
struct CorrelationTest{T<:Real} <: HypothesisTest
    r::T
    n::Int
    k::Int
    t::T

    # Error checking is done in `cor`
    function CorrelationTest(x::AbstractVector, y::AbstractVector)
        r = cor(x, y)
        n = length(x)
        t = r * sqrt((n - 2) / (1 - r^2))
        return new{typeof(r)}(r, n, 0, t)
    end

    # Error checking is done in `partialcor`
    function CorrelationTest(x::AbstractVector, y::AbstractVector, Z::AbstractMatrix)
        r = partialcor(x, y, Z)
        n, k = size(Z)
        t = r * sqrt((n - 2 - k) / (1 - r^2))
        return new{typeof(r)}(r, n, k, t)
    end

    function CorrelationTest(x::AbstractVector, y::AbstractVector, z::AbstractVector)
        r = partialcor(x, y, z)
        n = length(z)
        t = r * sqrt((n - 3) / (1 - r^2))
        return new{typeof(r)}(r, n, 1, t)
    end
end

testname(p::CorrelationTest) =
    string("Test for nonzero ", p.k != 0 ? "partial " : "", "correlation")

function population_param_of_interest(p::CorrelationTest)
    param = p.k != 0 ? "Partial correlation" : "Correlation"
    (param, zero(p.r), p.r)
end

StatsBase.nobs(p::CorrelationTest) = p.n
StatsBase.dof(p::CorrelationTest) = p.n - 2 - p.k

function StatsBase.confint(test::CorrelationTest{T}, level::Float64=0.95) where T
    dof(test) > 1 || return (-one(T), one(T))  # Otherwise we can get NaNs
    q = quantile(Normal(), 1 - (1-level) / 2)
    fisher = atanh(test.r)
    bound = q / sqrt(dof(test) - 1)
    elo = clampcor(tanh(fisher - bound))
    ehi = clampcor(tanh(fisher + bound))
    return (elo, ehi)
end

default_tail(::CorrelationTest) = :both
pvalue(test::CorrelationTest; tail=:both) = pvalue(TDist(dof(test)), test.t, tail=tail)

function show_params(io::IO, test::CorrelationTest, indent="")
    println(io, indent, "number of observations:          ", nobs(test))
    println(io, indent, "number of conditional variables: ", test.k)
    println(io, indent, "t-statistic:                     ", test.t)
    println(io, indent, "degrees of freedom:              ", dof(test))
end


struct Linear
end
struct Left
end
struct Right
end

"""
    interpolate(method, X::Trajectory, s)

Interpolate trajectory `X` using `method in [Linear(), Left(), ...]`
at time `s`.
"""
function interpolate
end

function interpolate(::Linear, X::Trajectory, s)
    i = searchsorted(keys(X), s)
    t, x = Pair(X)
    n = length(X)
    f, l = first(i), last(i)
    if f <= 1
        return x[1]
    elseif l >= n
        return x[end]
    elseif f <= l
        return x[f]
    else
        λ = (t[f] - s)/(t[f]-t[l])
        return λ*x[l] + (1-λ)*x[f]
    end
end

function interpolate(::Left, X::Trajectory, s)
    i = searchsorted(keys(X), s)
    t, x = Pair(X)
    n = length(X)
    f, l = first(i), last(i)
    return x[l]
end

function interpolate(::Right, X::Trajectory, s)
    i = searchsorted(keys(X), s)
    t, x = Pair(X)
    n = length(X)
    f, l = first(i), last(i)
    return x[f]
end


"""
    AbstractProjectiveSpace{𝔽} <: AbstractDecoratorManifold{𝔽}

An abstract type to represent a projective space over `𝔽` that is represented isometrically
in the embedding.
"""
abstract type AbstractProjectiveSpace{𝔽} <: AbstractDecoratorManifold{𝔽} end

@doc raw"""
    ProjectiveSpace{n,𝔽} <: AbstractProjectiveSpace{𝔽}

The projective space $𝔽ℙ^n$ is the manifold of all lines in $𝔽^{n+1}$.
The default representation is in the embedding, i.e. as unit norm vectors in
$𝔽^{n+1}$:
````math
𝔽ℙ^n := \bigl\{ [p] ⊂ 𝔽^{n+1} \ \big|\ \lVert p \rVert = 1, λ ∈ 𝔽, |λ| = 1, p ∼ p λ \bigr\},
````
where $[p]$ is an equivalence class of points $p$, and $∼$ indicates equivalence.
For example, the real projective space $ℝℙ^n$ is represented as the unit sphere $𝕊^n$, where
antipodal points are considered equivalent.

The tangent space at point $p$ is given by

````math
T_p 𝔽ℙ^{n} := \bigl\{ X ∈ 𝔽^{n+1}\ \big|\ ⟨p,X⟩ = 0 \bigr \},
````
where $⟨⋅,⋅⟩$ denotes the inner product in the embedding $𝔽^{n+1}$.

When $𝔽 = ℍ$, this implementation of $ℍℙ^n$ is the right-quaternionic projective
space.

# Constructor

    ProjectiveSpace(n[, field=ℝ])

Generate the projective space $𝔽ℙ^{n} ⊂ 𝔽^{n+1}$, defaulting to the real projective space
$ℝℙ^n$, where `field` can also be used to generate the complex- and right-quaternionic
projective spaces.
"""
struct ProjectiveSpace{N,𝔽} <: AbstractProjectiveSpace{𝔽} end
ProjectiveSpace(n::Int, field::AbstractNumbers=ℝ) = ProjectiveSpace{n,field}()

function active_traits(f, ::AbstractProjectiveSpace, args...)
    return merge_traits(IsIsometricEmbeddedManifold())
end

@doc raw"""
    ArrayProjectiveSpace{T<:Tuple,𝔽} <: AbstractProjectiveSpace{𝔽}

The projective space $𝔽ℙ^{n₁,n₂,…,nᵢ}$ is the manifold of all lines in $𝔽^{n₁,n₂,…,nᵢ}$.
The default representation is in the embedding, i.e. as unit (Frobenius) norm matrices in
$𝔽^{n₁,n₂,…,nᵢ}$:

````math
𝔽ℙ^{n_1, n_2, …, n_i} := \bigl\{ [p] ⊂ 𝔽^{n_1, n_2, …, n_i} \ \big|\ \lVert p \rVert_{\mathrm{F}} = 1, λ ∈ 𝔽, |λ| = 1, p ∼ p λ \bigr\}.
````
where $[p]$ is an equivalence class of points $p$, $\sim$ indicates equivalence, and
$\lVert ⋅ \rVert_{\mathrm{F}}$ is the Frobenius norm.
Note that unlike [`ProjectiveSpace`](@ref), the argument for `ArrayProjectiveSpace`
is given by the size of the embedding.
This means that [`ProjectiveSpace(2)`](@ref) and `ArrayProjectiveSpace(3)` are the same
manifold.
Additionally, `ArrayProjectiveSpace(n,1;field=𝔽)` and [`Grassmann(n,1;field=𝔽)`](@ref) are
the same.

The tangent space at point $p$ is given by

````math
T_p 𝔽ℙ^{n_1, n_2, …, n_i} := \bigl\{ X ∈ 𝔽^{n_1, n_2, …, n_i}\ |\ ⟨p,X⟩_{\mathrm{F}} = 0 \bigr \},
````

where $⟨⋅,⋅⟩_{\mathrm{F}}$ denotes the (Frobenius) inner product in the embedding
$𝔽^{n_1, n_2, …, n_i}$.

# Constructor

    ArrayProjectiveSpace(n₁,n₂,...,nᵢ; field=ℝ)

Generate the projective space $𝔽ℙ^{n_1, n_2, …, n_i}$, defaulting to the real projective
space, where `field` can also be used to generate the complex- and right-quaternionic
projective spaces.
"""
struct ArrayProjectiveSpace{N,𝔽} <: AbstractProjectiveSpace{𝔽} where {N<:Tuple} end
function ArrayProjectiveSpace(n::Vararg{Int,I}; field::AbstractNumbers=ℝ) where {I}
    return ArrayProjectiveSpace{Tuple{n...},field}()
end

function allocation_promotion_function(::AbstractProjectiveSpace{ℂ}, f, args::Tuple)
    return complex
end

@doc raw"""
    check_point(M::AbstractProjectiveSpace, p; kwargs...)

Check whether `p` is a valid point on the [`AbstractProjectiveSpace`](@ref) `M`, i.e.
that it has the same size as elements of the embedding and has unit Frobenius norm.
The tolerance for the norm check can be set using the `kwargs...`.
"""
function check_point(M::AbstractProjectiveSpace, p; kwargs...)
    if !isapprox(norm(p), 1; kwargs...)
        return DomainError(
            norm(p),
            "The point $(p) does not lie on the $(M) since its norm is not 1.",
        )
    end
    return nothing
end

@doc raw"""
    check_vector(M::AbstractProjectiveSpace, p, X; kwargs... )

Check whether `X` is a tangent vector in the tangent space of `p` on the
[`AbstractProjectiveSpace`](@ref) `M`, i.e. that `X` has the same size as elements of the
tangent space of the embedding and that the Frobenius inner product
$⟨p, X⟩_{\mathrm{F}} = 0$.
"""
function check_vector(M::AbstractProjectiveSpace, p, X; kwargs...)
    if !isapprox(dot(p, X), 0; kwargs...)
        return DomainError(
            dot(p, X),
            "The vector $(X) is not a tangent vector to $(p) on $(M), since it is not" *
            " orthogonal in the embedding.",
        )
    end
    return nothing
end

function decorated_manifold(M::AbstractProjectiveSpace{𝔽}) where {𝔽}
    return Euclidean(representation_size(M)...; field=𝔽)
end

get_embedding(M::AbstractProjectiveSpace) = decorated_manifold(M)

embed(::AbstractProjectiveSpace, p) = p
embed(::AbstractProjectiveSpace, p, X) = X

@doc raw"""
    distance(M::AbstractProjectiveSpace, p, q)

Compute the Riemannian distance on [`AbstractProjectiveSpace`](@ref) `M`$=𝔽ℙ^n$ between
points `p` and `q`, i.e.
````math
d_{𝔽ℙ^n}(p, q) = \arccos\bigl| ⟨p, q⟩_{\mathrm{F}} \bigr|.
````

Note that this definition is similar to that of the [`AbstractSphere`](@ref).
However, the absolute value ensures that all equivalent `p` and `q` have the same pairwise
distance.
"""
distance(::AbstractProjectiveSpace, p, q) = acos(min(abs(dot(p, q)), 1))

function exp!(M::AbstractProjectiveSpace, q, p, X)
    θ = norm(M, p, X)
    q .= cos(θ) .* p .+ usinc(θ) .* X
    return q
end

function get_basis(::ProjectiveSpace{n,ℝ}, p, B::DiagonalizingOrthonormalBasis{ℝ}) where {n}
    return get_basis(Sphere{n,ℝ}(), p, B)
end

@doc raw"""
    get_coordinates(M::AbstractProjectiveSpace, p, X, B::DefaultOrthonormalBasis{ℝ})

Represent the tangent vector $X$ at point $p$ from the [`AbstractProjectiveSpace`](@ref)
$M = 𝔽ℙ^n$ in an orthonormal basis by unitarily transforming the hyperplane containing $X$,
whose normal is $p$, to the hyperplane whose normal is the $x$-axis.

Given $q = p \overline{λ} + x$, where
$λ = \frac{⟨x, p⟩_{\mathrm{F}}}{|⟨x, p⟩_{\mathrm{F}}|}$, $⟨⋅, ⋅⟩_{\mathrm{F}}$ denotes the
Frobenius inner product, and $\overline{⋅}$ denotes complex or quaternionic conjugation, the
formula for $Y$ is
````math
\begin{pmatrix}0 \\ Y\end{pmatrix} = \left(X - q\frac{2 ⟨q, X⟩_{\mathrm{F}}}{⟨q, q⟩_{\mathrm{F}}}\right)\overline{λ}.
````
"""
get_coordinates(::AbstractProjectiveSpace{ℝ}, p, X, ::DefaultOrthonormalBasis)

function get_coordinates_orthonormal!(
    M::AbstractProjectiveSpace{𝔽},
    Y,
    p,
    X,
    ::RealNumbers,
) where {𝔽}
    n = div(manifold_dimension(M), real_dimension(𝔽))
    z = p[1]
    cosθ = abs(z)
    λ = nzsign(z, cosθ)
    pend, Xend = view(p, 2:(n + 1)), view(X, 2:(n + 1))
    factor = λ' * X[1] / (1 + cosθ)
    Y .= (Xend .- pend .* factor) .* λ'
    return Y
end

@doc raw"""
    get_vector(M::AbstractProjectiveSpace, p, X, B::DefaultOrthonormalBasis{ℝ})

Convert a one-dimensional vector of coefficients $X$ in the basis `B` of the tangent space
at $p$ on the [`AbstractProjectiveSpace`](@ref) $M=𝔽ℙ^n$ to a tangent vector $Y$ at $p$ by
unitarily transforming the hyperplane containing $X$, whose normal is the $x$-axis, to the
hyperplane whose normal is $p$.

Given $q = p \overline{λ} + x$, where
$λ = \frac{⟨x, p⟩_{\mathrm{F}}}{|⟨x, p⟩_{\mathrm{F}}|}$, $⟨⋅, ⋅⟩_{\mathrm{F}}$ denotes the
Frobenius inner product, and $\overline{⋅}$ denotes complex or quaternionic conjugation, the
formula for $Y$ is
````math
Y = \left(X - q\frac{2 \left\langle q, \begin{pmatrix}0 \\ X\end{pmatrix}\right\rangle_{\mathrm{F}}}{⟨q, q⟩_{\mathrm{F}}}\right) λ.
````
"""
get_vector(::AbstractProjectiveSpace, p, X, ::DefaultOrthonormalBasis{ℝ})

function get_vector_orthonormal!(
    M::AbstractProjectiveSpace{𝔽},
    Y,
    p,
    X,
    ::RealNumbers,
) where {𝔽}
    n = div(manifold_dimension(M), real_dimension(𝔽))
    z = p[1]
    cosθ = abs(z)
    λ = nzsign(z, cosθ)
    pend = view(p, 2:(n + 1))
    pX = dot(pend, X)
    Y[1] = -λ * pX * λ
    Y[2:(n + 1)] .= (X .- pend .* (pX / (1 + cosθ))) .* λ
    return Y
end

injectivity_radius(::AbstractProjectiveSpace) = π / 2
injectivity_radius(::AbstractProjectiveSpace, p) = π / 2
injectivity_radius(::AbstractProjectiveSpace, ::AbstractRetractionMethod) = π / 2
injectivity_radius(::AbstractProjectiveSpace, p, ::AbstractRetractionMethod) = π / 2

@doc raw"""
    inverse_retract(M::AbstractProjectiveSpace, p, q, method::ProjectionInverseRetraction)
    inverse_retract(M::AbstractProjectiveSpace, p, q, method::PolarInverseRetraction)
    inverse_retract(M::AbstractProjectiveSpace, p, q, method::QRInverseRetraction)

Compute the equivalent inverse retraction [`ProjectionInverseRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.ProjectionInverseRetraction),
[`PolarInverseRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.PolarInverseRetraction), and [`QRInverseRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.QRInverseRetraction) on the
[`AbstractProjectiveSpace`](@ref) manifold `M`$=𝔽ℙ^n$, i.e.
````math
\operatorname{retr}_p^{-1} q = q \frac{1}{⟨p, q⟩_{\mathrm{F}}} - p,
````
where $⟨⋅, ⋅⟩_{\mathrm{F}}$ is the Frobenius inner product.

Note that this inverse retraction is equivalent to the three corresponding inverse
retractions on [`Grassmann(n+1,1,𝔽)`](@ref), where the three inverse retractions in this
case coincide.
For $ℝℙ^n$, it is the same as the `ProjectionInverseRetraction` on the real
[`Sphere`](@ref).
"""
inverse_retract(
    ::AbstractProjectiveSpace,
    p,
    q,
    ::Union{ProjectionInverseRetraction,PolarInverseRetraction,QRInverseRetraction},
)

function inverse_retract_qr!(::AbstractProjectiveSpace, X, p, q)
    X .= q ./ dot(p, q) .- p
    return X
end
function inverse_retract_polar!(::AbstractProjectiveSpace, X, p, q)
    X .= q ./ dot(p, q) .- p
    return X
end
function inverse_retract_project!(::AbstractProjectiveSpace, X, p, q)
    X .= q ./ dot(p, q) .- p
    return X
end

@doc raw"""
    isapprox(M::AbstractProjectiveSpace, p, q; kwargs...)

Check that points `p` and `q` on the [`AbstractProjectiveSpace`](@ref) `M`$=𝔽ℙ^n$ are
members of the same equivalence class, i.e. that $p = q λ$ for some element $λ ∈ 𝔽$ with
unit absolute value, that is, $|λ| = 1$.
This is equivalent to the Riemannian
[`distance`](@ref distance(::AbstractProjectiveSpace, p, q)) being 0.
"""
function Base.isapprox(::AbstractProjectiveSpace, p, q; kwargs...)
    return isapprox(abs(dot(p, q)), 1; kwargs...)
end

@doc raw"""
    log(M::AbstractProjectiveSpace, p, q)

Compute the logarithmic map on [`AbstractProjectiveSpace`](@ref) `M`$ = 𝔽ℙ^n$,
i.e. the tangent vector whose corresponding [`geodesic`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/functions.html#ManifoldsBase.geodesic-Tuple{AbstractManifold,%20Any,%20Any}) starting from `p`
reaches `q` after time 1 on `M`. The formula reads

````math
\log_p q = (q λ - \cos θ p) \frac{θ}{\sin θ},
````
where $θ = \arccos|⟨q, p⟩_{\mathrm{F}}|$ is the
[`distance`](@ref distance(::AbstractProjectiveSpace, p, q)) between $p$ and $q$,
$⟨⋅, ⋅⟩_{\mathrm{F}}$ is the Frobenius inner product, and
$λ = \frac{⟨q, p⟩_{\mathrm{F}}}{|⟨q, p⟩_{\mathrm{F}}|} ∈ 𝔽$ is the unit scalar that
minimizes $d_{𝔽^{n+1}}(p - q λ)$.
That is, $q λ$ is the member of the equivalence class $[q]$ that is closest to $p$ in the
embedding.
As a result, $\exp_p \circ \log_p \colon q ↦ q λ$.

The logarithmic maps for the real [`AbstractSphere`](@ref) $𝕊^n$ and the real projective
space $ℝℙ^n$ are identical when $p$ and $q$ are in the same hemisphere.
"""
log(::AbstractProjectiveSpace, p, q)

function log!(M::AbstractProjectiveSpace, X, p, q)
    z = dot(q, p)
    cosθ = abs(z)
    λ = nzsign(z, cosθ)
    X .= (q .* λ .- cosθ .* p) ./ usinc_from_cos(cosθ)
    return project!(M, X, p, X)
end

@doc raw"""
    manifold_dimension(M::AbstractProjectiveSpace{𝔽}) where {𝔽}

Return the real dimension of the [`AbstractProjectiveSpace`](@ref) `M`, respectively i.e.
the real dimension of the embedding minus the real dimension of the field `𝔽`.
"""
function manifold_dimension(M::AbstractProjectiveSpace{𝔽}) where {𝔽}
    return manifold_dimension(get_embedding(M)) - real_dimension(𝔽)
end

"""
    mean(
        M::AbstractProjectiveSpace,
        x::AbstractVector,
        [w::AbstractWeights,]
        method = GeodesicInterpolationWithinRadius(π/4);
        kwargs...,
    )

Compute the Riemannian [`mean`](@ref mean(M::AbstractManifold, args...)) of points in vector `x`
using [`GeodesicInterpolationWithinRadius`](@ref).
"""
mean(::AbstractProjectiveSpace, ::Any...)

function default_estimation_method(::AbstractProjectiveSpace, ::typeof(mean))
    return GeodesicInterpolationWithinRadius(π / 4)
end

function mid_point!(M::ProjectiveSpace, q, p1, p2)
    z = dot(p2, p1)
    λ = nzsign(z)
    q .= p1 .+ p2 .* λ
    project!(M, q, q)
    return q
end

@doc raw"""
    project(M::AbstractProjectiveSpace, p)

Orthogonally project the point `p` from the embedding onto the
[`AbstractProjectiveSpace`](@ref) `M`:
````math
\operatorname{proj}(p) = \frac{p}{\lVert p \rVert}_{\mathrm{F}},
````
where $\lVert ⋅ \rVert_{\mathrm{F}}$ denotes the Frobenius norm.
This is identical to projection onto the [`AbstractSphere`](@ref).
"""
project(::AbstractProjectiveSpace, ::Any)

project!(::AbstractProjectiveSpace, q, p) = (q .= p ./ norm(p))

@doc raw"""
    project(M::AbstractProjectiveSpace, p, X)

Orthogonally project the point `X` onto the tangent space at `p` on the
[`AbstractProjectiveSpace`](@ref) `M`:

````math
\operatorname{proj}_p (X) = X - p⟨p, X⟩_{\mathrm{F}},
````
where $⟨⋅, ⋅⟩_{\mathrm{F}}$ denotes the Frobenius inner product.
For the real [`AbstractSphere`](@ref) and `AbstractProjectiveSpace`, this projection is the
same.
"""
project(::AbstractProjectiveSpace, ::Any, ::Any)

project!(::AbstractProjectiveSpace, Y, p, X) = (Y .= X .- p .* dot(p, X))

@doc raw"""
    representation_size(M::AbstractProjectiveSpace)

Return the size points on the [`AbstractProjectiveSpace`](@ref) `M` are represented as,
i.e., the representation size of the embedding.
"""
@generated representation_size(::ArrayProjectiveSpace{N}) where {N} = size_to_tuple(N)
@generated representation_size(::ProjectiveSpace{N}) where {N} = (N + 1,)

@doc raw"""
    retract(M::AbstractProjectiveSpace, p, X, method::ProjectionRetraction)
    retract(M::AbstractProjectiveSpace, p, X, method::PolarRetraction)
    retract(M::AbstractProjectiveSpace, p, X, method::QRRetraction)

Compute the equivalent retraction [`ProjectionRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.ProjectionRetraction), [`PolarRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.PolarRetraction),
and [`QRRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.QRRetraction) on the [`AbstractProjectiveSpace`](@ref) manifold `M`$=𝔽ℙ^n$,
i.e.
````math
\operatorname{retr}_p X = \operatorname{proj}_p(p + X).
````

Note that this retraction is equivalent to the three corresponding retractions on
[`Grassmann(n+1,1,𝔽)`](@ref), where in this case they coincide.
For $ℝℙ^n$, it is the same as the `ProjectionRetraction` on the real [`Sphere`](@ref).
"""
retract(
    ::AbstractProjectiveSpace,
    p,
    X,
    ::Union{ProjectionRetraction,PolarRetraction,QRRetraction},
)

function retract_polar!(M::AbstractProjectiveSpace, q, p, X)
    q .= p .+ X
    return project!(M, q, q)
end
function retract_project!(M::AbstractProjectiveSpace, q, p, X)
    q .= p .+ X
    return project!(M, q, q)
end
function retract_qr!(M::AbstractProjectiveSpace, q, p, X)
    q .= p .+ X
    return project!(M, q, q)
end

function Base.show(io::IO, ::ProjectiveSpace{n,𝔽}) where {n,𝔽}
    return print(io, "ProjectiveSpace($(n), $(𝔽))")
end
function Base.show(io::IO, ::ArrayProjectiveSpace{N,𝔽}) where {N,𝔽}
    return print(io, "ArrayProjectiveSpace($(join(N.parameters, ", ")); field = $(𝔽))")
end

"""
    uniform_distribution(M::ProjectiveSpace{n,ℝ}, p) where {n}

Uniform distribution on given [`ProjectiveSpace`](@ref) `M`. Generated points will be of
similar type as `p`.
"""
function uniform_distribution(M::ProjectiveSpace{n,ℝ}, p) where {n}
    d = Distributions.MvNormal(zero(p), 1.0)
    return ProjectedPointDistribution(M, d, project!, p)
end

@doc raw"""
    parallel_transport_to(M::AbstractProjectiveSpace, p, X, q)

Parallel transport a vector `X` from the tangent space at a point `p` on the
[`AbstractProjectiveSpace`](@ref) `M`$=𝔽ℙ^n$ to the tangent space at another point `q`.

This implementation proceeds by transporting $X$ to $T_{q λ} M$ using the same approach as
[`parallel_transport_direction`](@ref parallel_transport_direction(::AbstractProjectiveSpace, p, X, d)),
where $λ = \frac{⟨q, p⟩_{\mathrm{F}}}{|⟨q, p⟩_{\mathrm{F}}|} ∈ 𝔽$ is the unit scalar that
takes $q$ to the member $q λ$ of its equivalence class $[q]$ closest to $p$ in the
embedding.
It then maps the transported vector from $T_{q λ} M$ to $T_{q} M$.
The resulting transport to $T_{q} M$ is
````math
\mathcal{P}_{q ← p}(X) = \left(X - \left(p \frac{\sin θ}{θ} + d \frac{1 - \cos θ}{θ^2}\right) ⟨d, X⟩_p\right) \overline{λ},
````
where $d = \log_p q$ is the direction of the transport, $θ = \lVert d \rVert_p$ is the
[`distance`](@ref distance(::AbstractProjectiveSpace, p, q)) between $p$ and $q$, and
$\overline{⋅}$ denotes complex or quaternionic conjugation.
"""
parallel_transport_to(::AbstractProjectiveSpace, ::Any, ::Any, ::Any)

function parallel_transport_to!(::AbstractProjectiveSpace, Y, p, X, q)
    z = dot(q, p)
    λ = nzsign(z)
    m = p .+ q .* λ # un-normalized midpoint
    mnorm2 = real(dot(m, m))
    factor = λ' * dot(q, X) * (2 / mnorm2) # λ' * dot(q, X) ≡ dot(q * λ, X)
    # multiply by λ' to bring from T_{\exp_p(\log_p q)} M to T_q M
    # this ensures that subsequent functions like `exp(M, q, Y)` do the right thing
    Y .= (X .- m .* factor) .* λ'
    return Y
end
function vector_transport_to_project!(M::AbstractProjectiveSpace, Y, p, X, q)
    project!(M, Y, q, X)
    return Y
end

@doc raw"""
    parallel_transport_direction(M::AbstractProjectiveSpace, p, X, d)

Parallel transport a vector `X` from the tangent space at a point `p` on the
[`AbstractProjectiveSpace`](@ref) `M` along the [`geodesic`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/functions.html#ManifoldsBase.geodesic-Tuple{AbstractManifold,%20Any,%20Any}) in the direction
indicated by the tangent vector `d`, i.e.
````math
\mathcal{P}_{\exp_p (d) ← p}(X) = X - \left(p \frac{\sin θ}{θ} + d \frac{1 - \cos θ}{θ^2}\right) ⟨d, X⟩_p,
````
where $θ = \lVert d \rVert$, and $⟨⋅, ⋅⟩_p$ is the [`inner`](@ref) product at the point $p$.
For the real projective space, this is equivalent to the same vector transport on the real
[`AbstractSphere`](@ref).
"""
parallel_transport_direction(::AbstractProjectiveSpace, ::Any, ::Any, ::Any)

function parallel_transport_direction!(M::AbstractProjectiveSpace, Y, p, X, d)
    θ = norm(M, p, d)
    cosθ = cos(θ)
    dX = inner(M, p, d, X)
    α = usinc(θ) * dX
    β = ifelse(iszero(θ), zero(cosθ), (1 - cosθ) / θ^2) * dX
    Y .= X .- p .* α .- d .* β
    return Y
end


""" Returns the convolution of 2 arrays, arr1 and arr2
Pre: arr1 and arr2 must not be empty.
"""
function convolve(arr1, arr2)
    if length(arr1) > length(arr2)
        larger_arr = arr1
        smaller_arr = arr2
    else
        larger_arr = arr2
        smaller_arr = arr1
    end

    number_of_columns = length(arr1) + length(arr2) - 1
    conv_matrix = zeros(length(larger_arr), number_of_columns)
    for i in 1:length(larger_arr)
        mulitplied_row = smaller_arr * larger_arr[i]
        # Insert the multiplied row into the convolution matrix
        conv_matrix[CartesianIndex.(i, i:length(mulitplied_row)+i-1)] .= mulitplied_row
    end

    convolution = sum(conv_matrix, dims=1)  # Find sum of columns
    return convolution
end

""" Returns the product of 2 matrices.
Pre: The number of columns in matrix1 must equal the number of rows in matrix2 (throws
DimensionMismatch if not).
Post: The resulting matrix will have the same number of rows as matrix1 and the same number of
columns as matrix2.
"""
function matrixMultiply(matrix1, matrix2)
    # Handle the case when 1 matrix is empty and the other has a length of 1, or when both matrices
    # are emtpy.
    if length(matrix1) == 0 && length(matrix2) == 1 ||
            length(matrix1) == 1 && length(matrix2) == 0 ||
            length(matrix1) == 0 && length(matrix2) == 0
        return reshape([], 0, 0)
    elseif size(matrix1, 2) != size(matrix2, 1)
        throw(DimensionMismatch("The number of columns in matrix1 must equal the number of rows in matrix2"))
    end

    resultMatrix = zeros(size(matrix1, 1), size(matrix2, 2))
    for row in 1:size(matrix1, 1)
        for column in 1:size(matrix2, 2)
            row1 = matrix1[row, :]
            column2 = matrix2[:, column]
            resultMatrix[row, column] = sum(dotProduct(row1, column2))
        end
    end
    return resultMatrix
end

#  Returns the dot product of array1 and array2
function dotProduct(array1, array2)
    return array1 .* array2
end

""""""
function DFT(array)

end

""""""
function IDFT(array)

end