"""
    RecombineMatrix{T} <: AbstractMatrix{T}

Matrix for transformation from coefficients of the recombined basis, to the
corresponding B-spline basis coefficients.

# Extended help

The transformation matrix ``M`` is defined by
```math
ϕ_j = M_{ij} b_i,
```
where ``b_j(x)`` and ``ϕ_i(x)`` are elements of the B-spline and recombined
bases, respectively.

This matrix allows to pass from known coefficients ``u_j`` in the recombined
basis ``ϕ_j``, to the respective coefficients ``v_i`` in the B-spline basis
``b_i``:
```math
\\bm{v} = \\mathbf{M} \\bm{u}.
```

Note that the matrix is not square: it has dimensions ``N × M``, where ``N``
is the length of the B-spline basis, and ``M = N - δ`` is that of the
recombined basis (see [`RecombinedBSplineBasis`](@ref) for details).

Due to the local support of B-splines, basis recombination can be performed
by combining just a small set of B-splines near the boundaries (as discussed in
[`RecombinedBSplineBasis`](@ref)).
This leads to a recombination matrix which is almost a diagonal of ones, plus
a few extra super- and sub-diagonal elements in the upper left and lower right
corners, respectively.
The matrix is stored in a memory-efficient way that also allows fast access to
its elements.

Efficient implementations of matrix-vector products (using the `*` operator or
`LinearAlgebra.mul!`) and of left division of vectors (using `\\` or
`LinearAlgebra.ldiv!`) are included.
These two operations can be used to transform between coefficients in the
original and recombined bases.

Note that, since the recombined basis forms a subspace of the original basis
(which is related to the rectangular shape of the matrix),
it is generally not possible to obtain recombined coefficients from original
coefficients, unless the latter already satisfy the constraints encoded in the
recombined basis.
The left division operation will throw a [`NoUniqueSolutionError`](@ref) if that
is not the case.

---

    RecombineMatrix(ops::Tuple{Vararg{AbstractDifferentialOp}},
                    B::BSplineBasis, [T])

Construct recombination matrix describing a B-spline basis recombination.

The default element type `T` is generally `Float64`, except for specific
differential operators which yield a matrix of zeroes and ones, for which `Bool`
is the default.

See the [`RecombinedBSplineBasis`](@ref) constructor for details on the
`ops` argument.
"""
struct RecombineMatrix{T,
                       DiffOps <: Tuple{Vararg{AbstractDifferentialOp}},
                       n, n1,   # nc = n1 - n
                       Corner <: SMatrix{n1,n,T}} <: AbstractMatrix{T}
    ops :: DiffOps  # list of differential operators for BCs
    M :: Int      # length of recombined basis (= N - 2nc)
    N :: Int      # length of B-spline basis
    ul :: Corner  # upper-left corner of matrix, size (n1, n)
    lr :: Corner  # lower-right corner of matrix, size (n1, n)
    function RecombineMatrix(ops::Tuple{Vararg{AbstractDifferentialOp}},
                             N::Integer, ul::SMatrix{n1,n}, lr::SMatrix{n1,n}) where {n1,n}
        if n1 <= n
            throw(ArgumentError("matrices must have dimensions (m, n) with m > n"))
        end
        M = N - 2 * length(ops)
        T = eltype(ul)
        Corner = typeof(ul)
        Ops = typeof(ops)
        new{T, Ops, n, n1, Corner}(ops, M, N, ul, lr)
    end
end

# Default element type of recombination matrix.
# In some specific cases we can use Bool...
_default_eltype(::Vararg{AbstractDifferentialOp}) = Float64
_default_eltype(::Derivative{0}) = Bool  # Dirichlet BCs
_default_eltype(::Derivative{1}) = Bool  # Neumann BCs

# Case (D(0), D(1), D(2), ...)
_default_eltype(::Derivative{0}, ::Derivative{1}, ::Vararg{Derivative}) = Bool

RecombineMatrix(ops::Tuple{Vararg{AbstractDifferentialOp}}, B::BSplineBasis) =
    RecombineMatrix(ops, B, _default_eltype(ops...))

# Specialisation for Dirichlet BCs.
function RecombineMatrix(op::Tuple{Derivative{0}}, B::BSplineBasis,
                         ::Type{T}) where {T}
    _check_bspline_order(op, B)
    N = length(B)
    ul = SMatrix{1,0,T}()
    lr = copy(ul)
    RecombineMatrix(op, N, ul, lr)
end

# Specialisation for Neumann BCs.
function RecombineMatrix(op::Tuple{Derivative{1}}, B::BSplineBasis,
                         ::Type{T}) where {T}
    _check_bspline_order(op, B)
    N = length(B)
    ul = SMatrix{2,1,T}(1, 1)
    lr = copy(ul)
    RecombineMatrix(op, N, ul, lr)
end

# Generalisation to higher orders and to more general differential operators
# (this includes Robin BCs, for instance).
#
# If more than one operator is passed, they must be of the form
#
#    (D(0), D(1), ..., D(m - 2), D(m - 1), [D(n)]),
#
# where the last D(n) is optional, and satisfies n ≥ m + 1.
# In this case, the first `m` B-splines are dropped, and the next `n - m + 1`
# B-splines are recombined into `q = n - m` functions.
#
# That last operator is allowed to be a linear combination of `Derivative`s.
# In that case, `n` is the maximum degree of the operator.
function RecombineMatrix(ops::Tuple{Vararg{AbstractDifferentialOp}},
                         B::BSplineBasis, ::Type{T}) where {T}
    _check_bspline_order(ops, B)
    op = last(ops)
    n = max_order(op)
    m = _bsplines_to_drop(ops...)
    _make_matrix(Val(n + 1), Val(m), ops, B, T)
end

# Case of mixed BCs (D(0), D(1), ..., D(n)).
# B-splines are dropped, and new functions are not created.
# This is a generalisation of the Dirichlet case.
function _make_matrix(::Val{n}, ::Val{n}, ops, B, ::Type{T}) where {n,T}
    Nc = length(ops)
    @assert Nc >= 2  # case Nc = 1 is treated by different functions
    N = length(B)
    ul = SMatrix{Nc,0,T}()
    lr = copy(ul)
    RecombineMatrix(ops, N, ul, lr)
end

# Case of single BC, or mixed BCs where the last one is "different" from the
# others.
function _make_matrix(::Val{n1}, ::Val{m}, ops, B, ::Type{T}) where {n1,m,T}
    n = n1 - 1
    q = n - m
    @assert q >= 1

    Nc = length(ops)  # this is the number of constraints
    a, b = boundaries(B)
    Ca = zeros(T, q + Nc, q)
    Cb = copy(Ca)

    N = length(B)
    op = last(ops)

    let x = a
        # Indices of B-splines to recombine.
        is = ntuple(d -> m + d, Val(q + 1))

        # Coefficients of odd derivatives must change sign, since d/dn = -d/dx
        # on the left boundary.
        opn = dot(op, LeftNormal())

        # Evaluate n-th derivatives of bⱼ at the boundary.
        # TODO replace with analytical formula?
        bs = evaluate.(B, is, x, opn)
        for l = 1:q
            b0, b1 = bs[l], bs[l + 1]
            @assert !(b0 ≈ b1)
            r = 2 / (b0 - b1)  # normalisation factor
            Ca[l + Nc - 1, l] = -b1 * r
            Ca[l + Nc, l] = b0 * r
        end
    end

    let x = b
        M = N - m  # index of last undropped B-spline
        is = ntuple(d -> M - d + 1, Val(q + 1))
        opn = dot(op, RightNormal())
        @assert opn === op  # the right normal is in the x direction
        bs = evaluate.(B, is, x, opn)
        for l = 1:q
            b0, b1 = bs[q - l + 2], bs[q - l + 1]
            @assert !(b0 ≈ b1)
            r = 2 / (b0 - b1)
            Cb[l, l] = -b1 * r
            Cb[l + 1, l] = b0 * r
        end
    end

    ul = SMatrix{q + Nc, q}(Ca)
    lr = SMatrix{q + Nc, q}(Cb)

    RecombineMatrix(ops, N, ul, lr)
end

# Verify that the B-spline order is compatible with the given differential
# operators.
function _check_bspline_order(ops::Tuple, B::BSplineBasis)
    n = max_order(ops...)
    k = order(B)
    if n >= k
        throw(ArgumentError(
            "cannot resolve operators $ops with B-splines of order $k"))
    end
    nothing
end

# Single BC: no B-splines are dropped.
# (Except if op = Derivative{0}, but that case is treated separately.)
_bsplines_to_drop(op::AbstractDifferentialOp) = 0

_unval(::Val{n}) where {n} = n
_bsplines_to_drop(ops...) = _bsplines_to_drop(Val(0), ops...) |> _unval

function _bsplines_to_drop(::Val{n}, a, b, etc...) where {n}
    if a !== Derivative(n)
        throw(ArgumentError("unsupported combination of boundary conditions"))
    end
    _bsplines_to_drop(Val(n + 1), b, etc...)
end

# This is the case of the last operator, which is allowed to be something
# different than Derivative{n}.
function _bsplines_to_drop(::Val{n}, op) where {n}
    if op === Derivative(n)
        return Val(n + 1)
    end
    if max_order(op) ≤ n
        throw(ArgumentError("order of last operator is too low"))
    end
    Val(n)
end

# Unsupported cases
RecombineMatrix(ops::Tuple{Vararg{AbstractDifferentialOp}}, args...) =
    throw(ArgumentError(
        "boundary condition combination is currently unsupported: $ops"))

Base.size(A::RecombineMatrix) = (A.N, A.M)

constraints(A::RecombineMatrix) = (A.ops, A.ops)
constraints(::UniformScaling) = ((), ())

num_constraints(A::RecombineMatrix) = map(length, constraints(A))
num_constraints(::UniformScaling) = (0, 0)  # case of non-recombined bases

# Returns (left, right) tuple with maximum BC order on each boundary.
_max_order(A::RecombineMatrix) = map(ops -> max_order(ops...), constraints(A))

# Returns number of basis functions that are recombined from the original basis
# near each boundary.
# For instance, Neumann BCs have a single recombined function, ϕ_1 = b_1 + b_2;
# while mixed Dirichlet + Neumann have none, ϕ_1 = b_3.
num_recombined(A::RecombineMatrix) =
    map((m, c) -> m + 1 - c, _max_order(A), num_constraints(A))
num_recombined(::UniformScaling) = (0, 0)

# j: index in recombined basis
# M: length of recombined basis
@inline function which_recombine_block(A::RecombineMatrix, j)
    @boundscheck checkbounds(A, :, j)
    M = A.M
    nl, nr = num_recombined(A)  # left/right number of recombined bases
    j <= nl && return 1
    j > M - nr && return 3
    2
end

"""
    nzrows(A::RecombineMatrix, col::Integer) -> UnitRange{Int}

Returns the range of row indices `i` such that `A[i, col]` is non-zero.
"""
@propagate_inbounds function nzrows(A::RecombineMatrix,
                                    j::Integer) :: UnitRange{Int}
    block = which_recombine_block(A, j)
    j += num_constraints(A)[1]
    if block == 1
        (j - 1):j
    elseif block == 2
        j:j  # shifted diagonal of ones
    else
        j:(j + 1)
    end
end

# Pretty-printing, adapted from BandedMatrices.jl code.
function Base.replace_in_print_matrix(
        A::RecombineMatrix, i::Integer, j::Integer, s::AbstractString)
    iszero(A[i, j]) ? Base.replace_with_centered_mark(s) : s
end

@inline function Base.getindex(A::RecombineMatrix, i::Integer, j::Integer)
    @boundscheck checkbounds(A, i, j)
    T = eltype(A)
    @inbounds block = which_recombine_block(A, j)

    cl, cr = num_constraints(A)  # left/right constraints

    if block == 2
        return T(i == j + cl)  # δ_{i, j+c}
    end

    if block == 1
        C = A.ul
        if i <= size(C, 1)
            return @inbounds C[i, j] :: T
        end
        return zero(T)
    end

    # The lower-right corner starts at column h + 1.
    M = size(A, 2)
    nl, nr = num_recombined(A)
    h = M - nr

    @assert j > h
    C = A.lr
    ii = i - h - cr
    if ii ∈ axes(C, 1)
        return @inbounds C[ii, j - h] :: T
    end

    zero(T)
end

# Efficient implementation of matrix-vector multiplication.
# Generally much faster than using a regular sparse array.
function LinearAlgebra.mul!(y::AbstractVector, A::RecombineMatrix,
                            x::AbstractVector)
    checkdims_mul(y, A, x)
    N, M = size(A)

    nl, nr = num_recombined(A)
    cl, cr = num_constraints(A)
    n1 = nl + cl
    h = M - nr

    @inbounds y[1:n1] = A.ul * @view x[1:nl]

    for i = (nl + 1):h
        @inbounds y[i + cl] = x[i]
    end

    @inbounds y[(N - nr - cr + 1):N] = A.lr * @view x[(h + 1):M]

    y
end

# Five-argument mul!.
# Note that, since I have this, I could remove the 3-argument mul!  (which is
# equal to the 5-argument one with α = 1 and β = 0), but it would be a bit more
# inefficient to compute.
function LinearAlgebra.mul!(y::AbstractVector, A::RecombineMatrix,
                            x::AbstractVector, α::Number, β::Number)
    checkdims_mul(y, A, x)
    N, M = size(A)

    nl, nr = num_recombined(A)
    cl, cr = num_constraints(A)
    n1 = nl + cl
    h = M - nr

    @inbounds y[1:n1] = α * A.ul * view(x, 1:nl) + β * SVector{n1}(view(y, 1:n1))

    for i = (nl + 1):h
        @inbounds y[i + cl] = α * x[i] + β * y[i + cl]
    end

    js = (N - nr - cr + 1):N
    @inbounds y[js] =
        α * A.lr * view(x, (h + 1):M) + β * SVector{n1}(view(y, js))

    y
end

function LinearAlgebra.ldiv!(x::AbstractVector, A::RecombineMatrix,
                             y::AbstractVector)
    checkdims_mul(y, A, x)
    N, M = size(A)

    nl, nr = num_recombined(A)
    cl, cr = num_constraints(A)
    n1 = nl + cl
    h = M - nr

    @inbounds x[1:nl] = _ldiv_unique_solution(A.ul, view(y, 1:n1))

    for i = (nl + 1):h
        @inbounds x[i] = y[i + cl]
    end

    js = (N - nr - cr + 1):N
    @inbounds x[(h + 1):M] = _ldiv_unique_solution(A.lr, view(y, js))

    x
end

function LinearAlgebra.:\(A::RecombineMatrix, y::AbstractVector)
    x = similar(y, size(A, 2))
    ldiv!(x, A, y)
end

"""
    NoUniqueSolutionError <: Exception

Exception thrown when solving linear system using [`RecombineMatrix`](@ref),
when the system has no unique solution.
"""
struct NoUniqueSolutionError <: Exception end

function Base.showerror(io::IO, ::NoUniqueSolutionError)
    print(io,
          "overdetermined system has no unique solution.",
          " This means that the given function expanded in the parent basis",
          " has no exact representation (i.e. cannot be expanded) in the recombined basis."
         )
end

# Solve A \ y for overdetermined system with rectangular matrix A, assuming that
# the system has exactly one solution. Throws error if that's not the case.
function _ldiv_unique_solution(A::SMatrix, y::AbstractVector)
    N, M = size(A)
    @assert length(y) == N
    @assert N > M "system must be overdetermined"

    # Solve the first non-trivial M equations, skipping rows of zeroes.
    n = 1
    if M != 0
        @inbounds while n ≤ N && all(iszero.(A[n, :]))
            n += 1
        end
    end
    m = n + M - 1
    @assert m <= N
    @inbounds Am = SMatrix{M,M}(view(A, n:m, :))
    @inbounds ym = SVector{M}(view(y, n:m))
    x = Am \ ym

    # Check that equations (m+1):N are satisfied.
    @inbounds for i = (m + 1):N
        res = zero(eltype(x))
        for j in axes(A, 2)
            res += A[i, j] * x[j]
        end
        res ≈ y[i] || throw(NoUniqueSolutionError())
    end

    x
end

function checkdims_mul(y, A, x)
    M, N = size(A)
    if length(y) != M
        throw(DimensionMismatch("first dimension of A, $M, " *
                                "does not match length of y, $(length(y))"))
    end
    if length(x) != N
        throw(DimensionMismatch("second dimension of A, $N, " *
                                "does not match length of x, $(length(x))"))
    end
    nothing
end


# kuramoto_7-24-19.jl
#
# Back to work.

@time begin
    using Plots
    using DifferentialEquations

    function KuramotoModel(N, K, omega)
        @assert(N==size(omega)[1], "number of oscillators != N")

        function Kuramoto(dtheta, theta, p, t)
            for i in 1:N
                theta[i] = mod2pi(theta[i])

                stabilizers = map(theta_j -> sin(theta_j - theta[i]), theta)
                stabilizing_term = (K / N) * sum(stabilizers)

                dtheta[i] = omega[i] + stabilizing_term

                theta[i] = mod2pi(theta[i])
            end
        end

        return Kuramoto
    end
end
# This code takes about 30 seconds to run on my machine. Oof.

#######################################################################
# Simulate a two-oscillator Kuramoto model, over 60 seconds.

tspan = (0.0, 60.0)

K2 = KuramotoModel(2, 1.0, [1.0, 1.0])

theta0 = [0.0, pi]
prob = ODEProblem(K2, theta0, tspan)

@time sol = solve(prob, Euler(), dt=0.001) # Yes, Euler() is important.
@time plot(sol)

# Success!

# But how long does it take?
# sol = solve(...) takes around 4.2 seconds on my machine, and
# the plotting takes a full 17.7 seconds JESUS CHRIST

# I'm going to see if I can improve this a bit over in the
# techniques-to-increase-speed folder.

#######################################################################
# Simulate a 12-oscillator Kuramoto model, all with natural frequency 1
# radians / second, over 10 seconds.

tspan = (0.0, 10.0)

N = 12;
K = 1.0;

omega_n = Array{Float64}(undef, N)
for i in 1:N
    omega_n[i] = 1.0
end

theta_0 = Array{Float64}(undef, N)
for j in 1:N
    theta_0[j] = mod2pi(j)
        # This is just an easy hack to space these out. ∀ n_1, n_2 ∈ N :
        # n_1 != n_2 ⟹ mod2pi(n_1) != mod2pi(n_2). I got that from...
        # Somewhere. Trust me.
end

K12 = KuramotoModel(N, 1.0, omega_n)

prob = ODEProblem(K12, theta_0, tspan)

@time sol = solve(prob, Euler(), dt=0.0001) # Yes, Euler() is important.
@time plot(sol)
#######################################################################
# How about a *thousand*? 😉

tspan = (0.0, 10.0)

N = 1000;
K = 1.0;

omega_n = Array{Float64}(undef, N)
for i in 1:N
    omega_n[i] = 1.0
end

theta_0 = Array{Float64}(undef, N)
for j in 1:N
    theta_0[j] = mod2pi(j)
        # This is just an easy hack to space these out. ∀ n_1, n_2 ∈ N :
        # n_1 != n_2 ⟹ mod2pi(n_1) != mod2pi(n_2). I got that from...
        # Somewhere. Trust me.
end

K1000 = KuramotoModel(N, 1.0, omega_n)

prob = ODEProblem(K1000, theta_0, tspan)

sol = solve(prob, Euler(), dt=0.0001) # Yes, Euler() is important.
plot(sol)


using Plots, LaTeXStrings
gr()
using LinearAlgebra, Random, Distributions, Base.Threads, Distributed
@everywhere using SharedArrays
addprocs(Sys.CPU_THREADS - 1)
@everywhere function pointsDelaunay(dimBase::Float64, dim::Int64)
    points = rand(dim, 2)
    if dimBase != 1.0
        points[:,2] /= dimBase
    end
    n, tri = GR.delaunay(points[:,1], points[:,2])
    n, tri, points
end

@everywhere function triDelaunay(points)
    n, tri = GR.delaunay(points[:,1], points[:,2])
    n, tri, points
end

@everywhere function rcentersGeqOneLune(beta::Float64, x1::Float64, y1::Float64, x2::Float64, y2::Float64)
    c1 = [beta.*x1./2.0 .+ (1.0 .- beta./2).*x2 beta.*y1./2.0 .+ (1.0 .- beta./2.0).*y2]
    c2 = [(1.0 .- beta./2.0).*x1 .+ beta.*x2./2.0 (1.0 .- beta./2).*y1 .+ beta.*y2./2]
    r = beta.*sqrt((x2 .- x1).^2 + (y2 .- y1).^2)./2.0
    r, c1, c2
end

@everywhere function rcentersLeqOne(beta::Float64, x1::Float64, y1::Float64, x2::Float64, y2::Float64)
    dot_v1 = [x1, y1]
    dot_v2 = [x2, y2]
    vec_v1v2 = dot_v2 .- dot_v1
    rotvec = reverse(vec_v1v2).*[-1,1]
    radOut = sqrt.(sum(vec_v1v2.^2))./(2*beta)
    b_out = sqrt.(1 .- beta^2)./(2*beta)
    p_c1 = (dot_v1 .+ dot_v2)./2 .+ b_out.*rotvec
    p_c2 = (dot_v1 .+ dot_v2)./2 .- b_out.*rotvec
    radOut, p_c1', p_c2'
end

@everywhere function rcentersGeqOneCircle(beta::Float64, x1::Float64, y1::Float64, x2::Float64, y2::Float64)
    dot_v1 = [x1, y1]
    dot_v2 = [x2, y2]
    vec_v1v2 = dot_v2 .- dot_v1
    rotvec = reverse(vec_v1v2).*[-1,1]
    radOut = beta.*sqrt.(sum(vec_v1v2.^2))./2
    b_out = sqrt.(beta^2 .- 1)./2
    p_c1 = (dot_v1 .+ dot_v2)./2 .+ b_out.*rotvec
    p_c2 = (dot_v1 .+ dot_v2)./2 .- b_out.*rotvec
    radOut, p_c1', p_c2'
end

function betaRandSkeletonGeqOneLune(beta::Float64, dim::Int64, plotSkeleton = false, dimBase::Float64 = 1.0)
    n, tri, points = pointsDelaunay(dimBase, dim)
    mAdjacency = SharedArray{Float64}(diagm(0 => randn(dim)))
    indices = [1, 1, 2]
    j = [2, 3, 3]
    @sync @distributed for trindx in 1:n
        for (indx, i) in enumerate(indices)
            indx1 = tri[trindx,:][i]
            indx2 = tri[trindx,:][j[indx]]
            x1, y1 = points[indx1, :]
            x2, y2 = points[indx2, :]
            r, c1, c2 = rcentersGeqOneLune(beta, x1, y1, x2, y2)
            full_1 = sqrt.(sum((points .- c1).^2, dims = 2)) .< r
            full_2 = sqrt.(sum((points .- c2).^2, dims = 2)) .< r
            full_1[[indx1, indx2]] .= false
            if any(full_1 .+ full_2 .== 2) == false
                rnd = randn()./sqrt(2)
                mAdjacency[indx1, indx2] = rnd
                mAdjacency[indx2, indx1] = rnd
            end
        end
    end
    plotSkeleton == true ? (mAdjacency, points) : mAdjacency
end

function betaRandSkeletonGeqOneCircle(beta::Float64, dim::Int64, plotSkeleton = false, dimBase::Float64 = 1.0)
    n, tri, points = pointsDelaunay(dimBase, dim)
    mAdjacency = SharedArray{Float64}(diagm(0 => randn(dim)))
    indices = [1, 1, 2]
    j = [2, 3, 3]
    @sync @distributed for trindx in 1:n
        for (indx, i) in enumerate(indices)
            indx1 = tri[trindx,:][i]
            indx2 = tri[trindx,:][j[indx]]
            x1, y1 = points[indx1, :]
            x2, y2 = points[indx2, :]
            r, c1, c2 = rcentersGeqOneCircle(beta, x1, y1, x2, y2)
            full_1 = sqrt.(sum((points .- c1).^2, dims = 2)) .< r
            full_2 = sqrt.(sum((points .- c2).^2, dims = 2)) .< r
            full_1[[indx1, indx2]] .= false
            full_2[[indx1, indx2]] .= false
            if any(full_1 .| full_2) == false
                rnd = randn()./sqrt(2)
                mAdjacency[indx1, indx2] = rnd
                mAdjacency[indx2, indx1] = rnd
            end
        end
    end
    plotSkeleton == true ? (mAdjacency, points, tri) : mAdjacency
end

function betaRandSkeletonLeqOne(beta::Float64, dim::Int64, plotSkeleton = false, dimBase::Float64 = 1.0)
    mAdjacency = SharedArray{Float64}(diagm(0 => randn(dim)))
    points = rand(dim, 2)
    if dimBase != 1.0
        points[:,2] /= dimBase
    end
    @sync @distributed for indx1 in 1:dim-1
        for indx2 in indx1 + 1:dim
            x1, y1 = points[indx1, :]
            x2, y2 = points[indx2, :]
            r, c1, c2 = rcentersLeqOne(beta, x1, y1, x2, y2)
            full_1 = sqrt.(sum((points .- c1).^2, dims = 2)) .< r
            full_2 = sqrt.(sum((points .- c2).^2, dims = 2)) .< r
            full_1[[indx1, indx2]] .= false
            if any(full_1 .+ full_2 .== 2) == false
                rnd = randn()./sqrt(2)
                mAdjacency[indx1, indx2] = rnd
                mAdjacency[indx2, indx1] = rnd
            end
        end
    end
    plotSkeleton == true ? (mAdjacency, points) : mAdjacency
end

function betaSkeletonGeqOneLune(beta::Float64, points, plotSkeleton = false)
    n, tri, points = triDelaunay(points)
    dim = length(points[:,1])
    mAdjacency = SharedArray{Float64}(diagm(0 => randn(dim)))
    indices = [1, 1, 2]
    j = [2, 3, 3]
    @sync @distributed for trindx in 1:n
        for (indx, i) in enumerate(indices)
            indx1 = tri[trindx,:][i]
            indx2 = tri[trindx,:][j[indx]]
            x1, y1 = points[indx1, :]
            x2, y2 = points[indx2, :]
            r, c1, c2 = rcentersGeqOneLune(beta, x1, y1, x2, y2)
            full_1 = sqrt.(sum((points .- c1).^2, dims = 2)) .< r
            full_2 = sqrt.(sum((points .- c2).^2, dims = 2)) .< r
            full_1[[indx1, indx2]] .= false
            if any(full_1 .+ full_2 .== 2) == false
                rnd = randn()./sqrt(2)
                mAdjacency[indx1, indx2] = rnd
                mAdjacency[indx2, indx1] = rnd
            end
        end
    end
    plotSkeleton == true ? (mAdjacency, points) : mAdjacency
end

function betaSkeletonGeqOneCircle(beta::Float64, points, plotSkeleton = false)
    n, tri, points = triDelaunay(points)
    dim = length(points[:,1])
    mAdjacency = SharedArray{Float64}(diagm(0 => randn(dim)))
    indices = [1, 1, 2]
    j = [2, 3, 3]
    @sync @distributed for trindx in 1:n
        for (indx, i) in enumerate(indices)
            indx1 = tri[trindx,:][i]
            indx2 = tri[trindx,:][j[indx]]
            x1, y1 = points[indx1, :]
            x2, y2 = points[indx2, :]
            r, c1, c2 = rcentersGeqOneCircle(beta, x1, y1, x2, y2)
            full_1 = sqrt.(sum((points .- c1).^2, dims = 2)) .< r
            full_2 = sqrt.(sum((points .- c2).^2, dims = 2)) .< r
            full_1[[indx1, indx2]] .= false
            full_2[[indx1, indx2]] .= false
            if any(full_1 .| full_2) == false
                rnd = randn()./sqrt(2)
                mAdjacency[indx1, indx2] = rnd
                mAdjacency[indx2, indx1] = rnd
            end
        end
    end
    plotSkeleton == true ? (mAdjacency, points, tri) : mAdjacency
end

function betaSkeletonLeqOne(beta::Float64, points)
    dim = length(points[:,1])
    mAdjacency = SharedArray{Float64}(diagm(0 => rand(dim)))
    @sync @distributed for indx1 in 1:dim-1
        for indx2 in indx1 + 1:dim
            x1, y1 = points[indx1, :]
            x2, y2 = points[indx2, :]
            r, c1, c2 = rcentersLeqOne(beta, x1, y1, x2, y2)
            full_1 = sqrt.(sum((points .- c1).^2, dims = 2)) .< r
            full_2 = sqrt.(sum((points .- c2).^2, dims = 2)) .< r
            full_1[[indx1, indx2]] .= false
            if any(full_1 .+ full_2 .== 2) == false
                rnd = randn()./sqrt(2)
                mAdjacency[indx1, indx2] = rnd
                mAdjacency[indx2, indx1] = rnd
            end
        end
    end
    mAdjacency
end

function betaSkeleton(beta::Float64, dim::Int64, dimBase::Float64 = 1.0)
    if beta < 1.0
        madea, points = betaRandSkeletonLeqOne(beta, dim, true, dimBase)
        return points, madea
    elseif beta >= 1.0
        madeaLune, points =  betaRandSkeletonGeqOneLune(beta, dim, true, dimBase)
        madeaCircle = betaSkeletonGeqOneCircle(beta, points)
        return points, madeaLune, madeaCircle
    end
end

function betaSkeletonV2(beta::Float64, dim::Int64, dimBase::Float64 = 1.0)
    if beta < 1.0
        madea, points = betaRandSkeletonLeqOne(beta, dim, true, dimBase)
        return points, madea.s
    elseif beta >= 1.0
        madeaLune, points =  betaRandSkeletonGeqOneLune(beta, dim, true, dimBase)
        madeaCircle = betaSkeletonGeqOneCircle(beta, points)
        return points, madeaLune.s, madeaCircle.s
    end
end

function pointsHexagonX(center, size, i)
    angledeg = 60*i #- 30
    anglerad = pi/180*angledeg
    (center[1] .+ size*cos(anglerad), center[2] .+ size*sin(anglerad))
end

function plotBetaSkeleton(points, madjacency)
    x, y = points[:,1], points[:,2]
    lowerm = LowerTriangular(madjacency)
    n = length(x)
    p0 = plot(x, y,seriestype=:scatter, m=(2, 0.8, :black, stroke(0)),
        xlim =(0,1), ylim=(0,1), legend = false, grid = false, axis = :on,
        framestyle=:box, xtickfont = font(14, "sans-serif"),ytickfont = font(14, "sans-serif"),
        xticks = ([0,1],[L"0",  L"1"]), yticks = ([0,1],[L"0", L"1"]),
        size = (300, 300), aspect_ratio=1)
    for indxp in 1:n
        edges = findall(lowerm[:,indxp] .!= 0)
        for v in edges[2:end]
            plot!(x[[indxp, v]], y[[indxp, v]], linewidth = 0.2, linecolor = :black)
        end
    end
    p0
end

function plotDelaunay(points, triplete)
    x, y = points[:,1], points[:,2]
    p1 = plot(xlim =(0,1), ylim=(0,1), legend = false, grid = false, axis = :on,
        framestyle=:box,xtickfont = font(14, "sans-serif"),ytickfont = font(14, "sans-serif"),
        xticks = ([0,1],[L"0",  L"1"]), yticks = ([0,1],[L"0", L"1"]),
        size = (300, 300), aspect_ratio=1)
    for i in 1:size(tri)[1]
        plot!(x[triplete[i,:]], y[triplete[i,:]], seriestype = [:shape, :scatter],
            m = (2, 0.2, :black, stroke(0)), linestyle = :solid,
            linealpha = 0.4, linewidth = 0.2, linecolor = :black, fillalpha = 0.02) #  fillcolor = :false
    end
    p1
end


"""
    gradient(f, p::AbstractParticles)

Calculate the gradient of `f` in `p`. This corresponds to a smoothed finite-difference approximation where the smoothing kernel is given by the distribution of `p`.
Return mean and std.
"""
function gradient(f,p::MonteCarloMeasurements.AbstractParticles)
    r = 2(p\f(p))
    mean(r), std(r)
end
function gradient(f,p::Union{Integer, AbstractFloat})
    p = p  ± 0.000001
    r = 2(p\f(p))
    mean(r)
end

function gradient(f::Function,p::MonteCarloMeasurements.MvParticles)
    r = (p-mean(p))\(f(p) - f(mean(p)))
end

"""
    jacobian(f::Function, p::Vector{<:AbstractParticles})

Calculate the Jacobian of `f` in `p`. This corresponds to a smoothed finite-difference approximation where the smoothing kernel is given by the distribution of `p`.
"""
function jacobian(f::Function,p::MonteCarloMeasurements.MvParticles)
    r = (p-mean(p))\(f(p) - f(mean(p)))
end


@testset "Pauli matrices" begin
    @test σˣ^2 == σ⁰
    @test σʸ^2 == σ⁰
    @test σᶻ^2 == σ⁰
    @test σˣ*σʸ - σʸ*σˣ == 2im*σᶻ
    @test σʸ*σᶻ - σᶻ*σʸ == 2im*σˣ
    @test σᶻ*σˣ - σˣ*σᶻ == 2im*σʸ
end


function reduce_array_for_convex_hull!(point_array::Vector{<:AbstractPoint})::Vector{<:AbstractPoint}
    if length(point_array) <= 8
        return point_array
    end

    (min_x, max_x, min_y, max_y) = extrema(point_array)
    (skew_min_x, skew_max_x, skew_min_y, skew_max_y) = skew_extrema(point_array)

    approx_hull_array = [
        min_x,
        skew_min_x,
        min_y,
        skew_min_y,
        max_x,
        skew_max_x,
        max_y,
        skew_max_y]

    # approx_hull_array = [
    #     min_x,
    #     min_y,
    #     max_x,
    #     max_y]

    u_approx_hull_array = unique(p->p.x, approx_hull_array)
    uu_approx_hull_array = unique(p->p.y, u_approx_hull_array)
    approx_hull = naive_graham_scan!(uu_approx_hull_array)

    N_triangles = length(approx_hull)-2
    triangles = Vector{Triangle{Point{Float64}}}(undef, N_triangles)
    for kk = 1:N_triangles
        triangles[kk] = Triangle(approx_hull[1], approx_hull[kk+1], approx_hull[kk+2])
    end

    # bool = falses( length(point_array) )
    # for kk = 1:N_triangles
    #     bool_tmp = is_in_triangle.([triangles[kk]], point_array)
    #     bool = bool .| bool_tmp
    # end

    # reduced_array = [
    #     approx_hull_array...,
    #     point_array[.!bool]...
    #     ]

    for kk = 1:N_triangles
        bool_tmp = is_in_triangle.([triangles[kk]], point_array)
        point_array = point_array[.!bool_tmp]
    end

    uu_reduced_array = [
        approx_hull...,
        point_array...
        ]

    # u_reduced_array = unique(p->p.x, reduced_array)
    # uu_reduced_array = unique(p->p.y, u_reduced_array)
    return uu_reduced_array
end

function graham_scan!(point_array::Vector{<:AbstractPoint})
    reduced_array = reduce_array_for_convex_hull!(point_array)
    return naive_graham_scan!(reduced_array)
end

function naive_graham_scan!(point_array::Vector{<:AbstractPoint})
    # opti possibility: dont calculate the angle, but only the ordering is relevant
    L = length(point_array)
    if L <= 3
        return point_array
    end

    y_ = y(point_array);
    (miny, idx) = findmin(y_)
    point_array[idx], point_array[end] = point_array[end], point_array[idx]
    p0 = point_array[end]

    phi = Vector{Float64}(undef, L)
    for kk = 1:(L-1)
        # v = point_array[kk] - p0
        # phi[kk] = atan(v[2], v[1])
        phi[kk] = atan(point_array[kk].y - p0.y, point_array[kk].x - p0.x)
    end
    phi[end] = 1e308 # missing # missing is greater than anything else
    iU2S = sortperm(phi)
    point_array = point_array[iU2S]

    ElementType = eltype(point_array)
    stack = datastructs.Stack{ElementType}(L)

    push!(stack, p0)
    push!(stack, point_array[1])
    push!(stack, point_array[2])

    for kk = 3:(L-1)
        while nonleft_turn(
                datastructs.peek(stack, 1),
                datastructs.peek(stack, 0),
                point_array[kk] )
            pop!(stack)
        end
        push!(stack, point_array[kk])
    end
    ch = stack.data[1:stack.ptr]
    return ch
end

function nonleft_turn(p1::T, p2::T, p3::T)::Bool where {T<:AbstractPoint}
    # a = p2 - p1
    # b = p3 - p2
    # return cross2(a, b) <= 0

    a_x = p2.x - p1.x
    a_y = p2.y - p1.y

    b_x = p3.x - p2.x
    b_y = p3.y - p2.y
    return cross2(a_x, a_y, b_x, b_y) <= 0
end

function jarvis3!(point_array::Vector{<:AbstractPoint})
	L = length(point_array)
    if L <= 3
        return point_array
    end

    reduced_array = reduce_array_for_convex_hull!(point_array)
    L = length(reduced_array)
    (min_y, max_y, idx_min, idx_max) = extrema_y(reduced_array)

	stack = datastructs.Stack{Point{Float64}}(L)
	push!(stack, min_y)

	# util.swappop!(reduced_array, idx_min)
	util.swap!(reduced_array, idx_min, L)
	L = L - 1

    phi = Vector{Float64}(undef, L)
	ref_point = peek(stack, 0)

	a_x = zero(Float64)
	a_y = zero(Float64)
	b_x = zero(Float64)
	b_y = zero(Float64)

	for kk = 1:L
		# a_x = 1
		# a_y = 0
		b_x = reduced_array[kk].x - ref_point.x
		b_y = reduced_array[kk].y - ref_point.y
		sin_ = cross2(1.0, 0.0, b_x, b_y)
		cos_ = dot2(1.0, 0.0, b_x, b_y)
		phi[kk] = cos_ / sin_ # cotangent, the angle is irrelevant, only the ordering is important
		# phi[kk] = atan(reduced_array[kk].y - ref_point.y, reduced_array[kk].x - ref_point.x)
	end
	(minphi, idx) = findmax(phi) # for cotangent the min angle is the max value

	push!(stack, reduced_array[idx])
	util.swappop!(reduced_array, idx)
	# pop!(phi) # must be popped, in order to not search in the last not used element in findmin
	# L = L - 1
	first = true
	while true
		prev_point = peek(stack, 1)
		ref_point = peek(stack, 0)
		for kk = 1:L
			a_x = ref_point.x - prev_point.x
			a_y = ref_point.y - prev_point.y
			b_x = reduced_array[kk].x - ref_point.x
			b_y = reduced_array[kk].y - ref_point.y
			sin_ = cross2(a_x, a_y, b_x, b_y)
			cos_ = dot2(a_x, a_y, b_x, b_y)
			phi[kk] = cos_ / sin_ # cotangent, the angle is irrelevant, only the ordering is important
			# phi[kk] =  sin_ / cos_
			# phi[kk] = atan(reduced_array[kk].y - ref_point.y, reduced_array[kk].x - ref_point.x)
		end
		if first
			phi[isnan.(phi)] .= -Inf
			first = false
		end
		phi_view = view(phi, 1:L)
		(minphi, idx) = findmax(phi_view) # for cotangent the min angle is the max value
		if min_y == reduced_array[idx]
			break
		end
		push!(stack, reduced_array[idx])
		util.swap!(reduced_array, idx, L)
		# util.swappop!(reduced_array, idx)
		# pop!(phi) # must be popped, in order to not search in the last not used element in findmin
		L = L - 1
	end

	ch = [stack.data[1:stack.ptr]...]
	return ch

end

function jarvis2!(point_array::Vector{<:AbstractPoint})
	L = length(point_array)
    if L <= 3
        return point_array
    end

    reduced_array = reduce_array_for_convex_hull!(point_array)
    L = length(reduced_array)
    (min_y, max_y, idx_min, idx_max) = extrema_y(reduced_array)

	stack = datastructs.Stack{Point{Float64}}(div(L,2)+1)
	push!(stack, min_y)

	# util.swappop!(reduced_array, idx_min)
	util.swap!(reduced_array, idx_min, L)
	L = L - 1

    phi = Vector{Float64}(undef, L)
	ref_point = peek(stack, 0)

	a_x = zero(Float64)
	a_y = zero(Float64)
	b_x = zero(Float64)
	b_y = zero(Float64)

	for kk = 1:L
		# a_x = 1
		# a_y = 0
		b_x = reduced_array[kk].x - ref_point.x
		b_y = reduced_array[kk].y - ref_point.y
		sin_ = cross2(1.0, 0.0, b_x, b_y)
		cos_ = dot2(1.0, 0.0, b_x, b_y)
		phi[kk] = cos_ / sin_ # cotangent, the angle is irrelevant, only the ordering is important
		# phi[kk] = atan(reduced_array[kk].y - ref_point.y, reduced_array[kk].x - ref_point.x)
	end
	(minphi, idx) = findmax(phi) # for cotangent the min angle is the max value

	push!(stack, reduced_array[idx])
	util.swappop!(reduced_array, idx)
	# pop!(phi) # must be popped, in order to not search in the last not used element in findmin
	# L = L - 1
	first = true
	while true
		prev_point = peek(stack, 1)
		ref_point = peek(stack, 0)
		for kk = 1:L
			a_x = ref_point.x - prev_point.x
			a_y = ref_point.y - prev_point.y
			b_x = reduced_array[kk].x - ref_point.x
			b_y = reduced_array[kk].y - ref_point.y
			sin_ = cross2(a_x, a_y, b_x, b_y)
			cos_ = dot2(a_x, a_y, b_x, b_y)
			phi[kk] = cos_ / sin_ # cotangent, the angle is irrelevant, only the ordering is important
			# phi[kk] =  sin_ / cos_
			# phi[kk] = atan(reduced_array[kk].y - ref_point.y, reduced_array[kk].x - ref_point.x)
		end
		if first
			phi[isnan.(phi)] .= -Inf
			first = false
		end
		(minphi, idx) = findmax(phi) # for cotangent the min angle is the max value
		if min_y == reduced_array[idx]
			break
		end
		push!(stack, reduced_array[idx])
		util.swappop!(reduced_array, idx)
		pop!(phi) # must be popped, in order to not search in the last not used element in findmin
		L = L - 1
	end

	ch = [stack.data[1:stack.ptr]...]
	return ch

end

function jarvis_march!(point_array::Vector{<:AbstractPoint})

    L = length(point_array)
    if L <= 3
        return point_array
    end

    reduced_array = reduce_array_for_convex_hull!(point_array)
    L = length(reduced_array)
    (min_y, max_y, idx_min, idx_max) = extrema_y(reduced_array)
    # p_1 = reduced_array[1]
    # p_L = reduced_array[end]
    # reduced_array[1], reduced_array[idx_min] = reduced_array[idx_min], p_1
    # reduced_array[end], reduced_array[idx_max] = reduced_array[idx_max], p_L
    # at the index 1 is the point with min y coord, at L with max y

    stack_left = datastructs.Stack{Point{Float64}}(div(L,2)+1)
    stack_right = datastructs.Stack{Point{Float64}}(div(L,2)+1)

    push!(stack_right, min_y)
    push!(stack_left, max_y)

	# pop the min element, the angles shall not be computed for this point
	util.swappop!(reduced_array, idx_min)
	L = L - 1

    phi = Vector{Float64}(undef, L)

	idx = 0 # new local declaration
    # right segment
    while true
		ref_point = peek(stack_right, 0)
        for kk = 1:L
            phi[kk] = atan(reduced_array[kk].y - ref_point.y, reduced_array[kk].x - ref_point.x)
        end

        (minphi, idx) = findmin(phi)
        if max_y == reduced_array[idx]
            break
        end
        push!(stack_right, reduced_array[idx])
		util.swappop!(reduced_array, idx)
		pop!(phi) # must be popped, in order to not search in the last not used element in findmin
		L = L - 1
    end

	# at idx it is known that tha max_y is there, but min_y is missing now in the array
	# put min_y at idx
	reduced_array[idx] = min_y
    # left segment
	while true
		ref_point = peek(stack_left, 0)
        for kk = 1:L
            phi[kk] = π + atan(reduced_array[kk].y - ref_point.y, reduced_array[kk].x - ref_point.x)
        end

        (minphi, idx) = findmin(phi)
        if min_y == reduced_array[idx]
            break
        end
        push!(stack_left, reduced_array[idx])
		util.swappop!(reduced_array, idx)
		pop!(phi) # must be popped, in order to not search in the last not used element in findmin
		L = L - 1
    end

	ch = [
	stack_right.data[1:stack_right.ptr]...
	stack_left.data[1:stack_left.ptr]...
	]
	return ch
end

# function quick_hull!(point_array::Vector{<:AbstractPoint})
#
# 	L = length(point_array)
# 	if L <= 3
# 		return point_array
# 	end
#
# 	reduced_array = reduce_array_for_convex_hull!(point_array)
# 	L = length(reduced_array)
# 	(min_y, max_y, idx_min, idx_max) = extrema_y(reduced_array)
#
#
#
# end

function incremental_hull!(point_array::Vector{<:AbstractPoint})

	L = length(point_array)
    if L <= 3
        return point_array
    end

    reduced_array = reduce_array_for_convex_hull!(point_array)

    L = length(reduced_array)
    (min_y, max_y, idx_min, idx_max) = extrema_y(reduced_array)

	aL = reduced_array[end]
	aLm1 = reduced_array[end-1]

	reduced_array[idx_min] = aL
	reduced_array[idx_max] = aLm1
	pop!(reduced_array)
	pop!(reduced_array)

	list = initial_hull!(reduced_array, max_y, min_y)
	L = length(reduced_array)-1 # p3 is in the array

	for kk = L:-1:1
		new_point = reduced_array[kk]
		bools = is_lefts(list, new_point)
		if all(bools)
			continue
		else
			(t1, t2) = tangent_points_to_hull(list, bools)
			datastructs.remove_between!(list, t1, t2)
			new_node = datastructs.new_node(list)
			new_node.data = new_point
			datastructs.insert_after!(list, t1, new_node)
		end
	end

	return datastructs.linked_list_to_array(list)
end

function tangent_points_to_hull(list, bools)
	node = list.node
	kk = 1
	L = length(list)
	if bools[1] == true
		while kk <= L && bools[kk] == true
			node = node.next
			kk += 1
		end
		t1 = node
		while kk <= L && bools[kk] == false
			node = node.next
			kk += 1
		end
		t2 = node
	else
		while kk <= L && bools[kk] == false
			node = node.next
			kk += 1
		end
		t2 = node
		while kk <= L && bools[kk] == true
			node = node.next
			kk += 1
		end
		t1 = node
	end
	return (t1, t2)
end

function initial_hull!(reduced_array, p1, p2)

	list = datastructs.LinkedList{Point{Float64}}()
	# n1 = datastructs.new_node(list)
	n2 = datastructs.new_node(list)
	n3 = datastructs.new_node(list)

	list.node.data = p1

	idx = length(reduced_array)
	while idx >= 1
		p3 = reduced_array[idx]
		if !is_on_line(p1, p2, p3)

			if is_left(p1, p2, p3)
				# n1.data = p1
				n2.data = p2
				n3.data = p3
			else
				# n1.data = p1
				n2.data = p3
				n3.data = p2
			end
			break
		end
		pop!(reduced_array)
		idx -= 1
	end

	datastructs.insert_after!(list, list.node, n2)
	datastructs.insert_after!(list, n2, n3)

	return list
end

function is_lefts(polygon_list::datastructs.LinkedList, new_point::AbstractPoint)

	bools = falses(polygon_list.count)
	node2 = polygon_list.node.next

	p1 = polygon_list.node.data
	p2 = node2.data

	for kk = 1:length(polygon_list)
		b = is_left_on(p1, p2, new_point)
		bools[kk] = b
		node2 = node2.next
		p1 = p2
		p2 = node2.data
	end

	return bools
end

#=
function incremental_hull!(point_array::Vector{<:AbstractPoint})

	L = length(point_array)
    if L <= 3
        return point_array
    end

    reduced_array = reduce_array_for_convex_hull!(point_array)

    L = length(reduced_array)
    (min_y, max_y, idx_min, idx_max) = extrema_y(reduced_array)

	list = datastructs.LinkedList{Point{Float64}}()
	list.node.data = min_y

	node = datastructs.new_node(list)
	node.data = max_y
	datastructs.insert_after!(list, list.node, node)

	aL = reduced_array[end]
	aLm1 = reduced_array[end-1]

	reduced_array[idx_min] = aL
	reduced_array[idx_max] = aLm1
	pop!(reduced_array)
	pop!(reduced_array)
	L = L - 2

	node = datastructs.new_node(list)

	# ensure that the 3. point is not on the line
	p1 = list.node.data
	p2 = list.node.next.data
	line_12 = Line(list.node.data, list.node.next.data)
	while true
		p = reduced_array[end]
		if is_on_line(line_12, p)
			pop!(reduced_array)
		else
			break
		end
	end
	node.data = reduced_array[end]
	pop!(reduced_array)
	datastructs.insert_after!(list, list.node.next, node)

	# enforce that the third point is om the left of 1 and 2
	p3 = list.node.next.next.data
	if is_left(line_12, p3)
		# fine, counter clockwise order
	else
		# swap 2 and 3
		p2 = list.node.next.data
		p3 = list.node.next.next.data
		list.node.next.data = p3
		list.node.next.next.data = p2
	end

	idx = length(reduced_array)
	while idx >= 1
		new_point = reduced_array[idx]
		bools = is_lefts(list, new_point)

		idx -= 1
		if all(bools)
			continue
		else
			(n1, n2) = find_transition_points(list, bools)
			new_node = datastructs.new_node(list)
			new_node.data = new_point
			if n1.data == n2.data
				datastructs.insert_after!(list, n2, new_node)
				continue
			end
			n2 = n2.next
			next_node = n1.next
			while next_node.data != n2.data
				nn = next_node.next
				datastructs.remove_element!(list, next_node)
				next_node = nn
			end
			datastructs.insert_after!(list, n1, new_node)
		end
	end

	return datastructs.linked_list_to_array(list)

end

function find_transition_points(list, bools)
	L = length(list)
	first_zero = 0
	last_zero = 0

	if bools[1] == false
		# backwards search for first zero
		kk = 1
		while bools[kk] == false
			kk = kk - 1
			if kk == 0
				kk = L
			end
		end
		first_zero = util.modulo_index(kk + 1, L)

		kk = 1
		while bools[kk] == false
			kk = kk + 1
		end
		last_zero = kk - 1

	else # bools[1] == true
		# forward search
		kk = 1
		while bools[kk] == true
			kk = kk + 1
		end
		first_zero = kk

		# kk = kk + 1
		while bools[kk] == false
			if kk == L
				kk = 0
			end
			kk = kk + 1
		end
		if kk == 1
			kk = L + 1
		end
		last_zero = util.modulo_index(kk - 1, L)
	end

	node = list.node
	kk = 1

	if first_zero > last_zero
		# search backwards

		### TO DO ###
		# what happens if first_zero == L
		kk = L
		while kk >= first_zero
			kk = kk - 1
			node = node.prev
		end
		n1 = node

		kk = 1
		node = list.node
		while kk < last_zero
			kk = kk + 1
			node = node.next
		end
		n2 = node
	else
		# search forward
		while kk < first_zero
			kk = kk + 1
			node = node.next
		end
		n1 = node
		while kk < last_zero
			kk = kk + 1
			node = node.next
		end
		n2 = node
	end

	return (n1, n2)
end

function is_lefts(polygon_list::datastructs.LinkedList, new_point::AbstractPoint)

	bools = falses(polygon_list.count)

	node2 = polygon_list.node.next

	p1 = polygon_list.node.data
	p2 = node2.data

	for kk = 1:length(polygon_list)
		b = is_left_on(p1, p2, new_point)
		bools[kk] = b
		node2 = node2.next
		p1 = p2
		p2 = node2.data
	end

	return bools
end

# function is_in_polygon_list(list::datastructs.LinkedList, new_point::AbstractPoint)
# 	#stupid linear algo
#
# 	bool = true
# 	p1 = list.node.data
# 	node2 = list.node.next
# 	p2 = node2.data
# 	line_12 = Line(p1, p2)
#
# 	while p2 != list.node
# 		if !is_left_on(line_12, new_point)
# 			bool = false
# 			break
# 		end
# 		p1 = p2
# 		node2 = node2.next
# 		p2 = node2.data
# 	end
#
# 	return bool
# end
=#


#= original implementation from
Single-Particle Diffusion Characterization by Deep Learning
Biophys J. 2019 Jul 23;117(2):185-192. doi: 10.1016/j.bpj.2019.06.015. Epub 2019 Jun 22.
Naor Granik et. al.
https://github.com/AnomDiffDB/DB
=#

function mittag_leffer_rand(β::Real, N::Int, γ::Real)
    t = -log.(rand(N))
    u = rand(N)
    w = sin(β * π) ./ tan.(β * π .* u) .- cos(β * π)
    t .*= w ./ β
    t .*= γ
end

function symmetric_alpha_levy(α::Real, N::Int, γ::Real)
    u = rand(N)
    v = rand(N)
    ϕ = π .* (v .- 0.5)
    w = sin.(α .* ϕ) ./ cos.(ϕ)
    z = -log.(u) .* cos.(ϕ)
    z ./= cos.((1 - α) .* ϕ)
    γ .* w .* z .^ (1 - (1 / α))
end

find_nearest(array::AbstractArray, value) = argmin(abs.(array .- value))

"""
    ctrw(α, γ, T, N) -> Vector{Float}, Vector{Float}, Vector{Float64}

Return a simulated CTRW trajectory presented by Fulger et. al., Phys. Rev. E(2008).

**Arguments**

- `α`: exponent of the waiting time distribution function.
- `β`: scale parameter for the mittag-leffler and alpha stable distributions.
- `N`: number of points to generate.
- `T`: end time.
"""
function ctrw(α::Real, γ::Real, T::Int, N::Int)
    jumpsX = mittag_leffer_rand(α, N, γ)
    rawTimeX = cumsum(jumpsX)
    tX = rawTimeX * T / maximum(rawTimeX)
    jumpsY = mittag_leffer_rand(α, N, γ)
    rawTimeY = cumsum(jumpsY)

    tY = rawTimeY * T / maximum(rawTimeY)
    x = cumsum(symmetric_alpha_levy(α, N, γ^(α / 2)))
    y = cumsum(symmetric_alpha_levy(α, N, γ^(α / 2)))
    tOut = collect(0:1:N) .* T / N
    xOut = Vector{Float64}(undef, N)
    yOut = Vector{Float64}(undef, N)

    for n = 1:N
        xOut[n] = x[find_nearest(tX, tOut[n]), 1]
        yOut[n] = y[find_nearest(tY, tOut[n]), 1]
    end
    return xOut, yOut, tOut
end

"""
Permute the states of `hmm` according to `perm`.

**Arguments**

- `perm::Vector{<:Integer}`: permutation of the states.

**Example**
```julia
using Distributions, HMMBase
hmm = HMM([0.8 0.2; 0.1 0.9], [Normal(0,1), Normal(10,1)])
hmm = permute(hmm, [2, 1])
hmm.A # [0.9 0.1; 0.2 0.8]
hmm.B # [Normal(10,1), Normal(0,1)]
```
"""


using GalerkinSparseGrids
using Test
using HCubature
using StaticArrays
using SparseArrays

#---------------------------------------
# Testing regular hier DG reconstruction
#---------------------------------------

@info "Testing full discontinuous Galerkin (DG) reconstruction 1-D... "
@testset "hier_DG.jl" begin
    @testset "Regular hier DG reconstruction" begin
        for k in 1:5
            for l in 1:6
                dict = coeffs_DG(1, k, l, x->sin(4*x[1]); scheme="full")
                err = x->(reconstruct_DG(dict, [x])-sin(4*x))^2
                @test hquadrature(err, 0, 1; atol=1.0e-10)[1] < 1/(1<<(l+k-1))
            end
        end

        for k in 1:5
            for l in 1:6
                dict = coeffs_DG(1, k, l, x->sin(4*x[1]); scheme="sparse")
                err = x->(reconstruct_DG(dict, [x])-sin(4*x))^2
                @test hquadrature(err, 0, 1; atol=1.0e-10)[1] < 1/(1<<(l+k-1))
            end
        end
    end

    # Sparsification should be trivial in 1D
    @info "Testing that both reconstructions are equivalent... "
    @testset "Equivalent Reconstructions" begin
        for k in 1:5
            for l in 1:6
                full_coeffs = coeffs_DG(1, k, l, x->sin(4*x[1]); scheme="full")
                sparse_coeffs = coeffs_DG(1, k, l, x->sin(4*x[1]); scheme="full")
                diff_full = x->(reconstruct_DG(full_coeffs, [x])-sin(4*x))^2
                diff_sparse = x->(reconstruct_DG(sparse_coeffs, [x])-sin(4*x))^2
                diff_both = x->(reconstruct_DG(full_coeffs, [x])-reconstruct_DG(sparse_coeffs, [x]))^2

                @test abs(hquadrature(diff_full,0,1; atol=1.0e-9)[1]-
                          hquadrature(diff_sparse,0,1; atol=1.0e-9)[1]) < 1.0e-14
                @test hquadrature(diff_both,0,1; atol=1.0e-9)[1] < 1.0e-14
            end
        end
    end

# Testing sparse DG reconstruction in multidimensional space:

    @info "Testing sparse DG basis reconstruction 2-D..."
    @testset "Sparse DG reconstruction in multidimensional space" begin
        for k in 1:5
            for l in 1:6
                dict = coeffs_DG(2, k, l, x->sin(4*x[1]+x[2]))
                err = x->(reconstruct_DG(dict, [x[1],x[2]])-sin(4*x[1]+x[2]))^2
                @test hcubature(err, [0,0], [1,1]; atol=1.0e-10, maxevals=500)[1] < 1/(1<<(l+k-2))
            end
        end
    end
end


module TimeDomain

import Statistics

#=
This function calculates the differences between the NN intervals
:param n: is the array that contains the NN-inetrvals
:return diff: is an array containing the differences
=#
function nn_diff(n)
    diff=[]
    for i in 1:length(n)-1
        push!(diff,abs(n[i+1]-n[i]))
    end
    return diff
end #nn_diff

#=
This function calculates the standard deviation of the NN intervals
:param n: is the array that contains the NN-inetrvals
:return: the standard deviation
=#
function sdnn(n)
    return Statistics.std(n)
end # sdnn

#=
This function calculates the root mean square of successive differences
:param diff: is the array containing the differences between the NN intervals
:return: the rmssd
=#
function rmssd(diff)
    return sqrt(Statistics.mean(diff.^2))
end # rmssd

#=
This function calculates the standard deviation of successive differences
:param diff: is the array containing the differences between the NN intervals
:return: the sdsd
=#
function sdsd(diff)
    return Statistics.std(diff)
end # sdsd

#=
This function calculates the percentage of successive NN intervals,
with an interval smaller than x ms
:param diff: is the array containing the differences between the NN intervals
:param x: is the number of miliseconds the intervals may differ
:return: the percentage of successive intervals with a difference < x ms
=#
function pnn(diff,x)
    return nn(diff,x)/(length(diff)+1)*100
end # pnn

#=
This function calculates the number of successive NN intervals,
with an interval smaller than x ms
:param diff: is the array containing the differences between the NN intervals
:param x: is the number of miliseconds the intervals may differ
:return: the number of successive intervals with a difference < x ms
=#
function nn(diff,x)
    count=0
    for d in diff
        if d>x
            count+=1
        end
    end
    return count
end # nn

#=
This function calculates the mean of the NN intervals
:param n: is the array that contains the NN-inetrvals
:return: the mean value
=#
function mean_nn(n)
    return Statistics.mean(n)
end # mean_nn

#=
This function calculates the relative RR
:param n: is the array that contains the NN-inetrvals
:return: the relative RR
=#
function rRR(n)
    rr=[]
    for i in 2:length(n)
        r=(2*(n[i]-n[i-1])/(n[i]+n[i-1]))
        push!(rr,r)
    end
    m=sum(rr)/length(rr)
    d=[]
    for i in 1:length(rr)-1
        push!(d,sqrt((m-rr[i])^2+(m-rr[i+1])^2))
    end
    return Statistics.median(d)*100
end # rRR

end # module


module Metrics
using SparseArrays

struct CovariantBasis
        # a1
        x_r1::AbstractArray
        y_r1::AbstractArray
        # a2
        x_r2::AbstractArray
        y_r2::AbstractArray
end

struct ContravariantBasis
        # b1
        r1_x::AbstractArray
        r1_y::AbstractArray
        # b2
        r2_x::AbstractArray
        r2_y::AbstractArray
end

struct CovariantMetricTensor
        a11::AbstractArray
        a12::AbstractArray
        a21::AbstractArray
        a22::AbstractArray
end

struct ContravariantMetricTensor
        b11::AbstractArray
        b12::AbstractArray
        b21::AbstractArray
        b22::AbstractArray
end

function build_jacobian(a::CovariantBasis)
        return a.x_r1 .* a.y_r2 - a.x_r2 .* a.y_r1
end

function build_contravariant_basis(J::AbstractArray, a::CovariantBasis)
        return ContravariantBasis( a.y_r2 ./ J,
                                 - a.x_r2 ./ J,
                                 - a.y_r1 ./ J,
                                   a.x_r1 ./ J)

end

function build_covariant_metric_tensor(a::CovariantBasis)
        return CovariantMetricTensor(a.x_r1 .* a.x_r1 + a.y_r1 .* a.y_r1,
                                     a.x_r1 .* a.x_r2 + a.y_r1 .* a.y_r2,
                                     a.x_r2 .* a.x_r1 + a.y_r2 .* a.y_r1,
                                     a.x_r2 .* a.x_r2 + a.y_r2 .* a.y_r2)
end

function build_contravariant_metric_tensor(b::ContravariantBasis)
        return ContravariantMetricTensor(b.r1_x .* b.r1_x + b.r1_y .* b.r1_y,
                                         b.r1_x .* b.r2_x + b.r1_y .* b.r2_y,
                                         b.r2_x .* b.r1_x + b.r2_y .* b.r1_y,
                                         b.r2_x .* b.r2_x + b.r2_y .* b.r2_y)
end

function contravariant_metric_tensor_to_matrix(G::ContravariantMetricTensor)
        return ContravariantMetricTensor(spdiagm(0 => G.b11),
                                         spdiagm(0 => G.b12),
                                         spdiagm(0 => G.b21),
                                         spdiagm(0 => G.b22))
end

end


using DoubleFloats, GenericLinearAlgebra, LinearAlgebra
using Test

using Random
phi = Base.MathConstants.golden

include("matmul.jl")
include("specialvalues.jl")
include("arithmetic.jl")
include("functions.jl")

include("concrete_accuracy.jl")
include("function_accuracy.jl")

include("coverage.jl")
include("promotion.jl")

include("parse.jl")


function fractional_trophic_level(N::T) where {T<:UnipartiteNetwork}
  Y = nodiagonal(N)
  producers = keys(filter(spedeg -> spedeg.second == 0, degree(Y; dims=1)))
  sp = shortest_path(Y)
  prod_id = findall(isequal(0), vec(sum(sp; dims=2)))
  return Dict(zip(species(Y; dims=1), maximum(sp[:,prod_id]; dims=2).+1))
end

function trophic_level(N::T) where {T<:UnipartiteNetwork}
  TL = fractional_trophic_level(N)
  Y = nodiagonal(N)
  D = zeros(Float64, size(Y.A))
  ko = degree_out(Y)

  # inner loop to avoid dealing with primary producers
  for i in 1:richness(Y)
    if ko[species(Y)[i]] > 0.0
      D[i,:] = Y[i,:]./ko[species(Y)[i]]
    end
  end

  # return
  return Dict(zip(species(N), 1 .+ D * [TL[s] for s in species(Y)]))
end


using ShaleDrillingLikelihood: zero_out_small_probs!

import ShaleDrillingLikelihood: logprice, logrigrate

using Statistics: var, cov

export GridTransition

# ----------------------------
# Make Grid + transition
# ----------------------------

mysteprange(a,b,n) = range(a,b;length=n)

function PriceGrid(z, delta, len)
    x = first.(z)
    prng = range(minimum(x)-delta, maximum(x)+delta; length=len)
    sigsq = var(diff(x))
    println("Var(logprice) = $sigsq and sd(logprice) = $(sqrt(sigsq))")
    P = tauchen_1d(prng, identity, sigsq)
    return prng, P
end

function PriceCostGrid(z, delta, len)
    p = logprice.(z)
    c = logrigrate.(z)
    pc = hcat(p,c)

    # ranges
    mins = vec(minimum(pc; dims=1))
    maxs = vec(maximum(pc; dims=1))
    a = mins .- delta
    b = maxs .+ delta
    pcrng = mysteprange.(a, b, len)
    pcprod = product(pcrng...)

    # sigma
    dpc = diff(pc; dims=1)

    Sigma = cov(dpc)
    stdvs = sqrt.(diag(Sigma))
    sigcor = Sigma[2,1] / prod(stdvs)
    println("Sigma(logprice, logrigrate) = $Sigma.\nStdv(logprice, logrigrate) = $(round.(stdvs;digits=5)) and correlation = $(round(sigcor;digits=4))")

    P = tauchen_2d(pcprod, identity, Sigma)
    return pcrng, P
end

function YearGrid(z, args...)
    y = ShaleDrillingLikelihood.year.(z)
    miny, maxy = extrema(y)
    yrng = UnitRange(miny, maxy)
    nyears = length(yrng)
    nyearsm1 = nyears-1
    @assert nyearsm1 == maxy-miny

    l = zeros(nyearsm1)
    m = vcat(fill(3/4,nyearsm1), 1.0)
    r = fill(1/4, nyearsm1)

    P = Tridiagonal(l,m,r)
    return yrng, P
end

function GridTransition(rwrd::DrillReward{R,C}, z::Vector, delta::Number, len::Number; minp=minp_default()) where {
        R, C<:Union{DrillingCost_constant, DrillingCost_dgt1}
    }
    rng, P = PriceGrid(z,delta,len)
    zero_out_small_probs!(P, minp)
    return tuple(rng), sparse(P)
end

# # rigrate + price
# function GridTransition(rwrd::DrillReward{R,C}, z, delta, len; minp=minp_default()) where {
#         R, C<:Union{DrillingCost_constant, DrillingCost_dgt1}
#     }
#     rng, P = PriceCostGrid(z, delta, len)
#     zero_out_small_probs!(P, minp)
#     return rng, sparse(P)
# end

function GridTransition(rwrd::DrillReward{R,C}, z::Vector, delta::Number, len::Number; minp=minp_default()) where {
        R, C<:AbstractDrillingCost_TimeFE
    }
    p = tuple.(logprice.(z))
    prng, pP = PriceGrid(p, delta, len)
    yrng, yP = YearGrid(z)
    rng = (prng, yrng)
    P = kron(yP, pP)
    zero_out_small_probs!(P, minp)
    return rng, sparse(P)
end

function GridTransition(rwrd::DrillReward{R,C}, z::Vector, delta::Number, len::Number; minp=minp_default()) where {
        R, C<:Union{DrillingCost_TimeFE_rigrate, DrillingCost_TimeFE_rig_costdiffs}
    }
    pcrng, pcP = PriceCostGrid(z,delta,len)
    yrng, yP = YearGrid(z)
    rng = (pcrng..., yrng)
    P = kron(yP, pcP)
    zero_out_small_probs!(P, minp)
    return rng, sparse(P)
end

GridTransition(rwrd::DrillReward, etv::ExogTimeVars, args...; kwargs...) = GridTransition(rwrd, _timevars(etv), args...; kwargs...)
GridTransition(d::DataDrillPrimitive, args...; kwargs...) = GridTransition(reward(d), zchars(d), args...; kwargs...)
GridTransition(d::DataDrill,          args...; kwargs...) = zspace(_model(d)), ztransition(_model(d))


using Interpolations

f = [0 0 .1 .2  .25  .2 .25 .3 .35 .43 .45 .5 .55  .5 .4 .425 .45 .425 .4 .35 .3 .25 .225 .2 .1 0 0];
g = [0 0 .025 .1  .2  .175 .2 .25 .25 .3 .32 .35 .375  .325 .3 .275 .275 .25 .225 .225 .2 .175 .15 .15 .05 0 0].- 0.025;

x = 0.1 * (1:length(f));
xf = .01:.01:last(x)

ff = scale(interpolate(f, BSpline(Cubic(Line(OnGrid())))), x)


###########################################################
# Generate discretization matrices for FINITE DIFFERENCE
"""
    generate_fd_interior_mat

Generate the discretization matrices for the interior for Finite Difference.
"""
function generate_fd_interior_mat( nx, nz, hx, hz)
    ex = ones(nx)
    ez = ones(nz)

    # DISCRETIZATION OF THE SECOND DERIVATIVE
    Dxx = spdiagm(-1 => ex[1:end-1], 0 => -2*ex, 1 => ex[1:end-1])
    Dzz = spdiagm(-1 => ez[1:end-1], 0 => -2*ez, 1 => ez[1:end-1])
    #IMPOSE PERIODICITY IN Z-DIRECTION
    Dzz[1, end] = 1;
    Dzz[end, 1] = 1;

    Dxx = Dxx/(hx^2);
    Dzz = Dzz/(hz^2);

    # DISCRETIZATION OF THE FIRST DERIVATIVE
    Dz  = spdiagm(-1 => -ez[1:end-1], 1 => ez[1:end-1])

    #IMPOSE PERIODICITY
    Dz[1, end] = -1;
    Dz[end, 1] = 1;

    Dz = Dz/(2*hz);

    return Dxx, Dzz, Dz
end

"""
    generate_fd_boundary_mat

Generate the discretization matrices for the boundary for Finite Difference.
"""
function generate_fd_boundary_mat( nx, nz, hx, hz)
    # BUILD THE SECOND BLOCK C1
    e1 = spzeros(nx,1)
    e1[1] = 1
    en = spzeros(nx,1)
    en[end] = 1
    Iz = sparse(1.0I, nz, nz)
    C1 = [kron(e1,Iz) kron(en,Iz)]/(hx^2);

    # BUILD THE THIRD BLOCK C2^T
    d1 = 2/hx;
    d2 = -1/(2*hx);
    vm = spzeros(1,nx);
    vm[1] = d1;
    vm[2] = d2;
    vp = spzeros(1,nx);
    vp[end] = d1;
    vp[end-1] = d2;
    C2T = [kron(vm,Iz); kron(vp,Iz)];

    return C1, C2T
end

###########################################################
# Generate Wavenumber FINITE DIFFERENCE
"""
    generate_wavenumber_fd

Generate a wavenumber for Finite Difference.
"""
function generate_wavenumber_fd( nx::Integer, nz::Integer, wg::String, delta::Number)
    if wg == "TAUSCH"
        return generate_wavenumber_fd_tausch( nx, nz, delta)
    elseif wg == "JARLEBRING"
        return generate_wavenumber_fd_jarlebring( nx, nz, delta)
    end
    # Use early-bailout principle. If a supported waveguide is found, compute and return. Otherwise end up here and throw and error
    error("No wavenumber loaded: The given Waveguide '", wg ,"' is not supported in 'FD' discretization.")
end

"""
    generate_wavenumber_fd_tausch

Generate the wavenumber in FD discretization for the waveguide
described by TAUSCH.
"""
function generate_wavenumber_fd_tausch( nx::Integer, nz::Integer, delta::Number)
    xm = 0;
    xp = (2/pi) + 0.4;
    zm = 0;
    zp = 1;

    xm = xm - delta;
    xp = xp + delta;

    # Domain (First generate including the boundary)
    X = range(xm, stop = xp, length = nx+2)
    hx = step(X);
    X = collect(X);
    Z = range(zm, stop = zp, length = nz+1)
    hz = step(Z);
    Z = collect(Z);
    # Removing the boundary
    X = X[2:end-1];
    Z = Z[2:end];

    # The actual wavenumber
    k1 = sqrt(2.3)*pi;
    k2 = sqrt(3)*pi;
    k3 = pi;
    k = function(x,z)
            z_ones = ones(size(z)) #Placeholder of ones to expand x-vector
            k1*(x .<= 0) .* z_ones +
            k2*(x.>0) .* (x.<=2/pi) .* z_ones +
            k2*(x.>2/pi) .* (x.<=(2/pi+0.4)) .* (z.>0.5) +
            k3*(x.>2/pi) .* (z.<=0.5) .* (x.<=(2/pi+0.4)) +
            k3*(x.>(2/pi+0.4)) .* z_ones;
        end

    K = k(X', Z).^2;
    Km = k(-Inf, 1/2)[1];
    Kp = k(Inf, 1/2)[1];
    return K, hx, hz, Km, Kp
end

"""
    generate_wavenumber_fd_jarlebring

Generate the wavenumber in FD discretization for the waveguide
described by JARLEBRING.
"""
function generate_wavenumber_fd_jarlebring( nx::Integer, nz::Integer, delta::Number)
    xm = -1;
    xp = 1;
    zm = 0;
    zp = 1;

    xm = xm - delta;
    xp = xp + delta;

    # Domain (First generate including the boundary)
    X = range(xm, stop = xp, length = nx+2)
    hx = step(X);
    X = collect(X);
    Z = range(zm, stop = zp, length = nz+1)
    hz = step(Z);
    Z = collect(Z);
    # Removing the boundary
    X = X[2:end-1];
    Z = Z[2:end];

    # The actual wavenumber
    k1 = sqrt(2.3)*pi;
    k2 = 2*sqrt(3)*pi;
    k3 = 4*sqrt(3)*pi;
    k4 = pi;
    k = function(x,z)
            z_ones = ones(size(z)) #Placeholder of ones to expand x-vector
            x_ones = ones(size(x)) #Placeholder of ones to expand z-vector
            k1 *(x.<=-1) .* z_ones  +
            k4 *(x.>1) .* z_ones  +
            k4 *(x.>(-1+1.5)) .* (x.<=1) .* (z.<=0.4) +
            k3 *(x.>(-1+1)) .* (x.<=(-1+1.5)) .* z_ones +
            k3 *(x.>(-1+1.5)) .* (x.<=1) .* (z.>0.4) +
            k3 *(x.>-1) .* (x.<=(-1+1)) .* (z.>0.5) .* (z.*x_ones-(x.*z_ones)/2 .<= 1) +
            k2 *(x.>-1) .* (x.<=(-1+1)) .* (z.>0.5) .* (z.*x_ones-(x.*z_ones)/2 .> 1) +
            k3 *(x.>-1) .* (x.<=(-1+1)) .* (z.<=0.5) .* (z.*x_ones+(x.*z_ones)/2 .> 0) +
            k2 *(x.>-1) .* (x.<=(-1+1)) .* (z.<=0.5) .* (z.*x_ones+(x.*z_ones)/2 .<= 0);
        end

    K = k(X', Z).^2;
    Km = k(-Inf, 1/2)[1];
    Kp = k(Inf, 1/2)[1];
    return K, hx, hz, Km, Kp
end


"""
  coefs_table(mf,lo, up, betass, std_errors)

  This function takes a `ModelFrame` object `mf`, numeric arguments `lo`, `up`, `betass` estimates and `std_errors` (beta's
  standard errors, returns a table with model estimates, 95% CI, and p-values.
"""

coefs_table(mf, lo, up, betass, std_errors)

function coefs_table(; mf = throw(ArgumentError("mf is missing")),
   lo = throw(ArgumentError("lo is missing")),
   up = throw(ArgumentError("up is missing")),
   betass = throw(ArgumentError("betass is missing")),
   std_errors = throw(ArgumentError("std_errors is missing")))
   zz = betass ./ std_errors
   result = (StatsBase.CoefTable(hcat(round(betass,4),
              lo,
              up,
              round(std_errors,4),
              round(zz,4),
              2.0 * Distributions.ccdf(Distributions.Normal(),
              abs.(zz))),
              ["Estimate","2.5%","97.5%","Std.Error","t value", "Pr(>|t|)"],
              ["$i" for i = coefnames(mf)], 4))
  return(result)
end


### A Pluto.jl notebook ###
# v0.12.20

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 6710a438-60b3-11eb-3f4d-1f20bf10632b
begin
	import Pkg
	Pkg.activate("..")
	using Plots   # pour les graphiques
	using PlutoUI # pour la partie interactive
end

# ╔═╡ b8c02e5c-60ae-11eb-3406-71a8af749eb1
md"""
# Lois Continues
"""

# ╔═╡ e64dfd22-60ae-11eb-0a06-05c3bd1198ff
md"""
# Lois continues

Afin de faciliter la recherche et compréhension des concepts, je vais utiliser parfois des abbreviations en anglais. Je veux simplement présenter des différentes loix, enlister leurs paramètres et essayer de les expliquer tout d'abord pour confirmer ma propre compréhension de celles-ci et puis pour faciliter l'apprentissage de ceux qui me suivront.
"""

# ╔═╡ 8e20343c-657f-11eb-07c6-c90cd368c3c2
md"""
* poisson
* bernoulli / geometric ?
* exponential
* student
* F
* X^2
"""

# ╔═╡ 090dfb62-658e-11eb-1635-1d021fed9d2e
plotly()

# ╔═╡ e4886b96-60af-11eb-3446-3781f9e0a830
md"""
## Loi Gaussienne (Normale)

La loi des lois.

\begin{equation}
\mathcal{N}(\mu, \sigma) = f(x)
= \frac{1}{\sigma\sqrt{2\pi}} e^{-\frac{1}{2}(\frac{x - \mu}{\sigma})^2}
\end{equation}
"""

# ╔═╡ 87df4316-624f-11eb-3c51-4719053a20a8
function Normale(x, μ, σ)
	exp(-0.5 * ((x - μ)/σ)^2) / (σ*(2*π)^2)
end

# ╔═╡ dd4d901a-6582-11eb-2060-61d87809df7f
md"""
$(@bind go_normal Button("Reset"))
"""

# ╔═╡ eabbaf28-6250-11eb-086b-5bd8739b310d
let
	go_normal
	md"""
	μ $(@bind μ Slider(-10:0.1:10, default=0, show_value=true))

	σ $(@bind σ Slider(0.1:0.1:10, default=1, show_value=true))

	lower $(@bind n_lower NumberField(-3:0, default=(μ - 5*σ))) upper $(@bind n_upper
	NumberField(1:4, default=(μ + 5*σ)))
	"""
end

# ╔═╡ 948f84e0-6583-11eb-0a94-b7cff41c8a8d
x = n_lower:0.1:n_upper;

# ╔═╡ 6c8090ac-6583-11eb-1e5d-5fc9d3bcfe76
begin
	n_pdf = plot(x, Normale.(x, μ, σ), legend=false)
	title!("Densité de Probabilité")
end

# ╔═╡ 19afa73c-6588-11eb-2487-9da0cdeb4612
begin
	plot(x, cumsum(Normale.(x, μ, σ)), legend=false)
	title!("Fonction de répartition")
end

# ╔═╡ d09ee836-658d-11eb-082c-e79a6e76d453

# ╔═╡ Cell order:
# ╟─b8c02e5c-60ae-11eb-3406-71a8af749eb1
# ╟─e64dfd22-60ae-11eb-0a06-05c3bd1198ff
# ╠═8e20343c-657f-11eb-07c6-c90cd368c3c2
# ╠═6710a438-60b3-11eb-3f4d-1f20bf10632b
# ╠═090dfb62-658e-11eb-1635-1d021fed9d2e
# ╟─e4886b96-60af-11eb-3446-3781f9e0a830
# ╠═87df4316-624f-11eb-3c51-4719053a20a8
# ╟─6c8090ac-6583-11eb-1e5d-5fc9d3bcfe76
# ╟─948f84e0-6583-11eb-0a94-b7cff41c8a8d
# ╟─dd4d901a-6582-11eb-2060-61d87809df7f
# ╟─eabbaf28-6250-11eb-086b-5bd8739b310d
# ╟─19afa73c-6588-11eb-2487-9da0cdeb4612
# ╠═d09ee836-658d-11eb-082c-e79a6e76d453


using Clustering
using Test
using CodecZlib
using Distances

@testset "hclust() (hierarchical clustering)" begin

@testset "param checks" begin
    D = rand(5, 5)
    Dsym = D + D'
    Dnan = copy(Dsym)
    Dnan[1, 3] = Dnan[3, 1] = NaN
    @testset "hclust()" begin
        @test_throws ArgumentError hclust(Dsym, linkage=:typo)
        @test_throws ArgumentError hclust(D, linkage=:single)
        @test_throws ArgumentError hclust(Dnan, linkage=:single)
    end
    hclu = @inferred(hclust(Dsym, linkage=:single))
    @test hclu isa Clustering.Hclust
    @testset "cutree()" begin
        @test_throws ArgumentError cutree(hclu)
        @test_throws ArgumentError cutree(hclu, k=-1)
        @test_throws ArgumentError cutree(hclu, k=0)
        @test cutree(hclust(Dsym), k=10) isa Vector{Int}
        @test cutree(hclust(fill(0.0, 0, 0)), k=0) == Int[]
        @test cutree(hclust(fill(0.0, 0, 0)), k=1) == Int[]
    end
end

@testset "R hclust() generated examples" begin
# load the examples
hclu_examples_filename = joinpath(@__DIR__, "data", "hclust_generated_examples.jl.gz")
Base.include_string(@__MODULE__,
                    open(io -> read(io, String), GzipDecompressorStream, hclu_examples_filename),
                    hclu_examples_filename)

# test to make sure many random examples match R's implementation
@testset "example #$i (linkage=:$(example["linkage"]), n=$(size(example["D"], 1)))" for
        (i, example) in enumerate(examples)

    linkage = example["linkage"]
    hclu = @inferred(hclust(example["D"], linkage=linkage))
    @test hclu isa Clustering.Hclust
    @test Clustering.nnodes(hclu) == size(example["D"], 1)
    @test Clustering.nmerges(hclu) == Clustering.nnodes(hclu)-1
    @test Clustering.height(hclu) ≈ maximum(example["height"]) atol=1e-5
    @test hclu.merges == example["merge"]
    @test hclu.heights ≈ example["height"] atol=1e-5
    @test hclu.order == example["order"]

    # compare hclust_nn_lw() (the default) and hclust_nn() (slower) methods
    if linkage ∈ [:complete, :average]
        @testset "hclust_nn()" begin
            linkage_fun = linkage == :complete ? Clustering.slicemaximum : Clustering.slicemean
            hclu2 = Hclust(Clustering.orderbranches_r!(Clustering.hclust_nn(example["D"], linkage_fun)),
                linkage)
            @test hclu2.merges == hclu.merges
            @test hclu2.heights ≈ hclu.heights atol=1e-5
            @test hclu2.order == hclu.order
        end
    end

    local cut_k = example["cut_k"]
    local cut_h = example["cut_h"]
    if cut_h !== nothing
        # due to small arithmetic differences between R and Julia heights might be slightly different
        # find the matching height
        cut_h_r = cut_h
        cut_h_ix = findmin(abs.(hclu.heights .- cut_h_r))[2]
        cut_h = hclu.heights[cut_h_ix]
        @assert isapprox(cut_h, cut_h_r, atol=1e-6) "h=$cut_h ≈ h_R=$cut_h_r"
    end
    @testset "cutree(hclu, k=$(repr(cut_k)), h=$(repr(cut_h)))" begin
        cutt = @inferred(cutree(hclu, k=cut_k, h=cut_h))
        @test cutt isa Vector{Int}
        @test length(cutt) == Clustering.nnodes(hclu)
        @test cutt == example["cutree"]
    end
end
end

@testset "hclust_n3()" begin
    # no thorough testing (it's O(N³)), just test one example
    example_n3 = examples[10]
    hclu_n3 = @inferred(Clustering.orderbranches_r!(Clustering.hclust_n3(example_n3["D"], Clustering.slicemaximum)))
    @test hclu_n3.mleft == example_n3["merge"][:, 1]
    @test hclu_n3.mright == example_n3["merge"][:, 2]
    @test hclu_n3.heights ≈ example_n3["height"] atol=1e-5
end

local hclust_linkages = [:single, :average, :complete]

@testset "hclust(0×0 matrix, linkage=$linkage)" for linkage in hclust_linkages
    hclu = hclust(fill(0.0, 0, 0), linkage=linkage)
    @test Clustering.nnodes(hclu) == 0
    @test Clustering.nmerges(hclu) == 0
    @test Clustering.height(hclu) == -Inf
    cut1 = @inferred(cutree(hclu, h=1))
    @test cut1 == Int[]
end

@testset "hclust([$dist] 1×1 matrix, linkage=$linkage)" for
    linkage in hclust_linkages, dist in [-Inf, 0, 1, 2, Inf]
    hclu = hclust(fill(dist, 1, 1), linkage=linkage)
    @test Clustering.nnodes(hclu) == 1
    @test Clustering.nmerges(hclu) == 0
    @test Clustering.height(hclu) == -Inf
    cut1 = @inferred(cutree(hclu, h=1))
    @test cut1 == [1]
end

@testset "hclust(linkage=$linkage) when data contains an isolated point (#109)" for linkage in hclust_linkages
    # point #2 is isolated: distances to all the other points are Inf
    mdist = [
        0.0 Inf 0.1  Inf  Inf  Inf  Inf  Inf  Inf  Inf;
        Inf 0.0 Inf  Inf  Inf  Inf  Inf  Inf  Inf  Inf;
        0.1 Inf 0.0  0.11 Inf  Inf  Inf  Inf  Inf  Inf;
        Inf Inf 0.11 0.0  0.86 0.86 Inf  Inf  Inf  Inf;
        Inf Inf Inf  0.86 0.0  0.67 0.72 1.93 Inf  Inf;
        Inf Inf Inf  0.86 0.67 0.0  0.72 Inf  Inf  Inf;
        Inf Inf Inf  Inf  0.72 0.72 0.0  Inf  0.72 Inf;
        Inf Inf Inf  Inf  1.93 Inf  Inf  0.0  3.91 Inf;
        Inf Inf Inf  Inf  Inf  Inf  0.72 3.91 0.0  0.52;
        Inf Inf Inf  Inf  Inf  Inf  Inf  Inf  0.52 0.0]

    hclu = hclust(mdist, linkage=linkage)
    @test Clustering.nnodes(hclu) == 10
    @test Clustering.nmerges(hclu) == 9
    @test Clustering.height(hclu) == Inf
    cut1 = @inferred(cutree(hclu, h=1))
    @test cut1 isa Vector{Int}
    @test length(cut1) == Clustering.nnodes(hclu)
    if linkage == :single
        @test cut1 == [1, 2, 1, 1, 1, 1, 1, 3, 1, 1]
    end
end

@testset "cutree(hclust, h=h) when the height of all subtrees greater than h (#141)" begin
    A = [0.0 0.7; 0.7 0.0]
    hA = hclust(A, linkage=:average)
    @test cutree(hA, h=0.5) == [1, 2]
    @test cutree(hA, h=0.7) == [1, 1]
    @test cutree(hA, h=0.9) == [1, 1]
end

@testset "Leaf ordering methods" begin
    n = 10
    mat = zeros(Int, n, n)

    for i in 1:n
        last = minimum([i+Int(floor(n/5)), n])
        for j in i:last
            mat[i,j] = 1
        end
    end

    dm = pairwise(Euclidean(), mat, dims=2)

    hcl_r = hclust(dm, linkage=:average)
    hcl_barjoseph = hclust(dm, linkage=:average, branchorder=:barjoseph)
    hcl_optimal = hclust(dm, linkage=:average, branchorder=:optimal)

    @test hcl_r.order == [3, 1, 2, 4, 5, 9, 10, 6, 7, 8]
    @test hcl_r.merges == [-1 -2; -3 1; -4 -5; -9 -10; -7 -8; -6 5; 2 3; 4 6; 7 8]

    @test hcl_barjoseph.order == collect(1:10)
    @test hcl_barjoseph.merges == [-1 -2; 1 -3; -4 -5; -9 -10; -7 -8; -6 5; 2 3; 6 4; 7 8]

    @test hcl_barjoseph.merges == hcl_optimal.merges
    @test hcl_barjoseph.order == hcl_optimal.order

    @test_throws ArgumentError hclust(dm, linkage=:average, branchorder=:wrong)

    hcl_zero = hclust(fill(0.0, 0, 0), linkage=:average, branchorder=:barjoseph)
    @test Clustering.nnodes(hcl_zero) == 0

    hcl_one = hclust(fill(0.0, 1, 1), linkage=:average, branchorder=:barjoseph)
    @test Clustering.nnodes(hcl_one) == 1

    # Larger matrix to make sure all swaps are tested
    Random.seed!(1)
    D = rand(50,50)
    Dm = D + D'
    hcl_rand = hclust(Dm, linkage=:average, branchorder=:optimal)

    @test hcl_rand.merges == [-29 -1; -32 -24; -46 -44; -10 -41; -17 -12; -40 5;
                              -8 -28; -13 -35; -19 -20; -43 -42; -34 -18; -15 -39;
                              -30 -49; -26 -22; -36 -31; -38 -4; -5 -9; 1 16; -6 13;
                              17 10; -33 -45; -7 -2; -23 -21; -48 -27; -37 -16; -14 8;
                              2 -50; 19 20; -47 -11; 9 -3; 6 15; 14 22; 31 18; 23 25;
                              11 3; 32 29; 33 30; 7 12; 35 38; -25 4; 21 28; 26 34;
                              41 27; 24 40; 39 42; 37 43; 45 46; 47 36; 48 44]

    @test hcl_rand.order == [34, 18, 46, 44, 8, 28, 15, 39, 14, 13, 35, 23, 21, 37,
                             16, 40, 17, 12, 36, 31, 29, 1, 38, 4, 19, 20, 3, 33, 45,
                             6, 30, 49, 5, 9, 43, 42, 32, 24, 50, 26, 22, 7, 2, 47,
                             11, 48, 27, 25, 10, 41]

    @test Clustering.nnodes(hcl_rand) == 50
end

end # testset "hclust()"


export chebyshev, chebyshev!

chebyshev(A, b, λmin::Real, λmax::Real, Pr = 1, n = size(A,2);
          tol::Real = sqrt(eps(typeof(real(b[1])))), maxiter::Int = n^3) =
    chebyshev!(zerox(A, b), A, b, λmin, λmax, Pr, n; tol=tol,maxiter=maxiter)

function chebyshev!(x, A, b, λmin::Real, λmax::Real, Pr = 1, n = size(A,2);
	tol::Real = sqrt(eps(typeof(real(b[1])))), maxiter::Int = n^3)

	K = KrylovSubspace(A, n, 1, Adivtype(A, b))
	init!(K, x)
	chebyshev!(x, K, b, λmin, λmax, Pr; tol = tol, maxiter = maxiter)
end

function chebyshev!(x, K::KrylovSubspace, b, λmin::Real, λmax::Real, Pr = 1;
	tol::Real = sqrt(eps(typeof(real(b[1])))), maxiter::Int = K.n^3)

	local α, p
	K.order = 1
    tol = tol*norm(b)
	r = b - nextvec(K)
	d::eltype(b) = (λmax + λmin)/2
	c::eltype(b) = (λmax - λmin)/2
	resnorms = zeros(typeof(real(b[1])), maxiter)
	for iter = 1:maxiter
		z = Pr\r
		if iter == 1
			p = z
			α = 2/d
		else
			β = (c*α/2)^2
			α = 1/(d - β)
			p = z + β*p
		end
		append!(K, p)
		update!(x, α, p)
		r -= α*nextvec(K)
		#Check convergence
		resnorms[iter] = norm(r)
		if resnorms[iter] < tol
			resnorms = resnorms[1:iter]
			break
		end
	end
	x, ConvergenceHistory(resnorms[end] < tol, tol, K.mvps, resnorms)
end


# ---
# title: 1232. Check If It Is a Straight Line
# id: problem1232
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array, Math, Geometry
# link: <https://leetcode.com/problems/check-if-it-is-a-straight-line/description/>
# hidden: true
# ---
#
# You are given an array `coordinates`, `coordinates[i] = [x, y]`, where `[x,
# y]` represents the coordinate of a point. Check if these points make a
# straight line in the XY plane.
#
#
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2019/10/15/untitled-diagram-2.jpg)
#
#
#
#     Input: coordinates = [[1,2],[2,3],[3,4],[4,5],[5,6],[6,7]]
#     Output: true
#
#
# **Example 2:**
#
# **![](https://assets.leetcode.com/uploads/2019/10/09/untitled-diagram-1.jpg)**
#
#
#
#     Input: coordinates = [[1,1],[2,2],[3,4],[4,5],[5,6],[7,7]]
#     Output: false
#
#
#
#
# **Constraints:**
#
#   * `2 <= coordinates.length <= 1000`
#   * `coordinates[i].length == 2`
#   * `-10^4 <= coordinates[i][0], coordinates[i][1] <= 10^4`
#   * `coordinates` contains no duplicate point.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


using Metrics

@testset "Ranking and Statistical" begin

    y_rec = [0.85, 0.44, 0.71, 0.23, 0.90, 0.63, 0.16, 0.27]
    y_rel = [1, 0, 0, 1, 1, 1, 0, 0]
    output_rank_stats = Dict("precision_k" => 0.5, "recall_k" => 0.75, "f1_k" => 0.6)

    @testset "rank_stats_k" begin
        rank_out = ranking_stats_k(y_rec, y_rel; k=6)
        for i in keys(rank_out)
            @test rank_out[i] == output_rank_stats[i]
        end
    end

    @test avg_precision(y_rec, y_rel; k=6) == 0.6875
end


#This file will be turned in more flexible algebra routines

##header
import SpecialFunctions

## Polynomial type
"""
Polynomial type
"""
struct Polynomial #TODO: make type parametric use eltype(f)
 coeffs
end

#make Polynomial callable with Horner-scheme evaluation
function (p::Polynomial)(z,k::Int=0)
    p=derive(p,k)
    f=0.0+0.0im #typing
    for i = length(p.coeffs):-1:1
       f*=z
       f+=p.coeffs[i]
    end
    return f
end

import Base.show
import Base.string

function string(p::Polynomial)
    N=length(p.coeffs)
    if N==0
        txt="0"
    else
        txt="$(p.coeffs[1])"
        for n=2:N
            coeff=string(p.coeffs[n])
            if occursin("+",coeff) || occursin("-",coeff)
                txt*="+($coeff)*z^$(n-1)"
            else
                txt*="+$coeff*z^$(n-1)"
            end
        end
    end
    return txt
end

function show(io::IO,p::Polynomial)
    print(io,string(p))
end

import Base.+
import Base.-
import Base.*
import Base.^

function +(a::Polynomial,b::Polynomial)
    if length(a.coeffs)>length(b.coeffs)
        a,b=b,a
    end
    c=copy(b.coeffs)
    for (idx,val) in enumerate(a.coeffs)
        c[idx]+=val
    end
    return Polynomial(c)
end

function -(a::Polynomial,b::Polynomial)
    return +(a,-b)
end

function *(a::Polynomial,b::Polynomial)
    I=length(a.coeffs)
    J=length(b.coeffs)
    K=I+J-1
    c=zeros(ComplexF64,K) #TODO: sanity check for type of b
    idx=1
    for k =1:K
        for i =1:k
            #TODO: figure out when to break the loop
            j=k-i+1
            if i>I ||j>J
                continue
            end
            c[k]+=a.coeffs[i]*b.coeffs[j]
        end
    end
    return Polynomial(c)
end

#scalar multiplication
function *(a::Polynomial,b::Number)
    return a*Polynomial([b])
end
function *(a::Number,b::Polynomial)
    return Polynomial([a])*b
end

function ^(p::Polynomial, k::Int)
    b=Polynomial(copy(p.coeffs))
    for i=1:k-1
        b*=p
    end
    return b
end

function prodrange(start,stop)
    return SpecialFunctions.factorial(stop)/SpecialFunctions.factorial(start)
end

"""
    b=derive(a::Polynomial,k::Int=1)

Compute `k`th derivative of Polynomial `a`.
"""
function derive(a::Polynomial,k::Int=1)
    N=length(a.coeffs)
    if k>=N
        return Polynomial([]) #TODO: type
    end
    d=zeros(typeof(a.coeffs[1]),N-k)
    for i=1:N-k
        d[i]=a.coeffs[i+k]*prodrange(i-1,i+k-1)
    end
    return Polynomial(d)
end

"""
    g=shift(f,Δz)

Shift Polynomial with respect to its argument. (Taylor shift)
"""
function shift(f::Polynomial,Δz)
    g=Array{eltype(f)}(undef,length(f.coeffs))
    for n=0:length(f.coeffs)-1
        g[n+1]=f(Δz)/SpecialFunctions.factorial(n*1.0)
        f=Polynomial(f.coeffs[2:end])
        f.coeffs.*=1:length(f.coeffs)
    end
    return Polynomial(g)
end

function scale(p::Polynomial,a::Number)
        p=p.coeffs
        s=1
        for idx =1: length(p)
            p[idx]*=s
            s*=a
        end
        return Polynomial(p)
end

#TODO: write macro nto create !-versions

## tests
a=Polynomial([0.0,1.0])
b=a*a
c=derive(a)
d=a+b+c
e=d^3
f=shift(e,2)
e(2)-f(0)
## rational Polynomial
struct rational_Polynomial
    num::Polynomial
    den::Polynomial
end

function derive(a::rational_Polynomial)
    return rational_Polynomial(derive(a.num)*a.den-a.num*derive(a.den),a.den^2)
end

"""
    derive(a::Any, k:Int)

Take the `k`th derivative of `a` by calling the derive function multiple times.

# Notes
This is a fallback implementation.
"""
function derive(a::Any,k::Int)
    for i=1:k
        a=derive(a)
    end
    return a
end
##
function ndd(vararg...)
    #parse input TODO: sanity check that z is unique
    #get number of points
    #(confluent points are counted multiple times)
    N=0
    for i = 1:length(vararg)÷2
        N+=length(vararg[2*i])
    end
    #initialize lists
    z=Array{ComplexF64}(undef,N) #TODO: proper type rules
    coeffs=Array{ComplexF64}(undef,N)
    cnfl=Array{Int64}(undef,N) # confluence counter array
    n=0 #total counter
    for i = 1:length(vararg)÷2
        z[1+n:n+length(vararg[2*i])] .= vararg[2*i-1]
        coeffs[1+n:n+length(vararg[2*i])]  = vararg[2*i]
        cnfl[1+n:n+length(vararg[2*i])] = 0:length(vararg[2*i])-1
        n+=length(vararg[2*i])
    end

    #pyramid scheme for computation of divided-difference scheme
    println("###########")
    println(cnfl)
    Z=copy(z)
    for i=1:N-1
        println(coeffs)
        for j=N:-1:(i+1)
            Δz=z[j]-z[j-i]
            if Δz!=0 # this is a non-confluent point
               coeffs[j]=(coeffs[j]-coeffs[j-1-cnfl[j-1]])/Δz
            end
            if cnfl[j-1]!=0
                cnfl[j-1]-=1
            end
        end
    end
    println(coeffs)

    # construct newton polynomial from divided difference coefficients
    basis=Polynomial([1])
    p=Polynomial([0])
    for i=1:N
        p+=basis*coeffs[i]
        basis*=Polynomial([-z[i],1])
    end

    return p
end

##
m0=Polynomial([1])
m1=Polynomial([-1,1])
m2=Polynomial([-2,1])
m3=Polynomial([-3,1])

p=3*m0+(-1)*m1+2.5*m1*m2
##
p=ndd(1,[3,],2,[2,],3,[6,])
p.([1,2,3])
##
p=ndd(2,[6,3,5])
p(2,3)
##
p=ndd(1,[1,2],2,[4])
p.([1,2],0)
##
vals=(1,[1,2,3],2,[4,5,0,7,8,9],3,[60,1])
p=ndd(vals...)
##
println("#########Here##########")
for i=1:length(vals)÷2
    z=vals[2*i-1]
    for (k,coeff) in enumerate(vals[2*i])
        k=k-1
        test=p(z,k)/factorial(k)-coeff==0
        if !test
            println("z:$z k:$k coeff:$coeff")
        end
    end
end

## TODO next: Find reference for this algorithm.
function newton_divided_difffunction(vararg...)
    #hässliches steigungsschema...
    coeffs=Dict()
    z=Array{ComplexF64}(undef,length(vararg)÷2)
    comb=[]
    #initialize divided difference scheme
    #use taylor coefficient at confluent points
    for (idx,n) in enumerate(1:2:length(vararg))
        z[idx]=vararg[n]
        taylor_coeffs=vararg[n+1]
        loc_tab=repeat([idx],length(taylor_coeffs))
        push!(comb,loc_tab...)
        for (i,j) in enumerate(1:length(loc_tab)) #TODO: enumeration and i are not required
            coeffs[loc_tab[1:j]]=taylor_coeffs[j]
        end
    end

    #fill missing values in the newton divided difference table
    for n =1:length(comb)
        for j =1:length(comb)-n
            idx=comb[j:j+n]
            if idx ∉ keys(coeffs)
                a=coeffs[idx[1:end-1]]
                b=coeffs[idx[2:end]]
                z_a=z[idx[1]]
                z_b=z[idx[end]]
                coeffs[idx]=(b-a)/(z_b-z_a)
            end
        end
    end

    #construct newton_Polynomial
    basis_function=Polynomial([1])
    idx=comb[1:1]

    c=Polynomial(coeffs[idx])
    p=basis_function*c
    for n=2:length(comb)
        idx=comb[1:n]
        c=Polynomial(coeffs[idx])
        basis_function=basis_function*Polynomial(-z[idx[end-1]],1]
        p=p+ᴾc*ᴾbasis_function
    end
    basis_function=basis_function*ᴾ[-z[comb[end]],1]

    #Kronecker's Algorithm to find the interpolating Pade approximant
    #initialize Kronecker's algorithm
    # (see Chapter 7.1 (page 341) in G. A. Baker and P. Graves-Morris.
    # Padé Approximants, 2nd Edition)
    p0=basis_function
    q0=[]
    q=[1]
    pade_table=[]
    pade_table=push!(pade_table,(p,q))

    while length(p)>1
        K=2 #system size

        A=zeros(ComplexF64,K,K)
        y=zeros(ComplexF64,K)
        for k=1:K
            y[k]=p0[end-k+1]
            for i=1:k
                    A[k,i]=p[end-k+i]
            end
        end
        x=A\y
        #TODO: sanity check!
        reverse!(x)
        p,p0=x*ᴾp-ᴾp0,p
        q,q0=x*ᴾq-ᴾq0,q
        p=p[1:end-K]
        push!(pade_table,(p,q))
    end

    return pade_table

end

## helper functions to directly use arrays
function *ᴾ(a,b)
    I=length(a)
    J=length(b)
    K=I+J-1
    c=zeros(ComplexF64,K)
    idx=1
    for k =1:K
        for i =1:k
            #TODO: figure out when to break the loop
            j=k-i+1
            if i>I ||j>J
                continue
            end
            c[k]+=a[i]*b[j]
        end
    end
    return c
end

function +ᴾ(a,b)
    if length(a)>length(b)
        a,b=b,a
    end
    for (idx,val) in enumerate(a)
        b[idx]+=val
    end
    return b
end

function -ᴾ(a,b)
    return +ᴾ(a,-b)
end

function derive(a)
    N=length(a)
    d=zeros(ComplexF64,N-1)
    for i=1:N-1
        d[i]=a[i+1]*i
    end
    return d
end
##
function compute_newton_polynomial(vararg...)
    #hässliches steigungsschema...
    coeffs=Dict()
    z=Array{ComplexF64}(undef,length(vararg)÷2)
    comb=[]
    #initialize divided difference scheme
    #use taylor coefficient at confluent points
    for (idx,n) in enumerate(1:2:length(vararg))
        z[idx]=vararg[n]
        taylor_coeffs=vararg[n+1]
        loc_tab=repeat([idx],length(taylor_coeffs))
        push!(comb,loc_tab...)
        for (i,j) in enumerate(1:length(loc_tab))
            coeffs[loc_tab[1:j]]=taylor_coeffs[j]
        end
    end

    #fill missing values in the newton divided difference table
    for n =1:length(comb)
        for j =1:length(comb)-n
            idx=comb[j:j+n]
            if idx ∉ keys(coeffs)
                a=coeffs[idx[1:end-1]]
                b=coeffs[idx[2:end]]
                z_a=z[idx[1]]
                z_b=z[idx[end]]
                coeffs[idx]=(b-a)/(z_b-z_a)
            end
        end
    end

    #construct newton_polynomial
    basis_function=[1]
    idx=comb[1:1]

    c=[coeffs[idx]]
    p=basis_function*ᴾc
    for n=2:length(comb)
        idx=comb[1:n]
        c=[coeffs[idx]]
        basis_function=basis_function*ᴾ[-z[idx[end-1]],1]
        p=p+ᴾc*ᴾbasis_function
    end
    basis_function=basis_function*ᴾ[-z[comb[end]],1]

    #Kronecker's Algorithm to find the interpolating pade approximant
    #initialize Kronecker's algorithm
    p0=basis_function
    q0=[]
    q=[1]
    pade_table=[]
    pade_table=push!(pade_table,(p,q))

    while length(p)>1
        K=2 #system size

        A=zeros(ComplexF64,K,K)
        y=zeros(ComplexF64,K)
        for k=1:K
            y[k]=p0[end-k+1]
            for i=1:k
                    A[k,i]=p[end-k+i]
            end
        end
        x=A\y
        #TODO: sanity check!
        reverse!(x)
        p,p0=x*ᴾp-ᴾp0,p
        q,q0=x*ᴾq-ᴾq0,q
        println("#####")
        println(p[end-K+1:end])
        p=p[1:end-K]
        push!(pade_table,(p,q))
    end

    return pade_table

end

##
polyval(p,z)= sum(p.*(z.^(0:length(p)-1)))

function taylor_shift(f,Δz)
    g=Array{eltype(f)}(undef,length(f))
    for n=0:length(f)-1
        g[n+1]=polyval(f,Δz)/SpecialFunctions.factorial(n*1.0)
        f=f[2:end]
        f.*=1:length(f)
    end
    return g
end

#Test
f=[-42.168, 2im, π, 1, 0.05]
z= π*1im-5.923779
g=taylor_shift(f,z)
polyval(f,z)-polyval(g,0)

#TODO implement Kronecker's algorithm
function multi_point_pade(L,M,vararg...;Z0=0)
    #TODO sanity checks length vararg should be divisibl #and the sum of the entries should amound to L+M

    #Build linear system

end

function newton_polynomial(vararg...)
    N=0
    for idx =1:2:length(vararg)
        N+=length(vararg[idx+1])
    end
    A=zeros(ComplexF64,N,N)
    y=zeros(ComplexF64,N)
    eqidx=1
    for idx =1:2:length(vararg)
        coeffs=ones(ComplexF64,N)
        z0=vararg[idx]
        z=[z0^i for i in 0:N-1]
        k=1
        fac=1
        for val in vararg[idx+1]
            y[eqidx]=val*fac
            A[eqidx,:]=coeffs.*z
            coeffs[k]=0
            z[k]=0
            for (i,j) in enumerate(k+1:N)
                coeffs[j]*=i
                z[j]=z0^(i-1)
            end
            fac*=k
            k+=1
            eqidx+=1
        end

    end

    x=A\y
    return x

end

function newton_divided_difffunction(vararg...)
    #hässliches steigungsschema...
    coeffs=Dict()
    z=Array{ComplexF64}(undef,length(vararg)÷2)
    comb=[]
    #initialize divided difference scheme
    #use taylor coefficient at confluent points
    for (idx,n) in enumerate(1:2:length(vararg))
        z[idx]=vararg[n]
        taylor_coeffs=vararg[n+1]
        loc_tab=repeat([idx],length(taylor_coeffs))
        push!(comb,loc_tab...)
        for (i,j) in enumerate(1:length(loc_tab))
            coeffs[loc_tab[1:j]]=taylor_coeffs[j]
        end
    end

    #fill missing values in the newton divided diffrence table
    for n =1:length(comb)
        for j =1:length(comb)-n
            idx=comb[j:j+n]
            if idx ∉ keys(coeffs)
                a=coeffs[idx[1:end-1]]
                b=coeffs[idx[2:end]]
                z_a=z[idx[1]]
                z_b=z[idx[end]]
                coeffs[idx]=(b-a)/(z_b-z_a)
            end
        end
    end

    #construct newton_polynomial
    basis_function=[1]
    idx=comb[1:1]

    c=[coeffs[idx]]
    p=basis_function*ᴾc
    for n=2:length(comb)
        idx=comb[1:n]
        c=[coeffs[idx]]
        basis_function=basis_function*ᴾ[-z[idx[end-1]],1]
        p=p+ᴾc*ᴾbasis_function
    end
    basis_function=basis_function*ᴾ[-z[comb[end]],1]

    #Kronecker's Algorithm to find the interpolating pade approximant
    #initialize Kronecker's algorithm
    p0=basis_function
    q0=[]
    q=[1]
    pade_table=[]
    pade_table=push!(pade_table,(p,q))

    while length(p)>1
        K=2 #system size

        A=zeros(ComplexF64,K,K)
        y=zeros(ComplexF64,K)
        for k=1:K
            y[k]=p0[end-k+1]
            for i=1:k
                    A[k,i]=p[end-k+i]
            end
        end
        x=A\y
        #TODO: sanity check!
        reverse!(x)
        p,p0=x*ᴾp-ᴾp0,p
        q,q0=x*ᴾq-ᴾq0,q
        println("#####")
        println(p[end-K+1:end])
        p=p[1:end-K]
        push!(pade_table,(p,q))
    end

    return pade_table

end

pade_tab=newton_divided_difffunction(1,[1],2,[pi,3,-1],0,[1,2,5,1])
p,q=pade_tab[3]
test=polyval(p,2)/polyval(q,2)
##
q=newton_polynomial(1,[1],2,[0,3,-1],0,[1,2,5,1])

#TODO: write testing scheme


"""
```julia
vstd(A; dims=:, mean=nothing, corrected=true, multithreaded=:auto)
```
Compute the variance of all elements in `A`, optionally over dimensions specified by `dims`.
As `Statistics.var`, but vectorized and (optionally) multithreaded.

A precomputed `mean` may optionally be provided, which results in a somewhat faster
calculation. If `corrected` is `true`, then _Bessel's correction_ is applied, such
that the sum is divided by `n-1` rather than `n`.

## Examples
```julia
julia> using VectorizedStatistics

julia> A = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> vstd(A, dims=1)
1×2 Matrix{Float64}:
 1.41421  1.41421

julia> vstd(A, dims=2)
2×1 Matrix{Float64}:
 0.7071067811865476
 0.7071067811865476
```
"""
function vstd(A; dims=:, mean=nothing, corrected=true, multithreaded=:auto)
    if (multithreaded===:auto && length(A) > 4095) || multithreaded===true
        _vtstd(mean, corrected, A, dims)
    else
        _vstd(mean, corrected, A, dims)
    end
end
export vstd

sqrt!(x::Number) = sqrt(x)
function sqrt!(A::AbstractArray)
    @turbo for i ∈ eachindex(A)
        A[i] = sqrt(A[i])
    end
    return A
end

_vstd(mean, corrected, A, dims) = sqrt!(_vvar(mean, corrected, A, dims))

sqrtt!(x::Number) = sqrt(x)
function sqrtt!(A::AbstractArray)
    @tturbo for i ∈ eachindex(A)
        A[i] = sqrt(A[i])
    end
    return A
end

_vtstd(mean, corrected, A, dims) = sqrtt!(_vtvar(mean, corrected, A, dims))


"""
Represents the standard simplex: `∑x_i ≤ radius, x_i ≥ 0`.
"""
struct StandardSimplex{T} <: MOI.AbstractVectorSet
    dimension::Int
    radius::T
end

"""
Represents the probability simplex: `∑x_i == radius, x_i ≥ 0`.
"""
struct ProbabilitySimplex{T} <: MOI.AbstractVectorSet
    dimension::Int
    radius::T
end

MOI.constant(s::Union{StandardSimplex, ProbabilitySimplex}) = s.radius
MOI.dimension(s::Union{StandardSimplex, ProbabilitySimplex}) = s.dimension

"""
    NormBallFromCone{T, S}

Represents a norm ball built from the corresponding cone with a fixed radius.
"""
struct NormBallFromCone{T, S <: MOI.AbstractSet} <: MOI.AbstractVectorSet
    radius::Int
    cone::S
end

const NormOneBall{T} = NormBallFromCone{T, MOI.NormOneCone}
const NormInfinityBall{T} = NormBallFromCone{T, MOI.NormInfinityCone}
const NormTwoBall{T} = NormBallFromCone{T, MOI.SecondOrderCone}
const NormNuclearBall{T} = NormBallFromCone{T, MOI.NormNuclearCone}

MOI.dimension(s::NormBallFromCone) = MOI.dimension(s) - 1
MOI.constant(s::NormBallFromCone) = s.radius


using PyPlot

# Herein we compare the approximate kernel with the exact kernel.

ntime = 100
time = collect(linspace(0,10,ntime))

kex = zeros(ntime,ntime)
kap = zeros(ntime,ntime)
beta = 3.0
w = 1.e-9
#nc = 4
#amp = [2.953823582534888+1.5e-3,-0.04961614737314587,-1.6900719772140012,-0.21413545794774125]
#b = [1.589510614489265,3.4729345651560872,1.8698492189074047,6.363451205766626]
#omega = [0.3329119766576696,4.5999414581901386,1.7115890219170917,2.205939216106191]
#nc = 3
#amp = [2.7518074963750734,-0.3546256365636662,-1.3968344706386124]
#b = [1.5312499407621147,4.753504292338284,1.7967431129812566]
#omega = [0.34148888178746056,2.042571639590954,1.7641684073510928]

nc = 4
#amp = [2.953823582534889, -0.04961614737314587, -1.6900719772140012, -0.21413545794774125]
#b =[1.589510614489265, 3.4729345651560872, 1.8698492189074047, 6.363451205766626]
#omega =[0.3329119766576696, 4.5999414581901386,1.7115890219170917, 2.205939216106191]

amp = [-147.6965573, +85.8875034,-4.6540017,+67.4630744]
b =[1.0097610, 1.0621734,0.8353745, 0.9160394]
omega = [0.,0.,0.,0.]

# Matern 3/2 kernel:
#nc = 2
#amp = [-99.0,100.0]
#b = [1.0,0.99]
#omega = [0.0,0.0]
for i=1:ntime
  for j=1:ntime
    z= abs(time[i]-time[j])*beta
#    kex[i,j]=(1.0+z)*exp(-z)
#    kex[i,j]=exp(-z^2/2.0)
    kex[i,j]=(1+z+z^2/3.)*exp(-z)
#    kap[i,j]=2.670143*exp(-1.500258*z)*cos(0.346731*z)-
#             0.458705*exp(-4.125588*z)*cos(1.390912*z)-
#             1.211438*exp(-1.744602*z)*cos(1.798436*z)
     for k=1:nc
       kap[i,j] +=amp[k]*exp(-b[k]*z)*cos(omega[k]*z)
     end
  end
  kex[i,i]+=w
  kap[i,i]+=w
end
println("Determinant: ",det(kex))
eigex,eigvec = eig(kex)
println("Minimum eigenvalue: ",minimum(eigex))
eigap,eigvec = eig(kap)
println("Minimum eigenvalue: ",minimum(eigap))
# Cholesky decomposition (square root) of kernels:
sqrt_kex = chol(kex)
sqrt_kap = chol(kap)

# Random normal deviates:
y=randn(ntime)
# Compute correlated noise component:
corrnoise_ex=*(transpose(sqrt_kex),y);
corrnoise_ap=*(transpose(sqrt_kap),y);

plot(time,corrnoise_ex)
plot(time,corrnoise_ap,"r.")


"""
    EMAFn(x,m=1)

Equally weighted moving average (over the current and m-1 lagged values) for a matrix `x`.

"""
function EMAFn(x,m=1)

    (T,n) = (size(x,1),size(x,2))
    q = m - 1                               #m=2, q=1 (avg over [t-1,t])

    (q < 0) && error("q must be >= 0")

    y = fill(NaN,T,n)                       #y[t] = (x[t-1] + x[t])/2
    for t = 1:T
        vv     = max(1,t-q):t                 #with q=2; 1:1,1:2,1:3,2:4
        y[t,:] = sum(x[vv,:],dims=1)/m
    end

    return y

end