function check_model_form(m)

  if typeof(m) <:Blanchard_Kahn_Form
    assessment = check_blanchard_kahn_form(m)
  elseif typeof(m) <:Klein_Form
    assessment = check_klein_form(m)
  elseif typeof(m) <:Structural_Form
    assessment = check_structural_form(m)
  elseif typeof(m) <:Sims_Form
    assessment = check_sims_form(m)
  elseif typeof(m) <:Gomme_Klein_Form
    assessment = check_gomme_klein_form(m)
  elseif typeof(m) <:Lombardo_Sutherland_Form
    assessment = check_lombardo_sutherland_form(m)
  end

  return assessment

end

function check_blanchard_kahn_form(m::Blanchard_Kahn_Form)

  assessment = "Pass"

  if m.nx < 1
    assessment = "Fail: Too few predetermined variables"
  elseif m.ny < 1
    assessment = "Fail: Too few non-predetermined variables"
  elseif m.nx+m.ny != size(m.a,1)
    assessment = "Fail: A is not conformable with nx and ny"
  elseif size(m.a,1) != size(m.a,2)
    assessment = "Fail: A is not square"
  elseif size(m.a,1) != size(m.c,1)
    assessment = "Fail: A and C must have the same number of rows"
  elseif size(m.c,2) > m.nx
    assessment = "Fail: There are more innovations than predetermined variables"
    elseif size(m.sigma,1) != size(m.sigma,2)
    assessment = "Fail: SIGMA must be square"
  elseif size(m.c,2) != size(m.sigma,1)
    assessment = "Fail: C and SIGMA are not conformable"
  end

  return assessment

end

function check_klein_form(m::Klein_Form)

  assessment = "Pass"

  if m.nx < 1
    assessment = "Fail: Too few predetermined variables"
  elseif m.ny < 1
    assessment = "Fail: Too few non-predetermined variables"
  elseif m.nx+m.ny != size(m.a,1)
    assessment = "Fail: A is not conformable with nx and ny"
  elseif size(m.a,1) != size(m.a,2)
    assessment = "Fail: A is not square"
  elseif size(m.b,1) != size(m.b,2)
    assessment = "Fail: B is not square"
  elseif size(m.a) != size(m.b)
    assessment = "Fail: A and B are not conformable"
  elseif size(m.a,1) != size(m.c,1)
    assessment = "Fail: A and C must have the same number of rows"
  elseif size(m.c,2) > m.nx
    assessment = "Fail: There are more innovations than predetermined variables"
    elseif size(m.sigma,1) != size(m.sigma,2)
    assessment = "Fail: SIGMA must be square"
  elseif size(m.c,2) != size(m.sigma,1)
    assessment = "Fail: C and SIGMA are not conformable"
  end

  return assessment

end

function check_structural_form(m::Structural_Form)

  assessment = "Pass"

  if size(m.a,1) != size(m.a,2)
    assessment = "Fail: A is not square"
  elseif size(m.a) != size(m.a1)
    assessment = "Fail: A and A1 are not conformable"
  elseif size(m.a) != size(m.b)
    assessment = "Fail: A and B are not conformable"
  elseif size(m.a,1) != size(m.c,1)
    assessment = "Fail: A and C must have the same number of rows"
  elseif size(m.sigma,1) != size(m.sigma,2)
    assessment = "Fail: SIGMA must be square"
  elseif size(m.c,2) != size(m.sigma,1)
    assessment = "Fail: C and SIGMA are not conformable"
  end

  return assessment

end

function check_sims_form(m::Sims_Form)

  assessment = "Pass"

  if size(m.gamma0,1) != size(m.gamma0,2)
    assessment = "Fail: GAMMA0 is not square"
  elseif size(m.gamma1,1) != size(m.gamma1,2)
    assessment = "Fail: GAMMA1 is not square"
  elseif size(m.gamma0) != size(m.gamma1)
    assessment = "Fail: GAMMA0 and GAMMA1 are not conformable"
  elseif size(m.gamma0,1) != size(m.c,1)
    assessment = "Fail: GAMMA0 and C must have the same number of rows"
  elseif size(m.c,2) != 1
    assessment = "Fail: C must be a vector"
  elseif size(m.gamma0,1) != size(m.psi,1)
    assessment = "Fail: GAMMA0 and PSI must have the same number of rows"
  elseif size(m.gamma0,1) > size(m.pi,1)
    assessment = "Fail: GAMMA0 and PI must have the same number of rows"
  elseif size(m.sigma,1) != size(m.sigma,2)
    assessment = "Fail: SIGMA must be square"
  elseif size(m.psi,2) != size(m.sigma,1)
    assessment = "Fail: PSI and SIGMA are not conformable"
  end

  return assessment

end

function check_gomme_klein_form(m::Gomme_Klein_Form)

  assessment = "Pass"

  if m.nx < 1
    assessment = "Fail: Too few predetermined variables"
  elseif m.ny < 1
    assessment = "Fail: Too few non-predetermined variables"
  elseif m.nx+m.ny != size(m.derivs,1)
    assessment = "Fail: Need first derivative for all model equations"
  elseif 2*size(m.derivs,1) != size(m.derivs,2)
    assessment = "Fail: The dimensions of the first-derivative matrix are incorrect"
  elseif size(m.derivs,2) != size(m.hessians,2)
    assessment = "Fail: The derivative matrix and the hessian matrix are not conformable"
  elseif size(m.hessians,1) != (m.nx+m.ny)*size(m.hessians,2)
    assessment = "Fail: The dimensions of the hessian matrix are incorrect"
  elseif size(m.eta,1) != m.nx
    assessment = "Fail: Eta has too few rows"
  elseif size(m.eta,2) != size(m.sigma,1)
    assessment = "Fail: Eta has too few columns"
  elseif size(m.sigma,1) != size(m.sigma,2)
    assessment = "Fail: SIGMA must be square"
  end

  return assessment

end

function check_lombardo_sutherland_form(m::Lombardo_Sutherland_Form)

  assessment = "Pass"

  if m.nx < 1
    assessment = "Fail: Too few predetermined variables"
  elseif m.ny < 1
    assessment = "Fail: Too few non-predetermined variables"
  elseif m.nx+m.ny != size(m.derivs,1)
    assessment = "Fail: Need first derivative for all model equations"
  elseif 2*size(m.derivs,1) != size(m.derivs,2)
    assessment = "Fail: The dimensions of the first-derivative matrix are incorrect"
  elseif size(m.derivs,2) != size(m.hessians,2)
    assessment = "Fail: The derivative matrix and the hessian matrix are not conformable"
  elseif size(m.hessians,1) != (m.nx+m.ny)*size(m.hessians,2)
    assessment = "Fail: The dimensions of the hessian matrix are incorrect"
  elseif size(m.eta,1) != m.nx
    assessment = "Fail: Eta has too few rows"
  elseif size(m.eta,2) != size(m.sigma,1)
    assessment = "Fail: Eta has too few columns"
  elseif size(m.sigma,1) != size(m.sigma,2)
    assessment = "Fail: SIGMA must be square"
  end

  return assessment

end


# This file is part of the TaylorSeries.jl Julia package, MIT license
#
# Luis Benet & David P. Sanders
# UNAM
#
# MIT Expat license
#

function ^(a::HomogeneousPolynomial, n::Integer)
    n == 0 && return one(a)
    n == 1 && return copy(a)
    n == 2 && return square(a)
    n < 0 && throw(DomainError())
    return power_by_squaring(a, n)
end

#= The following method computes `a^float(n)` (except for cases like
Taylor1{Interval{T}}^n, where `power_by_squaring` is used), to
use internally `pow!`.
=#
^(a::Taylor1, n::Integer) = a^float(n)

function ^(a::TaylorN{T}, n::Integer) where {T<:Number}
    n == 0 && return one(a)
    n == 1 && return copy(a)
    n == 2 && return square(a)
    n < 0 && return inv( a^(-n) )
    return power_by_squaring(a, n)
end

for T in (:Taylor1, :TaylorN)
    @eval function ^(a::$T{T}, n::Integer) where {T<:Integer}
        n == 0 && return one(a)
        n == 1 && return copy(a)
        n == 2 && return square(a)
        n < 0 && throw(DomainError())
        return power_by_squaring(a, n)
    end

    @eval function ^(a::$T{Rational{T}}, n::Integer) where {T<:Integer}
        n == 0 && return one(a)
        n == 1 && return copy(a)
        n == 2 && return square(a)
        n < 0 && return inv( a^(-n) )
        return power_by_squaring(a, n)
    end

    @eval ^(a::$T, x::Rational) = a^(x.num/x.den)

    @eval ^(a::$T, b::$T) = exp( b*log(a) )

    @eval ^(a::$T, x::T) where {T<:Complex} = exp( x*log(a) )
end

# power_by_squaring; slightly modified from base/intfuncs.jl
# Licensed under MIT "Expat"
for T in (:Taylor1, :HomogeneousPolynomial, :TaylorN)
    @eval function power_by_squaring(x::$T, p::Integer)
        p == 1 && return copy(x)
        p == 0 && return one(x)
        p == 2 && return square(x)
        t = trailing_zeros(p) + 1
        p >>= t

        while (t -= 1) > 0
            x = square(x)
        end

        y = x
        while p > 0
            t = trailing_zeros(p) + 1
            p >>= t
            while (t -= 1) ≥ 0
                x = square(x)
            end
            y *= x
        end

        return y
    end
end

## Real power ##
function ^(a::Taylor1, r::S) where {S<:Real}
    a0 = constant_term(a)
    aux = one(a0^r)

    iszero(r) && return Taylor1(aux, a.order)
    aa = aux*a
    r == 1 && return aa
    r == 2 && return square(aa)
    r == 1/2 && return sqrt(aa)

    c = Taylor1( zero(aux), a.order)
    for k = 0:a.order
        pow!(c, aa, r, k)
    end

    return c
end

## Real power ##
function ^(a::TaylorN, r::S) where {S<:Real}
    a0 = constant_term(a)
    aux = one(a0^r)

    iszero(r) && return TaylorN(aux, a.order)
    aa = aux*a
    r == 1 && return aa
    r == 2 && return square(aa)
    r == one(r)/2 && return sqrt(aa)
    isinteger(r) && return aa^round(Int,r)

    # @assert !iszero(a0)
    iszero(a0) && throw(DomainError(a,
        """The 0-th order TaylorN coefficient must be non-zero
        in order to expand `^` around 0."""))

    c = TaylorN( a0^r, a.order)
    for ord in 1:a.order
        pow!(c, aa, r, ord)
    end

    return c
end

# Homogeneous coefficients for real power
@doc doc"""
    pow!(c, a, r::Real, k::Int)

Update the `k`-th expansion coefficient `c[k]` of `c = a^r`, for
both `c` and `a` either `Taylor1` or `TaylorN`.

The coefficients are given by

```math
c_k = \frac{1}{k a_0} \sum_{j=0}^{k-1} \big(r(k-j) -j\big)a_{k-j} c_j.
```

For `Taylor1` polynomials, a similar formula is implemented which
exploits `k_0`, the order of the first non-zero coefficient of `a`.

""" pow!

@inline function pow!(c::Taylor1{T}, a::Taylor1{T}, r::S, k::Int) where {T<:Number,S<:Real}

    if r == 0
        return one!(c, a, k)
    elseif r == 1
        return identity!(c, a, k)
    elseif r == 2
        return sqr!(c, a, k)
    elseif r == 0.5
        return sqrt!(c, a, k)
    end

    # First non-zero coefficient
    l0 = findfirst(a)
    if l0 < 0
        c[k] = zero(a[0])
        return nothing
    end

    # The first non-zero coefficient of the result; must be integer
    !isinteger(r*l0) && throw(DomainError(a,
        """The 0th order Taylor1 coefficient must be non-zero
        to raise the Taylor1 polynomial to a non-integer exponent."""))
    lnull = trunc(Int, r*l0 )
    kprime = k-lnull
    if (kprime < 0) || (lnull > a.order)
        @inbounds c[k] = zero(a[0])
        return nothing
    end

    # Relevant for positive integer r, to avoid round-off errors
    if isinteger(r) && r > 0 && (k > r*findlast(a))
        @inbounds c[k] = zero(a[0])
        return nothing
    end

    if k == lnull
        @inbounds c[k] = (a[l0])^r
        return nothing
    end

    # The recursion formula
    if l0+kprime ≤ a.order
        @inbounds c[k] = r * kprime * c[lnull] * a[l0+kprime]
    else
        @inbounds c[k] = zero(a[0])
    end
    for i = 1:k-lnull-1
        ((i +lnull) > a.order || (l0+kprime-i > a.order)) && continue
        aux = r*(kprime-i) - i
        @inbounds c[k] += aux * c[i+lnull] * a[l0+kprime-i]
    end
    @inbounds c[k] = c[k] / (kprime * a[l0])
    return nothing
end

@inline function pow!(c::TaylorN{T}, a::TaylorN{T}, r::S, k::Int) where
        {T<:NumberNotSeriesN,S<:Real}

    if r == 0
        return one!(c, a, k)
    elseif r == 1
        return identity!(c, a, k)
    elseif r == 2
        return sqr!(c, a, k)
    elseif r == 0.5
        return sqrt!(c, a, k)
    end

    if k == 0
        @inbounds c[0] = ( constant_term(a) )^r
        return nothing
    end

    for i = 0:k-1
        aux = r*(k-i) - i
        mul!(c[k], aux*a[k-i], c[i])
    end
    @inbounds c[k] = c[k] / (k * constant_term(a))

    return nothing
end

## Square ##
"""
    square(a::AbstractSeries) --> typeof(a)

Return `a^2`; see [`TaylorSeries.sqr!`](@ref).
""" square

for T in (:Taylor1, :TaylorN)
    @eval function square(a::$T)
        c = $T( constant_term(a)^2, a.order)
        for k in 1:a.order
            sqr!(c, a, k)
        end
        return c
    end
end

function square(a::HomogeneousPolynomial)
    order = 2*a.order
    order > get_order() && return HomogeneousPolynomial(zero(a[1]), get_order())

    res = HomogeneousPolynomial(zero(a[1]), order)
    sqr!(res, a)
    return res
end

# Homogeneous coefficients for square
@doc doc"""
    sqr!(c, a, k::Int) --> nothing

Update the `k-th` expansion coefficient `c[k]` of `c = a^2`, for
both `c` and `a` either `Taylor1` or `TaylorN`.

The coefficients are given by

```math
\begin{eqnarray*}
c_k & = & 2 \sum_{j=0}^{(k-1)/2} a_{k-j} a_j,
    \text{ if k is odd,} \\
c_k & = & 2 \sum_{j=0}^{(k-2)/2} a_{k-j} a_j + (a_{k/2})^2,
    \text{ if k is even. }
\end{eqnarray*}
```

""" sqr!

for T = (:Taylor1, :TaylorN)
    @eval begin
        @inline function sqr!(c::$T{T}, a::$T{T}, k::Int) where {T<:Number}
            if k == 0
                @inbounds c[0] = constant_term(a)^2
                return nothing
            end

            kodd = k%2
            kend = div(k - 2 + kodd, 2)
            @inbounds for i = 0:kend
                if $T == Taylor1
                    c[k] += a[i] * a[k-i]
                else
                    mul!(c[k], a[i], a[k-i])
                end
            end
            @inbounds c[k] = 2 * c[k]
            kodd == 1 && return nothing

            if $T == Taylor1
                @inbounds c[k] += a[div(k,2)]^2
            else
                sqr!(c[k], a[div(k,2)])
            end

            return nothing
        end
    end
end

"""
    sqr!(c, a)

Return `c = a*a` with no allocation; all parameters are `HomogeneousPolynomial`.

"""
@inline function sqr!(c::HomogeneousPolynomial{T}, a::HomogeneousPolynomial{T}) where
        {T<:NumberNotSeriesN}
    iszero(a) && return nothing

    @inbounds num_coeffs_a = size_table[a.order+1]

    @inbounds posTb = pos_table[c.order+1]
    @inbounds idxTb = index_table[a.order+1]

    @inbounds for na = 1:num_coeffs_a
        ca = a[na]
        iszero(ca) && continue
        inda = idxTb[na]
        pos = posTb[2*inda]
        c[pos] += ca^2
        @inbounds for nb = na+1:num_coeffs_a
            cb = a[nb]
            iszero(cb) && continue
            indb = idxTb[nb]
            pos = posTb[inda+indb]
            c[pos] += 2 * ca * cb
        end
    end

    return nothing
end

## Square root ##
function sqrt(a::Taylor1)

    # First non-zero coefficient
    l0nz = findfirst(a)
    aux = zero(sqrt( constant_term(a) ))
    if l0nz < 0
        return Taylor1(aux, a.order)
    elseif l0nz%2 == 1 # l0nz must be pair
        throw(DomainError(a,
        """First non-vanishing Taylor1 coefficient must correspond
        to an **even power** in order to expand `sqrt` around 0."""))
    end

    # The last l0nz coefficients are set to zero.
    lnull = div(l0nz, 2)
    c = Taylor1( aux, a.order )
    @inbounds c[lnull] = sqrt( a[l0nz] )
    aa = one(aux) * a
    for k = lnull+1:a.order
        sqrt!(c, aa, k, lnull)
    end

    return c
end

function sqrt(a::TaylorN)
    @inbounds p0 = sqrt( constant_term(a) )
    if iszero(p0)
        throw(DomainError(a,
        """The 0-th order TaylorN coefficient must be non-zero
        in order to expand `sqrt` around 0."""))
    end

    c = TaylorN( p0, a.order)
    aa = one(p0)*a
    for k in 1:a.order
        sqrt!(c, aa, k)
    end

    return c
end

# Homogeneous coefficients for the square-root
@doc doc"""
    sqrt!(c, a, k::Int, k0::Int=0)

Compute the `k-th` expansion coefficient `c[k]` of `c = sqrt(a)`
for both`c` and `a` either `Taylor1` or `TaylorN`.

The coefficients are given by

```math
\begin{eqnarray*}
c_k &=& \frac{1}{2 c_0} \big( a_k - 2 \sum_{j=1}^{(k-1)/2} c_{k-j}c_j\big),
    \text{ if k is odd,} \\
c_k &=& \frac{1}{2 c_0} \big( a_k - 2 \sum_{j=1}^{(k-2)/2} c_{k-j}c_j
    - (c_{k/2})^2\big), \text{ if k is even.}
\end{eqnarray*}
```

For `Taylor1` polynomials, `k0` is the order of the first non-zero
coefficient, which must be even.

""" sqrt!

@inline function sqrt!(c::Taylor1{T}, a::Taylor1{T}, k::Int, k0::Int=0) where {T<:Number}

    if k == k0
        @inbounds c[k] = sqrt(a[2*k0])
        return nothing
    end

    kodd = (k - k0)%2
    kend = div(k - k0 - 2 + kodd, 2)
    imax = min(k0+kend, a.order)
    imin = max(k0+1, k+k0-a.order)
    imin ≤ imax && ( @inbounds c[k] = c[imin] * c[k+k0-imin] )
    @inbounds for i = imin+1:imax
        c[k] += c[i] * c[k+k0-i]
    end
    if k+k0 ≤ a.order
        @inbounds aux = a[k+k0] - 2*c[k]
    else
        @inbounds aux = - 2*c[k]
    end
    if kodd == 0
        @inbounds aux = aux - (c[kend+k0+1])^2
    end
    @inbounds c[k] = aux / (2*c[k0])

    return nothing
end

@inline function sqrt!(c::TaylorN{T}, a::TaylorN{T}, k::Int) where {T<:NumberNotSeriesN}

    if k == 0
        @inbounds c[0] = sqrt( constant_term(a) )
        return nothing
    end

    kodd = k%2
    kend = div(k - 2 + kodd, 2)
    @inbounds for i = 1:kend
        mul!(c[k], c[i], c[k-i])
    end
    @inbounds aux = a[k] - 2*c[k]
    if kodd == 0
        @inbounds aux = aux - (c[kend+1])^2
    end
    @inbounds c[k] = aux / (2*constant_term(c))

    return nothing
end


"""
    functional_cartography(N::T, L::Dict{E,Int64}) where {T<:BinaryNetwork, E}

This function will take the output of a modularity analysis (*i.e.* a network
and a partition), and return a dictionary where every species is associated to
its functional role, as defined in Olesen et al (2005). The first element is the
within-module degree z-score, and the second is the participation coefficient.

#### References

Guimerà, R., Amaral, L.A.N., 2005. Cartography of complex networks: modules and
universal roles. Journal of Statistical Mechanics: Theory and Experiment 2005,
P02001. https://doi.org/10.1088/1742-5468/2005/02/P02001

Guimerà, R., Nunes Amaral, L.A., 2005. Functional cartography of complex
metabolic networks. Nature 433, 895–900. https://doi.org/10.1038/nature03288

Olesen, J.M., Bascompte, J., Dupont, Y.L., Jordano, P., 2007. The modularity of
pollination networks. Proceedings of the National Academy of Sciences 104,
19891–19896. https://doi.org/10.1073/pnas.0706375104
"""
function functional_cartography(N::T, L::Dict{E,Int64}) where {T<:BinaryNetwork, E}
    κ = Dict{last(eltype(N)), Int64}()
    S = Dict{Int64,Tuple{Float64, Float64}}()
    carto = Dict{last(eltype(N)),Tuple{Float64, Float64}}()

    for s in species(N)
        sp_module = L[s]
        k_i = 0
        if s ∈ species(N; dims=2)
            for s2 in N[:,s]
                L[s2] == sp_module && (k_i += 1)
            end
        end
        if s ∈ species(N; dims=1)
            for s2 in N[s,:]
                L[s2] == sp_module && (k_i += 1)
            end
        end
        κ[s] = k_i
    end

    for s in values(L)
        sp = filter(k -> k.second == s, L)
        k_i = [get(κ, x, 0) for x in keys(sp)]
        S[s] = (mean(k_i), std(k_i))
    end

    # Participation coefficient
    for s in species(N)
        ks = zeros(Int64, length(unique(values(L))))
        if s ∈ species(N; dims=2)
            for s2 in N[:,s]
                ks[L[s2]] += 1
            end
        end
        if s ∈ species(N; dims=1)
            for s2 in N[s,:]
                ks[L[s2]] += 1
            end
        end
        carto[s] = ((κ[s] - S[L[s]][1])/S[L[s]][2], 1-sum((ks./degree(N)[s]).^2))
    end

    return carto
end


module Tableaus

    import Polynomials
    import Polynomials: Polynomial

    using ..RungeKutta
    using ..RungeKutta: big, @big, _legendre, _shifted_legendre

    include("tableaus/dirk.jl")

    export TableauCrankNicolson,
           TableauCrouzeix,
           TableauKraaijevangerSpijker,
           TableauQinZhang

    include("tableaus/erk.jl")

    export TableauExplicitEuler, TableauForwardEuler,
           TableauExplicitMidpoint,
           TableauHeun2,
           TableauHeun3,
           TableauRalston2,
           TableauRalston3,
           TableauRunge, TableauRunge2,
           TableauKutta, TableauKutta3,
           TableauRK416, TableauRK4,
           TableauRK438,
           TableauSSPRK2,
           TableauSSPRK3

    include("tableaus/firk.jl")

    export TableauImplicitEuler, TableauBackwardEuler,
           TableauImplicitMidpoint,
           TableauSRK3

    include("tableaus/gauss.jl")

    export TableauGauss

    include("tableaus/lobatto.jl")

    export TableauLobattoIIIA, TableauLobattoIIIB, TableauLobattoIIIC, TableauLobattoIIIC̄,
           TableauLobattoIIID, TableauLobattoIIIE, TableauLobattoIIIF, TableauLobattoIIIG
    export get_lobatto_nullvector

    include("tableaus/radau.jl")

    export TableauRadauIA, TableauRadauIIA,
           TableauRadauIB, TableauRadauIIB

    TableauList = (
       # explicit
       forward_euler         = TableauForwardEuler,
       explicit_euler        = TableauExplicitEuler,
       explicit_midpoint     = TableauExplicitMidpoint,
       heun2                 = TableauHeun2,
       heun3                 = TableauHeun3,
       ralston2              = TableauRalston2,
       ralston3              = TableauRalston3,
       runge                 = TableauRunge,
       runge2                = TableauRunge2,
       kutta                 = TableauKutta,
       kutta3                = TableauKutta3,
       rk4                   = TableauRK4,
       rk416                 = TableauRK416,
       rk438                 = TableauRK438,
       ssprk2                = TableauSSPRK2,
       ssprk3                = TableauSSPRK3,
       # dirk
       crank_nicolson        = TableauCrankNicolson,
       crouzeix              = TableauCrouzeix,
       kraaijevanger_spijker = TableauKraaijevangerSpijker,
       qin_zhang             = TableauQinZhang,
       # firk
       backward_euler        = TableauBackwardEuler,
       implicit_euler        = TableauImplicitEuler,
       implicit_midpoint     = TableauImplicitMidpoint,
       srk3                  = TableauSRK3,
    )

    export TableauList

end


using JuliaFFT
using Test: @testset, @test

@testset "Known-output FFT" begin
    xs = [1.0, -1.0, 1.0, -1.0]
    ys = fft(xs)
    @test all(ys .== [0.0, 0.0, 4.0, 0.0])

    xs = zeros(8)
    xs[1] = 1.0
    ys = fft(xs)
    @test all(ys .== ones(8))

    xs = zeros(4)
    xs[2] = 1.0
    ys = fft(xs)
    ys_true = [1.0+0im, 0.0-1im, -1.0+0im, 0.0+1.0im]
    @test all(abs.(ys .- ys_true) .< 1e-8)
end


include("LL.jl")
#= include("CLL.jl") =#

function Simple_Smoother(x :: Array{Float64,1},
                  left :: Float64 = 0.25,
                  mid  :: Float64 = 0.5,
                  right :: Float64 = (1- left - mid))

  smo = fill(NaN, length(x))
  smo[2:end-1] = left* x[1:end-2] + mid * x[2:end-1] + right * x[3:end]
  smo[1] = (left * x[1] + right * x[2])/ (left + right)
  smo[end] = (left * x[end-1] + right * x[end])/ (left + right)
  return smo
end
export Simple_Smoother


using DelayEmbeddings
using DelayEmbeddings: AbstractDataset, Dataset, dimension
export ProbabilitiesEstimator, Probabilities
export EntropyEstimator
export probabilities, probabilities!
export genentropy, genentropy!
export Dataset, dimension

const Vector_or_Dataset = Union{AbstractVector, AbstractDataset}

"""
    Probabilities(x) → p
A simple wrapper type around an `x::AbstractVector` which ensures that `p` sums to 1.
Behaves identically to `Vector`.
"""
struct Probabilities{T} <: AbstractVector{T}
    p::Vector{T}
    function Probabilities(x::AbstractVector{T}) where T
        s = sum(x)
        if s ≠ 1
            x = x ./ s
        end
        return new{T}(x)
    end
end

# extend base Vector interface:
for f in (:length, :size, :eachindex, :eltype, :lastindex, :firstindex)
    @eval Base.$(f)(d::Probabilities) = $(f)(d.p)
end
Base.IteratorSize(d::Probabilities) = Base.HasLength()
@inline Base.iterate(d::Probabilities, i = 1) = iterate(d.p, i)
@inline Base.getindex(d::Probabilities, i) = d.p[i]
@inline Base.setindex!(d::Probabilities, v, i) = (d.p[i] = v)
@inline Base.:*(d::Probabilities, x::Number) = d.p * x
@inline Base.sum(d::Probabilities{T}) where T = one(T)

"""
An abstract type for entropy estimators that don't explicitly estimate probabilities,
but return the value of the entropy directly.
"""
abstract type EntropyEstimator end
const EntEst = EntropyEstimator # shorthand

"""
An abstract type for probabilities estimators.
"""
abstract type ProbabilitiesEstimator end
const ProbEst = ProbabilitiesEstimator # shorthand

"""
    probabilities(x::Vector_or_Dataset, est::ProbabilitiesEstimator) → p::Probabilities

Calculate probabilities representing `x` based on the provided
estimator and return them as a [`Probabilities`](@ref) container (`Vector`-like).
The probabilities are typically unordered and may or may not contain 0s, see the
documentation of the individual estimators for more.

The configuration options are always given as arguments to the chosen estimator.

    probabilities(x::Vector_or_Dataset, ε::AbstractFloat) → p::Probabilities

Convenience syntax which provides probabilities for `x` based on rectangular binning
(i.e. performing a histogram). In short, the state space is divided into boxes of length
`ε`, and formally we use `est = VisitationFrequency(RectangularBinning(ε))`
as an estimator, see [`VisitationFrequency`](@ref).

This method has a linearithmic time complexity (`n log(n)` for `n = length(x)`)
and a linear space complexity (`l` for `l = dimension(x)`).
This allows computation of probabilities (histograms) of high-dimensional
datasets and with small box sizes `ε` without memory overflow and with maximum performance.
To obtain the bin information along with `p`, use [`binhist`](@ref).

    probabilities(x::Vector_or_Dataset, n::Integer) → p::Probabilities
Same as the above method, but now each dimension of the data is binned into `n::Int` equal
sized bins instead of bins of length `ε::AbstractFloat`.

    probabilities(x::Vector_or_Dataset) → p::Probabilities
Directly count probabilities from the elements of `x` without any discretization,
binning, or other processing (mostly useful when `x` contains categorical or integer data).
"""
function probabilities end

"""
    probabilities!(args...)
Identical to `probabilities(args...)`, but allows pre-allocation of temporarily used
containers.

Only works for certain estimators. See for example [`SymbolicPermutation`](@ref).
"""
function probabilities! end

"""
    genentropy(p::Probabilities; q = 1.0, base = MathConstants.e)

Compute the generalized order-`q` entropy of some probabilities
returned by the [`probabilities`](@ref) function. Alternatively, compute entropy
from pre-computed `Probabilities`.

    genentropy(x::Vector_or_Dataset, est; q = 1.0, base)

A convenience syntax, which calls first `probabilities(x, est)`
and then calculates the entropy of the result (and thus `est` can be a
`ProbabilitiesEstimator` or simply `ε::Real`).

## Description

Let ``p`` be an array of probabilities (summing to 1). Then the generalized (Rényi) entropy is

```math
H_q(p) = \\frac{1}{1-q} \\log \\left(\\sum_i p[i]^q\\right)
```

and generalizes other known entropies,
like e.g. the information entropy
(``q = 1``, see [^Shannon1948]), the maximum entropy (``q=0``,
also known as Hartley entropy), or the correlation entropy
(``q = 2``, also known as collision entropy).

[^Rényi1960]: A. Rényi, *Proceedings of the fourth Berkeley Symposium on Mathematics, Statistics and Probability*, pp 547 (1960)
[^Shannon1948]: C. E. Shannon, Bell Systems Technical Journal **27**, pp 379 (1948)
"""
function genentropy end

function genentropy(prob::Probabilities; q = 1.0, α = nothing, base = MathConstants.e)
    if α ≠ nothing
        @warn "Keyword `α` is deprecated in favor of `q`."
        q = α
    end
    q < 0 && throw(ArgumentError("Order of generalized entropy must be ≥ 0."))
    haszero = any(iszero, prob)
    p = if haszero
        i0 = findall(iszero, prob.p)
        # We copy because if someone initialized Probabilities with 0s, I would guess
        # they would want to index the zeros as well. Not so costly anyways.
        deleteat!(copy(prob.p), i0)
    else
        prob.p
    end

    if q ≈ 0
        return log(base, length(p)) #Hartley entropy, max-entropy
    elseif q ≈ 1
        return -sum( x*log(base, x) for x in p ) #Shannon entropy
    elseif isinf(q)
        return -log(base, maximum(p)) #Min entropy
    else
        return (1/(1-q))*log(base, sum(x^q for x in p) ) #Renyi q entropy
    end
end

genentropy(x::AbstractArray{<:Real}) =
    error("For single-argument input, do `genentropy(Probabilities(x))` instead.")

function genentropy(x::Vector_or_Dataset, est; q = 1.0, α = nothing, base = MathConstants.e)
    if α ≠ nothing
        @warn "Keyword `α` is deprecated in favor of `q`."
        q = α
    end
    p = probabilities(x, est)
    genentropy(p; q = q, base = base)
end

"""
    genentropy!(p, x, est::ProbabilitiesEstimator; q = 1.0, base = MathConstants.e)

Similarly with `probabilities!` this is an in-place version of `genentropy` that allows
pre-allocation of temporarily used containers.

Only works for certain estimators. See for example [`SymbolicPermutation`](@ref).
"""
function genentropy!(p, x, est; q = 1.0, α = nothing, base = MathConstants.e)
    if α ≠ nothing
        @warn "Keyword `α` is deprecated in favor of `q`."
        q = α
    end
    probabilities!(p, x, est)
    genentropy(p; q = q, base = base)
end


"""
```
Module      : FinanceLib.FixedIncomes.Rates
Description : Implement Fixed Incomes Rates modules for the FinanceLib library
Copyright   : (c) 2022 Kishaloy Neogi
License     : MIT
Maintainer  : Kishaloy Neogi
Email       : nkishaloy@yahoo.com
```
The module describes the base modules of Fixed Incomes Rates.

You may see the github repository at <https://github.com/n-kishaloy/FinanceLib.jl>
"""
module Rates

import FinanceLib as Fl

"""
`parToSpotRates(x) = Convert par rates to spot rates`

* x = RateCurve of par rates
"""
function parToSpotRates(x :: Fl.RateCurve{Fl.NomRate})
  n = length(x.rate); y = Fl.RateCurve{Fl.NomRate}(Vector{Float64}(undef, n), x.freq)
  y.rate[1] = x.rate[1]
  for i ∈ 2:n
    xm = x.rate[i]/x.freq
    s = 1.0 - sum( (k -> xm/(1 + y.rate[k]/x.freq)^k).(1:(i-1)) )
    y.rate[i] = (((1.0 + xm) / s)^(1/i) - 1.0)*x.freq
  end
  y
end

"""
`spotToParRates(x) = Convert spot rates to par rates`

* x = RateCurve of spot rates
"""
spotToParRates(x::Fl.RateCurve{Fl.NomRate}) = Fl.RateCurve{Fl.NomRate}(1:length(x.rate) .|> i -> x.freq*(1-1/(1+x.rate[i]/x.freq)^i)/sum((k -> 1/(1 + x.rate[k]/x.freq)^k).(1:i)), x.freq)

end


@testset "linear algebra" begin
    @test issquare(reshape(rand(Double32,3*3), 3, 3))

    # issue #77
    M = Double64.([1 0; 1 1])
    @test_throws ErrorException exp(M)
    M = Complex{Double64}.([1 0; 1 1])
    @test_throws ErrorException exp(M)

    t=[Complex{Double64}(1.0,0.0) Complex{Double64}(0.0,1.0);
       Complex{Double64}(0.0,-1.0) Complex{Double64}(1.0,0.0)]
    @test ishermitian(t)

    @test norm(Complex{Double64}[2, 2im]) ≈ norm(ComplexF64[2, 2im])
    @test norm(Complex{Double64}[2, 2im]) isa Double64

    @test norm(Complex{Double64}[2, 2im], 3.0) ≈ norm(ComplexF64[2, 2im], 3.0)
    @test norm(Complex{Double64}[2, 2im], 3.0) isa Double64

    @test norm(Double64[2, 1]) ≈ norm(Float64[2, 1])
    @test norm(Double64[2, 1], 3.0) ≈ norm(Float64[2, 1], 3.0)

    # issue #105
    for T in [Double16, Double32, Double64]
        for p in [0, 1.0, 2.0, Inf, -Inf]
            @test norm(T[], p) == zero(T)
            @test norm(Complex{T}[], p) == zero(T)

            @test normalize(T[]) == T[]
            @test normalize(Complex{T}[]) == Complex{T}[]
        end
    end
end


Pkg.add("FastGaussQuadrature")
Pkg.add("DifferentialEquations")
Pkg.add("Parameters")


export graph_exp_native_jl, graph_exp_native_jl_degopt

function get_expm_pade_coeffs(d, T)
    if d == 13
        return T[
            64764752532480000.0,
            32382376266240000.0,
            7771770303897600.0,
            1187353796428800.0,
            129060195264000.0,
            10559470521600.0,
            670442572800.0,
            33522128640.0,
            1323241920.0,
            40840800.0,
            960960.0,
            16380.0,
            182.0,
            1.0,
        ]
    elseif d == 9
        C = T[
            17643225600.0,
            8821612800.0,
            2075673600.0,
            302702400.0,
            30270240.0,
            2162160.0,
            110880.0,
            3960.0,
            90.0,
            1.0,
        ]
    elseif d == 7
        C = T[
            17297280.0,
            8648640.0,
            1995840.0,
            277200.0,
            25200.0,
            1512.0,
            56.0,
            1.0,
        ]
    elseif d == 5
        C = T[30240.0, 15120.0, 3360.0, 420.0, 30.0, 1.0]
    elseif d == 3
        C = T[120.0, 60.0, 12.0, 1.0]
    else
        error("Unknown degree for expm-Padé coefficients.")
    end
end

"""
     (graph,crefs)=graph_exp_native_jl(A; input=:A)

Creates a graph for the native scaling-and-squaring for the matrix exponential,
as implemented in Julia. The matrix `A` is taken as input to determine the
length of the Padé approximant and the number of squares applied, as well as
determining the type of the coefficients. The kwarg `input` determines the name
of the matrix, in the graph.

**References**

1. N. J. Higham. "Functions of Matrices". SIAM, Philadelphia, PA, 2008.
   DOI: [10.1137/1.9780898717778](https://doi.org/10.1137/1.9780898717778)

2. N. J. Higham. "The Scaling and Squaring Method for the Matrix Exponential
   Revisited". SIAM Journal on Matrix Analysis and Applications,
   26(4):1179-1193, 2005.
   DOI: [10.1137/04061101X](https://doi.org/10.1137/04061101X)

3. "Julia's matrix exponential", [at the time of
   conversion](https://github.com/JuliaLang/julia/blob/697e782ab86bfcdd7fd15550241fe162c51d9f98/stdlib/LinearAlgebra/src/dense.jl#L554).
"""
function graph_exp_native_jl(A; input = :A)
    T = eltype(A)
    nA = opnorm(A, 1)
    d = get_expm_pade_degree_native_lj(nA)
    if d < 13
        C = get_expm_pade_coeffs(d, T)
        return graph_exp_native_jl_low(C, input)
    else
        C = get_expm_pade_coeffs(d, T)
        s = log2(nA / 5.4) # power of 2 later reversed by squaring
        return graph_exp_native_jl_high(C, s, input)
    end
end

function get_expm_pade_degree_native_lj(nA)
    if nA <= 2.1
        if nA > 0.95
            return 9
        elseif nA > 0.25
            return 7
        elseif nA > 0.015
            return 5
        else
            return 3
        end
    else
        return 13
    end
end

# For sufficiently small nA, use lower order Padé-Approximations
function graph_exp_native_jl_low(C, input)
    T = eltype(C)
    graph = Compgraph(T)

    n = length(C)
    s = (div(n, 2) - 1)
    cref = Vector{Tuple{Symbol,Int}}(undef, n)

    add_mult!(graph, :A2, input, input)
    evenmon = Vector{Symbol}(undef, s + 1)
    evenmon[1] = :I
    evenmon[2] = :A2
    for k = 2:s
        sym = Symbol("A$(2*k)")
        add_mult!(graph, sym, :A2, evenmon[k])
        evenmon[k+1] = sym
    end

    a = view(C, 1:2:n-1)
    cref[1:s+1] = add_sum!(graph, :V, a, evenmon, :V)

    a = view(C, 2:2:n)
    cref[s+2:n] = add_sum!(graph, :Ua, a, evenmon, :Ua)
    add_mult!(graph, :U, :Ua, input)

    add_lincomb!(graph, :X, 1.0, :V, 1.0, :U)
    add_lincomb!(graph, :Z, 1.0, :V, -1.0, :U)
    add_ldiv!(graph, :P, :Z, :X)

    add_output!(graph, :P)

    return (graph, cref)
end

# Full scaling and squaring
function graph_exp_native_jl_high(CC, s, input)
    T = eltype(CC)
    graph = Compgraph(T)

    C = input
    if s > 0
        si = ceil(Int, s)
        γ = 1 / convert(T, 2^si)
        C = :C
        add_lincomb!(graph, C, γ, input, 0, :I)
    end

    cref = Vector{Tuple{Symbol,Int}}(undef, 14)

    add_mult!(graph, :A2, C, C)
    add_mult!(graph, :A4, :A2, :A2)
    add_mult!(graph, :A6, :A2, :A4)

    #U  = A * (A6 * (CC[14].*A6 .+ CC[12].*A4 .+ CC[10].*A2) .+
    #          CC[8].*A6 .+ CC[6].*A4 .+ CC[4].*A2 .+ CC[2].*Inn)

    # Ub3= CC[14].*A6 .+ CC[12].*A4 .+ CC[10].*A2
    a = view(CC, 10:2:14)
    cref[1:3] = add_sum!(graph, :Ub3, a, [:A2, :A4, :A6], :Ub)
    add_mult!(graph, :Ub, :Ub3, :A6)

    # Ua = CC[8].*A6 .+ CC[6].*A4 .+ CC[4].*A2 .+ CC[2].*Inn
    a = view(CC, 2:2:8)
    cref[4:7] = add_sum!(graph, :Ua, a, [:I, :A2, :A4, :A6], :Ua)

    add_lincomb!(graph, :Uc, 1.0, :Ub, 1.0, :Ua)
    add_mult!(graph, :U, C, :Uc)

    # V  = A6 * (CC[13].*A6 .+ CC[11].*A4 .+ CC[9].*A2) .+
    #            CC[7].*A6 .+ CC[5].*A4 .+ CC[3].*A2 .+ CC[1].*Inn

    # Vb3= CC[13].*A6 .+ CC[11].*A4 .+ CC[9].*A2
    a = view(CC, 9:2:13)
    cref[8:10] = add_sum!(graph, :Vb3, a, [:A2, :A4, :A6], :Vb)
    add_mult!(graph, :Vb, :Vb3, :A6)

    # Va = CC[7].*A6 .+ CC[5].*A4 .+ CC[3].*A2 .+ CC[1].*Inn
    a = view(CC, 1:2:7)
    cref[11:14] = add_sum!(graph, :Va, a, [:I, :A2, :A4, :A6], :Va)

    add_lincomb!(graph, :V, 1.0, :Vb, 1.0, :Va)

    add_lincomb!(graph, :X, 1.0, :V, 1.0, :U)
    add_lincomb!(graph, :Z, 1.0, :V, -1.0, :U)
    add_ldiv!(graph, :P, :Z, :X)

    Qtm1 = :P
    Qt = :P
    if (s > 0)
        for t = 1:si
            Qt = Symbol("S" * string(t))
            add_mult!(graph, Qt, Qtm1, Qtm1)

            Qtm1 = Qt
        end
    end

    add_output!(graph, Qt)

    return (graph, cref)
end

"""
     (graph,crefs)=graph_exp_native_jl_degopt(A; input=:A)

Same as [`graph_exp_native_jl`](@ref) but with calls to [`graph_degopt`](@ref)
for contruction of numerator and denominator polynomials.
"""
function graph_exp_native_jl_degopt(A; input = :A)
    T = eltype(A)
    nA = opnorm(A, 1)
    d = get_expm_pade_degree_native_lj(nA)
    if d < 13
        C = get_expm_pade_coeffs(d, T)
        return graph_exp_native_jl_low_degopt(C, input)
    else
        C = get_expm_pade_coeffs(d, T)
        s = log2(nA / 5.4) # power of 2 later reversed by squaring
        return graph_exp_native_jl_high_degopt(C, s, input)
    end
end

# For sufficiently small nA, use lower order Padé-Approximations
function graph_exp_native_jl_low_degopt(C, input)
    T = eltype(C)
    graph = Compgraph(T)

    n = length(C)
    s = (div(n, 2) - 1)

    xU = Vector{Tuple{Vector{T},Vector{T}}}(undef, s + 1)
    # Even monomials
    xU[1] = (vcat(zeros(T, 1), one(T)), vcat(zeros(T, 1), one(T)))
    for k = 2:s
        xU[k] =
            (vcat(zeros(T, 2), one(T), zeros(k - 2)), vcat(zeros(T, k), one(T)))
    end

    # U
    a = view(C, 4:2:n)
    xU[s+1] =
        (vcat(zeros(T, 1), one(T), zeros(T, s + 1)), vcat(C[2], zero(T), a))
    zU = vcat(zeros(T, s + 2), one(T))
    (graphU, crefU) = graph_degopt(xU, zU, input = input)
    rename_node!(graphU, Symbol("T2k$(4+s)"), :U, crefU)

    # V
    xV = Vector{Tuple{Vector{T},Vector{T}}}(undef, s)
    xV[1:s] = xU[1:s]
    a = view(C, 3:2:n-1)
    zV = vcat(C[1], zero(T), a)
    (graphV, crefV) = graph_degopt(xV, zV, input = input)
    rename_node!(graphV, Symbol("T2k$(3+s)"), :V, crefV)

    graph = merge_graphs(
        graphU,
        graphV,
        prefix1 = "U",
        prefix2 = "V",
        skip_basic1 = true,
        skip_basic2 = true,
        cref1 = crefU,
        cref2 = crefV,
        input1 = input,
        input2 = input,
    )
    cref = vcat(crefU, crefV)
    empty!(graph.outputs)

    merge_rename_exp_native_jl_degopt!(graph, cref, s + 1)

    add_lincomb!(graph, :X, 1.0, :VV, 1.0, :UU)
    add_lincomb!(graph, :Z, 1.0, :VV, -1.0, :UU)
    add_ldiv!(graph, :P, :Z, :X)

    add_output!(graph, :P)

    return (graph, cref)
end

# Full scaling and squaring
function graph_exp_native_jl_high_degopt(CC, s, input)
    T = eltype(CC)
    graph = Compgraph(T)
    C = input
    if s > 0
        si = ceil(Int, s)
        C = :C
        # Actual scaling below, after graph is created
    end

    #U  = A * (A6 * (CC[14].*A6 .+ CC[12].*A4 .+ CC[10].*A2) .+
    #          CC[8].*A6 .+ CC[6].*A4 .+ CC[4].*A2 .+ CC[2].*Inn)
    xU = Vector{Tuple{Vector{T},Vector{T}}}(undef, 5)

    # Even monomials
    xU[1] = (vcat(zeros(T, 1), one(T)), vcat(zeros(T, 1), one(T)))
    xU[2] = (vcat(zeros(T, 2), one(T)), vcat(zeros(T, 2), one(T)))
    xU[3] = (vcat(zeros(T, 2), one(T), zero(T)), vcat(zeros(T, 3), one(T)))

    # Up = A6 * (CC[14].*A6 .+ CC[12].*A4 .+ CC[10].*A2)
    a = view(CC, 10:2:14)
    xU[4] = (vcat(zeros(T, 4), one(T)), vcat(zeros(T, 2), a))

    # U = A* (CC[8].*A6 .+ CC[6].*A4 .+ CC[4].*A2 .+ CC[2].*Inn + Up)
    a = view(CC, 4:2:8)
    xU[5] =
        (vcat(zero(T), one(T), zeros(T, 4)), vcat(CC[2], zero(T), a, one(T)))

    zU = vcat(zeros(T, 6), one(T))
    (graphU, crefU) = graph_degopt(xU, zU, input = C)
    rename_node!(graphU, :T2k8, :U, crefU)

    # V  = A6 * (CC[13].*A6 .+ CC[11].*A4 .+ CC[9].*A2) .+
    #            CC[7].*A6 .+ CC[5].*A4 .+ CC[3].*A2 .+ CC[1].*Inn
    xV = Vector{Tuple{Vector{T},Vector{T}}}(undef, 4)

    # Even monomials
    xV[1:3] = xU[1:3]

    # Vp = A6 * ( CC[13].*A6 .+ CC[11].*A4 .+ CC[9].*A2 )
    a = view(CC, 9:2:13)
    xV[4] = (vcat(zeros(T, 4), one(T)), vcat(zeros(T, 2), a))

    # V = Vp + CC[7].*A6 .+ CC[5].*A4 .+ CC[3].*A2 .+ CC[1].*Inn
    a = view(CC, 3:2:7)
    zV = vcat(CC[1], zero(T), a, one(T))
    (graphV, crefV) = graph_degopt(xV, zV, input = C)
    rename_node!(graphV, :T2k7, :V, crefV)

    graph = merge_graphs(
        graphU,
        graphV,
        prefix1 = "U",
        prefix2 = "V",
        skip_basic1 = true,
        skip_basic2 = true,
        cref1 = crefU,
        cref2 = crefV,
        input1 = C,
        input2 = C,
    )
    cref = vcat(crefU, crefV)
    empty!(graph.outputs)

    merge_rename_exp_native_jl_degopt!(graph, cref, 4)

    add_lincomb!(graph, :X, 1.0, :VV, 1.0, :UU)
    add_lincomb!(graph, :Z, 1.0, :VV, -1.0, :UU)
    add_ldiv!(graph, :P, :Z, :X)

    if (s > 0)
        γ = 1 / convert(T, 2^si)
        add_lincomb!(graph, C, γ, input, 0, :I)
        xS = Vector{Tuple{Vector{T},Vector{T}}}(undef, si)
        for i = 1:si
            xS[i] = (vcat(zeros(T, i), one(T)), vcat(zeros(T, i), one(T)))
        end
        zS = vcat(zeros(T, si + 1), one(T))
        (graphS, crefS) = graph_degopt(xS, zS, input = :P)

        graph = merge_graphs(
            graph,
            graphS,
            prefix1 = "",
            prefix2 = "S",
            skip_basic1 = true,
            skip_basic2 = true,
            cref1 = cref,
            cref2 = crefS,
            input1 = C,
            input2 = :P,
        )
        cref = vcat(cref, crefS)
    else
        add_output!(graph, :P)
    end

    return (graph, cref)
end

# Remove redundant creation of monomial basis, e.g., VB3 == UB3 and VBa4_3 == UBa4_3
function merge_rename_exp_native_jl_degopt!(graph, cref, M)
    for N in ["VB", "UB"]
        for k in reverse(2:M)
            rename_node!(graph, Symbol(string(N, "$k")), Symbol("B$(k)"), cref)
            for NN in ["a", "b"]
                for kk in reverse(2:k-1)
                    rename_node!(
                        graph,
                        Symbol(string(N, NN, "$(k)_$(kk)")),
                        Symbol(string("B", NN, "$(k)_$(kk)")),
                        cref,
                    )
                end
            end
        end
    end
end


include("Solution.jl")
include("Params.jl")

""" Descent Heuristic
1. Select a starting solution.
2. Check the change in the value of the objective function
     for all pairwise exchanges of facilities.
3. If an improving exchange is found,
     the best improving exchange is executed and we go to Step 2.
4. If no improving exchange is found, the algorithm terminates.

Local Search algorithm
S = Initial Solution
While not Terminated
    Explore( N(S) ) ;
    If there is no better neighbor in N(s) Then Stop ;
    S = Select( N(S) ) ;
End While
Return Final solution found (local optima)
"""
function descentHeuristic(sol::Solution; max_iter::Float64=Inf)::Solution
    improved::Bool = true
    num_iter = 0
    while ((improved == true) && (num_iter < max_iter))
        neighborhood = generateSwapNeighbors(sol) # Generate N(S)
        improved = false
        for neighbor in neighborhood    # Explore( N(S) )
            if (neighbor.cost < sol.cost)
                sol = neighbor
                improved = true
            end
        end
        num_iter += 1
    end
    return sol  # return best solution found
end

function cyclicHeuristic(sol::Solution; max_iter::Float64=Inf)::Solution
    improved = false
    neighborhood = generateCyclicNeighbors(sol) # Generate N(S)

    for neighbor in neighborhood    # Explore( N(S) )
        if (neighbor.cost < sol.cost)
            sol = neighbor
            improved = true
        end
    end
    return sol  # return best solution found
end

# Multi-start local search
function multistartLS(params::Params, num_trials::Int; localSearchFunc::Function=descentHeuristic)
    startTime = time()
    results = Dict{String,Any}()
    bestSol = Solution(params) # Random solution
    bestSol = localSearchFunc(bestSol) # Local search
    trial = 1
    while ((trial <= num_trials) && (time() - startTime < params.maxTime) && (bestSol.cost != params.solutionCost))
        sol = Solution(params) # Random solution
        sol = localSearchFunc(sol) # Local Search
        if (sol.cost < bestSol.cost)
            bestSol = sol
        end
        trial += 1
    end
    # Results
    results["time"] = time() - startTime
    results["finalCost"] = bestSol.cost
    results["permutation"] = bestSol.permutation
    results["numTrials"] = trial
    if isnan(params.solutionCost)
        results["gap"] = NaN
    else
        results["gap"] = (bestSol.cost - params.solutionCost) / params.solutionCost
    end
    return bestSol, results
end


filePath = @__DIR__
(@isdefined(SphericalHarmonics_Mod)) ? nothing : include(string(filePath,"\\SphericalHarmonics.jl"))
using .SphericalHarmonics_Mod
using Plots

savepath = string(filePath,"\\anim.gif")

function main(savepath)

    pyplot(size=(1080,1080))

    anim = @animate for l in 0:20, m = 0:l

        x,y,z,Y = Calc_SphericalHarmonics(l,m,500)
        if l==0
            surface(x,y,z, legend = nothing, show = false, axis=([], false))
        else
            surface(x,y,z, legend = nothing, fill_z = abs.(Y), show = false, axis=([], false))
        end
        title!(string("l = ", l, ", m = ", m))

    end

    gif(anim,savepath,fps=2)

    return

end

main(savepath)


########## Collections of some old functions (Not maintained and not exposed to main package) ##########

#### fBm-MLE ####

"""
    fBm_log_likelihood_H(X, H, ψ, G)

Log-likelihood of a fBm model with the optimal volatility.
"""
function fBm_log_likelihood_H(X::AbstractVecOrMat{<:Real}, H::Real, G::AbstractVector{<:Integer}=Int[])
    proc = FractionalBrownianMotion(H)

    Σ::AbstractMatrix = if length(G)>0
        @assert length(G) == size(X,1)
        @assert any(diff(G) .> 0)  # grid points are in increasing order
        covmat(proc, G)
    else
        covmat(proc, size(X,1))
    end

    return log_likelihood_H(Σ, X-X[1])  # relative to the first point
end

"""
Maximum likelihood estimation of Hurst exponent and volatility for fBm.

# Args
- X: sample vector or matrix. For matrix each column is a sample.
- ψ: wavelet filter used for computing `X`.
- G: integer time grid of `X`, by default the regular grid `1:size(X,1)` is used.
- method: `:optim` for optimization based or `:table` for lookup table based procedure
- ε: search hurst in the range [ε, 1-ε]

# Notes
- The MLE is known for its sensitivivity to mis-specification of model. In particular the fGn-MLE is sensitive to NaN value and outliers.
"""
function fBm_MLE_estim(X::AbstractVecOrMat{<:Real}, G::AbstractVector{<:Integer}=Int[]; method::Symbol=:optim, ε::Real=1e-2)
    # @assert 0. < ε < 1.
    if length(G)>0
        @assert length(G) == size(X,1)
        @assert minimum(abs.(diff(sort(G)))) > 0  # all elements are distinct
    end

    func = h -> -fBm_log_likelihood_H(X, h, G)

    opm = nothing
    hurst = nothing

    if method == :optim
        # Gradient-free constrained optimization
        opm = Optim.optimize(func, ε, 1-ε, Optim.Brent())
        # # Gradient-based optimization
        # optimizer = Optim.GradientDescent()  # e.g. Optim.BFGS(), Optim.GradientDescent()
        # opm = Optim.optimize(func, ε, 1-ε, [0.5], Optim.Fminbox(optimizer))
        hurst = Optim.minimizer(opm)[1]
    elseif method == :table
        Hs = collect(ε:ε:1-ε)
        hurst = Hs[argmin([func(h) for h in Hs])]
    else
        throw("Unknown method: ", method)
    end

    proc = FractionalBrownianMotion(hurst)
    Σ = covmat(proc, length(G)>0 ? G : size(X,1))
    σ = sqrt(xiAx(Σ, X) / length(X))
    L = log_likelihood_H(Σ, X)
    # # or equivalently
    # L = log_likelihood(σ^2*Σ, X)

    return hurst, σ, L, opm
end

"""
Accelerated fBm-MLE by dividing a large vector of samples into smaller ones.

The MLE method can be expensive on data of large dimensions due to the inversion of covariance matrix. This function accelerates the MLE method by dividing a large vector `X` into smaller vectors of size `s` downsampled by a factor `l`. The smaller vectors are treated by MLE as i.i.d. samples.

# Args
- X: same as in `fWn_MLE_estim()`
- s: sub window size
- l: length of decorrelation
"""
function fBm_MLE_estim(X::AbstractVector{<:Real}, s::Integer, l::Integer; kwargs...)
    t, V = rolling_vectorize(X, s, 1, l; mode=:causal)
    return fBm_MLE_estim(V; kwargs...)  # The regular grid is implicitely used here.
end

#### fGn-MLE ####
# A special case of fWn-MLE which deserves it own implementation.

function fGn_log_likelihood_H(X::AbstractVecOrMat{<:Real}, H::Real, d::Int, G::AbstractVector{<:Integer}=Int[])
    proc = FractionalGaussianNoise(H, d)

    Σ::AbstractMatrix = if length(G)>0
        @assert length(G) == size(X,1)
        covmat(proc, G)
    else
        covmat(proc, size(X,1))
    end

    return log_likelihood_H(Σ, X)
end

function fGn_MLE_estim(X::AbstractVecOrMat{<:Real}, d::Integer, G::AbstractVector{<:Integer}=Int[]; method::Symbol=:optim, ε::Real=1e-2)
    # @assert 0. < ε < 1.
    if length(G)>0
        @assert length(G) == size(X,1)
        # @assert minimum(abs.(diff(sort(G)))) > 0  # all elements are distinct
    end

    func = h -> -fGn_log_likelihood_H(X, h, d, G)

    opm = nothing
    hurst = nothing

    if method == :optim
        # Gradient-free constrained optimization
        opm = Optim.optimize(func, ε, 1-ε, Optim.Brent())
        # # Gradient-based optimization
        # optimizer = Optim.GradientDescent()  # e.g. Optim.BFGS(), Optim.GradientDescent()
        # opm = Optim.optimize(func, ε, 1-ε, [0.5], Optim.Fminbox(optimizer))
        hurst = Optim.minimizer(opm)[1]
    elseif method == :table
        Hs = collect(ε:ε:1-ε)
        hurst = Hs[argmin([func(h) for h in Hs])]
    else
        error("Unknown method: $method")
    end

    proc = FractionalGaussianNoise(hurst, d)
    Σ = covmat(proc, length(G)>0 ? G : size(X,1))
    σ = sqrt(xiAx(Σ, X) / length(X))
    L = log_likelihood_H(Σ, X)
    # or equivalently
    # L = log_likelihood(σ^2*Σ, X)

    return hurst, σ, L, opm
end

"""
Accelerated fGn-MLE by dividing a large vector of samples into smaller ones.

The MLE method can be expensive on data of large dimensions due to the inversion of covariance matrix. This function accelerates the MLE method by dividing a large vector `X` into smaller vectors of size `s` downsampled by a factor `l`. The smaller vectors are treated by MLE as i.i.d. samples.

# Args
- X, d: same as in `fGn_MLE_estim()`
- s,l: sub window size, length of decorrelation
"""
function fGn_MLE_estim(X::AbstractVector{<:Real}, d::Integer, s::Integer, l::Integer; kwargs...)
    t, V = rolling_vectorize(X, s, 1, l; mode=:causal)
    return fGn_MLE_estim(V, d; kwargs...)
end

# function bspline_scalogram_estim(X::AbstractMatrix{<:Real}, sclrng::AbstractVector{<:Integer}, v::Integer, pows::AbstractVector{<:Real}; kwargs...)

#     bspline_scalogram_estim()
# end

# """
# B-Spline scalogram estimator with a matrix of DCWT coefficients as input. Each column in `W` is a vector of DCWT coefficients.
# """
# function fBm_bspline_scalogram_estim(W::AbstractMatrix{T}, sclrng::AbstractVector{Int}, v::Int; dims::Int=1, mode::Symbol=:center) where {T<:Real}
#     return fBm_bspline_scalogram_estim(var(W,dims), sclrng, v; mode=mode)
# end

# """
# B-Spline scalogram estimator with an array of DCWT coefficients as input. Each row in `W` corresponds to a scale.
# """
# function fBm_bspline_scalogram_estim(W::AbstractVector{T}, sclrng::AbstractVector{Int}, v::Int; mode::Symbol=:center) where {T<:AbstractVector{<:Real}}
#     return fBm_bspline_scalogram_estim([var(w) for w in W], sclrng, v; mode=mode)
# end

"""
Generalized B-Spline scalogram estimator for Hurst exponent and volatility.

# Args
- Σ: covariance matrix of wavelet coefficients.
- sclrng: scale of wavelet transform. Each number in `sclrng` corresponds to one row in the matrix X
- v: vanishing moments
- r: rational ratio defining a line in the covariance matrix, e.g. r=1 corresponds to the main diagonal.
"""
function gen_bspline_scalogram_estim(Σ::AbstractMatrix{T}, sclrng::AbstractVector{Int}, v::Int, r::Rational=1//1; mode::Symbol=:center) where {T<:Real}
    @assert issymmetric(Σ)
    @assert size(Σ,1) == length(sclrng)
    @assert r >= 1
    if r > 1
        all(diff(sclrng/sclrng[1]) .== 1) || error("Imcompatible scales: the ratio between the k-th and the 1st scale must be k")
    end

    p,q,N = r.num, r.den, length(sclrng)
    @assert N>=2p

    # Σ = cov(X, X, dims=2, corrected=true)  # covariance matrix

    yr = [log(abs(Σ[q*j, p*j])) for j in 1:N if p*j<=N]
    xr = [log(sclrng[q*j] * sclrng[p*j]) for j in 1:N if p*j<=N]

    df = DataFrames.DataFrame(xvar=xr, yvar=yr)
    ols = GLM.lm(@GLM.formula(yvar~xvar), df)
    coef = GLM.coef(ols)

    hurst = coef[2]-1/2
    Aρ = Aρ_bspline(0, r, hurst, v, mode)
    σ = exp((coef[1] - log(abs(Aρ)))/2)
    return (hurst, σ), ols

    # Ar = hcat(xr, ones(length(xr)))  # design matrix
    # H0, η = Ar \ yr  # estimation of H and β
    # hurst = H0-1/2
    # Aρ = Aρ_bspline(0, r, hurst, v, mode)
    # σ = ℯ^((η - log(abs(Aρ)))/2)
    # return hurst, σ
end
# const fBm_gen_bspline_scalogram_estim = gen_bspline_scalogram_estim

"""
Compute the covariance matrix of a fWn at some time lag.

# Args
- F: array of band pass filters (no DC component)
- d: time lag
- H: Hurst exponent
"""
function fWn_covmat_lag(F::AbstractVector{<:AbstractVector{T}}, d::DiscreteTime, H::Real) where {T<:Real}
    L = maximum([length(f) for f in F])  # maximum length of filters
    # M = [abs(d+(n-m))^(2H) for n=0:L-1, m=0:L-1]  # matrix comprehension is ~ 10x slower
    M = zeros(L,L)
    for n=1:L, m=1:L
        M[n,m] = abs(d+(n-m))^(2H)
    end
    Σ = -1/2 * [f' * view(M, 1:length(f), 1:length(g)) * g for f in F, g in F]
end

"""
Compute the covariance matrix of a time-concatenated fWn.
"""
function fWn_covmat(F::AbstractVector{<:AbstractVector{T}}, lmax::Int, H::Real) where {T<:Real}
    J = length(F)
    Σ = zeros(((lmax+1)*J, (lmax+1)*J))
    Σs = [fWn_covmat_lag(F, d, H) for d = 0:lmax]

    for r = 0:lmax
        for c = 0:lmax
            Σ[(r*J+1):(r*J+J), (c*J+1):(c*J+J)] = (c>=r) ? Σs[c-r+1] : transpose(Σs[r-c+1])
        end
    end

    return Matrix(Symmetric(Σ))  #  forcing symmetry
end


struct Morlet <: ContWaveClass
    σ::Float64 # \sigma is the time/space trade-off. as sigma->0, the spacial resolution increases; below 5, there is a danger of being non-analytic. Default is 5.8
    κσ::Float64
    cσ::Float64
end
"""
    morl = Morlet(σ::T) where T<: Real

    return the Morlet wavelet with first central frequency parameter σ, which controls the time-frequency trade-off. As σ goes to zero, all of the information becomes spatial. Default is `` 2π``, and it is recommended that you stay within 3-12. Above this range, the overlap between wavelets becomes impractically small. Below it, the mean subtraction term approaches the magnitude of the wavelet, so they become a sum of Gaussians rather than Gaussians.
"""
function Morlet(σ::T) where T <: Real
    κσ = exp(-σ^2 / 2)
    cσ = 1. / sqrt(1 + κσ^2 - 2 * exp(-3 * σ^2 / 4))
    Morlet(σ, κσ, cσ)
end
Morlet() = Morlet(2π)
class(::Morlet) = "Morlet"; name(::Morlet) = "morl"; vanishingmoments(::Morlet) = 0; isAnalytic(::Morlet) = true
const morl = Morlet()
Base.show(io::IO, x::Morlet) = print(io, "Morlet mean $(x.σ)")

struct Morse <: ContinuousWavelets.ContWaveClass
    ga::Float64
    be::Float64
    cf::Float64
end
"""
    morse = Morse(ga::T,be::T,cf::T) where T<: Float64

    return the Morse wavelet with the central frequency parameter cf, gamma parameter ga and beta parameter be.
"""
function Morse_convert(ga::Real, be::Real, cf::Real)
    ga, be, cf = Float64.(ga), Float64.(be), Float64.(cf)
    Morse(ga,be,cf)
end

Morse() = Morse_convert(4, 2, 1)
class(::Morse) = "Morse"; name(::Morse) = "morse";
vanishingmoments(::Morse) = 0; isAnalytic(::Morse) = true
const morse = Morse()
Base.show(io::IO, x::Morse) = print(io, "Morse gamma = $(x.ga), Morse beta = $(x.be), center frequency = $(x.cf)")

# TODO: include a sombrero wavelet, which is dog2.

# Parameterized classes

# abstract type Dog <: ContWaveClass end
# abstract type Paul <: ContWaveClass end

# continuous parameterized
for (TYPE, NAMEBASE, MOMENTS, RANGE, ISAN) in ((:Paul, "paul", -1, 1:20, true), # moments? TODO: is this a good range of parameters?
        (:Dog, "dog",  -1, 0:6, false),)
    @eval begin
        struct $TYPE{N} <: ContWaveClass end# $TYPE end
        class(::$TYPE) = $(string(TYPE))
        name(::$TYPE{N}) where N = string($NAMEBASE, N)
        vanishingmoments(::$TYPE{N}) where N = -1
        order(::$TYPE{N}) where N = N # either order for Paul wavelets, or number of derivatives for DOGs
        Base.show(io::IO, x::$TYPE{N}) where N = print(io, $NAMEBASE * " order $(N)")
        isAnalytic(::$TYPE) = $(ISAN)
    end
    for NUM in RANGE
        CONSTNAME = Symbol(NAMEBASE, NUM)
        @eval begin
            const $CONSTNAME = $TYPE{$NUM}()                  # type shortcut
            export $CONSTNAME
    end
end
end

# this is a bit broken, but could unify these
# abstract type SupaType end
# macro makeType(FAMILY, FORPRINTING, PARAMS, RANGE)
#     parsedParams = mapreduce(x->"$(x[1])::$(x[2])", (a,b) ->a*"\n"*b, @eval($PARAMS))
#     println(parsedParams)
#     @eval begin
#         localParams =
#         struct $FAMILY{N} <: SupaType
#             :($(parsedParams))
#         end
#         family(::$FAMILY) = $(string(FAMILY))
#         order(::$FAMILY{N}) where N = N
#     end
#     # constants for convienence
#     for NUM in RANGE
#         CONSTNAME = Symbol(FAMILY, NUM)
#         @eval begin
#             const $CONSTNAME = $FAMILY{$NUM}()
#             export $CONSTNAME
#         end
#     end
# end
# adapting the orthogonal wavelet classes to the continuous case
struct ContOrtho{OWT} <: ContWaveClass where OWT <: OrthoFilter
    o::OWT
end
ContOrtho(o::W) where W <: WT.OrthoWaveletClass = ContOrtho{typeof(wavelet(o))}(wavelet(o))
class(a::ContOrtho{OWT}) where OWT = "Continuous $(class(a.o))"
name(a::ContOrtho{OWT}) where OWT = "c$(name(a.o))"
vanishingmoments(a::ContOrtho{OWT}) where OWT = vanishingmoments(a.o)
isAnalytic(::ContOrtho) = false
qmf(w::ContOrtho) = w.o.qmf
Base.show(io::IO, x::ContOrtho{W}) where W = print(io, "Continuous $(x.o.name)")

# Single classes
const cHaar = ContOrtho(WT.haar)
const cBeyl = ContOrtho(WT.beyl)
const cVaid = ContOrtho(WT.vaid)
# parametric orthogonal
for (TYPE, NAMEBASE, RANGE) in ((:Daubechies, "Db", 1:10),
        (:Coiflet, "Coif", 2:2:8),
        (:Symlet, "Sym", 4:10),
        (:Battle, "Batt", 2:2:6),)
    for NUM in RANGE
        CONSTNAME = Symbol(string("c", NAMEBASE, NUM))
        @eval begin
            const $CONSTNAME = ContOrtho(WT.$TYPE{$NUM}())        # type shortcut
            export $CONSTNAME
    end
end
end

### averaging types (for now this is already exported by wavelets, so just use that)

abstract type Average end
struct Dirac <: Average end
struct Father <: Average end
struct NoAve <: Average end

function Base.show(io::IO, cf::Dirac)
    print(io, "Dirac")
end
function Base.show(io::IO, cf::Father)
    print(io, "Father Wavelet")
end
function Base.show(io::IO, cf::NoAve)
    print(io, "No Averaging")
end


module aoc_12

export Ship, north, south, east, west, left, right, forward
export distance

abstract type Positionable end

mutable struct Ship
    direction::Int64
    x::Float64
    y::Float64
end

Ship() = Ship( 0, 0, 0 )

north( ship::Ship, dist ) = ship.y += dist
east( ship::Ship, dist )  = ship.x += dist
left( ship::Ship, degrees ) = ship.direction += degrees

south( s, d ) = north( s, -d)
west( s, d ) = east( s, -d)
right( s, d) = left( s, -d )

function forward( ship::Ship, dist )
    ship.x += cosd( ship.direction ) * dist
    ship.y += sind( ship.direction ) * dist
end

distance( ship::Ship ) = abs( ship.x ) + abs( ship.y )

const commands = Dict(
    'N' => north,
    'S' => south,
    'E' => east,
    'W' => west,
    'L' => left,
    'R' => right,
    'F' => forward
)

const regexp = r"([NSEWLRF])([0-9]+)"

function read_instructions( filename , ship::Union{Ship,ShipWithWaypoint} = Ship() )
    open( filename, "r") do io
        for line in eachline( filename )
            m = match( regexp, line )
            commands[ m.captures[1][1] ]( ship, parse( Int64, m.captures[2] ) )
        end
    end
    println( distance(ship) )
    return ship
end

# Part 2
export ShipWithWaypoint

mutable struct Waypoint
    x::Float64
    y::Float64
end
Waypoint() = Waypoint(10,1)
mutable struct ShipWithWaypoint
    s::Ship
    w::Waypoint
end
ShipWithWaypoint() = ShipWithWaypoint(Ship(),Waypoint())
north(s::ShipWithWaypoint, dist) = s.w.y += dist
east(s::ShipWithWaypoint, dist) = s.w.x += dist
function left(s::ShipWithWaypoint, degrees)
    radius = hypot(s.w.y, s.w.x)
    angle = atand(s.w.y, s.w.x )
    angle += degrees
    s.w.x = cosd( angle ) * radius
    s.w.y = sind( angle ) * radius
end
function forward(s::ShipWithWaypoint, dist)
    s.s.x += s.w.x * dist
    s.s.y += s.w.y * dist
end
distance(s::ShipWithWaypoint) = distance( s.s )

end


type BrownianBridge{I <: Integer}
  size_::I
  t::Vector{Float64}
  sqrtdt::Vector{Float64}
  bridgeIndex::Vector{I}
  leftIndex::Vector{I}
  rightIndex::Vector{I}
  leftWeight::Vector{Float64}
  rightWeight::Vector{Float64}
  stdDev::Vector{Float64}
end

function BrownianBridge(tg::TimeGrid)
  size_ = length(tg.times) - 1
  t = zeros(size_)
  for i in eachindex(t)
    t[i] = tg[i + 1]
  end
  bb = BrownianBridge(size_, t, zeros(size_), zeros(Int, size_), zeros(Int, size_), zeros(Int, size_), zeros(size_), zeros(size_), zeros(size_))
  initialize!(bb)

  return bb
end

function BrownianBridge(steps::Int)
  t = Vector{Float64}(steps)
  for i in eachindex(t)
    t[i] = float(i)
  end

  bb = BrownianBridge(steps, t, Vector{Float64}(steps), ones(Int, steps), ones(Int, steps), ones(Int, steps), Vector{Float64}(steps), Vector{Float64}(steps), Vector{Float64}(steps))
  initialize!(bb)

  return bb
end

function initialize!(bb::BrownianBridge)
  bb.sqrtdt[1] = sqrt(bb.t[1])
  for i = 2:bb.size_
    bb.sqrtdt[i] = sqrt(bb.t[i] - bb.t[i-1])
  end

  # mapVec is used to indicate which points are already constructed.
  # if mapVec[i] is zero, path point i is yet unconstructed
  # mapVec[i] - 1 is the index of the variate that constructs path point # i
  mapVec = zeros(Int, bb.size_)

  # the first point in the construction is the global step
  mapVec[bb.size_] = 1
  # the global step is constructed from the first variate
  bb.bridgeIndex[1] = bb.size_
  # the variance of the global step
  bb.stdDev[1] = sqrt(bb.t[bb.size_])
  # the global step to the last point in time is special
  bb.leftWeight[1] = bb.rightWeight[1] = 0.0

  j = 0
  for i = 1:bb.size_ - 1
    # find the next unpopulated entry in the map
    while mapVec[j+1] != 0
      j += 1
    end
    k = j
    # find the next populated entry from there
    while mapVec[k+1] == 0
      k += 1
    end
    # l - 1 is now the index of the point to be constructed next
    l = j + ((k - 1 - j) >> 1)
    mapVec[l+1] = i
    # the i-th Gaussian variate will be used to set point l - 1
    bb.bridgeIndex[i+1] = l + 1
    bb.leftIndex[i+1] = j + 1
    bb.rightIndex[i+1] = k + 1

    if j != 0
      bb.leftWeight[i+1] = (bb.t[k+1] - bb.t[l+1]) / (bb.t[k+1] - bb.t[j])
      bb.rightWeight[i+1] = (bb.t[l+1] - bb.t[j]) / (bb.t[k+1] - bb.t[j])
      bb.stdDev[i+1] = sqrt(((bb.t[l+1] - bb.t[j]) * (bb.t[k+1] - bb.t[l+1])) / (bb.t[k+1] - bb.t[j]))
    else
      bb.leftWeight[i+1] = (bb.t[k+1] - bb.t[l+1]) / bb.t[k+1]
      bb.rightWeight[i+1] = bb.t[l+1] / bb.t[k+1]
      bb.stdDev[i+1] = sqrt(bb.t[l+1] * (bb.t[k+1] - bb.t[l+1]) / bb.t[k+1])
    end
    j = k + 1
    if j >= bb.size_
      # wrap around
      j = 0
    end
  end

  return bb
end

function transform!(bb::BrownianBridge, randomVec::Vector{Float64}, output::Vector{Float64})
  length(randomVec) == bb.size_ || error("size mismatch $(bb.size_) $(length(randomVec))")
  # we use the output to store the path
  output[bb.size_] = bb.stdDev[1] * randomVec[1]

  for i = 2:bb.size_
    j = bb.leftIndex[i]
    k = bb.rightIndex[i]
    l = bb.bridgeIndex[i]

    if j != 1
      output[l] = bb.leftWeight[i] * output[j-1] + bb.rightWeight[i] * output[k] + bb.stdDev[i] * randomVec[i]
    else
      output[l] = bb.rightWeight[i] * output[k] + bb.stdDev[i] * randomVec[i]
    end
  end

  # after which we calculate the variations and normalize to unit times
  for i = bb.size_:-1:2
    output[i] -= output[i-1]
    output[i] /= bb.sqrtdt[i]
  end

  output[1] /= bb.sqrtdt[1]

  return output
end


using DataFrames
using MixedModels
using MixedModelsPermutations
using StableRNGs
using Test

# TODO: add in model cache for the sleepstudy model
isdefined(@__MODULE__, :io) || const io = IOBuffer()

sleepstudy = MixedModels.dataset(:sleepstudy)
m1 = fit(MixedModel, @formula(reaction ~ 1 + days + (1 + days|subj)), sleepstudy)

# TODO: add in testset showing that the OLS ranef match OLS fit to each participant
@testset "olsranef" begin
    # for a balanced design with one blocking variable, these methods should
    # give the same results
    @test all(only(olsranef(m1, :stratum)) .≈ only(olsranef(m1, :simultaneous)))
end

@testset "residuals" begin
    @test residuals(m1) ≈ residuals(m1, ranef(m1))
end


# ----------------------------------------------------
# --- Import modules
# ----------------------------------------------------
using DigitalComm
using Test
using Statistics

# ----------------------------------------------------
# --- Tests
# ----------------------------------------------------
println("Tests for noise addition");

@testset "Testing addNoise.jl..." begin
	# Minimal Tx
	mcs	  = 4;		  # Constellation size
	n	  = Int(log2(mcs));		  # Bit per symbol
	N	  = 4096;	  # Numbver of test symbols
	snrVise	  = 10;
	bitSeq	= genBitSequence(N*n);
	qamSeq	= bitMappingQAM(mcs,bitSeq);
	# Testing that SNR estimation corresponds
	for snr = 0:5:30
		qamNoise,= addNoise(qamSeq,snrVise);
		# Minimal Rx
		bitDec	= bitDemappingQAM(mcs,qamSeq);
		snr		= -10*log10(mean(abs2.(qamNoise .- qamSeq)));
		@test abs2(snr - snrVise) < 0.1
	end
	# Testing that bang method work
	snrVise	  = 10;
	mem	  = zeros(Complex{Float64},N);
	addNoise!(mem,qamSeq,snrVise);
	snr		= -10*log10(mean(abs2.(qamSeq .- mem)));
	@test abs2(snr - snrVise) < 0.1
end

@testset "Testing addCFO.jl..." begin
    # We check with array of ones, to be sure rotation is Ok
    x = ones(2048)
    y = addCFO(x,0,1,0)
    @test length(x) == length(y)
    @test all(x .== y)
    addCFO!(y,x,0,1,0)
    @test all(x .== y)

    # Check it works with complex input
    x = randn(ComplexF64,1024)
    y = addCFO(x,0,1,0)

    # Check that F32 is not promoted to F64
    x2 = randn(ComplexF32,1024)
    y = addCFO(x2,100,1e3,0)
    @test y isa Vector{ComplexF32}

    # Check that CFO is applied
    y = addCFO(x,100,1e3)
    @test y[1] == x[1]
    @test y[2] == x[2] * exp(2im*π*100/1e3)
    @test y[1024] == x[1024] * exp(2im*π*1023*100/1e3)
end


#!/usr/bin/env julia

using Distributions
using FileIO
using JLD2
using Random
using SpecialFunctions

"""
  gendat(nu::Int, t::Int)

Returns Matrix{Float64} - t realisations from t-student multivatiate distribution
with nu degress of freedom
"""

function gendat(nu::Int, t::Int = 150000000)
  cm = [[1. 0.7 0.7 0.7];[0.7 1. 0.7 0.7]; [0.7 0.7 1. 0.7]; [0.7 0.7 0.7 1]]
  p = MvTDist(nu, [0., 0., 0., 0.],cm)
  return Array(transpose(rand(p, t)))
end

"""
  tmom(nu::Int, k::Int)

Returns Float64, the k'th moment of standard t distribution with nu degreed of
freedom
"""
tmom(nu::Int, k::Int) = gamma((k+1)/2)*gamma((nu-k)/2)*nu^(k/2)/(sqrt(pi)*gamma(nu/2))
"""
  tcum(nu::Int, k::Int)

Returns Float64, the k'th cumulant of standard t distribution with nu degreed of
freedom
"""
function tcum(nu::Int, k::Int)
  if k in (1,3,5)
    return 0.
  elseif k == 2
    return tmom(nu, 2)
  elseif k == 4
    return tmom(nu, 4) - 3*tmom(nu, 2)^2
  elseif k == 6
    return tmom(nu, 6) - 15*tmom(nu, 4)*tmom(nu, 2) + 30*tmom(nu, 2)^3
  end
end

function main()
  nu = 14
  Random.seed!(42)
  data = gendat(nu::Int)
  d = Dict{String, Any}("theoretical diag" => Float64[tcum(14, k) for k in 1:6])
  push!(d, "data" => data)
  save("data/datafortests.jld2", d)
end

main()


using FrankWolfe
using ProgressMeter
using Arpack
using Plots
using DoubleFloats
using ReverseDiff

using LinearAlgebra
using Random
import GLPK

s = rand(1:100)
s = 98
@info "Seed $s"
Random.seed!(s)

n = Int(1e2)
k = 3000

xpi = rand(n * n);
total = sum(xpi);
xpi = reshape(xpi, n, n)
const xp = xpi # ./ total;

# better for memory consumption as we do coordinate-wise ops

function cf(x, xp)
    return LinearAlgebra.norm(x .- xp)^2 / n^2
end

function cgrad!(storage, x, xp)
    return @. storage = 2 * (x - xp) / n^2
end

# initial direction for first vertex
direction_vec = Vector{Float64}(undef, n * n)
randn!(direction_vec)
direction_mat = reshape(direction_vec, n, n)

# BirkhoffPolytopeLMO via Hungarian Method
lmo_native = FrankWolfe.BirkhoffPolytopeLMO()

# BirkhoffPolytopeLMO realized via LP solver
lmo_moi = FrankWolfe.convert_mathopt(lmo_native, GLPK.Optimizer(), dimension=n)

# choose between lmo_native (= Hungarian Method) and lmo_moi (= LP formulation solved with GLPK)
lmo = lmo_native

x00 = FrankWolfe.compute_extreme_point(lmo, direction_mat)

FrankWolfe.benchmark_oracles(
    x -> cf(x, xp),
    (str, x) -> cgrad!(str, x, xp),
    () -> randn(n, n),
    lmo;
    k=100,
)

# BCG run

x0 = deepcopy(x00)

@time x, v, primal, dual_gap, trajectoryBCG = FrankWolfe.blended_conditional_gradient(
    x -> cf(x, xp),
    (str, x) -> cgrad!(str, x, xp),
    lmo,
    x0,
    max_iteration=k,
    L=100,
    line_search=FrankWolfe.Adaptive(),
    print_iter=k / 10,
    linesearch_tol=1e-7,
    emphasis=FrankWolfe.memory,
    trajectory=true,
    verbose=true,
);

data = [trajectoryBCG]
label = ["BCG"]

FrankWolfe.plot_trajectories(data, label)


"""
    spectral_mixture_kernel(
        h::Kernel=SqExponentialKernel(),
        αs::AbstractVector{<:Real},
        γs::AbstractMatrix{<:Real},
        ωs::AbstractMatrix{<:Real},
    )

where αs are the weights of dimension (A, ), γs is the covariance matrix of
dimension (D, A) and ωs are the mean vectors and is of dimension (D, A).
Here, D is input dimension and A is the number of spectral components.

`h` is the kernel, which defaults to [`SqExponentialKernel`](@ref) if not specified.

Generalised Spectral Mixture kernel function. This family of functions is  dense
in the family of stationary real-valued kernels with respect to the pointwise convergence.[1]

```math
   κ(x, y) = αs' (h(-(γs' * t)^2) .* cos(π * ωs' * t), t = x - y
```

# References:
    [1] Generalized Spectral Kernels, by Yves-Laurent Kom Samo and Stephen J. Roberts
    [2] SM: Gaussian Process Kernels for Pattern Discovery and Extrapolation,
            ICML, 2013, by Andrew Gordon Wilson and Ryan Prescott Adams,
    [3] Covariance kernels for fast automatic pattern discovery and extrapolation
        with Gaussian processes, Andrew Gordon Wilson, PhD Thesis, January 2014.
        http://www.cs.cmu.edu/~andrewgw/andrewgwthesis.pdf
    [4] http://www.cs.cmu.edu/~andrewgw/pattern/.

"""
function spectral_mixture_kernel(
    h::Kernel,
    αs::AbstractVector{<:Real},
    γs::AbstractMatrix{<:Real},
    ωs::AbstractMatrix{<:Real},
)
    if !(size(αs, 1) == size(γs, 2) == size(ωs, 2))
        throw(DimensionMismatch("The dimensions of αs, γs, ans ωs do not match"))
    end
    if size(γs) != size(ωs)
        throw(DimensionMismatch("The dimensions of γs ans ωs do not match"))
    end

    return sum(zip(αs, eachcol(γs), eachcol(ωs))) do (α, γ, ω)
        a = TransformedKernel(h, LinearTransform(γ'))
        b = TransformedKernel(CosineKernel(), LinearTransform(ω'))
        return α * a * b
    end
end

function spectral_mixture_kernel(
    αs::AbstractVector{<:Real},
    γs::AbstractMatrix{<:Real},
    ωs::AbstractMatrix{<:Real}
)
    return spectral_mixture_kernel(SqExponentialKernel(), αs, γs, ωs)
end

"""
    spectral_mixture_product_kernel(
        h::Kernel=SqExponentialKernel(),
        αs::AbstractMatrix{<:Real},
        γs::AbstractMatrix{<:Real},
        ωs::AbstractMatrix{<:Real},
    )

where αs are the weights of dimension (D, A), γs is the covariance matrix of
dimension (D, A) and ωs are the mean vectors and is of dimension (D, A).
Here, D is input dimension and A is the number of spectral components.

Spectral Mixture Product Kernel. With enough components A, the SMP kernel
can model any product kernel to arbitrary precision, and is flexible even
with a small number of components [1]

`h` is the kernel, which defaults to [`SqExponentialKernel`](@ref) if not specified.

```math
   κ(x, y) = Πᵢ₌₁ᴷ Σ(αsᵢᵀ .* (h(-(γsᵢᵀ * tᵢ)²) .* cos(ωsᵢᵀ * tᵢ))), tᵢ = xᵢ - yᵢ
```

# References:
    [1] GPatt: Fast Multidimensional Pattern Extrapolation with GPs,
        arXiv 1310.5288, 2013, by Andrew Gordon Wilson, Elad Gilboa,
        Arye Nehorai and John P. Cunningham
"""
function spectral_mixture_product_kernel(
    h::Kernel,
    αs::AbstractMatrix{<:Real},
    γs::AbstractMatrix{<:Real},
    ωs::AbstractMatrix{<:Real},
)
    if !(size(αs) == size(γs) == size(ωs))
        throw(DimensionMismatch("The dimensions of αs, γs, ans ωs do not match"))
    end
    return TensorProduct(spectral_mixture_kernel(h, α, reshape(γ, 1, :), reshape(ω, 1, :))
        for (α, γ, ω) in zip(eachrow(αs), eachrow(γs), eachrow(ωs)))
end

function spectral_mixture_product_kernel(
    αs::AbstractMatrix{<:Real},
    γs::AbstractMatrix{<:Real},
    ωs::AbstractMatrix{<:Real}
)
    return spectral_mixture_product_kernel(SqExponentialKernel(), αs, γs, ωs)
end


"""
    lms(setting; iters = nothing, crit = 2.5)

Perform Least Median of Squares regression estimator with random sampling.

# Arguments
- `setting::RegressionSetting`: A regression setting object.
- `iters::Int`: Number of random samples.
- `crit::Float64`: Critical value for standardized residuals.

# Description
LMS (Least Median of Squares) estimator is highly robust with 50% breakdown property. The algorithm
searches for regression coefficients which minimize (h)th ordered squared residual where h is Int(floor((n + 1.0) / 2.0))

# Output
- `["stdres"]`: Array of standardized residuals
- `["S"]`: Standard error of regression
- `["outliers"]`: Array of indices of outliers
- `["objective"]`: LMS objective value
- `["coef"]`: Estimated regression coefficients
- `["crit"]`: Threshold value.

# Examples
```julia-repl
julia> reg = createRegressionSetting(@formula(calls ~ year), phones);

julia> lms(reg)
Dict{Any,Any} with 6 entries:
  "stdres"    => [2.28328, 1.55551, 0.573308, 0.608843, 0.220321, -0.168202, -0.471913, -0.860435, -0.31603, -0.110871  …  85.7265, 88.9849, 103.269, 116.705, 135.229, 159.69,…
  "S"         => 1.17908
  "outliers"  => [14, 15, 16, 17, 18, 19, 20, 21]
  "objective" => 0.515348
  "coef"      => [-56.1972, 1.1581]
  "crit"      => 2.5
```

# References
Rousseeuw, Peter J. "Least median of squares regression." Journal of the American
statistical association 79.388 (1984): 871-880.
"""
function lms(setting::RegressionSetting; iters=nothing, crit=2.5)
    X, y = @extractRegressionSetting setting
    return lms(X, y, iters=iters, crit=crit)
end

function lms(X::Array{Float64,2}, y::Array{Float64,1}; iters=nothing, crit=2.5)
    n, p = size(X)
    h = Int(floor((n + 1.0) / 2.0))
    if iters === nothing
        iters = minimum([500 * p, 3000])
    end
    bestobjective = Inf
    bestparamaters = []
    bestres = []
    indices = collect(1:n)
    kindices = collect(p:n)
    for iter in 1:iters
        try
            k = rand(kindices, 1)[1]
            sampledindices = sample(indices, k, replace=false)
            olsreg = ols(X[sampledindices,:], y[sampledindices])
            betas = coef(olsreg)
            origres = y .- X * betas
            res = sort(origres.^2.0)
            m2 = res[h]
            if m2 < bestobjective
                bestparamaters = betas
                bestobjective = m2
                bestres = origres
            end
        catch e
            @warn e
        end
    end
    s = 1.4826 * sqrt((1.0 + (5.0 / (n - p))) * bestobjective)
    standardizedres = bestres / s
    d = Dict()
    d["coef"] = bestparamaters
    d["objective"] = bestobjective
    d["S"] = s
    d["stdres"] = standardizedres
    d["crit"] = crit
    d["outliers"] = filter(i -> abs(standardizedres[i]) > crit, 1:n)
    return d
end


using Manifolds, Manopt, Test, ManifoldsBase

@testset "Differentials" begin
    p = [1.,0.,0.]
    q = [0.,1.,0.]
    M = Sphere(2)
    X = log(M,p,q)
    @testset "Differentials on Sn(2)" begin
        @test differential_log_basepoint(M,p,p,X) == -X
        @test differential_log_basepoint(M,p,q,X) == -X
        @test differential_log_argument(M,p,p,X) == X
        @test differential_log_argument(M,p,q,X) == zero_tangent_vector(M,q)
        @test differential_exp_basepoint(M,p,zero_tangent_vector(M,p),X) == X
        @test norm(M,q, differential_exp_basepoint(M,p,X,X) - [-π/2, 0., 0.]) ≈ 0 atol=6*10^(-16)
        @test differential_exp_argument(M,p,zero_tangent_vector(M,p),X) == X
        @test norm(M,q,differential_exp_argument(M,p,X,zero_tangent_vector(M,p))) ≈ 0
        for t in [0,0.15,0.33,0.66,0.9]
            @test differential_geodesic_startpoint(M,p,p,t,X) == (1-t)*X
            @test norm(M,p,differential_geodesic_endpoint(M,p,p,t,X) - t*X)  ≈ 0 atol=10.0^(-16)
        end
    end
    @testset "Differentials on Power of Sn(2)" begin
        N = PowerManifold(M, NestedPowerRepresentation(), 3)
        x = [p,q,p]
        y = [p,p,q]
        V = [X, zero_tangent_vector(M,p), -X]
        @test norm(
                N,
                x,
                differential_forward_logs(N,x,V)
                - [-X, [π/2, 0., 0.],zero_tangent_vector(M,p)]
            ) ≈ 0 atol=8*10.0^(-16)
        @test differential_log_argument(N, x, y, V) == [ V[1], V[2], V[2] ]
    end
    @testset "Differentials on SPD(2)" begin
        #
        # Single differentials on Hn
        M2 = SymmetricPositiveDefinite(2)
        p2 = [1. 0.; 0. 1.]
        X2 = [0.5 1.;1. 0.5]
        q2 = exp(M2,p2,X2)
        # Text differentials (1) Dx of Log_xy
        @test norm(M2, p2, differential_log_basepoint(M2, p2, p2, X2) + X2) ≈ 0 atol=4*10^(-16)
        @test norm(M2, q2, differential_log_argument(M2, p2, q2, zero_tangent_vector(M2,p2))) ≈ 0 atol=4*10^(-16)
        @test norm(M2, p2, differential_exp_basepoint(M2, p2, zero_tangent_vector(M2,p2), X2) - X2) ≈ 0 atol=4*10^(-16)
        @test norm(M2, p2, differential_exp_argument(M2,p2,zero_tangent_vector(M2,p2),X2) - X2) ≈ 0 atol=4*10^(-16)
        for t in [0,0.15,0.33,0.66,0.9]
            @test norm(M2, p2, differential_geodesic_startpoint(M2, p2, p2, t, X2) - (1-t)*X2 ) ≈ 0 atol=4*10^(-16)
            @test norm(M2, p2, differential_geodesic_endpoint(M2, p2, p2, t, X2) - t*X2) ≈ 0 atol=4*10.0^(-16)
        end
        @test norm(M2, q2, differential_geodesic_startpoint(M2, p2, q2, 1., X2)) ≈ 0 atol=4*10.0^(-16)
        @test norm(M2, q2, differential_exp_basepoint(M2, p2, X2, zero_tangent_vector(M2,p2) )) ≈ 0 atol=4*10.0^(-16)
        @test norm(M2,q2,differential_exp_argument(M2,p2,X2,zero_tangent_vector(M2,p2))) ≈ 0 atol=4*10.0^(-16)
        # test coeff of log_basepoint, since it is not always expicitly used.
        @test βdifferential_log_basepoint(-1.0, 1.0, 2.0) ≈ -2*cosh(2.0)/sinh(2.0)
    end
    @testset "Differentials on Euclidean(2)" begin
        M3 = Euclidean(2)
        x3 = [1., 2.]
        ξ3 = [1.,0.]
        @test norm(M3, x3, differential_exp_basepoint(M3, x3, ξ3, ξ3) - ξ3) ≈ 0 atol=4*10.0^(-16)
    end
    @testset "Differentials on the Circle" begin
        M = Circle()
        p = 0
        q = π/4
        X = π/8
        @test differential_log_argument(M,p,q,X) == X
    end
end


const A = 𝐹(0)
const B = 𝐹(7)

oncurve(𝑥::FieldElement,𝑦::FieldElement) = 𝑦^2 == 𝑥^3 + A*𝑥 + B

xField = Union{FieldElement, Infinity}

"""
    Point{T<:xField}

`Point{T<:xField}(𝑥::T, 𝑦::T)` represents a point in an scep256k1 field.
`xField` is equal to `Union{FieldElement, Infinity}`
"""
struct Point{T<:xField}
    𝑥::T
    𝑦::T
end

Point(::Infinity,::Infinity) = Point{Infinity}(∞,∞)
Point(𝑥::FieldElement,𝑦::FieldElement) = !oncurve(𝑥,𝑦) ? throw(NotOnCurve()) : Point{FieldElement}(𝑥,𝑦)
Point(𝑥::Integer,𝑦::Integer) = !oncurve(𝐹(𝑥),𝐹(𝑦)) ? throw(NotOnCurve()) : Point{FieldElement}(𝐹(𝑥),𝐹(𝑦))

const G = Point(big"0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
                big"0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")

"Formats Point{FieldElement} as `(𝑥, 𝑦)` in hexadecimal format"
function show(io::IO, z::Point{FieldElement})
    x, y = z.𝑥.𝑛, z.𝑦.𝑛
    print(io, "scep256k1 Point(𝑥,𝑦):\n", string(x, base = 16), ",\n", string(y, base = 16))
end

"Formats Point{Infinity} as `(∞, ∞)`"
function show(io::IO, z::Point{Infinity})
    print(io, "scep256k1 Point(∞, ∞)")
end

"Compares two Point, returns true if coordinates are equal"
==(x::Point, y::Point) = x.𝑥 == y.𝑥 && x.𝑦 == y.𝑦

"""
Returns the point resulting from the intersection of the curve and the
straight line defined by the points P and Q
"""
function +(𝑃::Point,𝑄::Point)
    if 𝑃.𝑥 == ∞
        return 𝑄
    elseif 𝑄.𝑥 == ∞
        return 𝑃
    elseif 𝑃.𝑥 == 𝑄.𝑥 && 𝑃.𝑦 != 𝑄.𝑦
        Point{Infinity}(∞, ∞)

    # Case 1
    elseif 𝑃.𝑥 != 𝑄.𝑥
        λ = (𝑄.𝑦 - 𝑃.𝑦) ÷ (𝑄.𝑥 - 𝑃.𝑥)
        𝑥 = λ^2 - 𝑃.𝑥 - 𝑄.𝑥
    # Case 2
    else
        λ = (3 * 𝑃.𝑥^2 + A) ÷ (2 * 𝑃.𝑦)
        𝑥 = λ^2 - 2 * 𝑃.𝑥
    end
    𝑦 = λ * (𝑃.𝑥 - 𝑥) - 𝑃.𝑦
    return Point{FieldElement}(𝑥, 𝑦)
end

"Scalar multiplication of an Point"
function *(λ::Integer,𝑃::Point)
    𝑅 = Point(∞, ∞)
    λ =  mod(λ, N)
    while λ > 0
        if λ & 1 != 0
            𝑅 += 𝑃
        end
        𝑃 += 𝑃
        λ >>= 1
    end
    return 𝑅
end

"""
    serialize(P::Point; compressed::Bool) -> Vector{UInt8}

Serialize a `Point` to its SEC format
- `compressed=true` by default
"""
function serialize(P::Point; compressed::Bool=true)
    xbin = bytes(P.𝑥.𝑛)
    if length(xbin) < 32
        prepend!(xbin, UInt8.(zeros(32 - length(xbin))))
    end
    if compressed
        if mod(P.𝑦.𝑛, 2) == 0
            prefix = 0x02
        else
            prefix = 0x03
        end
        return pushfirst!(xbin,prefix)
    else
        pushfirst!(xbin, 0x04)
        ybin = bytes(P.𝑦.𝑛)
        if length(ybin) < 32
            prepend!(ybin, UInt8.(zeros(32 - length(ybin))))
        end
        return append!(xbin, ybin)
    end
end

"""
    Point(io::IOBuffer) -> Point

Parse a SEC binary to an `Point`
"""
function Point(io::IOBuffer)
    prefix = read(io, 1)[1]
    if prefix == 4
        𝑥 = to_int(read(io, 32))
        𝑦 = to_int(read(io, 32))
        return Point(𝑥, 𝑦)
    end
    is_even = prefix == 2
    𝑥 = 𝐹(to_int(read(io, 32)))
    α = 𝑥^3 + 𝐹(B)
    β = sqrt(α)
    if mod(β.𝑛, 2) == 0
        evenβ = β
        oddβ = 𝐹(P - β.𝑛)
    else
        evenβ = 𝐹(P - β.𝑛)
        oddβ = β
    end
    if is_even
        return Point(𝑥, evenβ)
    else
        return Point(𝑥, oddβ)
    end
end

Point(sec::Vector{UInt8}) = Point(IOBuffer(sec))


"""
SquaredMatrixPlusIModule (C) 2020, Martin Gurtner
Efficient implementation of algebraic operations on matrices in the form D*D'+λ*I
"""
module SquaredMatrixPlusIModule

using LinearAlgebra

export SquaredMatrixPlusI

mutable struct SquaredMatrixPlusI{T<:Real} <: AbstractArray{T,2}
    # A = D*D' + λI
    D::AbstractArray{T};
    λ::T;
    tmp::Vector{T};

    function SquaredMatrixPlusI(D::AbstractArray{T}, λ::T;) where {T<:Real}
        tmp_size = length(size(D)) == 1 ? 1 : size(D)[2];
        new{T}(D, λ, zeros(T, tmp_size));
    end
end

function Base.size(sqm::SquaredMatrixPlusI)
    (size(sqm.D)[1], size(sqm.D)[1])
end

function LinearAlgebra.mul!(c::Vector{T}, A::SquaredMatrixPlusI{T}, b::Vector{T}) where T<:Real
    # c = (A.D*A.D' + A.λ*I)*b
    mul!(A.tmp, A.D', b);
    mul!(c, A.D, A.tmp);
    c .+= A.λ.*b;
end

end


@doc raw"""
    SymmetricPositiveDefinite{N} <: AbstractDecoratorManifold{𝔽}

The manifold of symmetric positive definite matrices, i.e.

````math
\mathcal P(n) =
\bigl\{
p ∈ ℝ^{n × n}\ \big|\ a^\mathrm{T}pa > 0 \text{ for all } a ∈ ℝ^{n}\backslash\{0\}
\bigr\}
````

The tangent space at ``T_p\mathcal P(n)`` reads

```math
    T_p\mathcal P(n) =
    \bigl\{
        X \in \mathbb R^{n×n} \big|\ X=X^\mathrm{T}
    \bigr\},
```
i.e. the set of symmetric matrices,

# Constructor

    SymmetricPositiveDefinite(n)

generates the manifold $\mathcal P(n) \subset ℝ^{n × n}$
"""
struct SymmetricPositiveDefinite{N} <: AbstractDecoratorManifold{ℝ} end

SymmetricPositiveDefinite(n::Int) = SymmetricPositiveDefinite{n}()

function active_traits(f, ::SymmetricPositiveDefinite, args...)
    return merge_traits(IsEmbeddedManifold(), IsDefaultMetric(LinearAffineMetric()))
end

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

checks, whether `p` is a valid point on the [`SymmetricPositiveDefinite`](@ref) `M`, i.e. is a matrix
of size `(N,N)`, symmetric and positive definite.
The tolerance for the second to last test can be set using the `kwargs...`.
"""
function check_point(M::SymmetricPositiveDefinite{N}, p; kwargs...) where {N}
    if !isapprox(norm(p - transpose(p)), 0.0; kwargs...)
        return DomainError(
            norm(p - transpose(p)),
            "The point $(p) does not lie on $(M) since its not a symmetric matrix:",
        )
    end
    if !all(eigvals(p) .> 0)
        return DomainError(
            eigvals(p),
            "The point $p does not lie on $(M) since its not a positive definite matrix.",
        )
    end
    return nothing
end

"""
    check_vector(M::SymmetricPositiveDefinite, p, X; kwargs... )

Check whether `X` is a tangent vector to `p` on the [`SymmetricPositiveDefinite`](@ref) `M`,
i.e. atfer [`check_point`](@ref)`(M,p)`, `X` has to be of same dimension as `p`
and a symmetric matrix, i.e. this stores tangent vetors as elements of the corresponding
Lie group.
The tolerance for the last test can be set using the `kwargs...`.
"""
function check_vector(M::SymmetricPositiveDefinite{N}, p, X; kwargs...) where {N}
    if !isapprox(norm(X - transpose(X)), 0.0; kwargs...)
        return DomainError(
            X,
            "The vector $(X) is not a tangent to a point on $(M) (represented as an element of the Lie algebra) since its not symmetric.",
        )
    end
    return nothing
end

function get_embedding(M::SymmetricPositiveDefinite)
    return Euclidean(representation_size(M)...; field=ℝ)
end

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

@doc raw"""
    injectivity_radius(M::SymmetricPositiveDefinite[, p])
    injectivity_radius(M::MetricManifold{SymmetricPositiveDefinite,LinearAffineMetric}[, p])
    injectivity_radius(M::MetricManifold{SymmetricPositiveDefinite,LogCholeskyMetric}[, p])

Return the injectivity radius of the [`SymmetricPositiveDefinite`](@ref).
Since `M` is a Hadamard manifold with respect to the [`LinearAffineMetric`](@ref) and the
[`LogCholeskyMetric`](@ref), the injectivity radius is globally $∞$.
"""
injectivity_radius(::SymmetricPositiveDefinite) = Inf
injectivity_radius(::SymmetricPositiveDefinite, p) = Inf
injectivity_radius(::SymmetricPositiveDefinite, ::AbstractRetractionMethod) = Inf
injectivity_radius(::SymmetricPositiveDefinite, p, ::AbstractRetractionMethod) = Inf

@doc raw"""
    manifold_dimension(M::SymmetricPositiveDefinite)

returns the dimension of
[`SymmetricPositiveDefinite`](@ref) `M`$=\mathcal P(n), n ∈ ℕ$, i.e.
````math
\dim \mathcal P(n) = \frac{n(n+1)}{2}.
````
"""
@generated function manifold_dimension(::SymmetricPositiveDefinite{N}) where {N}
    return div(N * (N + 1), 2)
end

"""
    mean(
        M::SymmetricPositiveDefinite,
        x::AbstractVector,
        [w::AbstractWeights,]
        method = GeodesicInterpolation();
        kwargs...,
    )

Compute the Riemannian [`mean`](@ref mean(M::AbstractManifold, args...)) of `x` using
[`GeodesicInterpolation`](@ref).
"""
mean(::SymmetricPositiveDefinite, ::Any)

function default_estimation_method(::SymmetricPositiveDefinite, ::typeof(mean))
    return GeodesicInterpolation()
end

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

project a matrix from the embedding onto the tangent space $T_p\mathcal P(n)$ of the
[`SymmetricPositiveDefinite`](@ref) matrices, i.e. the set of symmetric matrices.
"""
project(::SymmetricPositiveDefinite, p, X)

project!(::SymmetricPositiveDefinite, Y, p, X) = (Y .= Symmetric((X + X') / 2))

@doc raw"""
    rand(M::SymmetricPositiveDefinite; σ::Real=1)

Generate a random symmetric positive definite matrix on the
`SymmetricPositiveDefinite` manifold `M`.
"""
rand(M::SymmetricPositiveDefinite; σ::Real=1)

function Random.rand!(
    M::SymmetricPositiveDefinite{N},
    pX;
    vector_at=nothing,
    σ::Real=one(eltype(pX)) / (vector_at === nothing ? 1 : norm(vector_at)),
    tangent_distr=:Gaussian,
) where {N}
    if vector_at === nothing
        D = Diagonal(1 .+ rand(N)) # random diagonal matrix
        s = qr(σ * randn(N, N)) # random q
        pX .= Symmetric(s.Q * D * transpose(s.Q))
    elseif tangent_distr === :Gaussian
        # generate ONB in TxM
        I = one(vector_at)
        B = get_basis(M, vector_at, DiagonalizingOrthonormalBasis(I))
        Ξ = get_vectors(M, vector_at, B)
        Ξx =
            vector_transport_to.(
                Ref(M),
                Ref(I),
                Ξ,
                Ref(vector_at),
                Ref(ParallelTransport()),
            )
        pX .= sum(σ * randn(length(Ξx)) .* Ξx)
    elseif tangent_distr === :Rician
        C = cholesky(Hermitian(vector_at))
        R = C.L + sqrt(σ) * triu(randn(size(vector_at, 1), size(vector_at, 2)), 0)
        pX .= R * R'
    end
    return pX
end
function Random.rand!(
    rng::AbstractRNG,
    M::SymmetricPositiveDefinite{N},
    pX;
    vector_at=nothing,
    σ::Real=one(eltype(pX)) / (vector_at === nothing ? 1 : norm(vector_at)),
    tangent_distr=:Gaussian,
) where {N}
    if vector_at === nothing
        D = Diagonal(1 .+ rand(rng, N)) # random diagonal matrix
        s = qr(σ * randn(rng, N, N)) # random q
        pX .= Symmetric(s.Q * D * transpose(s.Q))
    elseif tangent_distr === :Gaussian
        # generate ONB in TxM
        I = one(vector_at)
        B = get_basis(M, vector_at, DiagonalizingOrthonormalBasis(I))
        Ξ = get_vectors(M, vector_at, B)
        Ξx =
            vector_transport_to.(
                Ref(M),
                Ref(I),
                Ξ,
                Ref(vector_at),
                Ref(ParallelTransport()),
            )
        pX .= sum(σ * randn(rng, length(Ξx)) .* Ξx)
    elseif tangent_distr === :Rician
        C = cholesky(Hermitian(vector_at))
        R = C.L + sqrt(σ) * triu(randn(rng, size(vector_at, 1), size(vector_at, 2)), 0)
        pX .= R * R'
    end
    return pX
end

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

Return the size of an array representing an element on the
[`SymmetricPositiveDefinite`](@ref) manifold `M`, i.e. $n × n$, the size of such a
symmetric positive definite matrix on $\mathcal M = \mathcal P(n)$.
"""
@generated representation_size(::SymmetricPositiveDefinite{N}) where {N} = (N, N)

function Base.show(io::IO, ::SymmetricPositiveDefinite{N}) where {N}
    return print(io, "SymmetricPositiveDefinite($(N))")
end

@doc raw"""
    zero_vector(M::SymmetricPositiveDefinite,x)

returns the zero tangent vector in the tangent space of the symmetric positive
definite matrix `x` on the [`SymmetricPositiveDefinite`](@ref) manifold `M`.
"""
zero_vector(::SymmetricPositiveDefinite, ::Any)

zero_vector!(::SymmetricPositiveDefinite{N}, v, ::Any) where {N} = fill!(v, 0)


abstract type _Function end

mutable struct Variable
    data::Array
    grad::Array
    creator::_Function
    Variable(data) = new(data)
end
function backward(v::Variable)::Nothing
    if isdefined(v, :creator)
        f::_Function = v.creator
        x::Variable = f.input
        x.grad = backward(f, v.grad)
        backward(x)
    end
end

function _function(f::_Function, forward::Function, input::Variable)::Variable
    x = input.data
    y::Array = forward(x)
    output = Variable(y)
    output.creator = f
    f.input = input
    f.output = output
    return output
end

mutable struct Square <: _Function
    input::Variable
    output::Variable
    Square() = new()
end
(s::Square)(input::Variable)::Variable = _function(s, x -> x .^ 2, input)
backward(s::Square, gy::Array) = 2 * s.input.data .* gy

mutable struct Exp <: _Function
    input::Variable
    output::Variable
    Exp() = new()
end
(s::Exp)(input::Variable)::Variable = _function(s, x -> exp.(x), input)
backward(e::Exp, gy::Array) = exp.(e.input.data) .* gy


using Gadfly

n = 10
plot(x=rand(n), y=rand(n), color=sqrt(rand(n)), Scale.color_sqrt)


using ConstraintSolver
CS = ConstraintSolver

function main(filename; benchmark=false)
    com = CS.init()

    lines = readlines(filename)
    num_colors = 0
    x = nothing
    for line in lines
        parts = split(line, " ")
        if parts[1] == "p"
            num_colors = parse(Int, parts[3])
            x = Vector{CS.Variable}(undef, num_colors)
            for i=1:num_colors
                x[i] = add_var!(com, 1, num_colors)
            end
        elseif parts[1] == "e"
            f = parse(Int, parts[2])
            t = parse(Int, parts[3])
            add_constraint!(com, x[f] != x[t])
        end
    end
    println("num_colors: ", num_colors)

    set_objective!(com, :Min, CS.vars_max(x))

    status = solve!(com)

    if !benchmark
        println("status: ", status)
        println("objective: ", com.best_sol)
        com.info
    end
end


using Logic
using Base.Test

# number
@test zero == identity(Number, +)
@test zero == identity(Number, -)
@test one == identity(Number, *)
@test one == identity(Number, /)
@test one == identity(Number, \)

@test (-) == inverse(Number, +)
@test (+) == inverse(Number, -)
@test :(1/n) == inverse(Number, *)
@test :(1\n) == inverse(Number, /)
@test :(n/1) == inverse(Number, \)

a = Number(5)
@test a == a + identity(a, +)
@test a == a - identity(a, -)
@test a == a * identity(a, *)
@test a == a / identity(a, /)
@test a == identity(a, \) \ a

@test 0 == a + inverse(a, +)
@test 0 == a - inverse(a, -)
@test 1 == a * inverse(a, *)
@test 1 == a / inverse(a, /)
@test 1 == inverse(a, \) \ a

# matrix
@test eye == identity(Matrix, *)
@test inv == inverse(Matrix, *)

A = [4 7; 2 6]
@test A == A * identity(A, *)
@test [1 0;0 1] ≈ A * inverse(A, *)
@test [1 0 0; 0 1 0; 0 0 1] == identity([3 2 1; 4 3 2; 4 4 3], *)

# logic
@test zero == logic(Number, +, identity)
@test one == logic(Number, *, identity)
@test (-) == logic(Number, +, inverse)
@test :(1/n) == logic(Number, *, inverse)
@test eye == logic(Matrix, *, identity)
@test inv == logic(Matrix, *, inverse)
@test 0 == logic(5, +, identity)
@test 1 == logic(5, *, identity)
@test 0 == 5 + logic(5, +, inverse)
@test 1 == 5 * logic(5, *, inverse)
@test [1 0;0 1] == logic([4 7;2 6], *, identity)
@test [0.6 -0.7;-0.2 0.4] ≈ logic([4 7;2 6], *, inverse)


using Flux

function build_model(name="MNIST"; kw...)
    return @eval $(Symbol(:build_, name))(;$kw...)
end

# Build model
function build_MNIST(; imgsize = (28,28, 1), nclasses = 10)
    cnn_output_size = Int.(floor.([imgsize[1]/8,imgsize[2]/8,32]))

    return Chain(
    # First convolution, operating upon a 28x28 image
    Conv((3, 3), imgsize[3]=>16, pad=(1,1), relu),
    MaxPool((2,2)),

    # Second convolution, operating upon a 14x14 image
    Conv((3, 3), 16=>32, pad=(1,1), relu),
    MaxPool((2,2)),

    # Third convolution, operating upon a 7x7 image
    Conv((3, 3), 32=>32, pad=(1,1), relu),
    MaxPool((2,2)),

    # Reshape 3d tensor into a 2d one using `Flux.flatten`, at this point it should be (3, 3, 32, N)
    flatten,
    Dense(prod(cnn_output_size), 10))
end

# VGG16
function build_CIFAR10()
    Chain(
        Conv((3, 3), 3 => 64, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(64),
        Conv((3, 3), 64 => 64, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(64),
        MaxPool((2,2)),
        Conv((3, 3), 64 => 128, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(128),
        Conv((3, 3), 128 => 128, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(128),
        MaxPool((2,2)),
        Conv((3, 3), 128 => 256, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(256),
        Conv((3, 3), 256 => 256, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(256),
        Conv((3, 3), 256 => 256, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(256),
        MaxPool((2,2)),
        Conv((3, 3), 256 => 512, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(512),
        Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(512),
        Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(512),
        MaxPool((2,2)),
        Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(512),
        Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(512),
        Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),
        BatchNorm(512),
        MaxPool((2,2)),
        flatten,
        Dense(512, 4096, relu),
        Dropout(0.5),
        Dense(4096, 4096, relu),
        Dropout(0.5),
        Dense(4096, 10)
    )
end


using ImageCore, StatsBase
include("b_idx.jl")

function map_contrast_to_brightness(img)

    #= assigning an image to a matrix object for now. I will be using
    high and low contrast values later. =#

    #= Compute the laplacian of the gray scale of the image. obtain
    the max value using minMacLoc. call it maxval. Estimate your
    sharpness/brightness index as - (maxval * average V channel values)
    / (average of the hist values from 0th channel), as said above. This
    would give certain values. Low bright images are usually below 30.
    30 - 50 can b taken as ok images. and above 50 as bright images.
    After that, a standardize function is called to keep the range from 0.5
    to 10.0. =#

    t = size(img)
    rows = t[1]
    columns = t[2]
    h, v = br_ind(img)
    h_avg = h/(rows*columns)
    v_avg = v/(rows*columns)

    ahigh, alow = compute_contr(img)

    brightness = (ahigh * v_avg)/h_avg

    min = 0.5
    max = 10

    std_bright = min + ((brightness-min)/((max-min)/2))*(max-min)
    return std_bright
end