# This file is a part of TextSearch.jl

import Base: +, -, *, /, ==, zero
using LinearAlgebra, SparseArrays
import LinearAlgebra: dot, norm, normalize!
import SparseArrays: nnz
using SimilaritySearch
import SimilaritySearch: evaluate
export centroid, evaluate, NormalizedAngleDistance, NormalizedCosineDistance, AngleDistance, CosineDistance

const DVEC{Ti,Tv<:Number} = Dict{Ti,Tv}
const SVEC = DVEC{UInt32,Float32}

export DVEC, SVEC
nnz(dvec::DVEC) = length(dvec)

function Base.findmax(voc::DVEC{I,F}) where {I,F}
    m = typemin(F)
    maxkey = "typemax(I)"
    for (k, v) in voc
        if v >= m
            maxkey = k
            m = v
        end
    end

    (m, maxkey)
end

Base.argmax(voc::DVEC{I,F}) where {I,F} = last(findmax(voc))
Base.maximum(voc::DVEC{I,F}) where {I,F} = maximum(values(voc))

function Base.findmin(voc::DVEC{I,F}) where {I,F}
    m = typemax(F)
    mk = typemax(I)
    for (k, v) in voc
        if v <= m
            mk = k
            m = v
        end
    end

    (m, mk)
end

Base.argmin(voc::DVEC{I,F}) where {I,F} = last(findmin(voc))
Base.minimum(voc::DVEC{I,F}) where {I,F} = first(findmin(voc))

"""
    normalize!(bow::DVEC)

Inplace normalization of `bow`
"""
function normalize!(bow::DVEC{Ti,Tv}) where {Ti,Tv<:AbstractFloat}
    s = 1.0 / norm(bow)
    for (k, v) in bow
        bow[k] = convert(Tv, v * s)
    end

    bow
end

function normalize!(bow::DVEC{Ti,Tv}) where {Ti,Tv<:Integer}
    s = 1.0 / norm(bow)
    for (k, v) in bow
        bow[k] = round(Tv, v * s)
    end

    bow
end

function normalize!(matrix::AbstractVector{<:DVEC})
    for bow in matrix
        normalize!(bow)
    end

    matrix
end

"""
    dot(a::DVEC, b::DVEC)::Float64

Computes the dot product for two DVEC vectors
"""
function dot(a::DVEC, b::DVEC)::Float64
    if length(b) < length(a)
        a, b = b, a  # a must be the smallest bow
    end

    s = 0.0
    for (k, v) in a
        w = convert(Float64, get(b, k, 0))
        s += v * w
    end

    s
end

"""
    norm(a::DVEC)::Float64

Computes a normalized DVEC vector
"""
function norm(a::DVEC)::Float64
    s = 0.0
    for w in values(a)
        s += w * w
    end

    sqrt(s)
end

"""
    zero(::Type{DVEC{Ti,Tv}}) where {Ti,Tv}

Creates an empty DVEC vector
"""
function zero(::Type{DVEC{Ti,Tv}}) where {Ti,Tv}
    DVEC{Ti,Tv}()
end

## inplace sum
"""
    add!(a::DVEC{Ti,Tv}, b::DVEC{Ti,Tv}) where {Ti,Tv<:Real}
    add!(a::DVEC{Ti,Tv}, b::AbstractSparseArray) where {Ti,Tv<:Real}
    add!(a::DVEC{Ti,Tv}, b::Pair{Ti,Tv}) where {Ti,Tv<:Real}

Updates `a` to the sum of `a+b`
"""
function add!(a::DVEC{Ti,Tv}, b::DVEC{Ti,Tv}) where {Ti,Tv<:Real}
    for (k, w) in b
        if w != 0
            a[k] = get(a, k, zero(Tv)) + w
        end
    end

    a
end

function add!(a::DVEC{Ti,Tv}, b::AbstractSparseArray) where {Ti,Tv<:Real}
    for (k, w) in zip(b.nzind, b.nzval)
        if w != 0
            a[k] = get(a, k, zero(Tv)) + w
        end
    end

    a
end

function add!(a::DVEC{Ti,Tv}, b::Pair{Ti,Tv}) where {Ti,Tv<:Real}
    k, w = b
    a[k] = get(a, k, zero(Tv)) + w
    a
end

"""
    Base.sum(col::AbstractVector{<:DVEC})

Computes the sum of the given list of vectors
"""
function Base.sum(col::AbstractVector{<:DVEC})
    v = copy(col[1])
    for i in 2:length(col)
        add!(v, col[i])
    end

    v
end

"""
    centroid(cluster::AbstractVector{<:DVEC})

Computes a centroid of the given list of DVEC vectors
"""
function centroid(cluster::AbstractVector{<:DVEC})
    normalize!(sum(cluster))
end

"""
    +(a::DVEC{Ti,Tv}, b::DVEC{Ti,Tv}) where {Ti,Tv<:Real}
    +(a::DVEC, b::Pair)

Computes the sum of `a` and `b`
"""
function +(a::DVEC{Ti,Tv}, b::DVEC{Ti,Tv}) where {Ti,Tv<:Real}
    if length(a) < length(b)
        a, b = b, a  # a must be the largest bow
    end

    c = copy(a)
    for (k, w) in b
        if w != 0
            c[k] = get(c, k, zero(Tv)) + w
        end
    end

    c
end

function +(a::DVEC, b::Pair)
    c = copy(a)
    add!(c, b)
end

## definitions for substraction

"""
    -(a::DVEC{Ti,Tv}, b::DVEC{Ti,Tv}) where {Ti,Tv<:Real}

Substracts of `b` of `a`
"""
function -(a::DVEC{Ti,Tv}, b::DVEC{Ti,Tv}) where {Ti,Tv<:Real}
    c = copy(a)
    for (k, w) in b
        if w != 0
            c[k] = get(c, k, zero(Tv)) - w
        end
    end

    c
end

## definitions for product
"""
    *(a::DVEC{Ti,Tv}, b::DVEC{Ti,Tv}) where {Ti,Tv<:Real}
    *(a::DVEC{K, V}, b::F) where K where {V<:Real} where {F<:Real}

Computes the element-wise product of a and b
"""
function *(a::DVEC{Ti,Tv}, b::DVEC{Ti,Tv}) where {Ti,Tv<:Real}
    if length(b) < length(a)
        a, b = b, a  # a must be the smallest bow
    end

    c = copy(a)
    for k in keys(a)
        w = get(b, k, zero(Tv))
        if w == 0
            delete!(c, k)
        else
            c[k] = convert(Tv, c[k] * w)
        end
    end

    c
end

function *(a::DVEC{K, V}, b::F) where K where {V<:Real} where {F<:Real}
    c = copy(a)
    for (k, v) in a
        c[k] = convert(V, v * b)
    end

    c
end

function *(b::F, a::DVEC) where {F<:Real}
    a * b
end

"""
    /(a::DVEC{K, V}, b::F) where K where {V<:Real} where {F<:Real}

Computes the element-wise division of a and b
"""
function /(a::DVEC{K,V}, b::F) where K where {V<:Real} where {F<:Real}
    a * (1.0/b)
end

"""
    evaluate(::NormalizedCosineDistance, a::DVEC, b::DVEC)::Float64

Computes the cosine distance between two DVEC sparse vectors

It supposes that bags are normalized (see `normalize!` function)

"""
function evaluate(::NormalizedCosineDistance, a::DVEC, b::DVEC)::Float64
    1.0 - dot(a, b)
end

"""
    evaluate(::CosineDistance, a::DVEC, b::DVEC)::Float64

Computes the cosine distance between two DVEC sparse vectors

"""
function evaluate(::CosineDistance, a::DVEC, b::DVEC)::Float64
    1.0 - full_cosine(a, b)
end

const π_2 = π / 2

"""
    evaluate(::NormalizedAngleDistance, a::DVEC, b::DVEC)::Float64

Computes the angle  between two DVEC sparse vectors

It supposes that all bags are normalized (see `normalize!` function)

"""
function evaluate(::NormalizedAngleDistance, a::DVEC, b::DVEC)::Float64
    d = dot(a, b)

    if d <= -1.0
        π
    elseif d >= 1.0
        0.0
    elseif d == 0  # turn around for zero vectors, in particular for denominator=0
        π_2
    else
        acos(d)
    end
end

"""
    evaluate(::AngleDistance, a::DVEC, b::DVEC)::Float64

Computes the angle between two DVEC sparse vectors

"""
function evaluate(::AngleDistance, a::DVEC, b::DVEC)::Float64
    d = full_cosine(a, b)

    if d <= -1.0
        π
    elseif d >= 1.0
        0.0
    elseif d == 0  # turn around for zero vectors, in particular for denominator=0
        π_2
    else
        acos(d)
    end
end

function full_cosine(a::DVEC, b::DVEC)::Float64
    return dot(a, b) / (norm(a) * norm(b))
end


# Checks for various fairness criteria.

"""
    check_complete(A)

Check that the allocation is complete, or effective, in the sense that each item
has been allocated to at least one agent.
"""
function check_complete(A)
    for g in items(A)
        length(owners(A, g)) >= 1 || return false
    end
    return true
end

"""
    check_partition(A)

Check that the allocation is a partition, i.e., that each item has been
allocated to exactly one agent.
"""
function check_partition(A)
    for g in items(A)
        length(owners(A, g)) == 1 || return false
    end
    return true
end

# General check for EF-like properties (EF_ = EF, EF1, EFX, ...), where the
# value of the other agent's bundle is somehow modified, e.g., by removing
# some item. This modified value is provided by the function value_:
function check_ef_(V, A, value_)

    N = agents(V)

    for i in N, j in N

        i !== j || continue

        if value(V, i, A) < value_(V, i, bundle(A, j))
            return false
        end

    end

    return true

end

"""
    check_ef(V, A)

Check whether the allocation `A` is *envy-free* for the profile `V`, i.e., if no
agent strictly prefers another agent's bundle.
"""
check_ef(V, A) = check_ef_(V, A, value)

"""
    check_ef1(V, A)

Check whether the allocation `A` is *envy-free up to one item* for the profile
`V`, i.e., if no agent strictly prefers another agent's bundle, given that an
appropriate (e.g., the most valuable) item is removed.
"""
check_ef1(V, A) = check_ef_(V, A, value_1)

"""
    check_efx(V, A)

Check whether the allocation `A` is *envy-free up to any item* for the profile
`V`, i.e., if no agent strictly prefers another agent's bundle, given that an
appropriate (e.g., the least valuable) item is removed.
"""
check_efx(V, A) = check_ef_(V, A, value_x)

"""
    check(V, A, C)

Check that the allocation `A` obeys the `Constraint` `C`, given the profile `V`.
"""
function check end

"""
    check(V, A, C::Counts)

Check whether the allocation `A` respects the cardinality constraints `C`.
"""
function check(V, A, C::Counts)

    for c in C

        counts = zeros(Int, na(A))

        for g in c, i in owners(A, g)
            counts[i] += 1
        end

        maximum(counts) <= c.threshold || return false

    end

    return true

end

"""
    check(V, A, C::Conflicts)

Check whether the allocation `A` respects the item conflicts `C`.
"""
function check(V, A, C::Conflicts)

    G = graph(C)

    for e in edges(G)
        isempty(owners(A, src(e)) ∩ owners(A, dst(e))) || return false
    end

    return true

end


"""
generates Z (full)  with M (natural mortality) and F (catch mortality)
"""
function genZ(M,F)
    (tmax,jmax)=size(M)
    Z=zeros(tmax,jmax)
    Z=M+F
    return(Z)
end

"""
generates G with M (natural mortality) and F (catch mortality), Z (full mortality)
"""
function genG(M,F)
    (tmax,jmax)=size(M)
    Z=genZ(M,F)
    G=zeros(tmax,jmax)
    for t in 1:tmax
        for j in 1:jmax
            G[t,j]=F[t,j]/Z[t,j]*(1-exp(-Z[t,j]))
        end
    end
    return(G)
end

#FILTERING

"""
filtering: returns a vector of deviations (sigma^2^f) for a cohort
sigma^2_0 - variance  for initial age
sigma^2_e -  variance  for the evidance vector (catches)
sigma^2_x -  variance  for the state vector (population)
l - the length of the cohort
"""
function sigma2f(sigma20,sigma2x,sigma2e,l)
    s2f=zeros(l)
    s2f[1]=sigma20*sigma2e/(sigma20+sigma2e)
    if l >1
        for i in 1:l-1
        s2f[i+1]=(s2f[i]+sigma2x)*sigma2e/(s2f[i]+sigma2x+sigma2e)
        end
    end
    return(s2f)
end

"""
filtering: returns a vector of means (mu^f) for a cohort
alpha - coefficients for the state x_{t+1}=x_t +alpha_t
beta - coefficients for the observation y_{t}=x_t +beta_t
 e - the evidance vector
mu_0 - mean for the initial age
sigma^2_0 - variance (deviation is the square root of its variance) for initial age
mu_e - mean for the evidence
sigma^2_e - variance  for the evidance  (catch)
mu_x - mean for the state
sigma^2_e - variance for the state (population)
l - the length of the cohort
"""
function muf(alpha, beta, e,mu0,sigma20,mux,sigma2x,mue,sigma2e)
    l=length(e)
    muf=zeros(l)
    muf[1]=mu0
    #muf[1]=x0+beta[1]+mue
    s2f= sigma2f(sigma20,sigma2e,sigma2x,l)
    if l>1
        for i in 1:l-1
            muf[i+1]=(muf[i]+alpha[i]+mux)+(e[i+1]-(muf[i]+alpha[i]+mux+beta[i+1]+mue))*
        (s2f[i]+sigma2x)/(s2f[i]+sigma2x+sigma2e)
        end
    end
        return(muf)
    end

"""
filtering: returns filtered cohorts represented as vecs: csvlogts1muf - means,csvlogts1s2f - sigmas^2 (deviations),
"""
function genmus2f(ts1,c1,yb,M,F,mu0,s20,mux,s2x,muy,s2y)
    tmax,jmax=size(ts1)

    csvlogts1= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),ts1)))
    csvlogc1= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),c1)))
    csvZ= csmtx2csvs(csmtx(yb,tmax,jmax,genZ(M,F)))
    csvlogG= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),genG(M,F))))

    csvlogts1muf= deepcopy(csvlogts1)
    csvlogts1s2f= deepcopy(csvlogts1)

    for i in 1:tmax+jmax-1
        x=csvlogts1.vecs[i].vec
        e=csvlogc1.vecs[i].vec
        alpha=-csvZ.vecs[i].vec
        beta=csvlogG.vecs[i].vec

        vec=muf(alpha, beta, e,x[1],s20,mux,s2x,muy,s2y)
        vecs2=sigma2f(s20,s2x,s2y,length(e))

        csvlogts1muf.vecs[i].vec=vec
          csvlogts1s2f.vecs[i].vec=vecs2

       # if i == n
        #    println(vec)
       #     println(csvlogts1f.vecs[i].vec)
      #  println(x)

       # end

    end
    return(csvlogts1muf,csvlogts1s2f)

end

#PREDICTION

"""
prediction: returns the vector of deviation predictions (sigma^2^p_{t+1},..., sigma^2^p_{t+k+1}) for a cohort
sigma^2_x - variance for the state vector (population)
l - the length of the cohort
"""
function sigma2p(sigma2ft,sigma2x,k)
    sigma2pk=zeros(k+1)
    sigma2pk[1]=sigma2ft+sigma2x
    for t in 1:k
        sigma2pk[t+1]=sigma2pk[t]+sigma2x
    end
    return(sigma2pk)
end

"""
prediction: returns the vector of mean predictions (mu^p_{t+1}, ..., mu^p_{t+k+1}) for a cohort
alphak=(alpha^t, ..., alpha_{t+k}) - (virtual) coefficients for x_{t+k+1}=x_{t+k} +alpha_{t+k} -
muft - mu^f_t
mu_x - mean for the state
k >= 1 - the prediction step
"""
function mup(alphatk,mux,muft,k)
    mupk=zeros(k+1)
    mupk[1]=muft+alphatk[1]+mux
            for t in 1:k
            mupk[t+1]=mupk[t]+alphatk[t+1]+mux
        end
        return(mupk)
    end

"""
makes k predictions for all cohorts (a^e+1, .., a^e+k)  with yr>=yr2. cuts extra elements with age greater than jmax.
returns filtered cohorts represented as vecs: csvlogts1mup - means,csvlogts1s2p - sigmas^2 (deviations),
mup and s2p (means and sigmas^2 (deviations))
"""
function genmus2p(ts1,c1,yb,M,F,mu0,s20,mux,s2x,muy,s2y,k)
    tmax,jmax=size(ts1)

    csvlogts1= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),ts1)))
    csvlogc1= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),c1)))
    csvZ= csmtx2csvs(csmtx(yb,tmax,jmax,genZ(M,F)))
    csvlogG= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),genG(M,F))))

    csvlogts1muf= deepcopy(csvlogts1)
    csvlogts1s2f= deepcopy(csvlogts1)
    csvlogts1mufp= deepcopy(csvlogts1)
     csvlogts1mufm= deepcopy(csvlogts1)

    csvlogts1mup= deepcopy(csvlogts1)
    csvlogts1s2p= deepcopy(csvlogts1)

    yr1=yb
    yr0=yr1-jmax+1
    yr2=yr1+tmax-jmax
    yr3=yr1+tmax-1

    for i in 1:tmax+jmax-1
        x=csvlogts1.vecs[i].vec
        e=csvlogc1.vecs[i].vec
        alpha=-csvZ.vecs[i].vec
        beta=csvlogG.vecs[i].vec
        yr=csvlogts1.vecs[i].yb

        vec=muf(alpha, beta, e,x[1],s20,mux,s2x,muy,s2y)
        vecs2=sigma2f(s20,s2x,s2y,length(e))

        csvlogts1muf.vecs[i].vec=vec
        csvlogts1s2f.vecs[i].vec=vecs2

       # if i == n
        #    println(vec)
       #     println(csvlogts1f.vecs[i].vec)
      #  println(x)
        csvlogts1mup.vecs[i].vec=vec

        if k > 0
        muzs=zeros(k)

        s2zs=zeros(k)

       # end
     if yr> yr2
        begin
            muzs[1],s2zs[1]=last(vec)+last(alpha)+mux,last(vecs2)+s2x

            for l in 2:k

                      muzs[l],s2zs[l]=
                        if length(vec)+l<=jmax
                        muzs[l-1]+last(alpha)+mux,s2zs[l-1]+s2x
                        else
                           muzs[l],s2zs[l]= muzs[jmax-length(vec)],s2zs[jmax-length(vec)]
                        end
            end
                       #if length(vec)+k+1 <= jmax
                        #    for l in length(vec)+k+1:jmax
                         # muzs[l],s2zs[l]=muzs[length(vec)+k],s2zs[length(vec)+k]
                       # end
                       # end

                    #println(i)
                    #println(yr)
         #println(muzs)
         #  println(s2zs)

      append!(vec,muzs)
      append!(vecs2,s2zs)
                    if length(vec)>jmax
                        vec, vecs2= vec[1:jmax], vecs2[1:jmax]
                    end
        end
        end
            csvlogts1mup.vecs[i].vec=vec
            csvlogts1s2p.vecs[i].vec=vecs2

    end
    end

    return(csvlogts1mup,csvlogts1s2p)

end

#SMOOTHING

"""
backward: returns a vector of deviations (sigma^2^b) for a cohort
sigma^2_0 - variance  for initial age
sigma^2_e -  variance  for the evidance vector (catches)
sigma^2_x -  variance  for the state vector (population)
l - the length of the cohort
"""
function sigma2b(sigma20,sigma2x,sigma2e,l)
    s2b=zeros(l)
    s2f=sigma2f(sigma20,sigma2x,sigma2e,l)
    s2b[l]=sigma2x+sigma2e
    if l >1
        i=l
        while i>1
        s2b[i-1]=(sigma2x*sigma2e +s2b[i]*(sigma2x+sigma2e))/(sigma2e+s2b[i])
            i=i-1
        end
    end
    return(s2b)
end

"""
smoothing: makes several cohorts represented as vecs: csvlogts1mus - means,csvlogts1s2s - sigmas^2 (deviations)
"""
function genmus2s(ts1,c1,yb,M,F,mu0,s20,mux,s2x,muy,s2y)
    tmax,jmax=size(ts1)

    csvlogts1= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),ts1)))
    csvlogc1= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),c1)))
    csvZ= csmtx2csvs(csmtx(yb,tmax,jmax,genZ(M,F)))
    csvlogG= csmtx2csvs(csmtx(yb,tmax,jmax,map(x->log(x),genG(M,F))))

    csvlogts1muf= deepcopy(csvlogts1)
    csvlogts1s2f= deepcopy(csvlogts1)

    csvlogts1mub= deepcopy(csvlogts1)
    csvlogts1s2b= deepcopy(csvlogts1)

    csvlogts1mus= deepcopy(csvlogts1)
    csvlogts1s2s= deepcopy(csvlogts1)

    for i in 1:tmax+jmax-1
        x=csvlogts1.vecs[i].vec
        e=csvlogc1.vecs[i].vec
        alpha=-csvZ.vecs[i].vec
        beta=csvlogG.vecs[i].vec

        li=length(e)

        #forward

        vecmuf=muf(alpha, beta, e,x[1],s20,mux,s2x,muy,s2y)
        vecs2f=sigma2f(s20,s2x,s2y,li)

        csvlogts1muf.vecs[i].vec=vecmuf
          csvlogts1s2f.vecs[i].vec=vecs2f

        #backward

        vecmub=zeros(li)
       vecs2b=sigma2b(s20,s2x,s2y,li)

        vecmub[li]=vecmuf[li]

       if li >1
            vecmub[li]=e[li]-alpha[li-1]-mux-beta[li]-muy
        i1=li

        while i1>2
        vecmub[i1-1]=((vecmub[i1]-alpha[i1-2]-mux)*s2y+(e[i1-1]-alpha[i1-2]-beta[i1-1]-mux)*vecs2b[i1])/
                (s2y+vecs2b[i1])
                i1=i1-1

        end
    end

        csvlogts1mub.vecs[i].vec=vecmub
          csvlogts1s2b.vecs[i].vec=vecs2b

     #smoothing

         vecmus=zeros(li)
        vecs2s= zeros(li)

        vecmus[li]=vecmuf[li]
        vecs2s[li]=vecs2f[li]

        if li >1

        for i2 in 1:li-1

            vecmus[i2]=(vecmuf[i2]*vecs2b[i2+1]+vecmub[i2+1]*vecs2f[i2])/(vecs2b[i2+1]+vecs2f[i2])
            vecs2s[i2]=(vecs2b[i2+1]*vecs2f[i2])/(vecs2b[i2+1]+vecs2f[i2])
                #println("i2= ",i2)
                #println("vecs2s=", vecs2s[i2],"  vecs2f=", vecs2f[i2]," vecs2b=", vecs2b[i2])
        end
            end
         csvlogts1mus.vecs[i].vec=vecmus
          csvlogts1s2s.vecs[i].vec=vecs2s

    end

    return(csvlogts1mus,csvlogts1s2s)

end

"""
at +p%
"""
function genQuantile(lmu,lsigma2,p)
    l=length(lmu)
    lq=zeros(l)
    for i in 1:l
        lq[i]=if lsigma2[i]>0.
           quantile(Distributions.Normal(lmu[i],sqrt(lsigma2[i])),p)
            else
            lmu[i]
        end

        end
    return(lq)
end

"""
at -p%
"""
function genQuantile1(lmu,lsigma2,p)
    l=length(lmu)
    lq=zeros(l)
    for i in 1:l
        lq[i]=if lsigma2[i]>0.
           2*lmu[i]-quantile(Distributions.Normal(lmu[i],sqrt(lsigma2[i])),p)
            else
            lmu[i]
        end

        end
    return(lq)
end


abstract Interpolation
abstract Interpolation2D <: Interpolation

abstract DerivativeApprox
abstract BoundaryCondition

type Spline <: DerivativeApprox end
type Lagrange <: BoundaryCondition end

# general interpolation methods #
# update if value passed in
function update!(interp::Interpolation, idx::Int, val::Float64)
  interp.y_vals[idx] = val

  update!(interp, idx)

  return interp
end

# locate x
function locate(interp::Interpolation, val::Float64)
  if val < interp.x_vals[1]
    return 1
  elseif val >= interp.x_vals[end - 1]
    # return interp.x_vals[end] - interp.x_vals[1] - 2
    return length(interp.x_vals) - 1
  else
    # return findfirst(interp.x_vals .> val) - 1 # need to look at this
    return searchsortedlast(interp.x_vals, val)
  end
end


import IntervalRootFinding, LinearAlgebra
using IntervalRootFinding: (..), (×), IntervalBox, interval
export fixedpoints, .., ×, IntervalBox, interval

"""
    fixedpoints(ds::DynamicalSystem, box, p = ds.p; kwargs...) → fp, eigs, stable
Return all fixed points `fp` of the given `ds`
that exist within the state space subset `box` for parameter configuration `p`.
Fixed points are returned as a [`Dataset`](@ref).
For convenience, a vector of the Jacobian eigenvalues of each fixed point, and whether
the fixed points are stable or not, are also returned.

`box` is an appropriate `IntervalBox` from IntervalRootFinding.jl.
E.g. for a 3D system it would be something like
```julia
v, z = -5..5, -2..2   # 1D intervals, can use `interval(-5, 5)` instead
box = v × v × z       # `\\times = ×`, or use `IntervalBox(v, v, z)` instead
```

Internally IntervalRootFinding.jl is used and as a result we are guaranteed to find all
fixed points that exist in `box`, regardless of stability. Since IntervalRootFinding.jl
returns an interval containing a unique fixed point, we return the midpoint of the
interval as the actual fixed point.
Naturally, limitations inherent to IntervalRootFinding.jl apply here.

The output of `fixedpoints` can be used in the [BifurcationKit.jl](https://github.com/rveltz/BifurcationKit.jl)
as a start of a continuation process. See also [`periodicorbits`](@ref).

## Keywords
* `method = IntervalRootFinding.Krawczyk` configures the root finding method,
  see the docs of IntervalRootFinding.jl for all posibilities.
* `tol = 1e-15` is the root-finding tolerance.
"""
function fixedpoints(ds::DynamicalSystem, box, p = ds.p;
        method = IntervalRootFinding.Krawczyk, o = nothing, tol = 1e-15
    )
    if DynamicalSystemsBase.isinplace(ds)
        error("`fixedpoints` works only for out-of-place dynamical systems.")
    end
    # Find roots via IntervalRootFinding.jl
    f = to_root_f(ds, p, o)
    jac = to_root_J(ds, p, o)
    r = IntervalRootFinding.roots(f, jac, box, method, tol)
    D = dimension(ds)
    fp::Dataset{D, Float64} = roots_to_dataset(r, D)
    # Find eigenvalues and stability
    eigs = Vector{Vector{Complex{Float64}}}(undef, length(fp))
    J = Array(jacobian(ds)) # `eigvals` doesn't work with StaticArrays.jl
    for (i, u) in enumerate(fp)
        J .= jacobian(ds, u, p, 0.0) # notice that we use the "pure" jacobian, no -u!
        eigs[i] = LinearAlgebra.eigvals(Array(J))
    end
    stable = Bool[isstable(ds, e) for e in eigs]
    return fp, eigs, stable
end

to_root_f(ds::CDS, p, ::Nothing) = u -> ds.f(u, p, 0.0)
to_root_J(ds::CDS, p, ::Nothing) = u -> ds.jacobian(u, p, 0.0)
to_root_f(ds::DDS, p, ::Nothing) = u -> ds.f(u, p, 0) - u
function to_root_J(ds::DDS{IIP, S}, p, ::Nothing) where {IIP, S}
    c = Diagonal(ones(S))
    return u -> ds.jacobian(u, p, 0) - c
end

# Discrete with periodic order
function to_root_f(ds::DDS, p, o::Int)
    u -> begin
        v = copy(u) # copy is free for StaticArrays
        for _ in 1:o
            v = ds.f(v, p, 0)
        end
        return v - u
    end
end
# TODO: Estimate periodic orbits of period `o` for discrete systems.
# What's left is to create the Jacobian for higher iterates.

function roots_to_dataset(r, D)
    if isempty(r)
        @warn "No fixed points found!"
        return Dataset{D, Float64}()
    end
    if any(root.status != :unique for root in r)
        @warn "Non-unique fixed points found!"
    end
    F = zeros(length(r), D)
    for (j, root) in enumerate(r)
        F[j, :] .= map(i -> (i.hi + i.lo)/2, root.interval)
    end
    return Dataset(F; warn = false)
end

isstable(::CDS, e) = maximum(real(x) for x in e) < 0
isstable(::DDS, e) = maximum(abs(x) for x in e) < 1


### A Pluto.jl notebook ###
# v0.17.1

using Markdown
using InteractiveUtils

# ╔═╡ 0936d1be-fb87-4e39-9573-8b1ddd4fd496
begin
	using PrettyTables
	using PlutoUI
end

# ╔═╡ a4feb3e1-48dd-4b74-9e64-65eec798b34c
md"""

### Solution: Open NH$_{3}$ CSTR Decomposition Problem

The teaching team built a reactor in the basement of Olin Hall to produce H$_{2}$ continuously by decomposing NH$_{3}(g)$ according to the reaction:

$$2NH_{3}~{\rightleftharpoons}~{N_{2}+3H_{2}}$$

The feed stream into the reactor (stream 1) consists of 32.0 mol/s of NH$_{3}$(g) and 0.1 mol/s of H$_{2}$O(g) (a contaminant that does not participate in the reaction).  There are no products in the feed stream. At the outlet (stream 2), the teaching team measured 47.24 mol/s of H$_{2}$ leaving the reactor.

__Assumptions__
* The gas phase is ideal
* The ideal gas law is a valid equation of state for this reactor
* The reactor operates at steady-state
* The reactor has a constant T, P and V

__Compute__
* Compute the open extent of reaction $\dot{\epsilon}_{1}$ for the decomposition reaction
* Compute the missing values in the state table XXX.
* Compute the fractional conversion of NH$_{3}$(g).

"""

# ╔═╡ 77e5e456-ca08-4d64-8927-d856e9eed328
begin

	# setup problem -
	n_dot_input = [
		32.0 	; # 1 NH₃ units: mol/s
		0.0 	; # 2 N₂ units: mol/s
		0.0 	; # 3 H₂ units: mol/s
		0.1 	; # 4 H₂O units: mol/s
	];

	# we only know 1 output -
	n_dot_32 = 47.24 # units: mol/s

	# stoichiometry -
	S = [
		-2.0 	; # 1 NH₃
		1.0 	; # 2 N₂
		3.0 	; # 3 H₂
		0.0 	; # 4 H₂O
	];

	# show -
	nothing
end

# ╔═╡ ba730327-4135-4301-9a78-a86c2a777834
md"""
###### a) Compute the open extent of reaction $\dot{\epsilon}_{1}$ (units: mol/s) for NH$_{3}$(g) decomposition.

To compute the open extent $\dot{\epsilon}_{1}$, we start by writing down the species mol balances for NH$_{3}$ (1),
N$_{2}$ (2), H$_{2}$ (3) and H$_{2}$O (4) at steady-state (where we have substituted the stoichiometric coefficients and input flow rates):

$$\begin{eqnarray}
\dot{n}_{12} &=& \dot{n}_{11} - 2\dot{\epsilon_{1}} \\
\dot{n}_{22} &=& \dot{\epsilon_{1}} \\
\dot{n}_{32} &=& 3\dot{\epsilon_{1}} \\
\dot{n}_{42} &=& \dot{n}_{41} \\
\end{eqnarray}$$

From the problem, we know $\dot{n}_{32}$. Thus, we can use the output condition for H$_{2}$ and the H$_{2}$ balance to estimate $\dot{\epsilon}_{1}$.
"""

# ╔═╡ 6a73f92f-c191-4622-9c37-016327616900
begin

	# compute the open extent -
	ϵ₁_dot = (n_dot_32/S[3])

	# display -
	with_terminal() do
		println("ϵ₁_dot = $(ϵ₁_dot) mol/s")
	end
end

# ╔═╡ 5f3c9de7-1d2e-4577-bf4f-e56c7eb579ab
md"""
###### b) Compute the missing values in the state table for the open NH$_{3}$ problem
Now that we have the open extent, we can compute all the missing values by solving the species mol balances for the outlet flow rates:

$$\dot{n}_{i2} = \dot{n}_{i1} + \sigma_{i1}\dot{\epsilon}_{1}\qquad{i=1,2,\cdots,4}$$
"""

# ╔═╡ 636b6093-c8e7-410d-9df0-d7466efa458b
begin

	# solve for outlet -
	n_dot_output = n_dot_input .+ S*ϵ₁_dot

	# just for the lol's - lets compute the yᵢ's
	n_total_dot = sum(n_dot_output)
	y_output = (1/n_total_dot)*n_dot_output

	# build a pretty table -
	# header row -
	state_table_header_row = (["Species","σ_i1","nᵢ_dot_in","nᵢ_dot_out","yᵢ_out"],["","","mol/s", "mol/s", ""]);

	# setup state table data array =
	state_table_array = Array{Any,2}(undef, 5,5)

	# NH3 -
	state_table_array[1,1] = "NH₃"
	state_table_array[1,2] = S[1]
	state_table_array[1,3] = n_dot_input[1]
	state_table_array[1,4] = n_dot_output[1]
	state_table_array[1,5] = y_output[1]

	# N2 -
	state_table_array[2,1] = "N₂"
	state_table_array[2,2] = S[2]
	state_table_array[2,3] = n_dot_input[2]
	state_table_array[2,4] = n_dot_output[2]
	state_table_array[2,5] = y_output[2]

	# H2 -
	state_table_array[3,1] = "H₂"
	state_table_array[3,2] = S[3]
	state_table_array[3,3] = n_dot_input[3]
	state_table_array[3,4] = n_dot_output[3]
	state_table_array[3,5] = y_output[3]

	# H20 -
	state_table_array[4,1] = "H₂O"
	state_table_array[4,2] = S[4]
	state_table_array[4,3] = n_dot_input[4]
	state_table_array[4,4] = n_dot_output[4]
	state_table_array[4,5] = y_output[4]

	# total -
	state_table_array[5,1] = "Total"
	state_table_array[5,2] = sum(S)
	state_table_array[5,3] = sum(n_dot_input)
	state_table_array[5,4] = sum(n_dot_output)
	state_table_array[5,5] = sum(y_output)

	# display the state table -
	with_terminal() do
		# write the table -
		pretty_table(state_table_array; header=state_table_header_row)
	end
end

# ╔═╡ e700a677-a565-41e6-b7ad-56d9a500229d
md"""

###### c) Compute the fractional conversion of NH$_{3}$(g)
For an open system with a single input and a single output, the fractional conversion is defined as:

$$f_{i} = \frac{\dot{n}_{i1} - \dot{n}_{i2}}{\dot{n}_{i1}}\qquad{i=1}$$

But this has the sample conceptual meaning as the closed system version; in the numerator, we are computing the consumption of reactant species $i$ by the reaction in the CSTR and then dividing by the mol flow rate of $i$ entering into the reactor.

"""

# ╔═╡ 35d8f807-f6cb-407b-b3f8-cab053d68ca8
begin

	# compute f₁:
	f₁ = (n_dot_input[1] - n_dot_output[1])/(n_dot_input[1])

	# display -
	with_terminal() do
		println("Fractional conversion of NH₃ f₁ = $(f₁)")
	end
end

# ╔═╡ 65225bd2-4186-11ec-3709-6bba7ee9bd05
html"""
<style>
main {
    max-width: 1200px;
    width: 85%;
    margin: auto;
    font-family: "Roboto, monospace";
}

a {
    color: blue;
    text-decoration: none;
}

.H1 {
    padding: 0px 30px;
}
</style>"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"

[compat]
PlutoUI = "~0.7.19"
PrettyTables = "~1.2.3"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "0bc60e3006ad95b4bb7497698dd7c6d649b9bc06"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.1"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Crayons]]
git-tree-sha1 = "3f71217b538d7aaee0b69ab47d9b7724ca8afa0d"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.0.4"

[[DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

[[IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "ae4bbcadb2906ccc085cf52ac286dc1377dceccc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.1.2"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "e071adf21e165ea0d904b595544a8e514c8bb42c"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.19"

[[PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "d940010be611ee9d67064fe559edbb305f8cc0eb"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.2.3"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─a4feb3e1-48dd-4b74-9e64-65eec798b34c
# ╠═77e5e456-ca08-4d64-8927-d856e9eed328
# ╟─ba730327-4135-4301-9a78-a86c2a777834
# ╠═6a73f92f-c191-4622-9c37-016327616900
# ╟─5f3c9de7-1d2e-4577-bf4f-e56c7eb579ab
# ╠═636b6093-c8e7-410d-9df0-d7466efa458b
# ╟─e700a677-a565-41e6-b7ad-56d9a500229d
# ╟─35d8f807-f6cb-407b-b3f8-cab053d68ca8
# ╠═0936d1be-fb87-4e39-9573-8b1ddd4fd496
# ╟─65225bd2-4186-11ec-3709-6bba7ee9bd05
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


using CuArrays, CUDAnative, CUDAdrv, Test

function add!(a,b,c)
    tid = threadIdx().x
    if (tid <= min(length(a), length(b), length(c)))
        c[tid] = a[tid] + b[tid]
    end
    return nothing
end

function main()

    # Make three CuArrays
    a = CuArrays.CuArray(fill(0, 10))
    b = CuArrays.CuArray(fill(0, 10))
    c = CuArrays.CuArray(fill(0, 10))

    # Fill a and b with values
    for i in 1:10
        a[i] = -i
        b[i] = i * i
    end

    # Execute the kernel
    @cuda threads=10 add!(a,b,c)

    # Copy a,b and c back from the device to the host
    a = Array(a)
    b = Array(b)
    c = Array(c)

    # Do a sanity check
    for i in 1:length(a)
        @test a[i] + b[i] ≈ c[i]
    end
end

main()


using BandedMatrices, LinearAlgebra, ArrayLayouts, Random, Test, GenericLinearAlgebra
import BandedMatrices: MemoryLayout, SymmetricLayout, HermitianLayout, BandedColumns

@testset "Symmetric" begin
    A = Symmetric(brand(10,10,1,2))
    @test isbanded(A)
    @test BandedMatrix(A) == A
    @test bandwidth(A) == bandwidth(A,1) == bandwidth(A,2) ==  2
    @test bandwidths(A) == bandwidths(BandedMatrix(A)) == (2,2)
    @test MemoryLayout(typeof(A)) == SymmetricLayout{BandedColumns{DenseColumnMajor}}()

    @test A[1,2] == A[2,1]
    @test A[1,4] == 0
    x=rand(10)
    @test A*x ≈ Matrix(A)*x
    y = similar(x)
    @test all(A*x .=== muladd!(1.0,A,x,0.0,similar(x)) .===
                BLAS.sbmv!('U', 2, 1.0, parent(A).data, x, 0.0, similar(x)))

    @test A[2:10,1:9] isa BandedMatrix
    @test [A[k,j] for k=2:10, j=1:9] == A[2:10,1:9]
    A = Symmetric(brand(10,10,1,2),:L)
    @test isbanded(A)
    @test BandedMatrix(A) == A
    @test bandwidth(A) == bandwidth(A,1) == bandwidth(A,2) ==  1
    @test bandwidths(A) == bandwidths(BandedMatrix(A)) == (1,1)
    @test MemoryLayout(typeof(A)) == SymmetricLayout{BandedColumns{DenseColumnMajor}}()

    @test A[1,2] == A[2,1]
    @test A[1,3] == 0
    @test A[2:10,1:9] isa BandedMatrix
    @test [A[k,j] for k=2:10, j=1:9] == A[2:10,1:9]

    x=rand(10)
    @test A*x ≈ Matrix(A)*x
    y = similar(x)
    @test all(A*x .=== muladd!(1.0,A,x,0.0,y) .===
                BLAS.sbmv!('L', 1, 1.0, view(parent(A).data,3:4,:), x, 0.0, similar(x)))

    @test norm(A) ≈ norm(Matrix(A))
    @test cond(A) ≈ cond(Matrix(A))

    # (generalized) eigen & eigvals
    Random.seed!(0)

    A = brand(Float64, 100, 100, 2, 4)
    std = eigvals(Symmetric(Matrix(A)))
    @test eigvals(Symmetric(A)) ≈ std
    @test eigvals(Hermitian(A)) ≈ std
    @test eigvals(Hermitian(big.(A))) ≈ std

    A = brand(ComplexF64, 100, 100, 4, 0)
    @test Symmetric(A)[2:10,1:9] isa BandedMatrix
    @test Hermitian(A)[2:10,1:9] isa BandedMatrix
    @test [Symmetric(A)[k,j] for k=2:10, j=1:9] == Symmetric(A)[2:10,1:9]
    @test [Hermitian(A)[k,j] for k=2:10, j=1:9] == Hermitian(A)[2:10,1:9]

    std = eigvals(Hermitian(Matrix(A), :L))
    @test eigvals(Hermitian(A, :L)) ≈ std
    @test eigvals(Hermitian(big.(A), :L)) ≈ std

    A = Symmetric(brand(Float64, 100, 100, 2, 4))
    F = eigen(A)
    Λ, Q = F
    @test Q'Matrix(A)*Q ≈ Diagonal(Λ)
    FD = convert(Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}, F)
    @test FD.vectors'Matrix(A)*FD.vectors ≈ Diagonal(F.values)

    function An(::Type{T}, N::Int) where {T}
        A = Symmetric(BandedMatrix(Zeros{T}(N,N), (0, 2)))
        for n = 0:N-1
            parent(A).data[3,n+1] = T((n+1)*(n+2))
        end
        A
    end

    function Bn(::Type{T}, N::Int) where {T}
        B = Symmetric(BandedMatrix(Zeros{T}(N,N), (0, 2)))
        for n = 0:N-1
            parent(B).data[3,n+1] = T(2*(n+1)*(n+2))/T((2n+1)*(2n+5))
        end
        for n = 0:N-3
            parent(B).data[1,n+3] = -sqrt(T((n+1)*(n+2)*(n+3)*(n+4))/T((2n+3)*(2n+5)*(2n+5)*(2n+7)))
        end
        B
    end

    for T in (Float32, Float64)
        A = An(T, 100)
        B = Bn(T, 100)

        λ = eigvals(A, B)
        @test λ ≈ eigvals(Symmetric(Matrix(A)), Symmetric(Matrix(B)))

        err = λ*(T(2)/π)^2 ./ (1:length(λ)).^2 .- 1

        @test norm(err[1:40]) < 100eps(T)

        Λ, V = eigen(A, B)
        @test V'Matrix(A)*V ≈ Diagonal(Λ)
        @test V'Matrix(B)*V ≈ I
    end
end

@testset "Hermitian" begin
    T = ComplexF64
    A = Hermitian(brand(T,10,10,1,2))
    @test isbanded(A)
    @test BandedMatrix(A) == A
    @test bandwidth(A) == bandwidth(A,1) == bandwidth(A,2) ==  2
    @test bandwidths(A) == bandwidths(BandedMatrix(A)) == (2,2)
    @test MemoryLayout(typeof(A)) == HermitianLayout{BandedColumns{DenseColumnMajor}}()

    @test A[1,2] == conj(A[2,1])
    @test A[1,4] == 0
    x=rand(T,10)
    @test A*x ≈ Matrix(A)*x
    @test all(A*x .=== materialize!(MulAdd(one(T),A,x,zero(T),copy(x))) .===
                BLAS.hbmv!('U', 2, T(1.0), parent(A).data, x, T(0.0), similar(x)))

    A = Hermitian(brand(T,10,10,1,2),:L)
    @test isbanded(A)
    @test BandedMatrix(A) == A
    @test bandwidth(A) == bandwidth(A,1) == bandwidth(A,2) ==  1
    @test bandwidths(A) == bandwidths(BandedMatrix(A)) == (1,1)
    @test MemoryLayout(typeof(A)) == HermitianLayout{BandedColumns{DenseColumnMajor}}()

    @test A[1,2] == conj(A[2,1])
    @test A[1,3] == 0
    x=rand(T, 10)
    @test A*x ≈ Matrix(A)*x

    @test all(A*x .=== muladd!(one(T),A,x,zero(T),copy(x)) .===
                materialize!(MulAdd(one(T),A,x,zero(T),copy(x))) .===
                BLAS.hbmv!('L', 1, one(T), view(parent(A).data,3:4,:), x, zero(T), similar(x)))
end

@testset "LDLᵀ" begin
    for T in (Float16, Float32, Float64, BigFloat, Rational{BigInt})
        A = BandedMatrix{T}(undef,(10,10),(0,2))
        A[band(0)] .= 4
        A[band(1)] .= -one(T)/4
        A[band(2)] .= -one(T)/16
        SAU = Symmetric(A, :U)
        F = ldlt(SAU)
        b = collect(one(T):size(F, 1))
        x = Matrix(SAU)\b
        y = F\b
        @test x ≈ y
        A = BandedMatrix{T}(undef,(10,10),(2,0))
        A[band(0)] .= 4
        A[band(-1)] .= -one(T)/3
        A[band(-2)] .= -one(T)/9
        SAL = Symmetric(A, :L)
        x = Matrix(SAL)\b
        F = ldlt(SAL)
        y = F\b
        @test x ≈ y
        @test_throws DimensionMismatch F\[b;b]

        T ≠ Float16 && (@test det(F) ≈ det(SAL))
    end
    for T in (Int16, Int32, Int64, BigInt)
        A = BandedMatrix{T}(undef, (4,4), (1,1))
        A[band(0)] .= 3
        A[band(1)] .= 1
        F = ldlt(Symmetric(A))
        @test eltype(F) == float(T)
    end

    @testset "#175" begin
        A = BandedMatrix(0 => [1,2])
        F = ldlt(Symmetric(A))
        @test F.d == [1.,2.]
        @test F.D == Diagonal([1.,2.])
    end
end

@testset "Generalized eigenvalues $W{$T}($Ua,$Ub)($n,$wa-$wb)" for (T,W) in (
                                    (Float32, Symmetric),
                                    (Float64, Symmetric),
                                    #(Float32, Hermitian),
                                    #(Float64, Hermitian),
                                    #(ComplexF32, Hermitian),
                                    #(ComplexF64, Hermitian),
                                   ),
    (Ua, Ub) in  ((:L,:L), (:U,:U)),
    (wa, wb) in ((2, 3), (3, 2)), n in (4,)
    #
    function sbmatrix(W, T, U, w, n)
        r = U == :L ? (0:-1:-w+1) : (0:w-1)
        band(k) = k => ones(T, n - abs(k)) * 2.0^-abs(k)
        W(BandedMatrix(band.(r)...), U)
    end
    A = sbmatrix(W, T, Ua, wa, n)
    B = sbmatrix(W, T, Ub, wb, n)
    @test eigvals(A, B) ≈ eigvals(Matrix(A), Matrix(B))
end

@testset "Cholesky" begin
    for T in (Float64, BigFloat)
        A = Symmetric(BandedMatrix(0 => one(T) ./ [12, 6, 6, 6, 12],
                                1 => ones(T,4) ./ 24))
        Ac = cholesky(A)

        @test Ac isa Cholesky{T,<:BandedMatrix{T}}
        @test Ac.U ≈ cholesky(Matrix(A)).U

        b = rand(T,size(A,1))
        @test Ac\b ≈ Matrix(A)\b
        @test_broken Ac\b ≈ A\b
    end

    for T in (Float64, BigFloat)
        A = Symmetric(BandedMatrix(0 => one(T) ./ [12, 6, 6, 6, 12],
                                -1 => ones(T,4) ./ 24), :L)
        Ac = cholesky(A)

        @test Ac isa Cholesky{T,<:BandedMatrix{T}}
        @test Ac.L ≈ cholesky(Matrix(A)).L

        b = rand(T,size(A,1))
        @test Ac\b ≈ Matrix(A)\b
        @test_broken Ac\b ≈ A\b
    end

    let T = ComplexF64
        A = Hermitian(BandedMatrix(0 => one(T) ./ [12, 6, 6, 6, 12],
                                   1 => ones(T,4) ./ 24))
        Ac = cholesky(A)

        @test Ac isa Cholesky{T,<:BandedMatrix{T}}
        @test Ac.U ≈ cholesky(Matrix(A)).U

        b = rand(T,size(A,1))
        @test Ac\b ≈ Matrix(A)\b
        @test_broken Ac\b ≈ A\b
    end

    @testset "UnitRange" begin
        A = brand(100,100,3,2)
        S = Symmetric(A)
        @test S[3:10,4:11] == Symmetric(Matrix(A))[3:10,4:11]
    end

    @testset "Sym of degenerate bands" begin
        A = SymTridiagonal(zeros(8), fill(0.5,7))
        B = Symmetric(BandedMatrix(1 => fill(0.5,7)))
        @test A ≈ B
        @test BandedMatrices.inbands_getindex(B, 1, 1) == 0
        @test BandedMatrices.inbands_getindex(B, 1, 2) == BandedMatrices.inbands_getindex(B, 2, 1) == 0.5
    end
end


function maketinfoplot(cinfo,x,y)
tinfo = Tinfo()

@unpack en,P,M,Ω,basis = cinfo
ωx,ωy=Ω; Mx,My=M;
Tx = eigmat(Mx,x,ω=ωx/ωy)
Ty = eigmat(My,y,ω=ωy/ωy)
@pack tinfo = Tx,Ty

return tinfo
end


module Aligator

export aligator, extract_loop, closed_forms, invariants

using PyCall
using SymPy
using Singular
using AlgebraicDependencies

const AppliedUndef = PyCall.PyNULL()

include("utils.jl")
include("closedform.jl")
include("recurrence.jl")
include("loop.jl")
include("invariants.jl")
include("parse_julia.jl")
include("ideals.jl")
include("singular_imap.jl")

function aligator(str::String)
    _, total = @timed begin

        loop, time = @timed extract_loop(str)
        @debug "Recurrence extraction" time

        cforms, time = @timed closed_forms(loop)
        @debug "Recurrence solving" time

        invs, time = @timed invariants(cforms)
        @debug "Ideal computation" time
    end
    @debug "Total time needed" total

    return InvariantIdeal(invs)
end

function __init__()
    copy!(AppliedUndef, PyCall.pyimport_conda("sympy.core.function", "sympy").AppliedUndef)

    include(joinpath(@__DIR__,"..", "benchmark", "singlepath.jl"))
    include(joinpath(@__DIR__,"..", "benchmark", "multipath.jl"))

    singlepath = [:cohencu, :freire1, :freire2, :(petter(1)), :(petter(2)), :(petter(3)), :(petter(4))]
    multipath = [:divbin, :euclidex, :fermat, :knuth, :lcm, :mannadiv, :wensley]
end

struct InvariantIdeal
    ideal::sideal
end

function Base.show(io::IO, I::InvariantIdeal)
    println(io, "Invariant ideal with $(ngens(I.ideal))-element basis:")
    Base.print_array(io, gens(I.ideal))
end

end # module


using Test
using SimpleGraphs
using LinearAlgebra
using SparseArrays
using SimplePartitions
using Polynomials

@testset "Core" begin
    G = Path(10)
    name(G,"Example")
    @test name(G)=="Example"
    @test NV(G)==10
    @test NE(G)==9
    @test G[2] == [1,3]
    @test deg(G,2) == 2
    @test sum(deg(G)) == 2NE(G)

    G = IntGraph(2)
    add!(G,1,2)
    add!(G,1,3)
    add!(G,2,3)
    @test NV(G) == NE(G)
    A = adjacency(G)
    H = SimpleGraph(A)
    @test G==H

    @test get_edge(G,1,2) == get_edge(G,2,1)

    G = StringGraph()
    add!(G,"alpha","beta")
    @test G["alpha", "beta"]
end

@testset "Ops" begin
    G = IntGraph(3)
    ee = [ (1,2), (2,3), (1,3)]
    add_edges!(G,ee)
    delete!(G,1,2)
    @test NE(G) == 2
    G = Cycle(5)
    delete!(G,5)
    @test G == Path(4)

    G = line_graph(Cycle(5))
    @test deg(G) == 2*ones(5)

    H = G'
    @test G == H'

    G = Cycle(4)
    H = cartesian(G,G)
    @test NV(H) == NV(G)^2

    H = disjoint_union(Cycle(4), Cycle(5))
    @test NV(H) == 9
    @test NE(H) == 9

    G = join(IntGraph(5),IntGraph(4))
    @test NE(G) == 20

    G = Cycle(9)
    H = Cycle(10)
    contract!(H,1,10)
    @test G==H

    G = Path(9)
    H = trim(G,1)
    @test NV(H) == 0

end

@testset "Constructors" begin
    G = Complete(5)
    @test NE(G) == 10
    G = Complete(4,5)
    @test NE(G) == 20
    G = Complete([5,5,5])
    @test NE(G') == 30

    G = RandomTree(10)
    @test NE(G) == 9
    G = Grid(3,3)
    @test NV(G) == 9
    G = Wheel(10)
    @test NV(G) == 10
    G = Cube(4)
    @test NV(G) == 16
    G = BuckyBall()
    @test NE(G) == 90
    G = Petersen()
    @test NE(G) == 15
    G = Paley(13)
    @test NE(G) == NE(G')
    G = RandomRegular(10,3)
    @test NE(G) == 15
    G = Knight(5,5)
    @test NV(G) == 25
    @test NE(HoffmanSingleton()) == 175
    p1 = char_poly(Hoffman())
    p2 = char_poly(Cube(4))
    @test p1==p2
end

@testset "Platonics" begin
    G = Icosahedron()
    H = Dodecahedron()
    @test NE(G) == NE(H)
    G = Octahedron()
    H = Complete([2,2,2])
    @test NE(G) == NE(H)
    @test Tetrahedron() == Complete(4)
end

@testset "Connectivity" begin
    G = Path(10)
    @test diam(G)==9
    @test eccentricity(G,2) == 8
    @test num_components(G) == 1
    @test spanning_forest(G) == G
    @test is_acyclic(G)
    @test is_cut_edge(G,3,4)
    @test radius(G) == 5
    @test graph_center(G) == Set([5,6])

    G = Complete(5,5)'
    @test num_components(G) == 2
    H = spanning_forest(G)
    @test num_components(H) == 2
    @test NE(H)==8
end

@testset "Matrices" begin
    G = Petersen()
    M = incidence(G)
    L = laplace(G)
    @test L == M*M'
    A = adjacency(G)
    v = A * ones(10)
    @test v == 3*ones(Int,10)
end

@testset "Twins" begin
    G = Complete(5,3)
    @test twins(G) == bipartition(G)
end

@testset "Coloring" begin
    G = RandomTree(10)
    d = two_color(G)
    @test Set(values(d)) == Set([1,2])
    f = greedy_color(G)
    @test length(keys(f)) == NV(G)
end

@testset "Euler" begin
    G = Cube(4)
    tour = euler(G)
    @test length(tour) == NE(G)+1
    @test tour[1] == tour[end]
end

@testset "Hamiltonian" begin
    G = Cube(4)
    tour = hamiltonian_cycle(G)
    @test length(tour) == NV(G)
end

@testset "Girth" begin
    G = Cube(4)
    @test girth(G) == 4
    G = RandomTree(10)
    @test girth(G) == 0
end

@testset "Bisect" begin
    G = RandomTree(10)
    A,B = bisect(G)
    @test G.V == union(A,B)
    @test length(intersect(A,B)) == 0
    @test length(cross_edges(G,A,B)) == 1
end

@testset "Transitive" begin
    G = RandomTree(10)
    D = transitive_orientation(G)
    @test G == simplify(D)
    @test num_trans_orientations(G) == 2
end

@testset "Polynomials" begin
    G = Complete(3,4)
    p = indep_poly(G)
    @test degree(p) == 4

    p = matching_poly(G)
    @test coeffs(p) == [0, -24, 0, 36, 0, -12, 0, 1]

    p = interlace(G)
    @test coeffs(p) == [0, 2, 3, 4, 3, 1]
end

# DIRECTED GRAPH STUFF

@testset "Basic Directed" begin
    G = StringDigraph()
    add!(G,"alpha","bravo")
    add!(G,"bravo","charlie")
    @test NV(G)==3
    @test NE(G)==2
    @test sum(out_deg(G)) == sum(in_deg(G))

    H = simplify(relabel(G))
    @test H == Path(3)
end

@testset "Digraph Constructors" begin
    G = DirectedPath(10)
    add!(G,10,1)
    @test G == DirectedCycle(10)

    G = DirectedComplete(10, false)
    @test NE(G) == 10*9
    G = DirectedComplete(10,true)
    @test NE(G) == 10*10

    G = RandomTournament(10)
    @test NE(G)==45
end

@testset "Directed Distance" begin
    G = DirectedCycle(10)
    @test diam(G) == 9
end

@testset "Directed Euler" begin
    G = TorusDigraph(4,4)
    P = euler(G)
    @test length(P) == NE(G)+1
end

@testset "Directed Hamiltonian Cycle" begin
    G = TorusDigraph(4,4)
    P = hamiltonian_cycle(G)
    @test length(P) == NV(G)
end

@testset "Strong Connectivity" begin
    G = TorusDigraph(5,5)
    @test is_strongly_connected(G)
end

@testset "Directed Matrices" begin
    G = RandomTournament(10)
    A = adjacency(G)
    B = A + A'
    @test B ==adjacency(Complete(10))

    G = RandomTournament(10)
    M = incidence(G)
    @test M*M' == laplace(Complete(10))
end


# This file was generated, do not modify it. # hide
using Plots

@userplot CirclePlot
@recipe function f(cp::CirclePlot)
    x, y, i = cp.args
    n = length(x)
    inds = circshift(1:n, 1 - i)
    linewidth --> range(0, 10, length = n)
    seriesalpha --> range(0, 1, length = n)
    aspect_ratio --> 1
    label --> false
    x[inds], y[inds]
end

n = 400
t = range(0, 2π, length = n)
x = 16sin.(t).^3
y = 13cos.(t) .- 5cos.(2t) .- 2cos.(3t) .- cos.(4t)
anim = @animate for i ∈ 1:n
    circleplot(x, y, i, line_z = 1:n, cbar = false, c = :reds, framestyle = :none)
end when i > 40 && mod1(i, 10) == 5

gif(anim, joinpath(@OUTPUT, "granim.gif")) #hide


using LinearAlgebra
using FluxDiffUtils
using ForwardDiff
using SparseArrays
using StaticArrays
using Test

@testset "Flux diff tests" begin
    u = collect(LinRange(-1,1,10))
    U = (u,2*u)
    function flux1D(UL,UR)
        uL,vL = UL
        uR,vR = UR
        avg(x,y) = @. .5*(x+y)
        Fx = SVector{2}(avg(uL,uR),avg(vL,vR))
        return Fx
    end
    A = randn(10,10)
    A[8:10,8:10] .= 0 # for testing skip_index
    skip_index(i,j) = (i>8)&&(j>8)
    A = A-A' # make skew for exact formula testing
    ATr = (A->Matrix(transpose(A)))(A)

    rhs = hadamard_sum((A,),tuple∘flux1D,U,skip_index=skip_index)
    @test (A*U[1] + U[1].*sum(A,dims=2)) ≈ 2*rhs[1]
    @test (A*U[2] + U[2].*sum(A,dims=2)) ≈ 2*rhs[2]
end

# make 3-field solution
u = collect(LinRange(-1,1,10))
U = SVector{3}(u,2*u,3*u)

# define flux function and its derivative
function flux_function(UL,UR)
    uL,vL,wL = UL
    uR,vR,wR = UR
    avg(x,y) = @. .5*(x+y)
    Fx = avg(uL,uR),avg(vL,vR),avg(wL,wR)
    Fy = avg(uL,uR),avg(vL,vR),avg(wL,wR)
    return SVector{3}(Fx),SVector{3}(Fy)
end

dfx(UL,UR) = ForwardDiff.jacobian(UR->flux_function(UL,UR)[1], SVector{3}(UR))
dfy(UL,UR) = ForwardDiff.jacobian(UR->flux_function(UL,UR)[2], SVector{3}(UR))
df(UL,UR) = dfx(UL,UR),dfy(UL,UR)

A = randn(10,10)
A[8:10,8:10] .= 0 # for testing skip_index
skip_index(i,j) = (i>8)&(j>8)
A = ntuple(x->A,2)
A = (A->A-A').(A) # make skew for exact formula testing

@testset "Flux diff tests w/StaticArrays" begin

    rhs = hadamard_sum(A,flux_function,U)
    @test (A[1]*U[1] + U[1].*sum(A[1],dims=2)) ≈ rhs[1]
    @test (A[1]*U[2] + U[2].*sum(A[1],dims=2)) ≈ rhs[2]
    @test (A[1]*U[3] + U[3].*sum(A[1],dims=2)) ≈ rhs[3]

    rhs_sparse = hadamard_sum(sparse.(A),flux_function,U)
    @test all(rhs .≈ rhs_sparse)

    rhs_skipped = hadamard_sum(sparse.(A),flux_function,U;
                               skip_index=skip_index)
    @test all(rhs .≈ rhs_skipped)

    # test tuple args too
    v = collect(LinRange(-1,1,10))
    V = (v,2*v,3*v)
    # define flux function and its derivative
    function flux_function_tuple(UL,UR)
        uL,vL,wL = UL
        uR,vR,wR = UR
        avg(x,y) = @. .5*(x+y)
        Fx = avg(uL,uR),avg(vL,vR),avg(wL,wR)
        Fy = avg(uL,uR),avg(vL,vR),avg(wL,wR)
        return SVector{3}(Fx),SVector{3}(Fy)
    end
    rhs2 = hadamard_sum(A,flux_function_tuple,V)
    @test all(rhs .≈ rhs2)

end

@testset "Jacobian tests" begin
    tol = 5e2*eps()

    Jdense = hadamard_jacobian(A,df,U)
    J = hadamard_jacobian(sparse.(A),df,U)
    # no factor of .5 since Alist = (A,A) and exact jac is ∑_i (A_i - diag(sum(A_i,2)))
    @test J[1,1] ≈ first(A) - diagm(vec(sum(first(A),dims=1)))
    @test J[2,2] ≈ first(A) - diagm(vec(sum(first(A),dims=1)))
    @test J[3,3] ≈ first(A) - diagm(vec(sum(first(A),dims=1)))
    accum_norm = 0.0
    for i = 1:3, j = 1:3
        if i!=j
            accum_norm += norm(J[i,j])
        end
    end
    @test accum_norm < tol

    Jskip = hadamard_jacobian(A,df,U,skip_index=skip_index)
    test_dense_eq_sparse = Bool[]
    test_skip = Bool[]
    for i=1:3,j=1:3
        push!(test_dense_eq_sparse, J[i,j] ≈ sparse(Jdense[i,j]))
        push!(test_skip, J[i,j] ≈ sparse(Jskip[i,j]))
    end
    @test all(test_dense_eq_sparse)
    @test all(test_skip)

    # for banded diagonal matrix test
    g(u) = SMatrix{2,2}([u[1] 0
                           0 u[2]])
    G = banded_function_evals(g,U[1:2])
    @test G[1,1] ≈ spdiagm(0=>U[1])
    @test norm(G[1,2]) + norm(G[2,1]) < tol
    @test G[2,2] ≈ spdiagm(0=>U[2])

    # test blockcat
    B = [[i] for i=1:2,j=1:2]
    @test blockcat(size(B,2),B) ≈ [1 1;2 2]

    # test flattening of matrix
    @test blockcat(size(J,2),J) ≈ kron(I(3),first(A) - diagm(vec(sum(first(A),dims=1))))
end

@testset "Extra sparse/dense tests" begin
    tol = 5e2*eps()
    A,B = ntuple(x->sprandn(25,25,.1),2)
    A_list = (A,B)
    function flux2D(UL,UR)
        uL,vL = UL
        uR,vR = UR
        return (uL+uR),2*(vL+vR)
    end
    U = randn(25),randn(25)
    rhs_exact = (A*U[1] + U[1].*sum(A,dims=2), 2*B*U[2] + 2*U[2].*sum(B,dims=2))
    rhs1 = zero.(U)
    rhs2 = zero.(U)
    hadamard_sum_ATr!(rhs1,A_list,flux2D,U)
    hadamard_sum_ATr!(rhs2,Matrix.(A_list),flux2D,U) # dense version
    @test sum(norm.(rhs1 .- rhs2)) < tol
    tol = 5e2*eps()
    N = 10

    # scalar test
    A = sparse(randn(N,N))
    ATr_list = (Matrix(transpose(A)),)
    function flux1(UL,UR)
        return UL .+ UR
    end
    U = (randn(N),)
    rhs_exact = (A*U[1] + U[1].*sum(A,dims=2),)
    rhs1, rhs2 = zero.(U), zero.(U)
    hadamard_sum_ATr!(rhs1, ATr_list,flux1,U)
    hadamard_sum_ATr!(rhs2, sparse.(ATr_list),flux1,U)
    @test sum(norm.(rhs1 .- rhs_exact)) < tol
    @test sum(norm.(rhs_exact .- rhs2)) < tol
    @test sum(norm.(rhs1 .- rhs2)) < tol

    # 2-component 2D flux test
    A,B = randn(N,N),randn(N,N)
    ATr_list = map(A->Matrix(transpose(A)),(A,B))
    function flux2(UL,UR)
        uL,vL = UL
        uR,vR = UR
        return SVector{2}(uL+uR,0),SVector{2}(0,2*(vL+vR))
    end
    u,v = (randn(N),randn(N))
    U = (u,v)

    # computes sum(A.*F1 + B.*F2,dims=2)
    rhs_exact = (A*u + u.*sum(A,dims=2),2*B*v + 2*v.*sum(B,dims=2))
    rhs1, rhs2 = zero.(U), zero.(U)
    hadamard_sum_ATr!(rhs1,ATr_list,flux2,U)
    hadamard_sum_ATr!(rhs2,sparse(ATr_list[1]),(uL,uR)->flux2(uL,uR)[1],U)
    hadamard_sum_ATr!(rhs2,sparse(ATr_list[2]),(uL,uR)->flux2(uL,uR)[2],U)

    @test sum(norm.(rhs1 .- rhs_exact)) < tol
    @test sum(norm.(rhs_exact .- rhs2)) < tol
    @test sum(norm.(rhs1 .- rhs2)) < tol
end


#deprecated example of creating custom types

using Equations
import Equations: simplify, *, dot #extending * breaks functionality...

mutable struct Exp<:Component #these are already implemented
	x
end
simplify(c::Exp)=isa(simplify(c.x),Number) ? exp(simplify(c.x)) : c
mutable struct Cos<:Component
	x
end
simplify(c::Cos)=isa(simplify(c.x),Number) ? cos(simplify(c.x)) : c
mutable struct Sin<:Component
	x
end
simplify(c::Sin)=isa(simplify(c.x),Number) ? sin(simplify(c.x)) : c
e1=Vec([1,0,0]);e2=Vec([0,1,0]);e3=Vec([0,0,1])

mutable struct Differentiable <: Component
	fun
	der::Dict
end
#getargs(d::Differentiable)=(d.fun,)
#Equations.maketype(d::Differentiable,fun)=Differentiable(fun(d.fun),d.der)
Differentiable(a,b)=Differentiable(a,[:x=>b])
#ed=Vec(Differentiable(1,0),Differentiable(0,1),0) #this causes infinite recursion...
mutable struct DerOp #<: Operator
	dx
end
DerOp()=DerOp(:x)
*(d::DerOp,f::Differentiable)=haskey(f.der,d.dx) ? f.der[d.dx] : Der(f,d.dx)
*(d::DerOp,ex::Factor)=Der(ex,d.dx)
mutable struct Grad <: Operator
	v
end
Grad()=Grad(Vec([DerOp(:x),DerOp(:y),DerOp(:z)]))
Base.print(io::IO,g::Grad)=print(io,"∇")
function dot(v1::Grad,v2::Differentiable)
	ex=Expression(Term[Factor[(v1.v[1]*v2)[1]]])
	for t in 2:length(v1.v)
		push!(ex,Factor[(v1.v[t]*v2)[t]])
	end
	return simplify(ex)
end

eq1=Cross(:j,:B)≖Grad()*:p
eq2=Cross(Grad(),:B)≖:μ0*:j
eq2p=Dot(Grad(),Cross(Grad(),:B))≖:μ0*Dot(Grad(),:j)
eq3=Dot(Grad(),:B)≖0

eqc=eq1≖0


"""
    algorithm971(;input::AbstractString="", outdir::Union{Nothing,AbstractString}=nothing, scale::AbstractString="ftt", pseudocount::Number=1f0, rowmeanlist::AbstractString="", rowvarlist::AbstractString="", colsumlist::AbstractString="", dim::Number=3, noversamples::Number=5, niter::Number=3, initW::Union{Nothing,AbstractString}=nothing, initV::Union{Nothing,AbstractString}=nothing, logdir::Union{Nothing,AbstractString}=nothing, perm::Bool=false, cper::Number=1f0)

Algorithm 971, which is one of randomized SVD algorithm.

Input Arguments
---------
- `input` : Julia Binary file generated by `OnlinePCA.csv2bin` function.
- `outdir` : The directory specified the directory you want to save the result.
- `scale` : {log,ftt,raw}-scaling of the value.
- `pseudocount` : The number specified to avoid NaN by log10(0) and used when `Feature_LogMeans.csv` <log10(mean+pseudocount) value of each feature> is generated.
- `rowmeanlist` : The mean of each row of matrix. The CSV file is generated by `OnlinePCA.sumr` functions.
- `rowvarlist` : The variance of each row of matrix. The CSV file is generated by `OnlinePCA.sumr` functions.
- `colsumlist` : The sum of counts of each columns of matrix. The CSV file is generated by `OnlinePCA.sumr` functions.
- `dim` : The number of dimension of PCA.
- `noversamples` : The number of over-sampling.
- `niter` : The number of power interation.
- `initW` : The CSV file saving the initial values of eigenvectors.
- `initV` : The CSV file saving the initial values of loadings.
- `logdir` : The directory where intermediate files are saved, in every evalfreq (e.g. 5000) iteration.
- `perm` : Whether the data matrix is shuffled at random.
- `cper` : Count per X (e.g. CPM: Count per million <1e+6>)

Output Arguments
---------
- `V` : Eigen vectors of covariance matrix (No. columns of the data matrix × dim)
- `λ` : Eigen values (dim × dim)
- `U` : Loading vectors of covariance matrix (No. rows of the data matrix × dim)
- `Scores` : Principal component scores
- `ExpVar` : Explained variance by the eigenvectors
- `TotalVar` : Total variance of the data matrix
"""
function algorithm971(;input::AbstractString="", outdir::Union{Nothing,AbstractString}=nothing, scale::AbstractString="ftt", pseudocount::Number=1f0, rowmeanlist::AbstractString="", rowvarlist::AbstractString="", colsumlist::AbstractString="", dim::Number=3, noversamples::Number=5, niter::Number=3, initW::Union{Nothing,AbstractString}=nothing, initV::Union{Nothing,AbstractString}=nothing, logdir::Union{Nothing,AbstractString}=nothing, perm::Bool=false, cper::Number=1f0)
    # Initial Setting
    pca = ALGORITHM971()
    pseudocount, W, D, rowmeanvec, rowvarvec, colsumvec, N, M, TotalVar = init(input, pseudocount, dim, rowmeanlist, rowvarlist, colsumlist, initW, initV, logdir, pca, cper, scale)
    # Perform PCA
    out = algorithm971(input, outdir, scale, pseudocount, rowmeanlist, rowvarlist, colsumlist, dim, noversamples, niter, logdir, pca, W, D, rowmeanvec, rowvarvec, colsumvec, N, M, TotalVar, perm, cper)
    # Output
    if outdir isa String
        writecsv(joinpath(outdir, "Eigen_vectors.csv"), out[1])
        writecsv(joinpath(outdir, "Eigen_values.csv"), out[2])
        writecsv(joinpath(outdir, "Loadings.csv"), out[3])
        writecsv(joinpath(outdir, "Scores.csv"), out[4])
        writecsv(joinpath(outdir, "ExpVar.csv"), out[5])
        writecsv(joinpath(outdir, "TotalVar.csv"), out[6])
    end
    return out
end

function algorithm971(input, outdir, scale, pseudocount, rowmeanlist, rowvarlist, colsumlist, dim, noversamples, niter, logdir, pca, W, D, rowmeanvec, rowvarvec, colsumvec, N, M, TotalVar, perm, cper)
    N, M = nm(input)
    tmpN = zeros(UInt32, 1)
    tmpM = zeros(UInt32, 1)
    x = zeros(UInt32, M)
    normx = zeros(Float32, M)
    l = dim + noversamples
    @assert 0 < dim ≤ l ≤ min(N, M)
    Ω = rand(Float32, M, l)
    Y = zeros(Float32, N, l)
    L = zeros(Float32, N, l)
    Q = zeros(Float32, N, l)
    B = zeros(Float32, l, M)
    G = zeros(Float32, M, l)
    # If not 0 the calculation is converged
    n = 1
    # Each epoch s
    println("Random Projection : Y = A Ω")
    progress = Progress(N)
    open(input) do file
        stream = ZstdDecompressorStream(file)
        read!(stream, tmpN)
        read!(stream, tmpM)
        # Each step n
        while(n <= N)
            next!(progress)
            # Row vector of data matrix
            read!(stream, x)
            normx = normalizex(x, n, stream, scale, pseudocount, rowmeanlist, rowmeanvec, rowvarlist, rowvarvec, colsumlist, colsumvec, cper)
            if perm
                normx .= normx[randperm(length(normx))]
            end
            # Random Projection
            Y[n,:] .= (normx'*Ω)[1,:]
            n += 1
        end
        close(stream)
    end

    if niter > 0
        # LU factorization
        println("LU factorization : L = lu(Y)")
        L .= lu!(Y).L
        for i in 1:niter
            println("Normalized power iterations (1/3) : A' L")
            n = 1
            AtL = zeros(Float32, M, l)
            progress = Progress(N)
            open(input) do file
                stream = ZstdDecompressorStream(file)
                read!(stream, tmpN)
                read!(stream, tmpM)
                # Each step n
                while(n <= N)
                    next!(progress)
                    # Row vector of data matrix
                    read!(stream, x)
                    normx = normalizex(x, n, stream, scale, pseudocount, rowmeanlist, rowmeanvec, rowvarlist, rowvarvec, colsumlist, colsumvec, cper)
                    if perm
                        normx .= normx[randperm(length(normx))]
                    end
                    AtL .+= normx*L[n,:]'
                    n += 1
                end
                close(stream)
            end
            println("L = lu(A' L)")
            G .= lu!(AtL).L

            println("Normalized power iterations (2/3) : Y = A lu(A' L)")
            n = 1
            progress = Progress(N)
            open(input) do file
                stream = ZstdDecompressorStream(file)
                read!(stream, tmpN)
                read!(stream, tmpM)
                # Each step n
                while(n <= N)
                    next!(progress)
                    # Row vector of data matrix
                    read!(stream, x)
                    normx = normalizex(x, n, stream, scale, pseudocount, rowmeanlist, rowmeanvec, rowvarlist, rowvarvec, colsumlist, colsumvec, cper)
                    if perm
                        normx .= normx[randperm(length(normx))]
                    end
                    Y[n,:] .= (normx'*G)[1,:]
                    n += 1
                end
                close(stream)
            end
            if i < niter
                println("Normalized power iterations (3/3) : L = lu(A lu(A' L))")
                # Renormalize with LU factorization
                L .= lu!(Y).L
            else
                println("QR factorization  (3/3) : Q = qr(A lu(A' L))")
                # Renormalize with QR factorization
                Q .= Array(qr!(Y).Q)
            end
        end
    else
        println("QR factorization : Q = qr(Y)")
        # Renormalize with QR factorization
        Q .= Array(qr!(Y).Q)
    end

    println("Calculation of small matrix : B = Q' A")
    n = 1
    progress = Progress(N)
    open(input) do file
        stream = ZstdDecompressorStream(file)
        read!(stream, tmpN)
        read!(stream, tmpM)
        # Each step n
        while(n <= N)
            next!(progress)
            # Row vector of data matrix
            read!(stream, x)
            normx = normalizex(x, n, stream, scale, pseudocount, rowmeanlist, rowmeanvec, rowvarlist, rowvarvec, colsumlist, colsumvec, cper)
            if perm
                normx .= normx[randperm(length(normx))]
            end
            B .+= Q[n,:]*normx'
            n += 1
        end
        close(stream)
    end
    # SVD with small matrix
    println("SVD with small matrix : svd(B)")
    W, σ, V = svd(B)
    U = Q*W
    λ = σ .* σ ./ M
    # PC scores, Explained Variance
    Scores = zeros(Float32, M, dim)
    for n = 1:dim
        Scores[:, n] .= λ[n] .* V[:, n]
    end
    ExpVar = sum(λ) / TotalVar
    # Return
    return (V[:,1:dim], λ[1:dim], U[:,1:dim], Scores[:,1:dim], ExpVar, TotalVar)
end


function summarize(d::AbstractDocument; ns=5)
    sentences = sentence_tokenize(language(d), text(d))
    s = StringDocument.(sentences)
    c = Corpus(s)
    prepare!(c, strip_case | strip_stopwords | stem_words )
    update_lexicon!(c)
    t = tf_idf(dtm(c))
    T = t * t'
    p=pagerank(T)
    return sentences[sort(sortperm(vec(p), rev=true)[1:ns])]
end

function pagerank( A; Niter=20, damping=.15)
         Nmax = size(A, 1)
         r = rand(1,Nmax);              # Generate a random starting rank.
         r = r ./ norm(r,1);            # Normalize
         a = (1-damping) ./ Nmax;       # Create damping vector

         for i=1:Niter
             s = r * A
             rmul!(s, damping)
             r = s .+ (a * sum(r, dims=2));   # Compute PageRank.
         end

         r = r./norm(r,1);

         return r
end


# Commonly used constants

const q  = 1.6021765e-19    # electron charge, [C]
const me = 9.10938356e-31   # electron mass, [kg]
const mp = 1.6726219e-27    # proton mass, [kg]
const qₑ = -q
const mₑ = me
const qᵢ = q
const mᵢ = mp
const amu= 1.66054e-27      # Atomic Mass Unit, 1/12 of C12, [kg]
const ϵ0 = 8.8542e-12       # vacuum permittivity, [F/m]
const ϵ₀ = ϵ0
const c  = 3e8              # speed of light, [m/s]
const μ₀ = 4π*1e-7          # Vacuum permeability, [H/m]

const Rₑ = 6.38e6           # Earth radius, [m]
const Rm = 2444000.         # Mercury's radius, [m]

# Solar
const Tsolarrotate = 25.05*24*3600 # [s]
const ωsun = 2π / Tsolarrotate # [rad/s]
const Rsun = 696340e3 # solar radius, [m]
const AU = 1.496e11 # sun-Earth distance, [m]

# Distance from sun
# https://www.enchantedlearning.com/subjects/astronomy/planets/
const dMercury = 0.39  # [AU], 57.91e9 [m]
const dVenus   = 0.723 # [AU], 108.2e9 [m]
const dMars    = 1.524 # [AU], 227.9e9 [m]
const dJupiter = 5.203 # [AU], 778.5e9 [m]
const dSaturn  = 9.539 # [AU], 1434e9  [m]
const dUranus  = 19.18 # [AU], 2871e9  [m]
const dNeptune = 30.06 # [AU], 4495e9  [m]


using ConditionalDistributionForest
using DecisionTree

using Test
using Random
using Distributions

@testset "Building forest" begin
    rng = MersenneTwister(1234)

    n_samples = 10
    X = Array{Float64, 2}(undef, n_samples, 3)
    X[:, 1] .= rand(rng, GeneralizedPareto(0., 1.5, 0.25), n_samples)
    X[:, 2] .= rand(rng, LogNormal(1.1, 0.6), n_samples)
    X[:, 3] .= rand(rng, Gamma(2, 5.0/3), n_samples)

    Y = vec(sum(X, dims=2)) + rand(rng, Normal(0, 2), n_samples)

    rng = MersenneTwister(5678)
    forest_1, bootstraps = build_distribution_forest(Y, X, -1, 1, 1.0, -1, 1, rng = rng, return_bootstrap = true)

    rng = MersenneTwister(5678)
    forest_2 = build_distribution_forest(Y, X, -1, 1, 1.0, -1, 1, rng = rng)

    res = Array{Int64, 2}(undef, n_samples, 1)
    res[:, 1] = [1; 3; 5; 5; 8; 3; 1; 9; 4; 10]

    original_stdout = stdout
    (rd, wr) = redirect_stdout()
    DecisionTree.print_tree(forest_1.trees[1])
    redirect_stdout(original_stdout)
    close(wr)
    data_1 = read(rd, String)
    close(rd)

    (rd, wr) = redirect_stdout()
    DecisionTree.print_tree(forest_2.trees[1])
    redirect_stdout(original_stdout)
    close(wr)
    data_2 = read(rd, String)
    close(rd)

    @test bootstraps == res
    @test data_1 == data_2
end

@testset "Computing forest weights" begin
    rng = MersenneTwister(1234)

    n_primes = 2
    Xvec = Array{Float64, 2}(undef, n_primes, 3)
    Xvec[:, 1] .= rand(rng, GeneralizedPareto(0., 1.5, 0.25), n_primes)
    Xvec[:, 2] .= rand(rng, LogNormal(1.1, 0.6), n_primes)
    Xvec[:, 3] .= rand(rng, Gamma(2, 5.0/3), n_primes)

    n_samples = 10
    X = Array{Float64, 2}(undef, n_samples, 3)
    X[:, 1] .= rand(rng, GeneralizedPareto(0., 1.5, 0.25), n_samples)
    X[:, 2] .= rand(rng, LogNormal(1.1, 0.6), n_samples)
    X[:, 3] .= rand(rng, Gamma(2, 5.0/3), n_samples)

    Y = vec(sum(X, dims=2)) + rand(rng, Normal(0, 2), n_samples)

    forest, bootstraps = build_distribution_forest(Y, X, -1, 1, 1.0, -1, 1, rng = rng, return_bootstrap = true)

    @test isapprox(ConditionalDistributionForest.compute_forest_weights(forest, X, Xvec, bootstraps), [0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0; 1.0 0.0; 0.0 1.0; 0.0 0.0; 0.0 0.0])
end

@testset "Computing conditional distribution function" begin
    rng = MersenneTwister(1234)

    n_primes = 5
    Xvec = Array{Float64, 2}(undef, n_primes, 3)
    Xvec[:, 1] .= rand(rng, GeneralizedPareto(0., 1.5, 0.25), n_primes)
    Xvec[:, 2] .= rand(rng, LogNormal(1.1, 0.6), n_primes)
    Xvec[:, 3] .= rand(rng, Gamma(2, 5.0/3), n_primes)

    n_samples = 10
    X = Array{Float64, 2}(undef, n_samples, 3)
    X[:, 1] .= rand(rng, GeneralizedPareto(0., 1.5, 0.25), n_samples)
    X[:, 2] .= rand(rng, LogNormal(1.1, 0.6), n_samples)
    X[:, 3] .= rand(rng, Gamma(2, 5.0/3), n_samples)

    Y = vec(sum(X, dims=2)) + rand(rng, Normal(0, 2), n_samples)

    rng = MersenneTwister(5678)
    forest, bootstraps = build_distribution_forest(Y, X, -1, 1, 1.0, -1, 1, rng = rng, return_bootstrap = true)

    Ydiscr = collect(0.5:0.5:10)
    fdr = Array{Float64, 2}(undef, length(Ydiscr), n_primes)
    fdr_ref = [0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 1.0 0.0 1.0; 0.0 0.0 1.0 0.0 1.0; 0.0 0.0 1.0 0.0 1.0; 0.0 0.0 1.0 0.0 1.0; 0.0 0.0 1.0 0.0 1.0; 1.0 0.0 1.0 0.0 1.0; 1.0 0.0 1.0 0.0 1.0; 1.0 0.0 1.0 0.0 1.0; 1.0 0.0 1.0 0.0 1.0]

    compute_conditional_distribution!(fdr, forest, Y, X, Xvec, Ydiscr, bootstraps)
    @test isapprox(fdr, fdr_ref)

    rng = MersenneTwister(5678)
    compute_conditional_distribution!(fdr, Y, X, Xvec, Ydiscr, -1, 1, 1.0, -1, 1, rng = rng)
    @test isapprox(fdr, fdr_ref)

    @test isapprox(compute_conditional_distribution(forest, Y, X, Xvec, Ydiscr, bootstraps), fdr_ref)

    rng = MersenneTwister(5678)
    @test isapprox(compute_conditional_distribution(Y, X, Xvec, Ydiscr, -1, 1, 1.0, -1, 1, rng = rng), fdr_ref)

    fdr_ref = [0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.5 0.0 0.5; 0.0 0.0 0.5 0.0 0.5; 0.0 0.0 0.5 0.0 0.5; 0.0 0.0 0.5 0.0 0.5; 0.0 0.0 0.5 0.5 0.5; 1.0 0.0 0.5 0.5 0.5; 1.0 0.0 0.5 0.5 0.5; 1.0 0.0 0.5 0.5 0.5; 1.0 0.0 0.5 0.5 0.5]

    compute_conditional_distribution!(fdr, forest, Y, X, Xvec, Ydiscr)
    @test isapprox(fdr, fdr_ref)

    @test isapprox(compute_conditional_distribution(forest, Y, X, Xvec, Ydiscr), fdr_ref)
end

@testset "Computing conditional quantiles" begin
    rng = MersenneTwister(1234)

    n_primes = 5
    Xvec = Array{Float64, 2}(undef, n_primes, 3)
    Xvec[:, 1] .= rand(rng, GeneralizedPareto(0., 1.5, 0.25), n_primes)
    Xvec[:, 2] .= rand(rng, LogNormal(1.1, 0.6), n_primes)
    Xvec[:, 3] .= rand(rng, Gamma(2, 5.0/3), n_primes)

    n_samples = 10
    X = Array{Float64, 2}(undef, n_samples, 3)
    X[:, 1] .= rand(rng, GeneralizedPareto(0., 1.5, 0.25), n_samples)
    X[:, 2] .= rand(rng, LogNormal(1.1, 0.6), n_samples)
    X[:, 3] .= rand(rng, Gamma(2, 5.0/3), n_samples)

    Y = vec(sum(X, dims=2)) + rand(rng, Normal(0, 2), n_samples)

    rng = MersenneTwister(5678)
    forest, bootstraps = build_distribution_forest(Y, X, -1, 1, 1.0, -1, 1, rng = rng, return_bootstrap = true)

    alphas = collect(0.1:0.1:0.9)
    quant = Array{Float64, 2}(undef, n_primes, length(alphas))
    quant_ref = [8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971; 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878; 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174; 10.922288040267013 10.922288040267013 10.922288040267013 10.922288040267013 10.922288040267013 10.922288040267013 10.922288040267013 10.922288040267013 10.922288040267013; 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174]

    compute_conditional_quantiles!(quant, forest, Y, X, Xvec, alphas, bootstraps)
    @test isapprox(quant, quant_ref)

    rng = MersenneTwister(5678)
    compute_conditional_quantiles!(quant, Y, X, Xvec, alphas, -1, 1, 1.0, -1, 1, rng = rng)
    @test isapprox(quant, quant_ref)

    @test isapprox(compute_conditional_quantiles(forest, Y, X, Xvec, alphas, bootstraps), quant_ref)

    rng = MersenneTwister(5678)
    @test isapprox(compute_conditional_quantiles(Y, X, Xvec, alphas, -1, 1, 1.0, -1, 1, rng = rng), quant_ref)

    quant_ref = [8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971 8.450178408561971; 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878 14.762920820567878; 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 10.923414732418404 10.923414732418404 10.923414732418404 10.923414732418404 10.923414732418404; 7.582297789430057 7.582297789430057 7.582297789430057 7.582297789430057 10.922288040267013 10.922288040267013 10.922288040267013 10.922288040267013 10.922288040267013; 5.566834469394174 5.566834469394174 5.566834469394174 5.566834469394174 10.923414732418404 10.923414732418404 10.923414732418404 10.923414732418404 10.923414732418404]

    compute_conditional_quantiles!(quant, forest, Y, X, Xvec, alphas)
    @test isapprox(quant, quant_ref)

    @test isapprox(compute_conditional_quantiles(forest, Y, X, Xvec, alphas), quant_ref)
end

@testset "Tools" begin
    rng = MersenneTwister(1234)

    n_samples = 10
    X = Array{Float64, 2}(undef, n_samples, 3)
    X[:, 1] .= rand(rng, GeneralizedPareto(0., 1.5, 0.25), n_samples)
    X[:, 2] .= rand(rng, LogNormal(1.1, 0.6), n_samples)
    X[:, 3] .= rand(rng, Gamma(2, 5.0/3), n_samples)

    Y = vec(sum(X, dims=2)) + rand(rng, Normal(0, 2), n_samples)

    forest, bootstraps = build_distribution_forest(Y, X, -1, 1, 1.0, -1, 1, rng = rng, return_bootstrap = true)

    tree = forest.trees[1]
    @test ConditionalDistributionForest.list_leafs(tree) == Dict(4 => 1,13 => 4,14 => 5,31 => 7,5 => 2,12 => 3,30 => 6)

    @test ConditionalDistributionForest.get_leaf_of_feature(tree, X) == [4, 6, 6, 7, 4, 7, 1, 5, 2, 3]

    @test ConditionalDistributionForest.get_repartition(tree, X, 1:n_samples) == [[7], [9], [10], [1, 5], [8], [2, 3], [4, 6]]
    @test ConditionalDistributionForest.get_repartition(tree, X, bootstraps[:, 1]) == [[7], [9, 9], [10], [1], [8, 8], [3], [6, 6]]
end


struct TwoDimSubspace end

function (tds::TwoDimSubspace)(state, B, Δ)
    isposdef(B) ||
        error("Two-dimensional subspace method only works for positive definite B.")
    p = similar(state.x)
    p1 = ∇f = state.∇f
    p2 = inv(B) * ∇f
    norm_∇f = norm(∇f)
    norm_p2 = norm(p2)
    prod = ∇f' * B * ∇f
    inv_prod = ∇f' * inv(B) * ∇f

    if (norm_p2 ≤ Δ) || (norm_p2 ≈ Δ) # analytical solution
        p .= -p2
    elseif prod * inv_prod ≈ norm_∇f^4 # fall back to Cauchy Point
        _cauchypoint!(p, Δ, ∇f, norm_∇f, prod)
    else # solve constrained two-dimensional problem
        a = norm_∇f^2
        b = norm_p2^2
        c = prod
        d = inv_prod
        B̃ = [c a; a d]
        g̃ = [a; d]
        B̄ = [a d; d b]
        poly(λ) = (b - Δ^2)*(a^2 - c*d)^2 -
            2*(a^2 - c*d)*(Δ^2*a*d - Δ^2*b*c + a*b*d - d^3)*λ +
            (2*Δ^2*a^3*b - 3*Δ^2*a^2*d^2 - Δ^2*b^2*c^2 + 2*Δ^2*c*d^3 + a^3*b^2 - 2*a^2*b*d^2 + a*d^4)*λ^2 +
            2*Δ^2*(a*b - d^2)*(a*d - b*c)*λ^3 -
            Δ^2*(a*b - d^2)^2*λ^4
        λ = _positive_root(poly)
        α, β = -inv(B̃ + λ*B̄)*g̃
        @. p = α*p1 + β*p2
    end
    return p
end

function _positive_root(poly)
    λ = step = 0.01
    scale = 2.0
    while poly(λ) > 0
        step *= scale
        λ += step
    end
    sol = _optimize(λ -> nothing, poly, 0.0, λ, Bisection();
        reltol=sqrt(eps(Float64)), abstol=eps(Float64), maxiter=1_000)
    sol.converged || error("Failed to solve fourth degree polynomial")
    return sol.minimizer
end


@everywhere using CSV, GLMNet, Combinatorics, DataFrames, Plots
#@everywhere using ScikitLearn: fit!, predict, @sk_import, fit_transform!
#@everywhere @sk_import ensemble: RandomForestRegressor

include("../forc-helpers/OLSestimator.jl")
include("../forc-helpers/create_matrices.jl")
include("../forc-models/all_models.jl")

function prepare(level, h, J)
    h_lag = 12 ## Number of months over which to calculate inflation
    J_cv = (J - (1 + h_lag + h)) ÷ 2 + (1 + h_lag + h) ## Half of the within sample size used as for cross validation
    head_line = true ## Choose the head line price to construct the inflation rates
    println("h = $h, J = $J, J_cv = $J_cv")
    Y, Y_lag, X_lag, X_lag2 = create_matrices(level, h, h_lag, head_line)
    return h, h_lag, J, J_cv, level, head_line, Y, Y_lag, X_lag, X_lag2
end


###################################################################
#                         Particles
###################################################################

abstract type AbstractParticle end

"""
    SimpleParticle{T1 <: AbstractVector, T2 <: Real} <: AbstractParticle

Paticle for N-body simulation (e.g. Euler or leapfrog integrator)

# Fields
- `r`   # Position
- `v`   # Velocity
- `a`   # Acceleration

- `m`   # Mass of the particle
- `R`   # Radius of the particle
"""
struct SimpleParticle{T1 <: AbstractVector, T2 <: Real} <: AbstractParticle
    r::T1
    v::T1
    a::T1

    m::T2
    R::T2
end

SimpleParticle(r, v, m, R) = SimpleParticle(r, v, zero(r), m, R)
SimpleParticle(r, v, m) = SimpleParticle(r, v, m, zero(m))
SimpleParticle(p) = SimpleParticle(p.r, p.v, p.a, p.m, p.R)

"""
    Particle{T1 <: AbstractVector, T2 <: Real} <: AbstractParticle

Paticle for N-body simulation (4th-degree Hermite integrator)

# Fields
- `r`   # Position
- `v`   # Velocity
- `a`   # Acceleration

- `a¹`  # 1st dervative of acceleration (jerk)
- `a²`  # 2nd dervative of acceleration
- `a³`  # 3rd dervative of acceleration

- `⁺r`   # Position at next timestep
- `⁺v`   # Velocity at next timestep
- `⁺a`   # Acceleration at next timestep

- `⁺a¹`  # 1st dervative of acceleration at next timestep
- `⁺a²`  # 2nd dervative of acceleration at next timestep
- `⁺a³`  # 3rd dervative of acceleration at next timestep

- `m`   # Mass of the particle
- `R`   # Radius of the particle
"""
struct HermiteParticle{T1 <: AbstractVector, T2 <: Real} <: AbstractParticle
    r::T1
    v::T1
    a::T1

    a¹::T1
    a²::T1
    a³::T1

    ⁺r::T1
    ⁺v::T1
    ⁺a::T1

    ⁺a¹::T1
    ⁺a²::T1
    ⁺a³::T1

    m::T2
    R::T2
end

function HermiteParticle(r, v, a, m, R)
     a¹,  a²,  a³ = zero(r), zero(r), zero(r)
    ⁺r , ⁺v , ⁺a  = zero(r), zero(r), zero(r)
    ⁺a¹, ⁺a², ⁺a³ = zero(r), zero(r), zero(r)

    HermiteParticle(r, v, a, a¹, a², a³, ⁺r, ⁺v, ⁺a, ⁺a¹, ⁺a², ⁺a³, m, R)
end

HermiteParticle(r, v, m, R) = HermiteParticle(r, v, zero(r), m, R)
HermiteParticle(r, v, m) = HermiteParticle(r, v, m, zero(m))
HermiteParticle(p) = HermiteParticle(p.r, p.v, p.a, p.m, p.R)

###################################################################
#                       Initial conditions
###################################################################

setParticles(ps...) = StructArray([ps...])
setParticles(ps::NamedTuple) = StructArray([values(ps)...])

function addParticle!(ps, elms, m, R)
    E = solveKeplerEquation2(elms, rand()*elms.T)
    r, v = get_rv(elms, E)
    r = orb_to_ref(r, elms)
    v = orb_to_ref(v, elms)

    push!(ps, HermiteParticle(collect(r), collect(v), m, R))
end

function initialize_SimpleParticle!(ps, ϵ)
    for p in ps
        p.a .= 0
    end

    for i in eachindex(ps)
        for j in eachindex(ps)
            i ≥ j && continue
            ps[j].m == 0 && continue

            r = SVector{3}(ps[j].r) - SVector{3}(ps[i].r)
            r² = norm(r)^2 + ϵ^2
            r⁻³ = r²^(-3/2)

            ps[i].a .+= G * ps[j].m * r * r⁻³
            ps[j].a .-= G * ps[j].m * r * r⁻³
        end
    end
end

function initialize_HermiteParticle!(ps, ϵ)
    for p in ps
        p.a  .= 0
        p.a¹ .= 0
    end

    for i in eachindex(ps)
        for j in eachindex(ps)
            j == i && continue
            ps[j].m == 0 && continue

            r = SVector{3}(ps[j].r) - SVector{3}(ps[i].r)
            v = SVector{3}(ps[j].v) - SVector{3}(ps[i].v)

            r² = norm(r)^2 + ϵ^2
            r⁻³ = r²^(-3/2)
            r⁻⁵ = r²^(-5/2)

            ps[i].a  .+= G * ps[j].m * r * r⁻³
            ps[i].a¹ .+= G * ps[j].m * (v*r⁻³ - 3(v ⋅ r)*r*r⁻⁵)
        end
    end
end

function initialize!(ps, ϵ)
    eltype(ps) <: SimpleParticle && initialize_SimpleParticle!(ps, ϵ)
    eltype(ps) <: HermiteParticle && initialize_HermiteParticle!(ps, ϵ)
end

initialize!(ps) = initialize!(ps, 0)

###################################################################
#                         Simulation
###################################################################

function run_nbody!(ps, params, filepath)

    @unpack SOFTENING, TIMESTEP, INTEGRATOR = params

    SOFTENING ? (ϵ = params.ϵ) : (ϵ = 0)

    initialize!(ps, ϵ)

    TIMESTEP == :CONST    && (Δt = params.Δt)
    TIMESTEP == :VARIABLE && (η = params.η)

    # INTEGRATOR = :HERMITE4
    # INTEGRATOR = :LEAPFROG

    # TIMESTEP = :SHARED
    # TIMESTEP = :INDIVIDUAL

    @show SOFTENING
    @show ϵ
    @show TIMESTEP
    @show INTEGRATOR

#     open(filepath, "w") do io

#         @unpack η, t_end, save_interval = params_sim

#         ⁺ps = deepcopy(ps)
#         initialize!(ps, ⁺ps)
#         Δt = η * minimum(norm(p.a)/norm(p.a¹) for p in ps)
#         t = 0.
#         i = 0

#         E₀ = getTotalEnergy(ps)
#         while t < t_end
#             i%save_interval == 0 && save_snapshot_txt(io, t, ps)
#             collide(ps) != (0, 0) && break

#             Hermite4_shared!(ps, ⁺ps, params_sim, Δt)
#             Δt = get_Δt_Aarseth(⁺ps, params_sim)
#             t += Δt
#             i += 1
#         end
#         E₁ = getTotalEnergy(ps)
#         ΔE = (E₁ - E₀)/E₀
#         @show E₀
#         @show E₁
#         @show ΔE
#         println("Collision: ", collide(ps))
#     end
end

###################################################################
#                         Input / Output
###################################################################

function Base.show(io::IO, p::SimpleParticle)
    println(io, typeof(p))

    println(" r  : ", p.r)
    println(" v  : ", p.v)
    println(" a  : ", p.a)

    println(" m  : ", p.m)
    println(" R  : ", p.R)
end

function Base.show(io::IO, p::HermiteParticle)
    println(io, typeof(p))

    println(" r  : ", p.r)
    println(" v  : ", p.v)
    println(" a  : ", p.a)

    println(" a¹ : ", p.a¹)
    println(" a² : ", p.a²)
    println(" a³ : ", p.a³)

    println(" m  : ", p.m)
    println(" R  : ", p.R)
end

"""
    save_snapshot(out::IOStream, t, ps)

# Format of snapshot
    t
    n
    m1 x1 y1 z1 vx1 vy1 vz1
    m2 x2 y2 z2 vx2 vy2 vz2
    :
    :
    mN xN yN zN vxN vyN vzN
"""
function save_snapshot_txt(io::IO, t, ps)
    println(io, t)
    println(io, length(ps))
    for p in ps
        println(io, p.m, ", ",
            p.r[1], ", ", p.r[2], ", ", p.r[3], ", ",
            p.v[1], ", ", p.v[2], ", ", p.v[3]
        )
    end
end

function load_snapshot(filepath)
    ts = Float64[]
    snapshots = []
    open(filepath, "r") do io
        while !eof(io)
            t = parse(Float64, readline(io))
            N = parse(Int64, readline(io))
            ps = StructArray(SimpleParticle{Vector{Float64}, Float64}[])
            for i in 1:N
                line = parse.(Float64, split(readline(io), ","))
                m = line[1]
                r = line[2:4]
                v = line[5:7]
                p = SimpleParticle(r, v, zeros(3), m, 0.)
                push!(ps, p)
            end
            push!(ts, t)
            push!(snapshots, ps)
        end
    end
    ts, snapshots
end

###################################################################
#                          Collision
###################################################################

distance(p₁, p₂) = norm(SVector{3}(p₁.r) - SVector{3}(p₂.r))

collide(p₁, p₂) = distance(p₁, p₂) < p₁.R + p₂.R ? true : false

function collide(ps)
    for i in eachindex(ps)
        for j in eachindex(ps)
            i ≥ j && continue
            collide(ps[i], ps[j]) && return (i, j)
        end
    end
    return (0, 0)
end

###################################################################
#                    Barycenter of particles
###################################################################

"""
    getBaryCenter(ps) -> r_G, v_G, a_G

Position, velocity and acceleration of the system's barycenter
"""
function getBaryCenter(ps)
    Σm = sum(ps.m)
    r_G = sum(ps.m .* ps.r) / Σm
    v_G = sum(ps.m .* ps.v) / Σm
    a_G = sum(ps.m .* ps.a) / Σm

    r_G, v_G, a_G
end

function setOrigin2BaryCenter!(ps)
    r_G, v_G, a_G = getBaryCenter(ps)
    for p in ps
        p.r .-= r_G
        p.v .-= v_G
        p.a .-= a_G
    end
end

function getBaryCenter(ps, rs, vs, as)
    rs_G = zeros(size(rs[:,:,end]))
    vs_G = zeros(size(vs[:,:,end]))
    as_G = zeros(size(as[:,:,end]))

    for i in 1:size(rs, 3)
        @. rs_G += ps.m[i] * rs[:, :, i]
        @. vs_G += ps.m[i] * vs[:, :, i]
        @. as_G += ps.m[i] * as[:, :, i]
    end

    Σm = sum(ps.m)
    rs_G /= Σm
    vs_G /= Σm
    as_G /= Σm

    rs_G, vs_G, as_G
end

function setOrigin2BaryCenter!(ps, rs, vs, as)
    rs_G, vs_G, as_G = getBaryCenter(ps, rs, vs, as)

    for i in 1:size(rs, 3)
        @. rs[:,:,i] -= rs_G
        @. vs[:,:,i] -= vs_G
        @. as[:,:,i] -= as_G
    end
end

###################################################################
#
###################################################################

getTotalEnergy(ps) = sumKineticEnergy(ps) + sumPotentialEnergy(ps)

getKineticEnergy(p::AbstractParticle) = 0.5 * p.m * norm(p.v)^2
sumKineticEnergy(ps) = sum(getKineticEnergy(p) for p in ps)

getPotentialEnergy(pᵢ::AbstractParticle, pⱼ::AbstractParticle) = - G * pᵢ.m * pⱼ.m / norm(pᵢ.r .- pⱼ.r)

function sumPotentialEnergy(ps)
    E = 0.
    for i in eachindex(ps)
        for j in eachindex(ps)
            i ≥ j && continue
            E += getPotentialEnergy(ps[i], ps[j])
        end
    end
    E
end


using MultivariateOrthogonalPolynomials, StaticArrays, BlockArrays, BlockBandedMatrices, ArrayLayouts,
        QuasiArrays, Test, ClassicalOrthogonalPolynomials, BandedMatrices, FastTransforms, LinearAlgebra
import MultivariateOrthogonalPolynomials: dunklxu_raising, dunklxu_lowering, AngularMomentum

@testset "Dunkl-Xu disk" begin
    @testset "basics" begin
        P = DunklXuDisk()
        @test copy(P) ≡ P
        @test P ≠ DunklXuDisk(0.123)

        xy = axes(P,1)
        x,y = first.(xy),last.(xy)
        @test xy[SVector(0.1,0.2)] == SVector(0.1,0.2)
        @test x[SVector(0.1,0.2)] == 0.1
        @test y[SVector(0.1,0.2)] == 0.2
    end

    @testset "operators" begin
        N = 5
        β = 0.123

        P = DunklXuDisk(β)
        Q = DunklXuDisk(β+1)
        WP = WeightedDunklXuDisk(β)
        WQ = WeightedDunklXuDisk(β+1)

        x, y = first.(axes(P, 1)), last.(axes(P, 1))

        L = WP \ WQ
        R = Q \ P

        X = P \ (x .* P)
        Y = P \ (y .* P)

        @test (L * R)[Block.(1:N), Block.(1:N)] ≈ (I - X^2 - Y^2)[Block.(1:N), Block.(1:N)]

        @test (DunklXuDisk() \ WeightedDunklXuDisk(1.0))[Block.(1:N), Block.(1:N)] ≈ (WeightedDunklXuDisk(0.0) \ WeightedDunklXuDisk(1.0))[Block.(1:N), Block.(1:N)]

        ∂x = PartialDerivative{1}(axes(P, 1))
        ∂y = PartialDerivative{2}(axes(P, 1))

        Dx = Q \ (∂x * P)
        Dy = Q \ (∂y * P)

        Mx = Q \ (x .* Q)
        My = Q \ (y .* Q)

        A = (Mx * Dy - My * Dx)[Block.(1:N), Block.(1:N)]

        B = (Q \ P)[Block.(1:N), Block.(1:N)]

        C = B \ A

        @test C ≈ Tridiagonal(C)

        λ = eigvals(Matrix(C))

        @test λ ≈ im*imag(λ)

        ∂θ = AngularMomentum(axes(P, 1))
        A = P \ (∂θ * P)
        A2 = P \ (∂θ^2 * P)

        @test A[Block.(1:N), Block.(1:N)] ≈ C
        @test A2[Block.(1:N), Block.(1:N)] ≈ (A^2)[Block.(1:N), Block.(1:N)] ≈ A[Block.(1:N), Block.(1:N)]^2

        ∂x = PartialDerivative{1}(axes(WQ, 1))
        ∂y = PartialDerivative{2}(axes(WQ, 1))

        wDx = WP \ (∂x * WQ)
        wDy = WP \ (∂y * WQ)

        @testset "truncations" begin
            KR,JR = Block.(1:N),Block.(1:N)

            @test L[KR,JR] isa BandedBlockBandedMatrix
            @test R[KR,JR] isa BandedBlockBandedMatrix
            @test X[KR,JR] isa BandedBlockBandedMatrix
            @test Y[KR,JR] isa BandedBlockBandedMatrix
            @test Dx[KR,JR] isa BandedBlockBandedMatrix
            @test Dy[KR,JR] isa BandedBlockBandedMatrix
        end
    end
end


using OrdinaryDiffEq
using Trixi

##########  EINSTELLUNGEN     #########
# BLZ: we adjust the CFL and the terminal time to test the different methods for stability
# maximal setting for N = 3, Nq = 16: Chandrashekar without dissipation, CFL = 2.37, t = 100

initial_condition = initial_condition_weak_blast_wave
N = 3
cells_per_dimension = (16, 16)

CFL = 2.37
tspan = (0.0, 100)

# Standard DGSEM Entropy STability
# polydeg = N
# surface_flux = flux_lax_friedrichs # flux_chandrashekar # flux_central #
# volume_integral = VolumeIntegralWeakForm()
# solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, volume_integral = volume_integral )

# CHandrashekar DGSEM Entropy STability
surface_flux = flux_chandrashekar # FluxPlusDissipation(flux_chandrashekar, DissipationLocalLaxFriedrichs(max_abs_speed_naive)) #
volume_flux  = flux_chandrashekar
volume_integral = VolumeIntegralFluxDifferencing(volume_flux)
basis = LobattoLegendreBasis(N)
solver = DGSEM(basis, surface_flux, volume_integral)

###############################################################################
# semidiscretization of the compressible Euler equations

equations = CompressibleEulerEquations2D(1.4)

coordinates_min = (-2.0, -2.0)
coordinates_max = ( 2.0,  2.0)
mesh = CurvedMesh(cells_per_dimension, coordinates_min, coordinates_max)

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver)

###############################################################################
# ODE solvers, callbacks etc.

ode = semidiscretize(semi, tspan)

summary_callback = SummaryCallback()

save_solution = SaveSolutionCallback(interval=100,
                                     save_initial_solution=true,
                                     save_final_solution=true,
                                     solution_variables=cons2prim)

stepsize_callback = StepsizeCallback(cfl=CFL)
analysis_interval = 1000
analysis_callback = AnalysisCallback(semi, interval=analysis_interval, save_analysis=true,
                                                 extra_analysis_errors=(:conservation_error,),
                                                 extra_analysis_integrals=(entropy,)# energy_total,
                                                                           #energy_kinetic, energy_internal)

                                                )

callbacks = CallbackSet(summary_callback, stepsize_callback, analysis_callback)

###############################################################################
# run the simulation

sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false),
            dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
            save_everystep=false, callback=callbacks);
summary_callback() # print the timer summary


# standard binomial
function pmf(x::TI, n::TI, p::TR; partial::Tuple{TI,TI} = (0, 0)) where {TI<:Integer,TR<:Real}

    if !(0 <= partial[1] <= partial[2] <= n)
        throw(DomainError((partial..., n), @sprintf "0 <= xpartial=%i <= npartial=%i <= n=%i violated" partial[1] partial[2] n))
    end
    deltax = x - partial[1]
    deltan = n - partial[2]
    if deltax < 0; return 0.0 end
    if deltan < deltax; return 0.0 end
    if x > n
        throw(DomainError((x, n), @sprintf "x=%i <= n=%i violated" x n))
    end
    return (gamma(deltan + 1)/gamma(deltax + 1)/gamma(deltan - deltax + 1)) *
        p^deltax * (1 - p)^(deltan - deltax)
end

function pmf(x::TI, n::TI, p::TP; partial::Tuple{TI,TI} = (0, 0)) where {TI<:Integer,TP<:Prior}
    expectation(
        p -> pmf(x, n, p; partial = partial),
        update(p, partial[1], partial[2])
    )
end

function cdf(x::TR1, n::TI, p::TR2; partial::Tuple{TI,TI} = (0, 0)) where {TI<:Integer,TR1,TR2<:Real}

    if x < 0; return 0.0 end
    if x > n; return 1.0 end
    if x == n; return 1.0 end
    if !(0 <= partial[1] <= partial[2] <= n)
        throw(DomainError((partial..., n), @sprintf "0 <= xpartial=%i <= npartial=%i <= n=%i violated" partial[1] partial[2] n))
    end
    deltax = x - partial[1]
    deltan = n - partial[2]
    if deltax < 0; return 0.0 end # is x < xpartial, so is x-1 .. 0
    if deltax > deltan; return 1.0 end
    return beta_inc(deltan - deltax, deltax + 1, 1 - p, p)[1]
end

function cdf(x::TR, n::TI, p::TP; partial::Tuple{TI,TI} = (0, 0)) where {TI<:Integer,TR<:Real,TP<:bad.Prior}
    expectation(
        p -> cdf(x, n, p; partial = partial),
        update(p, partial[1], partial[2])
    )
end


# State transition function
function POMDPs.transition(mdp::HCAS_MDP, s::stateType, ra::actType)
    r = s[1]; t = s[2]; p = s[3]; vown = s[4]; vint = s[5]; pra = s[6];

    # Computation is faster when using vector of static size
    nextStates = MVector{9, stateType}(undef)
    nextProbs = @MVector(zeros(9))
    next_pra = ra
    ind=1

    # Compute probabilities of next states using sigma point sampling
    ownProbs, ownTurns = mdp.turns[pra]
    intProbs, intTurns = mdp.turns[-1]
    for i = 1:3
        for j = 1:3
            next_r,next_t,next_p,next_vown,next_vint = dynamics(r,t,p,vown,vint,ownTurns[i],intTurns[j],pra)
            nextStates[ind] = (next_r,next_t,next_p,next_vown,next_vint,next_pra)
            nextProbs[ind]  = ownProbs[i]*intProbs[j]
            ind+=1
        end
    end

    return SparseCat(nextStates,nextProbs)
end

# Dynamic equations
function dynamics(r::Float64,t::Float64,p::Float64,vown::Float64,vint::Float64,ownTurn::Float64, intTurn::Float64, ra::Int)
    x_own = 0.0; y_own = 0.0; x_int = r*cos(t); y_int = r*sin(t)
    dx_own = vown; dy_own = 0.0; dx_int = vint*cos(p); dy_int = vint*sin(p)
    x_own += dx_own; y_own += dy_own; x_int += dx_int; y_int += dy_int

    x_int_new = x_int - x_own; y_int_new = y_int - y_own
    heading_own = ownTurn; heading_int = p + intTurn

    r_new = sqrt(x_int_new.^2 + y_int_new.^2)
    t_new = atan(y_int_new,x_int_new) - heading_own
    p_new = heading_int - heading_own

    t_new = t_new > pi  ? t_new-2*pi : t_new
    t_new = t_new < -pi ? t_new+2*pi : t_new
    p_new = p_new > pi  ? p_new-2*pi : p_new
    p_new = p_new < -pi ? p_new+2*pi : p_new

    return r_new, t_new, p_new, vown, vint

end