@testset "Gauss–Radau" begin
    n = 1
    x, w = gaussradau(n)
    @test x[1] ≈ -1.
    @test w[1] ≈ 2.

    n = 2
    x, w = gaussradau(n)
    @test x[1] ≈ -1.
    @test x[2] ≈ 1/3
    @test w[1] ≈ .5
    @test w[2] ≈ 1.5

    tol = 1e-14
    n = 42
    ntests = 4
    x, w = gaussradau(n)
    @test length(x) == n && length(w) == n
    @test abs(x[37] - 0.908847278001044) < tol
    @test abs(w[37] - 0.031190846817016) < tol
    @test x[1] == -1

    @test dot( w,exp.(x)) ≈ exp(1)-exp(-1)
end


# project x1 on to x2
project(x1, x2) = (x1' * x2) / (x2' * x2) * x2

# compute intercept and slope from two points
function get_coefs(x1, x2)
     β1 = (x2[2] - x1[2]) / (x2[1] - x1[1])
     return x2[2] - β1 * x2[1], β1
end

# make a line
make_line(β0, β1, x) = β0 .+ β1 .* x


immutable State
    intercept::Float64
    slope::Float64
end

immutable Datum
    x::Float64
    y::Float64
end

immutable LinregMHSampler <: Sampler
    num_steps::Int
    prior_std::Float64
    noise_std::Float64
    data::Array{Datum,1}
    function LinregMHSampler(x::Array{Float64,1}, y::Array{Float64,1},
                            prior_std::Float64, noise_std::Float64, num_steps::Int)
        @assert length(x) == length(y)
        data = Array{Datum,1}()
        for (xi, yi) in zip(x, y)
            push!(data, Datum(xi, yi))
        end
        new(num_steps, prior_std, noise_std, data)
    end
end

function prior_sample(sampler::LinregMHSampler)
    intercept = rand(Normal(0., sampler.prior_std))
    slope = rand(Normal(0., sampler.prior_std))
    State(intercept, slope)
end

function log_prior(sampler::LinregMHSampler, state::State)
    lp = logpdf(Normal(0., sampler.prior_std), state.intercept)
    lp += logpdf(Normal(0., sampler.prior_std), state.slope)
    lp
end

function log_likelihood(sampler::LinregMHSampler, state::State)
    ll = 0.0
    for datum in sampler.data
        y_expected = state.intercept + state.slope * datum.x
        ll += logpdf(Normal(y_expected, sampler.noise_std), datum.y)
    end
    ll
end

function log_unnormalized_posterior(sampler::LinregMHSampler, state::State)
    log_prior(sampler, state) + log_likelihood(sampler, state)
end

function imh_step(sampler::LinregMHSampler, state::State)
    # perform one step of independent MH
    # propose from prior
    state_proposed = prior_sample(sampler)
    if log(rand()) <= log_likelihood(sampler, state_proposed) - log_likelihood(sampler, state)
        # accept
        new_state = state_proposed
    else
        # reject
        new_state = state
    end
    new_state
end

function run_markov_chain(sampler::LinregMHSampler, state::State, steps::Int)
    for step in 1:steps
        state = imh_step(sampler, state)
    end
    state
end

function run_inference(sampler::LinregMHSampler)
    init_state = prior_sample(sampler)
    state = run_markov_chain(sampler, init_state, sampler.num_steps)
    # returns the initial and final state of the Markov chain
    # the initial state is all we need from the trace
    return (init_state, state)
end

function log_weight(sampler::LinregMHSampler, init_state::State, output::State)
    lw = 0.
    lw += log_prior(sampler, init_state)
    lw += log_unnormalized_posterior(sampler, output)
    lw -= log_unnormalized_posterior(sampler, init_state)
    lw
end

function simulate(sampler::LinregMHSampler)
    (init_state, state) = run_inference(sampler)
    ([state.intercept, state.slope], log_weight(sampler, init_state, state))
end

function regenerate(sampler::LinregMHSampler, intercept_and_slope::Array{Float64,1})
    @assert length(intercept_and_slope) == 2
    intercept, slope = intercept_and_slope
    state = State(intercept, slope)
    init_state = run_markov_chain(sampler, state, sampler.num_steps)
    log_weight(sampler, init_state, state)
end

immutable PriorSampler <: Sampler
    x::Array{Float64,1}
    y::Array{Float64,1}
    prior_std::Float64
    noise_std::Float64
end

function simulate(sampler::PriorSampler)
    dist = Normal(0., sampler.prior_std)
    intercept = rand(dist)
    slope = rand(dist)
    ([intercept, slope], logpdf(dist, intercept) + logpdf(dist, slope))
end

function regenerate(sampler::PriorSampler, intercept_and_slope::Array{Float64,1})
    (intercept, slope) = intercept_and_slope
    dist = Normal(0., sampler.prior_std)
    logpdf(dist, intercept) + logpdf(dist, slope)
end


export sbesselj, shankelh1, diffsbessel, diffbessel
export diffbesselj, diffhankelh1, diffsbesselj, diffshankelh1
export gaunt_coefficient
export associated_legendre_indices, spherical_harmonics_indices, lm_to_spherical_harmonic_index
export spherical_harmonics
export cartesian_to_radial_coordinates, radial_to_cartesian_coordinates
export atan

#NOTE spherical bessel and hankel functions soon coming to SpecialFunctions.jl https://github.com/JuliaMath/SpecialFunctions.jl/pull/196

#NOTE atan(x,y) is defined here but will likely be added to base soon.

import Base.atan
atan(y::Complex,x::Complex) = - im * log( (x+y*im) / sqrt(x^2+y^2) )

"""
    sbesselj(m,x)

Returns the spherical besselj function. The order is 'm' and the argument is 'x'. Note 'x' can be a complex number.
"""
# function sbesselj(m::Number,x::T) where T<: Union{F,Complex{F}} where F <: AbstractFloat
    # if (abs(x) > eps(F))
    #     return sqrt(pi/(T(2)*x)) * besselj(m+1/2,x)
    # else
    #     return (m > 0 ? zero(T) : one(T))
    # end
# end
function sbesselj(nu::Number, x::T) where {T}
    besselj_nuhalf_x = besselj(nu + one(nu)/2, x)
    if abs(x) ≤ sqrt(eps(real(zero(besselj_nuhalf_x))))
        nu == 0 ? one(besselj_nuhalf_x) : zero(besselj_nuhalf_x)
    else
        √((float(T))(π)/2x) * besselj_nuhalf_x
    end
end

"""
    shankelh1(m,x)

Returns the spherical hankel function of the first kind. The order is 'm' and the argument is 'x'. Note 'x' can be a complex number.
"""
shankelh1(nu::Number,x::T) where T = √((float(T))(π)/2x) * hankelh1(nu+one(nu)/2,x)

"""
    diffsbessel(f::Function,m,x)

Differentiates the spherical bessel function 'f' (for any spherical bessel). The order is 'm' and the argument is 'x'. Note 'x' can be a complex number.
"""
function diffsbessel(f::Function,n::Number,z::Number)
    return f(n-1,z) - (n+1) * f(n,z) / z
end

function diffsbesselj(n::Number,z::Number)
    return if n == 0
        - sbesselj(1,z) # case due to numerical stability
    else
        sbesselj(n-1,z) - (n+1) * sbesselj(n,z) / z
    end
end

function diffshankelh1(n::Number,z::Number)
    return shankelh1(n-1,z) - (n+1) * shankelh1(n,z) / z
end

"""
    diffbessel(f::Function,m,x,n::Int)

Differentiates 'n' times any bessel function 'f' of order 'm' and at the argument 'x'.
"""
function diffbessel(f::Function,m::Number,z,n::Int)
    if n == 0
        return f(m, z)
    elseif n > 0
        n = n - 1
        return 0.5*(diffbessel(f,m-1,z,n) - diffbessel(f,m+1,z,n))
    else
        error("Can not differentiate a negative number of times")
    end
end

"""
    diffhankelh1(m,x,n::Int)

Differentiates 'n' times the hankelh1 function of order 'm' and at the argument 'x'.
"""
function diffhankelh1(m::Number,z::T,n::Int) where T<:Number
    if n == 0
        return hankelh1(m, z)
    elseif n > 0
        n = n - 1
        return 0.5*(diffhankelh1(m-1,z,n) - diffhankelh1(m+1,z,n))
    else
        error("Can not differentiate a negative number of times")
    end
end

"Derivative of Hankel function of the first kind"
diffhankelh1(n,z) = 0.5*(hankelh1(-1 + n, z) - hankelh1(1 + n, z))

"Derivative of Bessel function of first kind"
diffbesselj(n::Number,z::T) where T<:Number = 0.5*(besselj(-1 + n, z) - besselj(1 + n, z))

"m-th Derivative of Hankel function of the first kind"
function diffbesselj(n::Number,z::T,m::Int) where T<:Number
    if m == 0
        return besselj(n, z)
    elseif m > 0
        m = m - 1
        return T(0.5)*(diffbesselj(n-1,z,m) - diffbesselj(n+1,z,m))
    else
        error("Can not differentiate a negative number of times")
    end
end

"""
    gaunt_coefficient(l1,m1,l2,m2,l3,m3)

A version of the Gaunt coefficients which are used to write the product of two spherical harmonics. If Y_{l,m} is a complex spherical harmonic, with the typical phase conventions from quantum mechanics, then:

    gaunt_coefficient(l1,m1,l2,m2,l3,m3) = 4*π*im^{l2+l3-l1} Integral[Y_{l1,m1}*conj(Y_{l2,m2})*conj(Y_{l3,m3})]

where the integral is over the solid angle.

The most standard gaunt coefficients `G(l1,m1;l2,m2;l3)` are related through the identity:

    4pi * G(l1,m1;l2,m2;l3) = im^(l1-l2-l3) * (-1)^m2 * gaunt_coefficient(l1,m1,l2,-m2,l3,m1+m2)

"""
function gaunt_coefficient(T::Type{<:AbstractFloat},l1::Int,m1::Int,l2::Int,m2::Int,l3::Int,m3::Int)
    # note the wigner3j has only one convention, and is highly symmetric.
    return (one(T)*im)^(l2+l3-l1) * (-T(1))^m1 * sqrt(4pi*(2*l1+1)*(2*l2+1)*(2*l3+1)) *
        wigner3j(T,l1,l2,l3,0,0,0) * wigner3j(T,l1,l2,l3,m1,-m2,-m3)
end
gaunt_coefficient(l1::Int,m1::Int,l2::Int,m2::Int,l3::Int,m3::Int) = gaunt_coefficient(Float64,l1,m1,l2,m2,l3,m3)

lm_to_spherical_harmonic_index(l::Int,m::Int)::Int = l^2 + m + l + 1

function spherical_harmonics_indices(l_max::Int)
    ls = [l for l in 0:l_max for m in -l:l]
    ms = [m for l in 0:l_max for m in -l:l]

    return ls, ms
end

function associated_legendre_indices(l_max::Int)
    ls = [l for l in 0:l_max for m in 0:l]
    ms = [m for l in 0:l_max for m in 0:l]

    return ls, ms
end

"""
`spherical_harmonics(l_max::Int, θ::T, φ::T)`

returns a vector of all spherical harmonics with degree `l <= l_max`. The degree and order (indices) of the elements of the vector are given by `spherical_harmonics_indices(l_max::Int)`.

The associated legendre polynomials are taken from the package GSL.jl.
"""
function spherical_harmonics(l_max::Int, θ::Complex{T}, φ::Union{T,Complex{T}}) where T <: AbstractFloat

    throw(DomainError(θ, "Currently GLS.jl is used to calculate associated legendre polynomials, which only take real angles as arguments. Hopefully soon SpecialFunctions.jl will implement associated Legendre for complex arguments: https://github.com/JuliaMath/SpecialFunctions.jl/pull/175"))

end
function spherical_harmonics(l_max::Int, θ::T, φ::T) where T <: AbstractFloat

    ls, ms = associated_legendre_indices(l_max)
    Plm_arr = sf_legendre_array(GSL_SF_LEGENDRE_SPHARM, l_max, cos(θ))[eachindex(ls)]

    Ylm_vec = Vector{Complex{T}}(undef, (l_max+1)^2)
    Ylm_vec[1] = Plm_arr[1]

    ind1 = 1
    ind2 = 1
    for i = 1:l_max
        inds1 = (ind1+i):(ind1+2i)
        Ylm_vec[(ind2+i):(ind2+3i)] = [reverse((-1).^ms[inds1[2:end]] .* conj(Plm_arr[inds1[2:end]])); Plm_arr[inds1]]
        ind1 += i
        ind2 += 2i
    end

    ls, ms = spherical_harmonics_indices(l_max)
    Ylm_vec = (-one(T)) .^ ms .* exp.(ms .* (im*φ)) .* Ylm_vec

    return Ylm_vec
end

cartesian_to_radial_coordinates(x::Vector) = cartesian_to_radial_coordinates(SVector(x...))
radial_to_cartesian_coordinates(θ::Vector) = radial_to_cartesian_coordinates(SVector(θ...))

function cartesian_to_radial_coordinates(x::SVector{3,CT}) where CT
    r = sqrt(sum(x .^2)) # note this is, and should be, a complex number when x is a complex vector
    θ = atan(sqrt(x[1]^2+x[2]^2),x[3])
    φ = (!iszero(x[1]) || !iszero(x[2])) ? atan(x[2], x[1]) : zero(CT)
    return [r,θ,φ]
end

function cartesian_to_radial_coordinates(x::SVector{2,CT}) where CT
    r = sqrt(sum(x .^2)) # note this should be complex if x is complex
    θ = atan(x[2], x[1])
    return [r,θ]
end

function radial_to_cartesian_coordinates(rθφ::SVector{3,CT}) where CT
    r, θ, φ = rθφ
    x = r * sin(θ) * cos(φ)
    y = r * sin(θ) * sin(φ)
    z = r * cos(θ)

    return [x,y,z]
end

function radial_to_cartesian_coordinates(rθ::SVector{2,CT}) where CT
    r, θ = rθ
    x = r * cos(θ)
    y = r * sin(θ)

    return [x,y]
end


module SUN_generator
    using LinearAlgebra

    struct Generator
        NC::Int64
        generator::Array{Array{ComplexF64,2},1}
        function Generator(NC)
            return new(NC,make_generators(NC))
        end
    end

    function Base.getindex(g::Generator,i)
        return g.generator[i]
    end

    function Base.length(a::Generator)
        return length(a.generator)
    end

    function lie2matrix!(matrix,g::Generator,a)
        matrix .= 0
        NC = g.NC
        for (i,genmatrix) in enumerate(g.generator)
            matrix .+= a[i]*genmatrix
        end
        return
    end

    function matrix2lie!(a,g::Generator,A)
        for i=1:length(a)
            #println("i = $i")
            #display(g.generator[i]*g.generator[i])
            #println("\t")
            #println(tr(g.generator[i]*A)/2)
            a[i] = tr(g.generator[i]*A)/2
        end
        return
    end

    sigma = []
    s = ComplexF64[
        0 1
        1 0
    ]
    push!(sigma,s)
    s = ComplexF64[
        0 -im
        im 0
    ]
    push!(sigma,s)
    s = ComplexF64[
        1 0
        0 -1
    ]
    push!(sigma,s)

    function make_largematrix(σ,i,j,Nc)
        λ = zeros(ComplexF64,Nc,Nc)
        λ[i,i] = σ[1,1]
        λ[i,j] = σ[1,2]
        λ[j,i] = σ[2,1]
        λ[j,j] = σ[2,2]
        return λ
    end

    function normalization(A)
        Norm = sqrt(real(tr(A*A))/2)
        #print("    Normalization = √",real(tr(A*A))/2,"= ")
        #println("$Norm")
        return Norm
    end

    function make_generators(Nc)
        d=["x","y","z"]
        #
        lams = Array{Complex,2}[]
        #
        if Nc < 2
            error("Invalid Nc=$Nc")
        end
        #
        # off-diagonal part
        #println("Off diagonals")
        for i=1:Nc
            for j=i+1:Nc
                for a = 1:2
                    #println("$(d[a]): i j = $i $j")
                    A = make_largematrix(sigma[a],i,j,Nc)
                    #showmat(A)
                    N=normalization(A)
                    A = A/N
                    push!(lams,A)
                    #println(" ")
                end
            end
        end
        #
        # diagonal part
        #println("diagonals")
        for a=1:Nc-1
            A = zeros(ComplexF64,Nc,Nc)
            for i=1:a
                A[i,i] = 1
            end
            A[a+1,a+1] = -tr(A)
            #showmat(A)
            N=normalization(A)
            A = A/N
            push!(lams,A)
            #println(" ")
        end
        return lams
    end
end


"""
    Chebyshev( xmin, xmax, nx )

Chebyshev nodes

"""
struct Chebyshev <: NodesType

    nx    :: Int64
    xmin  :: Float64
    xmax  :: Float64
    xk    :: Vector{Float64}

    function Chebyshev( xmin, xmax, nx )

        xk  = zeros(Float64, nx)
        θ = (π:-π/(nx-1):-1e-14)
        xk  .= ((cos.(θ).+1)*(xmax-xmin))/2 .+ xmin
        new( nx, xmin, xmax, xk )

    end

end


using Statistics
function fit(x; axis = 1)
    if axis == 2
        x = x'
    end
    if ndims(x) == 1
        p = [quantile(x, 1/4), quantile(x, 1/2), quantile(x, 3/4)]
    else
        p = []
        for i in 1:size(x)[2]
            push!(p, [quantile(x[:, i], 1/4), quantile(x[:, i], 1/2), quantile(x[:, i], 3/4)])
        end
    end
    return p
end
function transform(x, p; axis = 1)
    if axis == 2
        x = x'
    end
    if ndims(x) == 1
        t = (x .- p[2]) / (p[3] - p[1])
    else
        t = []
        for i in 1:size(x)[2]
            push!(t, (x[:, i] .- p[i][2]) / (p[i][3] - p[i][1]))
        end
    end
    return t
end
function inverse_transform(x, p; axis = 1)
    if axis == 2
        x = x'
    end
    if ndims(x) == 1
        t = (x * (p[3] - p[1])) .+ p[2]
    else
        t = []
        for i in 1:size(x)[2]
            push!(t, (x[:, i][1] * (p[i][3] - p[i][1])) .+ p[i][2])
        end
    end
    return t
end
function fit_transform(x; axis = 1)
    if axis == 2
        x = x'
    end
    if ndims(x) == 1
        p = [quantile(x, 1/4), quantile(x, 1/2), quantile(x, 3/4)]
        t = (x .- p[2]) / (p[3] - p[1])
    else
        t, p = [], []
        for i in 1:size(x)[2]
            push!(p, [quantile(x[:, i], 1/4), quantile(x[:, i], 1/2), quantile(x[:, i], 3/4)])
            push!(t, (x[:, i] .- p[i][2]) / (p[i][3] - p[i][1]))
        end
    end
    return t, p
end


# Used "Conjugate Bayesian analysis of the Gaussian distribution" by Murphy as
# a reference.  Note that there were some typos in that document so the code
# here may not correspond exactly.

struct NormalGamma{T<:Real} <: ContinuousUnivariateDistribution
    mu::T
    nu::T     # scales precision of Normal
    shape::T
    rate::T

    function NormalGamma{T}(mu::T, nu::T, sh::T, r::T) where T<:Real
    	nu > zero(nu) && sh > zero(sh) && r > zero(r) || error("Both shape and scale must be positive")
    	new(T(mu), T(nu), T(sh), T(r))
    end
end

function NormalGamma(mu::Real, nu::Real, sh::Real, r::Real)
    T = promote_type(typeof(mu), typeof(nu), typeof(sh), typeof(r))
    return NormalGamma{T}(T(mu),T(nu),T(sh),T(r))
end

mu(d::NormalGamma) = d.mu
nu(d::NormalGamma) = d.nu
shape(d::NormalGamma) = d.shape
scale(d::NormalGamma) = 1. / d.rate
rate(d::NormalGamma) = d.rate

insupport(::Type{NormalGamma}, x::T, tau2::T) where T<:Real =
    isfinite(x) && zero(tau2) <= tau2 < Inf

# Probably should guard agains dividing by and taking the log of 0.
function pdf(d::NormalGamma, x::T, tau2::T) where T<:Real
    Zinv = d.rate.^d.shape / gamma(d.shape) * sqrt(d.nu / (2.0*pi))
    return Zinv * tau2.^(d.shape-0.5) * exp(-0.5*tau2*(d.nu*(x-d.mu).^2 + 2.0*d.rate))
end
function logpdf(d::NormalGamma, x::T, tau2::T) where T<:Real
    lZinv = d.shape*log(d.rate) - lgamma(d.shape) + 0.5*(log(d.nu) - log(2.0*pi))
    return lZinv + (d.shape-0.5)*log(tau2) - 0.5*tau2*(d.nu*(x-d.mu).^2 + 2*d.rate)
end

function rand(d::NormalGamma)
    # Guard against invalid precisions
    tau2 = rand(Gamma(d.shape, scale(d)))
    if tau2 <= zero(Float64)
        tau2 = eps(Float64)
    end
    mu = rand(Normal(d.mu, sqrt(1.0/(tau2*d.nu))))
    return mu, tau2
end


using LinearAlgebra

function backtrackLineSearch(xInit, dirΔ, f, dfdx, paramA, paramB, verbose = false)
    # Output an updated x

    if verbose
        println("\nBeginning Line Search")
        println("xInit = $xInit")
        println("dirΔ = $dirΔ")
        println("objective function (current x) = $(f(xInit))")
        println("Parameters: $paramA and $paramB")
        println("Verbose = $verbose \n")
    end

    # First, initializae α = 1. This is the "learning rate"

    α = 1
    if verbose
        print("Left side: ")
        println(f(xInit + α * dirΔ))
        print("Right side: ")
        print(f(xInit))
        print(" + ")
        print(paramA * α * dirΔ'dfdx(xInit))
        print(" = ")
        println(f(xInit) + paramA * α * dirΔ'dfdx(xInit))
    end

    # Check the condition:
    # f(x + alpha * dir) > f(x) + a * grad f of (alpha * delta)
    while f(xInit + α * dirΔ) > (f(xInit) + paramA * α * (dirΔ'dfdx(xInit))[1])
        # If the condition passes, set α to b * α
        if verbose
            print("Left side: ")
            println(f(xInit + α * dirΔ))
            print("Right side: ")
            print(f(xInit))
            print(" + ")
            print(paramA * α * dirΔ'dfdx(xInit))
            print(" = ")
            println(f(xInit) + paramA * α * dirΔ'dfdx(xInit))
        end

        α = paramB * α

        if verbose
            println(α)
        end
    end

    # the updated x is xInit - alpha * gradf(xInit)
    xUpdated = xInit + α * dirΔ
    # Return updated x
    return xUpdated, α
end

runTest = false

if runTest
    # Test Script

    # One Dimensional Function
    xInit1 = 600
    fFun(x) = x^2
    dfdx(x) = 2 * x
    dir = -dfdx(xInit1)
    aTest = 0.3
    bTest = 0.707

    xNew = backtrackLineSearch(xInit1, dir, fFun, dfdx, aTest, bTest, true)
    println("New x = $xNew")
    println("Result should by 0.181")

    # Two Dimensional Function
    xInit2 = [600, 400]
    fFun(x) = x'x
    dfdx(x) = 2 * x
    dir2 = -dfdx(xInit2)
    aTest = 0.3
    bTest = 0.707

    xNew = backtrackLineSearch(xInit2, dir2, fFun, dfdx, aTest, bTest, true)
    println("New x = $xNew")
    println("Result should by 0.181")
end


using FFTW, DSP

const h1 = [-8, 2, -9, -2, 9, -8, -2]
const f1 = [ 6, -9, -7, -5]
const g1 = [-48, 84, -16, 95, 125, -70, 7, 29, 54, 10]

const h2nested = [
      [-8, 1, -7, -2, -9, 4],
      [4, 5, -5, 2, 7, -1],
      [-6, -3, -3, -6, 9, 5]]
const f2nested = [
      [-5, 2, -2, -6, -7],
      [9, 7, -6, 5, -7],
      [1, -1, 9, 2, -7],
      [5, 9, -9, 2, -5],
      [-8, 5, -2, 8, 5]]
const g2nested = [
      [40, -21, 53, 42, 105, 1, 87, 60, 39, -28],
      [-92, -64, 19, -167, -71, -47, 128, -109, 40, -21],
      [58, 85, -93, 37, 101, -14, 5, 37, -76, -56],
      [-90, -135, 60, -125, 68, 53, 223, 4, -36, -48],
      [78, 16, 7, -199, 156, -162, 29, 28, -103, -10],
      [-62, -89, 69, -61, 66, 193, -61, 71, -8, -30],
      [48, -6, 21, -9, -150, -22, -56, 32, 85, 25]]

const h3nested = [
      [[-6, -8, -5, 9], [-7, 9, -6, -8], [2, -7, 9, 8]],
      [[7, 4, 4, -6], [9, 9, 4, -4], [-3, 7, -2, -3]]]
const f3nested = [
      [[-9, 5, -8], [3, 5, 1]],
      [[-1, -7, 2], [-5, -6, 6]],
      [[8, 5, 8],[-2, -6, -4]]]
const g3nested = [
      [  [54, 42, 53, -42, 85, -72],
         [45, -170, 94, -36, 48, 73],
         [-39, 65, -112, -16, -78, -72],
         [6, -11, -6, 62, 49, 8]],
      [  [-57, 49, -23, 52, -135, 66],
         [-23, 127, -58, -5, -118, 64],
         [87, -16, 121, 23, -41, -12],
         [-19, 29, 35, -148, -11, 45]],
      [  [-55, -147, -146, -31, 55, 60],
         [-88, -45, -28, 46, -26, -144],
         [-12, -107, -34, 150, 249, 66],
         [11, -15, -34, 27, -78, -50]],
      [  [56, 67, 108, 4, 2, -48],
         [58, 67, 89, 32, 32, -8],
         [-42, -31, -103, -30, -23, -8],
         [6, 4, -26, -10, 26, 12]]]

function flatnested2d(a, siz)
    ret = zeros(Int, prod(siz))
    for i in 1:length(a), j in 1:length(a[1])
        ret[siz[2] * (i - 1) + j] = a[i][j]
    end
    Float64.(ret)
end

function flatnested3d(a, siz)
    ret = zeros(Int, prod(siz))
    for i in 1:length(a), j in 1:length(a[1]), k in 1:length(a[1][1])
        ret[siz[2] * siz[3] * (i - 1) + siz[3] * (j - 1) + k] = a[i][j][k]
    end
    Float64.(ret)
end

topow2(siz) = map(x -> nextpow(2, x), siz)
deconv1d(f1, g1) = Int.(round.(deconv(Float64.(g1), Float64.(f1))))

function deconv2d(f2, g2, xd2)
    siz = topow2([length(g2), length(g2[1])])
    h2 = Int.(round.(real.(ifft(fft(flatnested2d(g2, siz)) ./ fft(flatnested2d(f2, siz))))))
    [[h2[siz[2] * (i - 1) + j] for j in 1:xd2[2]] for i in 1:xd2[1]]
end

function deconv3d(f3, g3, xd3)
    siz = topow2([length(g3), length(g3[1]), length(g3[1][1])])
    h3 = Int.(round.(real.(ifft(fft(flatnested3d(g3, siz)) ./ fft(flatnested3d(f3, siz))))))
    [[[h3[siz[2] * siz[3] *(i - 1) + siz[3] * (j - 1) + k] for k in 1:xd3[3]]
        for j in 1:xd3[2]] for i in 1:xd3[1]]
end

deconvn(f, g, tup=()) = length(tup) < 2 ? deconv1d(f, g) :
                       length(tup) == 2 ? deconv2d(f, g, tup) :
                       length(tup) == 3 ? deconv3d(f, g, tup) :
                       println("Array nesting > 3D not supported")

deconvn(f1, g1)  # 1D
deconvn(f2nested, g2nested, (length(h2nested), length(h2nested[1]))) # 2D
println(deconvn(f3nested, g3nested,
    (length(h3nested), length(h3nested[1]), length(h3nested[1][1])))) # 3D


using Plots: plot3d, plot, Animation, frame, gif, GridLayout, EmptyLayout, create_grid
using StaticArrays

"""
    Body(m, x, y, z, v)

Create a celestial body object with mass `m` kilograms, position `x` `y` `z`,
and 3-component velocity vector `v` (m/s).
"""
mutable struct Body{T <: Float64}
	# The mass of the body
	m::T

	# Its coords
	x::T
	y::T
	z::T

	# Its velocity as a vector of x y z values
	v::MVector{3, T}
end

"""
    getforce(b1, b2) -> float

Return the magnitude of the force between Body objects `b1` and `b2`.

Uses the standard gravitational equation.

See also: [`getforcevector`](@ref)
"""
function getforce(b1::Body, b2::Body)::Float64
	a = b1.x - b2.x
	b = b1.y - b2.y
	c = b1.z - b2.z

	r2 = a^2 + b^2 + c^2
	if r2 == 0; error("Cannot divide by 0. The positions of two bodies cannot be the same."); end

	# G \frac{m_1 m_2}{r^2}
	(6.674e-11 * b1.m * b2.m) / r2
end

"""
    getforcevector(b1, b2) -> [float, float, float]

Return the 3D vector of the force from Body object `b1` towards `b2`.

Expressed as a 3-component vector. Magnitude can be computed
by `getforce(b1, b2)`. Angles must be computed separately.

See also: [`getforce`](@ref)
"""
function getforcevector(b1::Body, b2::Body)::MVector{3, Float64}
	# azimuth is the angle on the x-y plane
	Δx = b2.x - b1.x
	Δy = b2.y - b1.y

	azimuth = atan(Δy, Δx)

	# altitude is the angle from the x-y plane in the z axis
	hyp = sqrt(Δx^2 + Δy^2)
	Δz = b2.z - b1.z
	altitude = atan(Δz, hyp)

	F = getforce(b1, b2)

	# We use F to compute the z component and the new hypotenuse to use
	# for computing the x and y components
	vec_z = F * sin(altitude)
	xy_plane_hyp = F * cos(altitude)

	# We then do some more trig to compute the x and y components
	vec_y = xy_plane_hyp * sin(azimuth)
	vec_x = xy_plane_hyp * cos(azimuth)

	MVector{3, Float64}(vec_x, vec_y, vec_z)
end

"""
    step!(bodies, Δt)

Apply forces between all Body objects in `bodies` over time step `Δt` seconds.
"""
function step!(bodies::Vector{Body}, Δt::Float64)
	# Calculate the force on each body and update its velocity accordingly
	for i in 1:length(bodies)
		b = bodies[i]

		forceonbody = copy(b.v)
		# We loop over every body and if it's not the same one, we add the add the force
		for j in 1:length(bodies)
			if j != i
				# We add the body's velocity to the force applied by gravity
				# to get the resultant force
				forceonbody .+= getforcevector(b, bodies[j])
			end

		end

		# Update the velocity of the body

		# a = (v - u)/t and F = ma ⟹  F = m * (v - u)/t
		# ⟹  v = Ft/m + u

		# Since u is the initial value, we can just use +=
		# We actually need .+= here to broadcast over the MVector
		b.v .+= ((forceonbody * Δt) / b.m)
	end

	# We need to wait until we've calculated all the forces before we start to change the positions,
	# otherwise we would change a position and then calculate a force based on the new position,
	# rather than the old one
	for i in 1:length(bodies)
		b = bodies[i]

		# Then we update the position of the body using its velocity
		b.x += Δt * b.v[1]
		b.y += Δt * b.v[2]
		b.z += Δt * b.v[3]
	end
end

"""
    drawframes(bodies, framecount, Δt)

Return frame data for the list of Body objects `bodies` with time step `Δt`.

This frame data is a Vector{Vector{SVector{3, Float64}}}. It can be thought of as a list of frames,
where each frame has a list of bodies, and each body has a list of x y z coordinates.

See also: [`drawgif`](@ref), [`creategif`](@ref)
"""
function drawframes(bodies::Vector{Body}, framecount::Int, Δt::Float64)::Vector{Vector{SVector{3, Float64}}}
	frames::Vector{Vector{SVector{3, Float64}}} = [[SVector{3, Float64}(bodies[i].x, bodies[i].y, bodies[i].z) for i in 1:length(bodies)]]

	for _ in 1:framecount
		step!(bodies, Δt)

		# We build the position data for this frame
		thisframe::Vector{SVector{3, Float64}} = [SVector{3, Float64}(bodies[i].x, bodies[i].y, bodies[i].z) for i in 1:length(bodies)]

		push!(frames, thisframe)
	end

	return frames
end

"""
    drawgif(positions, cube=false, bounds=nothing, filename=nothing)

Draw a GIF using the position data.

`positions` is a Vector{Vector{SVector{3, Float64}}}. It can be thought of as a list of frames,
where each frame has a list of bodies, and each body has a list of x y z coordinates.

`cube` is a bool for whether to draw the plot bounds as a cube.

`bounds` is a Vector{Tuple{Float64, Float64}} - a list of xlimits, ylimits, and zlimits.
If it's nothing (by default), then the bounds will be auto-generated.

`filename` is an optional filename. If none is provided, one will be programmatically generated,
following the pattern of `out.gif`, `out_1.gif`, `out_2.gif`, etc.

See also: [`drawframes`](@ref), [`creategif`](@ref)
"""
function drawgif(positions::Vector{Vector{SVector{3, Float64}}}, cube::Bool, bounds::Union{Nothing, Vector{Tuple{Float64, Float64}}}=nothing, filename::Union{String, Nothing}=nothing)
	# This is a multiplier to make the bounding box just a bit bigger than strictly necessary,
	# just to make it look a bit nicer
	multiplier = 1.05

	if cube && !isnothing(bounds)
		# We get the maximum absolute value in the bounds and use that to build the cube
		maxbound = multiplier * max(abs.(Iterators.flatten(bounds))...)

		xlimits = (-1 * maxbound, maxbound)
		ylimits = (-1 * maxbound, maxbound)
		zlimits = (-1 * maxbound, maxbound)

	elseif !cube && !isnothing(bounds)
		# We just use the bounds given, multiplied by the multiplier
		xlimits = multiplier .* bounds[1]
		ylimits = multiplier .* bounds[2]
		zlimits = multiplier .* bounds[3]

	elseif cube && isnothing(bounds)
		# This line gets absolute value of every float in positions and then gets the max
		# Because we're dealing with a nested vector, we have to flatten it, and then
		# flatten the result, and then splat it for max
		maxbound = multiplier * max(abs.(Iterators.flatten(Iterators.flatten(positions)))...)

		xlimits = (-1 * maxbound, maxbound)
		ylimits = (-1 * maxbound, maxbound)
		zlimits = (-1 * maxbound, maxbound)

	# Not cube or explicit bounds
	else
		# These list comprehensions loop through the list to get the coords
		# i is every frame in the positions list, and j is every body in each frame
		# We only need to check length(positions[1]) to get j because the length of
		# every sub-list is the same because every frame has the same number of bodies
		xs = [positions[i][j][1] for i in 1:length(positions) for j in 1:length(positions[1])]
		ys = [positions[i][j][2] for i in 1:length(positions) for j in 1:length(positions[1])]
		zs = [positions[i][j][3] for i in 1:length(positions) for j in 1:length(positions[1])]

		xlimits = multiplier .* extrema(xs)
		ylimits = multiplier .* extrema(ys)
		zlimits = multiplier .* extrema(zs)
	end

	# This is just the number of bodies
	n = length(positions[1])

	# We have 4 different subplots. xyz is the 3D perspective camera angle. The others are the orthogonal views.
	# We then put these subplots into a full plot to show everything comprehensively
	xyz = plot3d(n; legend=false, lw=1, title="$n Body Gravity Simulation", xlim=xlimits, ylim=ylimits, zlim=zlimits)
	# We're using standard 2D line plots here to avoid issues with cameras in 3D space
	xy = plot(n; legend=false, lw=0.5, title="Plan", titlefontsize=8, xlim=xlimits, ylim=ylimits, tickfontsize=4)
	xz = plot(n; legend=false, lw=0.5, title="Front", titlefontsize=8, xlim=xlimits, ylim=zlimits, tickfontsize=4)
	yz = plot(n; legend=false, lw=0.5, title="Side", titlefontsize=8, xlim=ylimits, ylim=zlimits, tickfontsize=4)

	# This is the expanded form of the @gif macro over a for loop
	# We're using the expanded form rather than the macro itself because
	# that lets us control the filename
	anim = Animation()
	counter = 1
	# For each frame
	for i = 1:length(positions)
		# For each body in this frame
		for j in 1:length(positions[i])
			# We add the position data of every body to its respective series in the plots
			bodydata = positions[i][j]
			push!(xyz, j, bodydata[1], bodydata[2], bodydata[3])
			push!(xy, j, bodydata[1], bodydata[2])
			push!(xz, j, bodydata[1], bodydata[3])
			push!(yz, j, bodydata[2], bodydata[3])
		end

		# Only if this counter is a multiple of 10, do we add this frame to the animation
		if counter % 10 == 0
			# I can't directly import the @layout macro, so we do it this way
			layout = eval(create_grid(:([a{0.7h}; b c d])))
			frame(anim, plot(xyz, xy, xz, yz; layout = layout, size = (1000, 1000)))
		end

		counter += 1
	end

	if isnothing(filename)
		if !in("out.gif", readdir())
			filename = "out.gif"
		else
			i = 1
			while in("out_$i.gif", readdir())
				i += 1
			end
			filename = "out_$i.gif"
		end
	end

	gif(anim, filename)
end

"""
    creategif(bodies, framecount, Δt, cube=false, bounds=nothing, filename=nothing)

Generate a GIF from the list of Body objects `bodies`, with `framecount` frames and a time step of `Δt` seconds.

`cube` is a bool for whether to draw the plot bounds as a cube.

`bounds` is a Vector{Tuple{Float64, Float64}} - a list of xlimits, ylimits, and zlimits.
If it's nothing (by default), then the plot bounds will be auto-generated.

`filename` is an optional filename. If none is provided, one will be programmatically generated,
following the pattern of `out.gif`, `out_1.gif`, `out_2.gif`, etc.

See also: [`drawgif`](@ref), [`drawframes`](@ref)
"""
creategif(bodies::Vector{Body}, framecount::Int, Δt::Float64, cube::Bool=false, bounds::Union{Nothing, Vector{Tuple{Float64, Float64}}}=nothing, filename::Union{String, Nothing}=nothing) = drawgif(drawframes(bodies, framecount, Δt), cube, bounds, filename)


# In this exercise we will try to predict the average housing value in suburbs of Boston given some characteristics of the suburb.

# These are the detailed attributes of the dataset:
#   1. CRIM      per capita crime rate by town
#   2. ZN        proportion of residential land zoned for lots over 25,000 sq.ft.
#   3. INDUS     proportion of non-retail business acres per town
#   4. CHAS      Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
#   5. NOX       nitric oxides concentration (parts per 10 million)
#   6. RM        average number of rooms per dwelling
#   7. AGE       proportion of owner-occupied units built prior to 1940
#   8. DIS       weighted distances to five Boston employment centres
#   9. RAD       index of accessibility to radial highways
#   10. TAX      full-value property-tax rate per $10,000
#   11. PTRATIO  pupil-teacher ratio by town
#   12. B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
#   13. LSTAT    % lower status of the population
#   14. MEDV     Median value of owner-occupied homes in $1000's

# Further information concerning this dataset can be found on [this file](https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.names)

# Our prediction concern the median value (column 14 of the dataset)

# 1) Start by setting the working directory to the directory of this file and activate it. If you have the provided `Manifest.toml` file in the directory, just run `Pkg.instantiate()`, otherwise manually add the packages Pipe, HTTP, CSV, DataFrames, Plots and BetaML.
# Also, seed the random seed with the integer `123`.

# 2) Load the packages/modules Pipe, HTTP, CSV, DataFrames, Plots, BetaML

# 3) Load from internet or from local file the input data into a DataFrame or a Matrix.
# You will need the CSV options `header=false` and `ignorerepeated=true``
dataURL="https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data"

# 4) The 4th column is a dummy related to the information if the suburb bounds a certain Boston river. Use the BetaML function `oneHotEncoder` to encode this dummy into two separate vectors, one for each possible value. Note that you will need to transform the range {0,1} into {1,2} before running the oneHotEncoder function (this can be done by simply uinsg `data[:,4] .+1`)

# 5) Now create the X matrix of features concatenating horizzontaly the 1st to 3rd column of `data`, the 5th to 13th columns and the two columns you created with the one hot encoding. Make sure you have a 506×14 matrix.

# 6) Similarly define Y to be the 14th column of data

# 7) Partition the data in (`xtrain`,`xval`) and (`ytrain`,`yval`) keeping 80% of the data for training and reserving 20% for testing. Keep the default option to shuffle the data, as the input data isn't.

# 8) Define a Neural Network model with the following characteristics:
#   - 3 dense layers with respectively 14, 20 and 1 nodes and activation function `relu`
#   - cost function `squaredCost`

# 9) Train your model using `ytrain` and a scaled version of xtrain (where all columns have zero mean and 1 standard deviaiton) for 400 epochs and use a batch size of 6 records.
# Save the output of your training function to `trainingLogs`

# 10) Predict the training labels ŷtrain and the validation labels ŷval. Recall you did the training on the scaled features!

# 11) Compute the train and test relative mean error using the function `meanRelError` with the parameter `normRec` set to `false`

# 12) Run the following commands to plots the average loss per epoch and the true vs estimation validation values
plot(trainingLogs.ϵ_epochs)
scatter(yval,ŷval,xlabel="true values", ylabel="estimated values", legend=nothing)

# 13) (Optional) Run the same workflow without scaling the data. How this affect the quality of your predictions ?


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# ╔═╡ 28042615-8f2b-41fa-9b14-28d69180debb
using Pkg; Pkg.develop(path=".."); Pkg.activate("..")

# ╔═╡ 3ec6aae0-c233-11eb-355d-3dfe780e33bc
begin
	using DataFrames
	using JuMP
	using Ipopt
	using StatsPlots
	plotly()
end

# ╔═╡ a6997248-aee3-4ebb-8a1e-f791ad503aa8
begin
	n = 100
	μ1 = 10
	μ2 = 15

	df = append!(
		DataFrame(x1=randn(n).+μ1, x2=randn(n).+μ1, y=ones(n)),
		DataFrame(x1=randn(n).+μ2, x2=randn(n).+μ2, y=-ones(n))
	)

	p = @df df scatter(:x1, :x2, group=:y, legend=:topleft)
end

# ╔═╡ bf7d0459-4b47-4839-800d-01bdcd500633
md"
$arg\ min_{𝐰, b}\ \frac{1}{2}𝐰^T𝐰$

$\text{subject to }\forall i, y_i(𝐰^T𝐱_i+b) \geq 1$
"

# ╔═╡ 27dfce4e-3ccd-4221-8eca-144ae87e2998
md"
respect to

$arg\ min_{𝐰, b}\ \frac{1}{2}𝐮^T 𝐐 𝐮 + 𝐏^T 𝐮$

$\text{subject to }\forall i, 𝐚_i^T 𝐮) \geq c_i$

Therefore,

$𝐮 = \begin{bmatrix}b \\ 𝐰\end{bmatrix}$
$𝐐 = \begin{bmatrix}0 & 0^T_d \\ 0^T_d & 𝐈_d\end{bmatrix}$
$𝐏 = 0_{d+1}$
$𝐚^T_i = y_i\begin{bmatrix}1 & 𝐱^T_i\end{bmatrix}$
$c_i = 1$
"

# ╔═╡ 5056cc0a-f832-47b3-a6a5-f7ee2d7bc5ba
begin
	x = Matrix(df[:, 1:2])
	y = Vector(df[:, 3])

	function solve_svm(x, y)
		svm = Model(Ipopt.Optimizer)
		@variable(svm, w[1:2])
		@variable(svm, b)
		@objective(svm, Min, 0.5 * w' * w)
		@constraint(svm, y.*(x * w .+ b) .>= 1)
		optimize!(svm)

		@show objective_value(svm)

		return value.(w), value(b)
	end

	w, b = solve_svm(x, y)
end

# ╔═╡ e5334dab-56b2-4dd0-a12c-e17f1c8e584b
ŷ(x1, x2) = sign([x1, x2]' * w + b)

# ╔═╡ 16448a5a-090f-4a6a-a9a0-55192a77dfb3
heatmap!(p, 5:0.1:20, 5:0.1:20, ŷ, color=:coolwarm)

# ╔═╡ Cell order:
# ╟─28042615-8f2b-41fa-9b14-28d69180debb
# ╠═3ec6aae0-c233-11eb-355d-3dfe780e33bc
# ╠═a6997248-aee3-4ebb-8a1e-f791ad503aa8
# ╟─bf7d0459-4b47-4839-800d-01bdcd500633
# ╟─27dfce4e-3ccd-4221-8eca-144ae87e2998
# ╠═5056cc0a-f832-47b3-a6a5-f7ee2d7bc5ba
# ╠═e5334dab-56b2-4dd0-a12c-e17f1c8e584b
# ╠═16448a5a-090f-4a6a-a9a0-55192a77dfb3


using Findpeaks
using Test

gaussian(x,μ,σ) = 1/sqrt(2*π)/σ*exp(-((x-μ)^2)/2/σ^2)
x = collect(range(1,stop=1000,length=1000))
μ = rand(x)
data = gaussian.(x,μ,rand(x)/3)

p = findpeaks(data)
@test length(p) == 1
@test p[1] == μ
p = findpeaks(data,x)
@test length(p) == 1
@test p[1] == μ


function solve_002()
    fib_vec = [1, 2]
    i = 3
    # Generate fibonacci numbers
    while fib_vec[end] < 4000000
        push!(fib_vec, fib_vec[i-1] + fib_vec[i-2])
        i += 1
    end
    # Sum even terms
    sum(filter(x -> x % 2 == 0, fib_vec))
end


module SampleProject

export sin_sum, cos_sum, geometric_series, pi_gregory_leibniz, pi_nilakantha

sin_sum(n) = sum(sin, range(0, 2pi, length=n))

cos_sum(n) = sum(cos, range(0, 2pi, length=n))

geometric_series(a, r, n) = sum(k -> a * r^k, 0:n)

pi_gregory_leibniz(n) = sum(k -> 4/(4k+1) - 4/(4k+3), 0:n)

pi_nilakantha(n) = 3 + sum(k -> 4/((4k-2)*(4k-1)*4k) - 4/(4k*(4k+1)*(4k+2)), 1:n)

end


module Shamir

using Polynomials

function l(x, j, k)
"""
    Create a Lagrange basis polynomial
    Reference: https://wikimedia.org/api/rest_v1/media/math/render/svg/6e2c3a2ab16a8723c0446de6a30da839198fb04b
"""
    polys = []
    for m in 1:k
        if m != j
            d = x[j] - x[m]
            r = Poly([-1 * x[m], 1]) / d
            push!(polys, r)
        end
    end

    #println(polys)

    return prod(polys)
end

function L(x, y, k)
"""
    Create a linear combination of Lagrange basis polynomials
    Reference: https://wikimedia.org/api/rest_v1/media/math/render/svg/d07f3378ff7718c345e5d3d4a57d3053190226a0
"""

    s = []

    for j in 1:k
        r = y[j] * l(x, j, k)
        push!(s, r)
    end

    #println(sum(s))

    return sum(s)
end

function construct_shares(n, production_poly)
"""
    Create shares of the secret

    Parameters:
    n: Total number of shares
    production_poly : The Polynomial created with the secret
                    to produce secret shares
"""

    share = []

    for i in 1:n
        push!(share, [i, production_poly(i)])
    end

    return share
end

function recover_secret(shares, n, k, p)
"""
    Recover the secret by finding the coefficient of x^0 i.e.
    when the x=0 in the polynomial.

    Parameters:
    shares : An array of shares of the individuals.
    n : total number of shares
    k: minimum number of shares required to unravel the secret
    p: Field number (to restrict the computation space)
"""
    if length(shares) < k
        throw("Need more parties!")
    end

    x = []
    y = []
    for i in 1:n
        push!(x, shares[i][1])
        push!(y, shares[i][2])
    end

    f = L(x, y, k)
    f = mod(f(0), p)

    return f
end

export Shamir

end


export mean_squared_error

"""
    MeanSquaredError <: Func
"""
@func mutable struct MeanSquaredError end

function forward(f::MeanSquaredError, x1, x2)
    diff = x1 .- x2
    y = sum(diff.^2) / length(diff)
    return y
end

function backward(f::MeanSquaredError, gy)
    x1, x2 = f.args
    diff = x1 - x2
    gx1 = gy .* diff .* (2.0 / length(diff))
    gx2 = -gx1
    return gx1, gx2
end

mean_squared_error(x1, x2) = MeanSquaredError()(x1, x2)


function find_energy(img)

    energy_x = imfilter(brightness.(img), Kernel.sobel()[2])
    energy_y = imfilter(brightness.(img), Kernel.sobel()[1])

    return sqrt.(energy_x.^2 + energy_y.^2)
end

function find_energy_map(energy)

    sz = size(energy)
    energy_map = zeros(size(sz))
    energy_map[end,:] = energy[end,:]

    next_elements = zeros(Int, sz)

    for i in sz[1]-1:-1:1, j in sz[2]
        left = max(1, j-1)
        right = min(j+1, sz[2])
        local_energy, next_element = findmin(@view energy_map[i+1, left:right])

        # Minimal energy to bottom
        energy_map[i,j] += local_energy + energy[i,j]

        # Element that minimizes the energy
        next_elements[i,j] = next_element - 2

        if left == 1
            next_elements[i,j] += 1
        end
    end

    return energy_map, next_elements
end

"""
Just looks up the minimal path from the `next_elements` array.
"""
function find_seam_at(next_elements, element)

    seam = zeros(Int, size(next_elements)[1])
    seam[1] = element
    for i = 2:length(seam)
        seam[i] = seam[i-1] + next_element[i, seam[i-1]]
    end

    return seam
end

function find_seam(energy)

    energy_map, next_elements = find_energy_map(energy)

    _, min_element = findmin(@view energy_map[1,:])

    return find_seam_at(next_elements, min_element)
end


function e006(N)
    return difference(N)
end

function difference(N)
    sum_squares = begin
        s = 0
        for i = 1:N
            s += i ^ 2
        end
        s
    end

    square_sum = begin
        s = 0
        for i = 1:N
            s += i
        end
        s ^ 2
    end

    return square_sum - sum_squares
end

function solution006()
    N = 100
    answer = e006(N)
    println("The answer to problem 006 is $answer")
end


N, faces = 10^6, 1:6

numSol = sum([iseven(i+j) for i in faces, j in faces]) / length(faces)^2
mcEst  = sum([iseven(rand(faces) + rand(faces)) for i in 1:N]) / N

println("Numerical solution = $numSol \nMonte Carlo estimate = $mcEst")


# TODO: limit types for arguments of f
struct Predicate
  f::Function
end

struct Mapping
  f::Function
end

(p::Predicate)(x,y) = p.f(x,y)
(m::Mapping)(x) = m.f(x)

# ====================  Auxiliary functions ====================

"""
    check_for_equivalence_relation(f::Predicate, dom)

Given a Predicate `f` over a domain `dom`, return `true` if `f`
is a equivalence relation, `false` otherwise.
One can create a symmetric equivalency relation with [`make_symmetric(f)`](@ref)
TODO: check transitivity.

# Examples
```
julia> check_for_equivalence_relation(Predicate((x,y) -> x == -y), -5:10)
true
julia> check_for_equivalence_relation(Predicate((x,y) -> x == y+2), -1:10)
false
```
"""
function check_for_equivalence_relation(f::Predicate, dom)
  for (i,el1) in enumerate(dom)
    for el2 in dom
      !(f(el1,el2) == f(el2,el1)) && return false
    end
  end
  #TODO: check transitivity
  return true
end


#
#
#

function run_dsatur_heuristc(infile_name::ASCIIString)
	# Read the graph from the disk (as adjacency list)
	G = simpleGraph(infile_name)

	# Apply the dsatur method
	number_color, vertex_color = dsatur(G.Graph)

	# Improve the coloring using Iterated Greedy techniques

	# Improve the coloring using min-conflicts local search

	# Return the coloring

end

function run_optdsatur_heuristc()
end

function dsatur(G::Array{Array{Int64},1})
	l = length(G)
	vertex_color = zeros(Int,l)
	degrees = Array{Tuple{Int,Int}}(l)
	satur = zeros(Int,l)
	uncolored = Set(collect(1:l))
	color_counter = 1

	for i in 1:l
		degrees[i] = (length(G[i]), i)
	end

	#coloring max degree vertex

	max_deg_vertex = maximum(degrees)[2]
	vertex_color[max_deg_vertex] = color_counter

	delete!(uncolored, max_deg_vertex)

	#Increase saturation of max_degree_vertex neighbours
	for j in G[max_deg_vertex]
		satur[j]+=1
	end

	coloring = 0
	while !isempty(uncolored)

		max_satur_degree_vertices = []
		aux = []
		for i in uncolored
			push!(aux, (satur[i], i))
		end

		max_sat = findmax(aux)[1][1]
		for i in aux
			if i[1] == max_sat
				push!(max_satur_degree_vertices, i[2])
			end
		end

		coloring = max_satur_degree_vertices[1]

		# If there are more than 1 vertex with max_satur, untie by max_degree
		if length(max_satur_degree_vertices) > 1
			max_degree = -1
			node_index = -1
			for i in max_satur_degree_vertices
				degree, node_index = degrees[i]
				if degree > max_degree
					coloring = node_index
					max_degree = degree
				end
			end
		end

		# Closure to return how many vertex have some color.
		function amount_color(node_indexes, color_number, vertex_color)
			color_counter = 0
			for i in node_indexes
				if vertex_color[i] == color_number
					color_counter +=1
				end
			end
			return color_counter
		end

		# Coloring
		for number_color in 1:color_counter
			if amount_color(G[coloring], number_color, vertex_color) == 0
				vertex_color[coloring] = number_color
				break
			end
		end

		#In case it wasnt colored
		if vertex_color[coloring] == 0
			color_counter+=1
			vertex_color[coloring] = color_counter
		end

		delete!(uncolored, coloring)

		for j in G[coloring]
			if amount_color(G[j], vertex_color[coloring], vertex_color) == 1
				satur[j] +=1
			end
		end
	end
	return number_color, vertex_color
end

function optdsatur()
end

function iteratedGreedy()

end

function localSearch()

end

type colorGraph
end


"""
    𝑛(v::Vector{T}) where T<:Number

Scales a length-``n`` vector ``v``  such that the last component
of the vector is one, provided that the last component is not zero. If the last
component is zero then the vector is left unchanged.

# Details
Suppose the length-``n`` vector ``v`` represents the homogeneous coordinates  of
a point in a projective space. The corresponding Cartesian coordinates  usually
just the first ``n-1`` numbers of homogeneous coordinates divided by  the last
component. So if the last component is one, then the first  ``n-1`` homogeneous
coordinates can be interpreted as Cartesian.  The exceptional case is when the
last component of the homogenenous coordinates is zero. These homogeneous
coordinates are associated with so-called *points at infinity* and have no
Cartesian counterparts.

# Example
```julia
h = [4, 4 , 2]
c = 𝑛(h)

3-element Array{Float64,1}:
 2.0
 2.0
 1.0
```

"""
function 𝑛(v::AbstractArray)
    if v[end] != 0 && v[end] != 1
        v .= v ./ v[end]
    else
        v
    end
end

function 𝑛(v::SVector)
    if v[end] != 0 && v[end] != 1
        v / v[end]
    else
        v
    end
end

function hom⁻¹(v::SVector)
    if isapprox(v[end], 0.0; atol = 1e-14)
        pop(v)
    else
        pop(v / v[end])
    end
end

function hom(v::SVector)
    push(v,1)
end

function ∂hom⁻¹(𝐧::SVector)
    k = length(𝐧)
    𝐞ₖ = push(zeros(SVector{k-1}),1.0)
    𝐈 = SMatrix{3,3}(1.0I)
    1/𝐧[k]*𝐈 - 1/𝐧[k]^2 * 𝐧 * 𝐞ₖ'
end

function ∂𝑛(𝐧::AbstractArray)
    k = length(𝐧)
    𝐞ₖ = fill(0.0,(k,1))
    𝐞ₖ[k] = 1
    1/𝐧[k]*Matrix{Float64}(I, k, k) - 1/𝐧[k]^2 * 𝐧 * 𝐞ₖ'
end

function smallest_eigenpair(A::AbstractArray)
    F = eigen(A)
    index = argmin(F.values)
    (F.values[index], F.vectors[:,index])
end

function smallest_eigenpair(A::AbstractArray,B::AbstractArray)
    F = eigfact(A,B)
    index = indmin(F[:values])
    (F[:values][index], F[:vectors][:,index])
end

function vec2antisym(v::AbstractArray)
    if length(v) != 3
         throw(ArgumentError("The operation is only defined for a length-3 vector."))
    end
    𝐒  = @SMatrix [   0  -v[3]    v[2] ;
                    v[3]    0    -v[1] ;
                   -v[2]  v[1]      0]

end


"""
    fdweights(z, x, m)

Calculates FD (finite difference) weights for arbitrarily spaced nodes in 1D.

Return an array of size m+1 by length(x) containing, in successive rows, the
weights for derivatives 0, 1, ..., m. The zero (m = 0) derivative corresponds
to interpolation.

# Arguments
- `z`: location at which to approximate the derivative (may but need to be a grid point)
- `x`: vector of x-coordinates (grid points; distinct, otherwise arbitrary)
- `m`: highest derivative for which weights are sought (default value is 0)

# Examples
```julia-repl
julia> fdweights(0, -2:2, 6)
5×5 adjoint(::Matrix{Float64}) with eltype Float64:
 -0.0         0.0        1.0   0.0       -0.0
  0.0833333  -0.666667   0.0   0.666667  -0.0833333
 -0.0833333   1.33333   -2.5   1.33333   -0.0833333
 -0.5         1.0        0.0  -1.0        0.5
  1.0        -4.0        6.0  -4.0        1.0
```
"""
function fdweights(z, x, m = 0)
    n = length(x)
    if m >= n
        m = n - 1 # set to highest possible derivative
    end

    c1 = 1
    c4 = x[1] - z
    C = zeros(n, m+1)
    C[1, 1] = 1

    for i = 1:n-1
        i1 = i + 1
        mn = min(i, m)
        c2 = 1
        c5 = c4
        c4 = x[i1] - z
        for j = 0:i-1
            j1 = j + 1
            c3 = x[i1] - x[j1]
            c2 = c2 * c3
            if j == i-1
                for s = mn:-1:1
                    s1 = s + 1
                    C[i1, s1] = c1 * (s * C[i1-1, s1-1] - c5 * C[i1-1, s1]) / c2
                end
                C[i1, 1] = -c1 * c5 * C[i1-1, 1] / c2
            end
        for s = mn:-1:1
            s1 = s + 1
            C[j1, s1] = (c4 * C[j1, s1] - s*C[j1, s1-1]) / c3
        end
        C[j1, 1] = c4 * C[j1,1] / c3
        end
        c1 = c2
    end
    return C'
end


module kuramoto_sivashinsky

using SparseArrays
using LinearAlgebra

"""
    B = calcBernoulli(n)

Return the `n`th Bernoulli number.  Follows the convention that `n=1`
Bernoulli number is equal to -1/2.
"""
function calcBernoulli(n)
    A = Vector{Rational{BigInt}}(undef, n + 1)
    for m = 0:n
        A[m + 1] = 1 // (m + 1)
        for j = m:-1:1
            A[j] = j * (A[j] - A[j + 1])
        end
    end
    if n == 1
        return -A[1]
    else
        return A[1]
    end
end

"""
    quad = buildQuadrature(order [, T=Float64])

Return a Gregory-type quadrture rule for uniformly spaced data.
"""
function buildQuadrature(order::Int)
    # Form the linear system to solve
    numbnd = order - 1
    A = zeros(Rational{BigInt}, order-1, numbnd)
    b = zeros(Rational{BigInt}, order-1)
    for j = 1:order-1
        for i = 1:numbnd
            A[j,i] = j*(numbnd - (i-1))^(j-1)
        end
        b[j] = numbnd^j - ((-1)^j)*calcBernoulli(j)
    end
    w = A\b
    return w
    # if order <= 2
    #     quad = [1//2]
    # elseif order <= 4
    #     quad = [17//48; 59//48; 43//48; 49//48]
    # elseif order <= 6
    #     quad = [13649//43200; 12013//8640; 2711//4320; 5359//4320; 7877//8640;
    #             43801//43200]
    # else
    #     quad = [1498139//5080320; 1107307//725760; 20761//80640;
    #             1304999//725760; 299527//725760; 103097//80640;
    #             670091//725760; 5127739//5080320]
    # end
    # return convert(Array{T,1}, quad)
end

"""
Data structure for finite-difference operators on uniformly spaced points
"""
struct FiniteDiff{T}
    """Defines the nodes in the stencil; node 0 is rel. location of operator"""
    stencil::Array{Int,1}
    """Actual finite-difference coefficients"""
    coeffs::Array{T,1}
end

"""
    fd = buildFiniteDifference(deriv, order [, T=Float64, h=1.0])

Returns a finite difference operator struct for a derivative of order `deriv`
and order of accuracy of `order`. The optional argument `T` controls the
precision used, and `h` sets the (uniform) mesh spacing.
"""
function buildFiniteDifference(deriv::Int, order::Int, T::Type=Float64;
                               h=convert(T,1) )
    num_pts = deriv + order - 1
    if iseven(num_pts) num_pts += 1 end
    stencil = Array{Int}(-div(num_pts-1,2):div(num_pts-1,2))
    V = zeros(T, num_pts, num_pts)
    for i = 1:num_pts
        V[i,:] = stencil.^(i-1)
    end
    b = zeros(T, num_pts)
    b[deriv+1] = factorial(deriv)
    c = V\b
    c /= (h^deriv)
    return FiniteDiff{T}(stencil, c)
end

"""
    interp = buildInterp(order [, T=Float64])

Returns a centered interpolation operator of order `order` that does **not**
use data at the central node.
"""
function buildInterp(order::Int, T::Type=Float64)
    num_pts = order
    if isodd(num_pts) num_pts += 1 end
    stencil = Array{Int}([-div(num_pts,2):-1;1:div(num_pts,2)])
    V = zeros(T, num_pts, num_pts)
    for i = 1:num_pts
        V[i,:] = stencil.^(i-1)
    end
    b = zeros(T, num_pts)
    b[1] = 1.0
    c = V\b
    return FiniteDiff{T}(stencil, c)
end

"""
    num_ghost, num_bndry, ghost_op = buildGhostOp(order [, T=Float64])

Constructs a matrix operator `ghost_op` that, when applied to `num_bndry` nodes
at the boundary, produces `order` accurate `num_ghost` values.  **Note**, this
operator is designed to enforce a zero function value and zero derivative value
at the boundary, as per the Blonigan and Wang paper.

The ghost operator is constructed by solving a linearly constrained minimum
norm problem for the ghost node values.
"""
function buildGhostOp(order::Int, T::Type=Float64)
    # first, build the constraint Jacobian that enforces u=0 and du/dx = 0
    # A*u_ghost - B*u_true = c
    fd = buildFiniteDifference(1, order, T)
    #interp = buildInterp(order, T)
    num_nodes = length(fd.stencil)
    num_ghost = div(num_nodes-1,2) + 1 # this includes node at boundary
    num_bndry = num_nodes - num_ghost
    A = zeros(T, 2, num_ghost)
    A[1,end] = 1.0 # last node is the node on the boundary
    #A[1,1:end-1] = interp.coeffs[1:num_ghost-1]
    A[2,:] = fd.coeffs[1:num_ghost]
    B = zeros(T, 2, num_bndry)
    #B[1,:] = interp.coeffs[num_ghost:end]
    B[2,:] = fd.coeffs[num_ghost+1:end]
    return num_ghost, num_bndry, -A'*( (A*A') \ B )

    if false
        # This version uses a TVD-type regularization
        # first, build the constraint Jacobian that enforces u=0 and du/dx = 0
        # A*u_ghost = B*u_true + c
        fd = buildFiniteDifference(1, order, T)
        #interp = buildInterp(order, T)
        num_nodes = length(fd.stencil)
        num_ghost = div(num_nodes-1,2) + 1 # this includes node at boundary
        num_bndry = num_nodes - num_ghost
        A = zeros(T, 2, num_ghost)
        A[1,end] = 1.0 # last node
        #A[1,1:end-1] = interp.coeffs[1:num_ghost-1]
        A[2,:] = fd.coeffs[1:num_ghost]
        B = zeros(T, 2, num_bndry)
        #B[1,:] = interp.coeffs[num_ghost:end]
        B[2,:] = fd.coeffs[num_ghost+1:end]

        D = zeros(T, num_ghost, num_ghost)
        D += diagm(2*ones(T,num_ghost))
        D -= diagm(ones(T, num_ghost-1), 1)
        D -= diagm(ones(T, num_ghost-1), -1)
        K = [D A'; A zeros(T, 2, 2)]
        G = -inv(K)[1:num_ghost,end-1:end]*B
        return num_ghost, num_bndry, G
    end
end

"""
Data for KS function evaluation to avoid reallocation
"""
struct KSData{T}
    """Number of internal nodes (nodes we are solving for)"""
    num_nodes::Int
    """Number of ghost nodes at each end; include x=0 and x=L"""
    num_ghost::Int
    """Number of internal nodes used to determine ghost values"""
    num_bndry::Int
    """Stores the boundary first-derivative operator"""
    bndry_op::Array{T,2}
    """Stores the linear operatrors"""
    linear_op::SparseMatrixCSC{T,Int}
    """finite difference operator for the first-derivative"""
    fd::FiniteDiff{T}
end

"""
    ks = buildKSDataPeriodic(order, num_nodes [, T=Float64])

Returns a KSData structure that can be used to solve the Kuramoto-Sivashinsky
equations on _periodic domains_, while avoiding reallocation of arrays.
"""
function buildKSDataPeriodic(order::Int, num_nodes::Int, T::Type=Float64)
    # domain length used in Blonigan and Wang paper, but here periodic
    Lx = convert(T, 128)
    h = Lx/num_nodes
    # generate the matrix used for the linear operators
    A = zeros(T, num_nodes, num_nodes)
    # second, add the Laplacian contributions
    fd = buildFiniteDifference(2, order, T)
    fac = 1/(h*h)
    for i = 1:num_nodes
        A[i, mod.(fd.stencil .+ i .- 1, num_nodes) + 1] -= fd.coeffs*fac
    end
    # finally, add the BiLaplacian terms
    fd = buildFiniteDifference(4, order, T)
    fac *= fac
    for i = 1:num_nodes
        A[i, mod.(fd.stencil .+ i .- 1, num_nodes) + 1] -= fd.coeffs*fac
    end
    Asparse = sparse(A)
    # finally, get the first-derivative operator for nonlinear terms
    fd = kuramoto_sivashinsky.buildFiniteDifference(1, order, T, h=h)
    return KSData{T}(num_nodes, 0, 0, zeros(0,0), Asparse, fd)
end

"""
    getKSFuncPeriodic!(ks, t, u, du)

A function that returns the perodic spatial discretization of the
Kuramoto-Sivashinsky PDE for use in a time marching method.
"""
function getKSFuncPeriodic!(ks::KSData{Tks}, t, u::AbstractArray{T,1},
                            du::AbstractArray{T,1}) where {Tks, T}
    # apply the linear operators
    du[:] = ks.linear_op*u
    # add the nonlinear terms; this uses a skew-symmetric split-form
    third = convert(T, 1//3)
    for i = 1:ks.num_nodes
        for k = 1:size(ks.fd.stencil,1)
            j = mod(ks.fd.stencil[k]+i-1, ks.num_nodes) + 1
            du[i] -= third*u[i]*ks.fd.coeffs[k]*u[j]
            du[i] -= third*ks.fd.coeffs[k]*u[j]*u[j]
        end
        # This version uses central differencing for the nonlinear term
        #du[i] -= u[i]*dot(ks.fd.coeffs,
        #                  u[mod.(ks.fd.stencil+i-1,ks.num_nodes)+1])
    end
    return nothing
end

"""
    getKSJacPeriodic!(ks, t, u, Jac)

A function that returns the Jacobian of the perodic spatial discretization of
the Kuramoto-Sivashinsky PDE for use in a time marching method.
"""
function getKSJacPeriodic!(ks::KSData{T}, t, u::AbstractArray{T,1},
                           Jac::SparseMatrixCSC{T,Int}) where {T}
    Jac[:,:] = sparse(ks.linear_op)
    third = convert(T, 1//3)
    for i = 1:ks.num_nodes
        for k = 1:size(ks.fd.stencil,1)
            j = mod(ks.fd.stencil[k]+i-1, ks.num_nodes) + 1
            #du[i] -= fac*u[i]*ks.fd.coeffs[k]*u[j]
            Jac[i,i] -= third*ks.fd.coeffs[k]*u[j]
            Jac[i,j] -= third*u[i]*ks.fd.coeffs[k]
            #du[i] -= fac*ks.fd.coeffs[k]*u[j]*u[j]
            Jac[i,j] -= 2*third*ks.fd.coeffs[k]*u[j]
        end
        # This version uses central differencing for the nonlinear term
        #Jac[i,i] -= dot(ks.fd.coeffs,
        #                u[mod.(ks.fd.stencil+i-1,ks.num_nodes)+1])
        #Jac[i,mod.(ks.fd.stencil+i-1,ks.num_nodes)+1] -= u[i]*ks.fd.coeffs
    end
end

"""
    ks = buildKSData(order, num_nodes [, T=Float64])

Returns a KSData structure that can be used to solve the Kuramoto-Sivashinsky
equations while avoiding reallocation of arrays.  This version imposes
homogeneous Dirichlet and Neumann conditions at the ends of the spatial domain.
"""
function buildKSData(order::Int, num_nodes::Int, T::Type=Float64)
    # domain length used in Blonigan and Wang paper
    Lx = convert(T, 128)
    h = Lx/(num_nodes+1)
    # get the ghost node operator, call it Bg
    num_ghost, num_bndry, Bg = buildGhostOp(order, T)
    # println("Bg = ", Bg)

    # generate the matrix used for the linear operators
    A = zeros(T, num_nodes, num_nodes)
    # first, add the Laplacian contributions
    fd = buildFiniteDifference(2, order, T)
    fac = 1/(h*h)
    for i = 1:num_nodes
        idx = [k for k = 1:size(fd.stencil,1)
               if fd.stencil[k] + i >= 1 && fd.stencil[k] + i <= num_nodes]
        A[i, fd.stencil[idx] .+ i] -= fd.coeffs[idx]*fac
    end

    # eliminate ghost node contributions for Laplacian
    Ag = zeros(T, num_bndry, num_ghost)
    for i = 1:num_bndry
        idx = [k for k = 1:size(fd.stencil,1) if fd.stencil[k] + i < 1]
        Ag[i, fd.stencil[idx] .+ num_ghost .+ i] -= fd.coeffs[idx]*fac
    end
    # println("Ag (Laplacian) = ", Ag)
    G = Ag*Bg
    A[1:num_bndry,1:num_bndry] += G
    A[end:-1:end-num_bndry+1,end:-1:end-num_bndry+1] += G

    # next, add the BiLaplacian terms
    fd = buildFiniteDifference(4, order, T)
    fac = 1/(h*h*h*h)
    for i = 1:num_nodes
        idx = [k for k = 1:size(fd.stencil,1)
               if fd.stencil[k] + i >= 1 && fd.stencil[k] + i <= num_nodes]
        A[i, fd.stencil[idx] .+ i] -= fd.coeffs[idx]*fac
    end

    # eliminate ghost node contributions for BiLaplacian
    fill!(Ag, zero(T))
    for i = 1:num_bndry
        idx = [k for k = 1:size(fd.stencil,1) if fd.stencil[k] + i < 1]
        Ag[i, fd.stencil[idx] .+ num_ghost .+ i] -= fd.coeffs[idx]*fac
    end
    # println("Ag (BiLaplacian) = ", Ag)
    G = Ag*Bg
    A[1:num_bndry,1:num_bndry] += G
    A[end:-1:end-num_bndry+1,end:-1:end-num_bndry+1] += G

    # println("A[1,1:5] = ",A[1,1:5])
    # println("A[end,end-4:end] = ",A[end,end-4:end])

    Asparse = sparse(A)

    # finally, get the first-derivative operator for nonlinear terms
    fd = kuramoto_sivashinsky.buildFiniteDifference(1, order, T, h=h)
    # Get the first-derivative operator at the boundary
    fill!(Ag, zero(T))
    G = zeros(T, num_bndry, div(size(fd.stencil,1)+1,2) + num_bndry - 1 )
    for i = 1:num_bndry
        idx = [k for k = 1:size(fd.stencil,1) if fd.stencil[k] + i < 1]
        Ag[i, fd.stencil[idx] .+ num_ghost .+ i] += fd.coeffs[idx]
        idx = [k for k = 1:size(fd.stencil,1) if fd.stencil[k] + i >= 1]
        G[i, fd.stencil[idx] .+ i] += fd.coeffs[idx]
    end
    # println("Ag (first-derivative) = ", Ag)
    # println("G before = ",G)
    G[:,1:num_bndry] += Ag*Bg
    # println("G after = ",G)

    return KSData{T}(num_nodes, num_ghost, num_bndry, G, Asparse, fd)
end

"""
    getKSFunc!(ks, t, u, du)

A function that returns the spatial discretization of the
Kuramoto-Sivashinsky PDE for use in a time marching method.  Homogeneous BCs
for u and dudx are used at both ends of the domain.
"""
function getKSFunc!(ks::KSData{Tks}, t, u::AbstractArray{T,1},
                    du::AbstractArray{T,1}) where {Tks, T}
    # apply the linear operators
    du[:] = ks.linear_op*u
    # add the nonlinear terms to interior nodes
    fac = convert(T, 1//3)
    for i = ks.num_bndry + 1:ks.num_nodes - ks.num_bndry
        for k = 1:size(ks.fd.stencil,1)
            j = mod(ks.fd.stencil[k]+i-1, ks.num_nodes) + 1
            du[i] -= fac*u[i]*ks.fd.coeffs[k]*u[j]
            du[i] -= fac*ks.fd.coeffs[k]*u[j]*u[j]
        end
        #du[i] -= u[i]*dot(ks.fd.coeffs,
        #                  u[mod.(ks.fd.stencil+i-1,ks.num_nodes)+1])
    end
    # add the nonlinear terms to the boundary nodes
    for i = 1:size(ks.bndry_op,1)
        for k = 1:size(ks.bndry_op,2)
            du[i] -= fac*u[i]*ks.bndry_op[i,k]*u[k]
            du[i] -= fac*ks.bndry_op[i,k]*u[k]*u[k]
            du[end-i+1] += fac*u[end-i+1]*ks.bndry_op[i,k]*u[end-k+1]
            du[end-i+1] += fac*ks.bndry_op[i,k]*u[end-k+1]*u[end-k+1]
        end
    end
    return nothing
end

"""
    getKSJac!(ks, t, u, Jac)

A function that returns the Jacobian of the spatial discretization of
the Kuramoto-Sivashinsky PDE for use in a time marching method. Homogeneous BCs
for u and dudx are used at both ends of the domain.
"""
function getKSJac!(ks::KSData{T}, t, u::AbstractArray{T,1},
                   Jac::SparseMatrixCSC{T,Int}) where {T}
    # Jacobian contribution due to linear terms
    Jac[:,:] = sparse(ks.linear_op)
    #Jac[:,:] = deepcopy(ks.linear_op)
    # Jacobian due to nonlinear terms on interior
    fac = convert(T, 1//3)
    for i = ks.num_bndry+1:ks.num_nodes - ks.num_bndry
        for k = 1:size(ks.fd.stencil,1)
            j = mod(ks.fd.stencil[k]+i-1, ks.num_nodes) + 1
            # du[i] -= fac*u[i]*ks.fd.coeffs[k]*u[j]
            Jac[i,i] -= fac*ks.fd.coeffs[k]*u[j]
            Jac[i,j] -= fac*u[i]*ks.fd.coeffs[k]
            # du[i] -= fac*ks.fd.coeffs[k]*u[j]*u[j]
            Jac[i,j] -= 2*fac*ks.fd.coeffs[k]*u[j]
        end
        # This version uses central differencing for the nonlinear term
        #Jac[i,i] -= dot(ks.fd.coeffs,
        #                u[mod.(ks.fd.stencil+i-1,ks.num_nodes)+1])
        #Jac[i,mod.(ks.fd.stencil+i-1,ks.num_nodes)+1] -= u[i]*ks.fd.coeffs
    end
    # Jacobian due to nonlinear terms on boundary
    for i = 1:size(ks.bndry_op,1)
        for k = 1:size(ks.bndry_op,2)
            # du[i] -= fac*u[i]*ks.bndry_op[i,k]*u[k]
            Jac[i,i] -= fac*ks.bndry_op[i,k]*u[k]
            Jac[i,k] -= fac*u[i]*ks.bndry_op[i,k]
            # du[i] -= fac*ks.bndry_op[i,k]*u[k]*u[k]
            Jac[i,k] -= 2*fac*ks.bndry_op[i,k]*u[k]
            # du[end-i+1] += fac*u[end-i+1]*ks.bndry_op[i,k]*u[end-k+1]
            Jac[end-i+1,end-i+1] += fac*ks.bndry_op[i,k]*u[end-k+1]
            Jac[end-i+1,end-k+1] += fac*u[end-i+1]*ks.bndry_op[i,k]
            # du[end-i+1] += fac*ks.bndry_op[i,k]*u[end-k+1]*u[end-k+1]
            Jac[end-i+1,end-k+1] += 2*fac*ks.bndry_op[i,k]*u[end-k+1]

            # This version uses central differencing for the nonlinear term
            #du[i] -= u[i]*ks.bndry_op[i,k]*u[k]
            #  Jac[i,i] -= ks.bndry_op[i,k]*u[k]
            #  Jac[i,k] -= u[i]*ks.bndry_op[i,k]
            #du[end-i+1] -= u[end-i+1]*ks.bndry_op[i,k]*u[end-k+1]
            #  Jac[end-i+1,end-i+1] += ks.bndry_op[i,k]*u[end-k+1]
            #  Jac[end-i+1,end-k+1] += u[end-i+1]*ks.bndry_op[i,k]
        end
    end
end

"""
    Jac = getJacobian(u, ks)

This function uses the complex-step method to get the Jacobian; used for
verification of getKSJac!.  **Do not use** in simulations, due to cost.
"""
function getJacobian(u::AbstractArray{T,1}, ks::KSData{T}) where{T}
    fd_c = FiniteDiff{Complex128}(ks.fd.stencil, ks.fd.coeffs)
    ks_c = KSData{Complex128}(ks.num_nodes, ks.num_ghost, ks.num_bndry,
                              ks.bndry_op, ks.linear_op, fd_c)
    Jac = zeros(size(u,1),size(u,1))
    u_c = complex.(u, 0.0)
    du_c = zeros(u_c)
    ceps = 1e-60
    for i = 1:ks.num_nodes
        u_c[i] += complex(0.0, ceps)
        getKSFunc!(ks_c, 0.0, u_c, du_c)
        Jac[:,i] = imag.(du_c)/ceps
        u_c[i] -= complex(0.0, ceps)
    end
    return Jac
end

"""
    sol = solveUsingMidpoint(ks, Time, num_stpes, u)

Solve the KS problem on the nonperiodic domain using the midpoint rule.  The
problem is run from t=0 to t=`Time` using `num_steps` steps.  The initial
condition is `u`.  Returns `sol`, the solution over all space and temporal
nodes; `sol[i,n]` stores the `i`th space node at time step `n`.  Note that
`sol` stores the boundary nodes, whereas `u` does not.
"""
function solveUsingMidpoint(ks::KSData{T}, Time::T, num_steps::Int,
                            u::Array{T,1}) where {T}
    num_nodes = size(u,1)
    # storage for solution over all time steps
    dt = Time/num_steps
    sol = zeros(num_nodes+2, num_steps+1)
    sol[2:end-1,1] = u

    t = convert(T, 0.0)
    Jac = similar(ks.linear_op)
    F = lu(ks.linear_op) # initialize memory for the lu factorization
    u_mid = zeros(size(u))
    u_old = zeros(size(u))
    r = zeros(size(u))
    newt_tol = 1e-10 # We will need to ensure this is sufficiently small
    max_newt_iter = 50
    for n = 1:num_steps
        #println("Iteration ",n,": time ",t)
        # Newton iterations
        u_old = u
        norm0 = one(T)
        for k = 1:max_newt_iter
            u_mid = 0.5*u + 0.5*u_old
            kuramoto_sivashinsky.getKSFunc!(ks, t, u_mid, r)
            r = u - u_old - dt*r
            #println("\tNewton iter ",k,": res norm = ",norm(r))
            if k == 1
                norm0 = norm(r)
            else
                if norm(r) < newt_tol*norm0
                    break
                end
            end
            kuramoto_sivashinsky.getKSJac!(ks, t, u_mid, Jac)
            Jac *= 0.5*dt
            Jac -= I #speye(T, num_nodes)
            lu!(F, Jac)
            du = F\r
            u += du
        end
        t += dt
        sol[2:end-1,n+1] = u
    end
    return sol
end

"""
    avg = calcSolutionAverage(order, sol, func)

Returns the space and time average of `func(sol[:,:])` using an `order` order
accurate diagonal norm scheme.
"""
function calcSolutionAverage(order::Int, sol::AbstractArray{T,2},
                             func::Function) where {T}
    avg = zero(T)
    quad = convert(Vector{T}, buildQuadrature(order))
    num_bndry = size(quad,1)
    for n = 1:size(sol,2)
        wt = one(T)
        if n <= num_bndry
            wt *= quad[n]
        elseif n >= size(sol,2) - num_bndry + 1
            wt *= quad[size(sol,2)-n+1]
        end
        for i = 1:size(sol,1)
            if i <= num_bndry
                avg += func(sol[i,n])*wt*quad[i]
            elseif i >= size(sol,1) - num_bndry + 1
                avg += func(sol[i,n])*wt*quad[size(sol,1)-i+1]
            else
                avg += func(sol[i,n])*wt
            end
        end
    end
    avg /= (size(sol,1)-1)*(size(sol,2)-1)
    return avg
end

# function factorBandedSparse(A::SparseMatrixCSC{T,Int64}) where {T}

#     fac = one(T)
#     # loop over the elementary matrix operations
#     for k = 1:A.m-1
#         # update column k of the lower triangular matrix
#         # This assumes the rows are sorted, which Julia matrices should be
#         for ptr = A.colptr[k]:A.colptr[k+1]-1
#             if rowval[ptr] > k
#                 continue
#             elseif rowval[ptr] == k
#                 fac = one(T)/A.nzval[ptr]
#             else
#                 A.nzval[ptr] *= fac
#             end
#         end
#         # apply the elementary matrix to the lower block matrix
#         for j = k+1:A.n
#             if A.rowval[A.colptr[j]] > k
#                 break # there are no further nonzeros in row k beyond col j
#             end
#             for ptr = A.colptr[j]:A.colptr[j+1]-1
#                 i = A.rowval[ptr]
#                 if i <= k continue end
#                 A.nzval[ptr] -= A[i,k]*A[k,j] # this is not efficient
#             end
#         end
#     end
# end

end # module


module IRSpectrum

using FFTW

export autocorrelation,
       read_cp2k_dipoles,
       spectrum

"""
read_cp2k_dipoles(fname) -> Matrix{Float64}

Read dipole trajectory file created by CP2K and return dipolemoments as
a two dimensional array.
"""
function read_cp2k_dipoles(fname::AbstractString)
    out = Float64[]
    open(fname,"r") do file
        re = r"X=\ +(?<X>-?\d+.?\d+)\ +Y=\ +(?<Y>-?\d+.?\d+)\ +Z=\ +(?<Z>-?\d+.?\d+)"
        for line in eachline(file)
            m = match(re, line)
            if m !== nothing
                push!(out,parse(Float64,m[:X]))
                push!(out,parse(Float64,m[:Y]))
                push!(out,parse(Float64,m[:Z]))
            end
        end
    end
    if length(out)%3 == 0
        return reshape(out, (3, Int(length(out)/3)))
    else
        error("Error in reading file")
    end
end

"""
autocorrelation(μ::AbstractMatrix) -> Vector

Calculates autocorrelation function using [Wiener-Khinchin Theorem](http://mathworld.wolfram.com/Wiener-KhinchinTheorem.html).
"""
function autocorrelation(μ::AbstractMatrix)
    tmp = [ fft(abs2.(ifft(r))) for r in eachrow(μ)]
    return real.(reduce(+,tmp))
end

"""
spectrum(μ::AbstractMatrix; tstep=0.5, maxfreq=4000) -> Dict

Calculates spectrum from dipolemoment trajectory. Calculates autocorrelation function and
its Fourier transform.

# Arguments
- `μ::AbstractMatrix` :  data where autocorrelation funtion is calculated for each row

# Keywords
- `tstep` : timestep for trajectory in fempto seconds
- `maxfreq` : maximum frequency in wavenumbers for resulting spectrum

# Returns
- `Dict` : with fields `"absorption"` and `"wavenumber"`.
"""
function spectrum(μ::AbstractMatrix; tstep=0.5, maxfreq=4000)
    l = size(μ)[2]
    lh = Int(floor(l/2))
    acor = autocorrelation(μ)
    s = abs.(fft(acor))[1:lh]
    # freq = n/NΔ and wavenumber = freq/100c
    w = [ n/(l*tstep*1E-15*299792458E2) for n in 1:lh ]
    i = w .<= maxfreq
    return Dict("absorption"=>s[i], "wavenumber"=>w[i])
end

"""
spectrum(fname::AbstractString; tstep=0.5, maxfreq=4000) -> Dict

Read dipolemoments from CP2K dipole file `fname` and calculate spectrum.
"""
function spectrum(fname::AbstractString; tstep=0.5, maxfreq=4000)
    μ = read_cp2k_dipoles(fname)
    return spectrum(μ; tstep=tstep, maxfreq=maxfreq)
end

end # module


function InverseLU(A::Array)

  #A = the matrix of coefficients 'A' must be squared

    m,n =size(A)

    if m != n
        error("Matrix must be squared")
    end

    In=zeros(m,m)
    B=Matrix{Float64}(I,m,m)
    D=zeros(m)

    L,U,P, p=DolittleLU(A)

    for j=1:m

        #Fordward Substitution
        D=zeros(m)
        D[1]=B[1,j]
        for i=2:m
           D[i]=(B[i,j] - (L[i,1:i-1]'*D[1:i-1]))
        end

        #Back substitution

          In[m,j]=D[end]/U[end,end]

    for i=m-1:-1:1
       In[i,j]=(D[i] - (U[i,i+1:m]'*In[i+1:m,j]))/U[i,i]
    end

    end
    return In, P
end


# single qubit Cliffords

export localclifford

const pX = [0 1; 1 0]
const pY = [0 -im; im 0]
const pZ = [1 0; 0 -1]

C1 = Vector{Clifford}(24)

# identity
C1[1]  = RI

# pi/2 and pi rotations about X,Y,Z
C1[2]  = expm(-im*pi/4*pX)
C1[3]  = im * pX
C1[4]  = expm(im*pi/4*pX)
C1[5]  = expm(-im*pi/4*pY)
C1[6]  = im * pY
C1[7]  = expm(im*pi/4*pY)
C1[8]  = expm(-im*pi/4*pZ)
C1[9]  = im * pZ
C1[10] = expm(im*pi/4*pZ)

# Hadamard class
C1[11] = expm(-im*pi/2/sqrt(2) * (pX+pY))
C1[12] = expm(-im*pi/2/sqrt(2) * (pX-pY))
C1[13] = expm(-im*pi/2/sqrt(2) * (pX+pZ)) # standard Hadamard
C1[14] = expm(-im*pi/2/sqrt(2) * (pX-pZ))
C1[15] = expm(-im*pi/2/sqrt(2) * (pY+pZ))
C1[16] = expm(-im*pi/2/sqrt(2) * (pY-pZ))

# Axis exchange class
C1[17] = expm(-1im*pi/3/sqrt(3) * (pX+pY+pZ))
C1[18] = expm(-2im*pi/3/sqrt(3) * (pX+pY+pZ))
C1[19] = expm(-1im*pi/3/sqrt(3) * (pX-pY+pZ))
C1[20] = expm(-2im*pi/3/sqrt(3) * (pX-pY+pZ))
C1[21] = expm(-1im*pi/3/sqrt(3) * (pX+pY-pZ))
C1[22] = expm(-2im*pi/3/sqrt(3) * (pX+pY-pZ))
C1[23] = expm(-1im*pi/3/sqrt(3) * (-pX+pY+pZ))
C1[24] = expm(-2im*pi/3/sqrt(3) * (-pX+pY+pZ))

rC1 = Dict{Clifford,UInt}()

for (k,v) in enumerate(C1)
    rC1[v] = k
end

localclifford(i::Int) = C1[i]
localclifford(v::Vector) = kron(C1[v]...)

localcliffordindex(c::Clifford) = rC1[c]


function K_eular_shear(beam::Beam)::Matrix{Float64}
    E,ν=beam.material.E,beam.material.ν
    A,I₂,I₃,J,l=beam.section.A,beam.section.I₂,beam.section.I₃,beam.section.J,beam.l
    As₂,As₃=beam.section.As₂,beam.section.As₃
    G=E/2/(1+ν)
    ϕ₂,ϕ₃=12E*I₃/(G*As₂*l^2),12E*I₂/(G*As₃*l^2)
    K=zeros(12,12)
    K[1,1]=E*A/l
    K[2,2]=12E*I₃/l^3/(1+ϕ₂)
    K[3,3]=12E*I₂/l^3/(1+ϕ₃)
    K[4,4]=G*J/l
    K[5,5]=(4+ϕ₃)*E*I₂/l/(1+ϕ₃)
    K[6,6]=(4+ϕ₂)*E*I₃/l/(1+ϕ₂)
    K[7,7]=E*A/l
    K[8,8]=12E*I₃/l^3/(1+ϕ₂)
    K[9,9]=12E*I₂/l^3/(1+ϕ₃)
    K[10,10]=G*J/l
    K[11,11]=(4+ϕ₃)*E*I₂/l/(1+ϕ₃)
    K[12,12]=(4+ϕ₂)*E*I₃/l/(1+ϕ₂)

    K[3,5]=K[5,3]=-6E*I₂/l^2/(1+ϕ₃)
    K[6,8]=K[8,6]=-6E*I₃/l^2/(1+ϕ₂)
    K[9,11]=K[11,9]=6E*I₂/l^2/(1+ϕ₃)

    K[2,6]=K[6,2]=6E*I₃/l^2/(1+ϕ₂)
    K[5,9]=K[9,5]=6E*I₂/l^2/(1+ϕ₃)
    K[8,12]=K[12,8]=-6E*I₃/l^2/(1+ϕ₂)

    K[7,1]=K[1,7]=-E*A/l
    K[8,2]=K[2,8]=-12E*I₃/l^3/(1+ϕ₂)
    K[9,3]=K[3,9]=-12E*I₂/l^3/(1+ϕ₃)
    K[10,4]=K[4,10]=-G*J/l
    K[11,5]=K[5,11]=(2-ϕ₃)*E*I₂/l/(1+ϕ₃)
    K[12,6]=K[6,12]=(2-ϕ₂)*E*I₃/l/(1+ϕ₂)

    K[3,11]=K[11,3]=-6E*I₂/l^2/(1+ϕ₃)

    K[2,12]=K[12,2]=6E*I₃/l^2/(1+ϕ₂)

    return K
end

function K2_eular_shear(beam::Beam)::Matrix{Float64}
    E,ν=beam.material.E,beam.material.ν
    A,I₂,I₃,J,l=beam.section.A,beam.section.I₂,beam.section.I₃,beam.section.J,beam.l
    As₂,As₃=beam.section.As₂,beam.section.As₃
    G=E/2/(1+ν)
    T=σ*A
    ϕ₂,ϕ₃=12E*I₃/(G*As₂*l^2),12E*I₂/(G*As₃*l^2)
    K=zeros(12,12)

    K[2,2]=(6/5+2ϕ₂+ϕ₂^2)/(1+ϕ₂)^2
    K[3,3]=(6/5+2ϕ₃+ϕ₃^2)/(1+ϕ₃)^2
    K[4,4]=J/A
    K[5,5]=(2*l^2/15+l^2*ϕ₃/6+l^2*ϕ₃^2/12)/(1+ϕ₃)^2
    K[6,6]=(2*l^2/15+l^2*ϕ₂/6+l^2*ϕ₂^2/12)/(1+ϕ₂)^2
    K[8,8]=(6/5+2ϕ₂+ϕ₂^2)/(1+ϕ₂)^2
    K[9,9]=(6/5+2ϕ₃+ϕ₃^2)/(1+ϕ₃)^2
    K[10,10]=J/A
    K[11,11]=(2*l^2/15+l^2*ϕ₃/6+l^2*ϕ₃^2/12)/(1+ϕ₃)^2
    K[12,12]=(2*l^2/15+l^2*ϕ₂/6+l^2*ϕ₂^2/12)/(1+ϕ₂)^2

    K[3,5]=K[5,3]=-(l/10)/(1+ϕ₃)^2
    K[6,8]=K[8,6]=-(l/10)/(1+ϕ₂)^2
    K[9,11]=K[11,9]=(l/10)/(1+ϕ₃)^2

    K[2,6]=K[6,2]=(l/10)/(1+ϕ₂)^2
    K[5,9]=K[9,5]=(l/10)/(1+ϕ₃)^2

    K[2,8]=K[8,2]=-(6/5+2ϕ₂+ϕ₂^2)/(1+ϕ₂)^2
    K[3,9]=K[9,3]=-(6/5+2ϕ₃+ϕ₃^2)/(1+ϕ₃)^2
    K[4,10]=K[10,4]=-J/A
    K[5,11]=K[11,5]=-(l^2/30+l^2*ϕ₃/6+l^2*ϕ₃^2/12)/(1+ϕ₃)^2
    K[6,12]=K[12,6]=-(l^2/30+l^2*ϕ₂/6+l^2*ϕ₂^2/12)/(1+ϕ₂)^2

    K[3,11]=K[11,3]=-(l/10)/(1+ϕ₃)^2

    K[2,12]=K[12,2]=(l/10)/(1+ϕ₂)^2

    K*=T/l
    return K
end


function giant_component_size(g, present)
    n = nv(g)
    Q = Queue{Int}()
	label = zeros(Int,n)
	cnt = zeros(Int,n)
	maxcnt = 0
    @inbounds for u in vertices(g)
		if present[u]
	        label[u] != 0 && continue
	        label[u] = u
			cnt[u] += 1
	        enqueue!(Q, u)
	        while !isempty(Q)
	            src = dequeue!(Q)
	            for vertex in all_neighbors(g, src)
					if present[vertex]
		                if label[vertex] == 0
		                    enqueue!(Q, vertex)
		                    label[vertex] = u
							cnt[u] += 1
		                end
					end
	            end
	        end
			if cnt[u] > maxcnt
				maxcnt = cnt[u]
			end
		end
    end
    return maxcnt
end

function recover_add_nodes(g, attack_nodes)
	n = nv(g)
	max_comp_sizes = Int[]
	ds = IntDisjointSets(n)
	present = fill(true, n)
	present[attack_nodes] .= false
	comp_sizes = ones(Int, n)
	max_comp_size = 1

	# add edges between present nodes and record component sizes
	for i in vertices(g)
		if present[i]
			for j in neighbors(g,i)
				if present[j]
					newroot = merge_nodes!(ds, comp_sizes, i, j)
					if comp_sizes[newroot] > max_comp_size
						max_comp_size = comp_sizes[newroot]
					end
				end
			end
		end
	end

	for i in reverse(attack_nodes)
		for j in neighbors(g, i)
			if present[j]
				newroot = merge_nodes!(ds, comp_sizes, i, j)
				if comp_sizes[newroot] > max_comp_size
					max_comp_size = comp_sizes[newroot]
				end
			end
		end
		present[i] = true
		push!(max_comp_sizes, max_comp_size)
	end
	reverse(max_comp_sizes)
end


using Pkg, DrWatson, Random, Test

Random.seed!(123)

@quickactivate "StatisticalRethinkingTuring"
using Turing
using StatisticalRethinking

#=
using Turing, DataFrames
=#

Turing.setprogress!(false);

@model function linear_reg(x, y)
    β ~ Normal(0, 1)
    α ~ Normal(0, 5)
    σ ~ Exponential(1)

    for i ∈ eachindex(y)
        y[i] ~ Normal(α + β * x[i], σ)
    end
    return (α, β, σ)
end

f(x) = 5 + 2 * x + 0.5 * randn()

Δ = 1.0; xs_train = 0:Δ:20; ys_train = f.(xs_train);
xs_test = [10 + i * Δ for i in 1:4]; ys_test = f.(xs_test);

# Infer
m_train = linear_reg(xs_train, ys_train);
#precis(m_train) |> display  # From StatisticalRethinking.precis

chain_lin_reg = sample(m_train, NUTS(100, 0.65), 200);
chain_lin_reg |> display

#m_test = linear_reg(xs_test, fill(missing, length(ys_test)));
m_test = linear_reg(xs_test, similar(xs_test, Missing));
#m_test = linear_reg(xs_test, missing);

# Use the new predict function!
predictions = predict(m_test, chain_lin_reg)
predictions |> display

# Get the mean predicted values.
ys_mean_pred = vec(mean(Array(group(predictions, :y)); dims = 1))
ys_std_pred = vec(std(Array(group(predictions, :y)); dims = 1))

# Get the prediction error:
ys_test - ys_mean_pred |> display
ys_test - ys_mean_pred2 |> display

#@test sum(abs2, ys_test - ys_pred) ≤ σ


#---------------------------------------------------------------------#
#This code computes the Chebyshev points & weights
#Written by F.X. Giraldo on 7/2014
#           Department of Applied Mathematics
#           Naval Postgraduate School
#           Monterey; CA 93943-5216
#---------------------------------------------------------------------#
function chebyshev_gauss(P::Integer)

    #Initialize arrays
    xgl=zeros(P)
    wgl=zeros(P)

    #Construct arrays
    for i=1:P
        xgl[P+1-i]=cos( (2*i-1)*pi/(2*P) );
        wgl[P+1-i]=pi/P
    end
    return (xgl,wgl)
end #function


using Plots

using ForwardDiff
eta_d = 1.0
eta_e = 0.5

pot(eta, e) = 1/eta * log(exp(eta*e) + exp(-eta*e))

e(y, y0) = abs(y0 - y)

yd = 0.5
ye = 1.0

potd(y) = pot(eta_d, e(y, yd))
pote(y) = pot(eta_e, e(y, ye))

pot2(y) = (pot(eta_d, e(y, yd)) + pot(eta_e, e(y, ye)))/2

# plot(potd, label="d")
# plot!(pote, label="e")
# plot!(pot2, label="sum")

nabla_pot2(y) = ForwardDiff.derivative(pot2, y)

y = -2.0:0.01:2.0
p2 = pot2.(y)
pd = potd.(y)
pe = pote.(y)

dp2 = [nabla_pot2(_y) for _y in y]

plot(y, p2, label="p2")
plot!(y, dp2,label="dp2")
plot!(y, pd, label="pd")
plot!(y, pe, label="pe")


"""
cssls!(AtA,AtB,X,P)

Solve combinatorial subspace least squares (CSSLS) problem. Find X[P] that
minimizes ||A*X[P] - B[P]|| in the least squares sense, where P is a
1-bit matrix encoding the combinatorial subspace. Input X is overwritten
with the solution.

In the context of nonnegative least squares, each column of X has a passive
set which is encoded by "true" entries in P, and an active set which is encoded
by "false" entries in P.

Inputs:
	AtA = A' * A = (n x n) matrix
	AtB = A' * B = (n x k)
	P = 1-bit matrix of Bools = (? x ?) matrix

References:
	J. Kim and H. Park, Fast nonnegative matrix factorization: An
	active-set-like method and comparisons, SIAM J. Sci. Comput., 33 (2011),
	pp. 3261–3281.

	M. H. Van Benthem and M. R. Keenan, Fast algorithm for the solution of
	large-scale non-negativity-constrained least squares problems. J.
	Chemometrics 2004; 18: 441-450
"""
function cssls!(
	AtA::Matrix{Float64}, # (n x n) matrix
	AtB::Matrix{Float64}, # (n x p) matrix
	X::Matrix{Float64},   # (n x p) matrix
	P::AbstractArray{Bool}
	)

	n,p = size(AtB)

	# Find unique columns in P
	U = unique(P,dims=2)
	num_unique = size(U,2)

	# Find indices associates with unique columns in P
	E = (Array{Int})[]
	rp = collect(1:p)
	for i = 1:num_unique
		# array of indices where P[:,e] == U[:,i]
		e = (Int)[]

		# find columns of P that match U[:,i]
		j = 1
		while j <= length(rp)
			# rp contains columns of P that haven't been assigned
			t = rp[j]

			# if column t matches U[:,i] store it, and delete it from rp (don't
			# revisit preiously assigned columns)
			if all(P[:,t] .== U[:,i])
				push!(e,t)      # store column t
				deleteat!(rp,j) # delete t from rp (stored at position j)
			else
				# column t didn't match, move to next column
				j += 1
			end
		end

		# store the set of columns matching U[:,i]
		push!(E,e)
	end

	# Update X, solve each unique combinatorial subspace
	for i = 1:num_unique
		rr = U[:,i] # row mask (the passive set)

		# If the passive set is empty, these columns are already feasible.
		# So don't waste time updating them.
		if any(rr)
			cc = E[i]   # column mask (columns of P matching U[:,i])
			X[rr,cc] = pinv(AtA[rr,rr])*AtB[rr,cc]
		end
	end
end


abstract type AbstractSplineLayer <: Function end
Flux.trainable(m::AbstractSplineLayer) = (m.p,)

"""
Constructs a Spline Layer. At a high-level, it performs the following:
1. Takes as input a one-dimensional training dataset, a time span, a time step and
an interpolation method.
2. During training, adjusts the values of the function at multiples of the time-step
such that the curve interpolated through these points has minimum loss on the corresponding
one-dimensional dataset.

```julia
SplineLayer(time_span,time_step,spline_basis,saved_points=nothing)
```
Arguments:
- `time_span`: Tuple of real numbers corresponding to the time span.
- `time_step`: Real number corresponding to the time step.
- `spline_basis`: Interpolation method to be used yb the basis (current supported
  interpolation methods: ConstantInterpolation, LinearInterpolation, QuadraticInterpolation,
  QuadraticSpline, CubicSpline).
- 'saved_points': values of the function at multiples of the time step. Initialized by default
to a random vector sampled from the unit normal.
"""
struct SplineLayer{T<:Tuple{Real, Real},R<:Real,S1<:AbstractVector,S2<:UnionAll} <: AbstractSplineLayer
    time_span::T
    time_step::R
    saved_points::S1
    spline_basis::S2
    function SplineLayer(time_span,time_step,spline_basis,saved_points=nothing)
        saved_points = randn(length(time_span[1]:time_step:time_span[2]))
        new{typeof(time_span),typeof(time_step),typeof(saved_points),typeof(spline_basis)}(time_span,time_step,saved_points,spline_basis)
    end
end

function (layer::SplineLayer)(t::Real,p=layer.saved_points)
    return layer.spline_basis(p,layer.time_span[1]:layer.time_step:layer.time_span[2])(t)
end


# Tests the null of normality using the Jarque-Bera test statistic.
function jbtest(x::Vector)
    n = length(x)
    m1 = sum(x)/n
    m2 = sum((x - m1).^2)/n
    m3 = sum((x - m1).^3)/n
    m4 = sum((x - m1).^4)/n
    b1 = (m3/m2^(3/2))^2
    b2 = (m4/m2^2)
    statistic = n * b1/6 + n*(b2 - 3)^2/24
    d = Chisq(2.)
    pvalue = 1.-cdf(d,statistic)
    statistic, pvalue
end


@doc raw"""
    MinimumError <: AbstractImageBinarizationAlgorithm
    MinimumError()

    binarize([T,] img, f::MinimumError)
    binarize!([out,] img, f::MinimumError)

Under the assumption that the image histogram is a mixture of two Gaussian
distributions the binarization threshold is chosen such that the expected
misclassification error rate is minimised.

# Output

Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If
`T` is not specified, it is inferred from `out` and `img`.

# Details

Let ``f_i`` ``(i=1 \ldots I)`` denote the number of observations in the
``i``th bin of the histogram. Then the probability that an observation
belongs to the ``i``th bin is given by  ``p_i = \frac{f_i}{N}`` (``i = 1,
\ldots, I``), where ``N = \sum_{i=1}^{I}f_i``.

The minimum error thresholding method assumes that one can find a threshold
``T`` which partitions the data into two categories,  ``C_0`` and ``C_1``, such that
the data can be modelled by a mixture of two Gaussian distribution. Let
```math
P_0(T) = \sum_{i = 1}^T p_i \quad \text{and} \quad P_1(T) = \sum_{i = T+1}^I p_i
```
denote the cumulative probabilities,
```math
\mu_0(T) = \sum_{i = 1}^T i \frac{p_i}{P_0(T)} \quad \text{and} \quad \mu_1(T) = \sum_{i = T+1}^I i \frac{p_i}{P_1(T)}
```
denote the means, and
```math
\sigma_0^2(T) = \sum_{i = 1}^T (i-\mu_0(T))^2 \frac{p_i}{P_0(T)} \quad \text{and} \quad \sigma_1^2(T) = \sum_{i = T+1}^I (i-\mu_1(T))^2 \frac{p_i}{P_1(T)}
```
denote the variances of categories ``C_0`` and ``C_1``, respectively.

Kittler and Illingworth proposed to use the minimum error criterion function
```math
J(T) = 1 + 2 \left[ P_0(T) \ln \sigma_0(T) + P_1(T) \ln \sigma_1(T) \right] - 2 \left[P_0(T) \ln P_0(T) + P_1(T) \ln P_1(T) \right]
```
to assess the discreprancy between the mixture of Gaussians implied by a particular threshold ``T``,
and the piecewise-constant probability density function represented by the histogram.
The discrete value ``T`` which minimizes the function ``J(T)`` produces
the sought-after threshold value (i.e. the bin which determines the threshold).

# Arguments

The function argument is described in more detail below.

##  `img::AbstractArray`

The image that needs to be binarized. The image is automatically converted
to `Gray` in order to construct the requisite graylevel histogram.

# Example

Binarize the "cameraman" image in the `TestImages` package.

```julia
using TestImages, ImageBinarization

img = testimage("cameraman")
img_binary = binarize(img, MinimumError())
```

# References

1. J. Kittler and J. Illingworth, “Minimum error thresholding,” Pattern Recognition, vol. 19, no. 1, pp. 41–47, Jan. 1986. [doi:10.1016/0031-3203(86)90030-0](https://doi.org/10.1016/0031-3203%2886%2990030-0)
2. Q.-Z. Ye and P.-E. Danielsson, “On minimum error thresholding and its implementations,” Pattern Recognition Letters, vol. 7, no. 4, pp. 201–206, Apr. 1988. [doi:10.1016/0167-8655(88)90103-1](https://doi.org/10.1016/0167-8655%2888%2990103-1)
"""
struct MinimumError <: AbstractImageBinarizationAlgorithm end

function (f::MinimumError)(out::GenericGrayImage, img::GenericGrayImage)
    edges, counts = build_histogram(img,  256)
    t = find_threshold(HistogramThresholding.MinimumError(), counts[1:end], edges)
    @simd for i in CartesianIndices(img)
        out[i] = img[i] < t ? 0 : 1
    end
    out
end

(f::MinimumError)(out::GenericGrayImage, img::AbstractArray{<:Color3}) =
    f(out, of_eltype(Gray, img))


### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ ad96c6b4-add2-11eb-1a72-8993033e1259
using Plots, LaTeXStrings, QuadGK, Roots, DifferentialEquations

# ╔═╡ 5168971b-2b60-494c-a8d8-cdfa8e1b8102
md"# 1D Bistable Example"

# ╔═╡ 261580d9-f1a5-442c-8e7f-3479f993f9b2
md"_Define separate production and degradation terms_"

# ╔═╡ 02d0fcf2-ca87-49ae-a8f3-f14310d0182a
begin
	n_max = 200.
	n_min = 1 # need to avoid 0 for log we'll evaluate later
	n = n_min:0.1:n_max
	γ = 1
	ks = 1e-4
	ν0 = 12.5
	ν1 = 200
	production(n) = (ν0+ ν1 * ks*n^2)/(1+ks*n^2)
	degradation(n) = γ*n
end

# ╔═╡ 13ddc727-e9bd-44f2-ba5e-c6c311b41723
begin

	plot(production, n, label=L"f(n)")
	p1 = plot!(degradation, n, label=L"g(n)", xlabel=L"n", title="Rate of Change", scale=:log10, xlimits=(15, n_max), ylimits=(15, 200), legend=:topleft)
# plot!(n->production(n)  - degradation(n), n, label=L"\frac{dn}{dt}",
# 		ylims=(-20, 150), xlims=(0, 150), legend=:topleft, title="Time evolution")
end

# ╔═╡ 868ba091-d0d2-42a2-bca7-f9e996b3870d
md"_Compute the fixed points to split up the 2 attractors into **Basins of Attractions**_"

# ╔═╡ 348718a0-8e19-4911-8998-5de9689bce1d
fixed_points = find_zeros(n-> production(n) - degradation(n), n_min, n_max)

# ╔═╡ 51d6af76-5ed3-41f8-9351-91e683c7bb57
md"_Integrate to get the quasi-potential_"

# ╔═╡ acb0adcf-fc19-45cf-bf0f-eb5532743b1a
begin
	f = production
	g = degradation

	ϕ(n) = quadgk(n -> -2*(f(n) -g(n))/(f(n) + g(n)), n_min, n)[1]
end

# ╔═╡ b4c511ea-fc96-475e-a5fc-1c9e282ca0e5
ϕ_vec = ϕ.(n)

# ╔═╡ 3373f26d-a6a4-4378-a8e1-c011c2e9f7a9
p2 = plot(n, ϕ_vec, ylabel=L"\phi(n)", xlabel=L"n", label="", title="Quasi-potential")

# ╔═╡ e147802a-2cc5-4610-821b-bf351ed62f9f
md"_Show the the probability due to the quasi-potential dominates compared to the other term in the equation_"

# ╔═╡ 178564ff-da53-4dd5-ba1f-b3e5db24b2ea
begin
	plot(n->log10.(f(n) + g(n)), n, label=L"f + g")
	plot!(n, log10.(ℯ.^(-ϕ_vec)), label=L"e^{-\phi}")
	p3 = plot!(xaxis=:log10, legend=:topleft)
end

# ╔═╡ 33cf16fa-679d-4447-aa59-b6d48e8fd15a
md"_Compute the normalized probability_"

# ╔═╡ a7e9be12-a1d4-4f1a-bbfc-d2642e5a9dd5
begin
	fn = f.(n)
	gn = g.(n)
	p_unnormal(n) =  ℯ^(-ϕ(n)) / (f(n)+g(n))
	norm = quadgk(p_unnormal, n_min, n_max)[1]
	p_normal(n) = 1/norm * p_unnormal(n)
	p4 = plot(p_normal,n,  title = "Normalized Probability", label="")
end

# ╔═╡ f5b8a081-222f-4dd6-ae4c-af7060aa4223
md"_Set up the jump process simulation_"

# ╔═╡ 467386e3-2ee8-46d4-9b40-831ae2778a57
random_count() = floor(Int, n_min + rand() * n_max)

# ╔═╡ 6ae4ed84-80c2-4c06-b69a-c81e6485b78b
begin
	affect1!(integrator) = integrator.u += 1
	affect2!(integrator) = integrator.u -= 1
	prodJump = ConstantRateJump((u, p, t) -> f(u), affect1!)
	degJump = ConstantRateJump((u, p, t) -> g(u), affect2!)
	u0 = random_count()
	t = (0.0, 5000.0)
	dprob = DiscreteProblem(u0, t)
	jprob = JumpProblem(dprob, Direct(), prodJump, degJump)
end

# ╔═╡ f32b2ab8-a941-463b-bb27-968c09e24777
begin
	sim_res = solve(jprob, SSAStepper())
	p5 = plot(sim_res, xlabel=L"t", label=L"n(t)", title="Simulation trace")
end

# ╔═╡ 50c911b1-57cf-4de4-9d65-054318c445fe
begin
	pf = plot(p1, p2, p4, p5, layout = grid(2,2), size=(750, 750))
	# savefig(pf, "ch3_bistable_prob.pdf")
	pf
end

# ╔═╡ 4547d20c-dab1-4df2-aa54-b8c52b1b5eeb
md"_Setup functions to do a longer time simulation and compare to integrated probability distribution_"

# ╔═╡ 456090be-e1c0-452e-b0ad-98fbc9f0576f
function calculate_time_fraction(sim_result, split)
	dts = diff(sim_result.t)
	dt_vals = collect(zip(dts, map(last, sim_result.u[1:end])))
	high = sum(map(first, filter(dtu -> last(dtu) > split, dt_vals)))
	low = sum(map(first, filter(dtu -> last(dtu) < split, dt_vals)))
	high, low
end

# ╔═╡ 23494ad1-82e3-4eee-86e3-695511de502e
function big_sim(num_sim = 25)
	function prob_func(prob, i, repeat)
        remake(prob, u0=random_count())
    end
	num_steps = 0
    ensemble_prob = EnsembleProblem(jprob, prob_func=prob_func)
	sim = solve(ensemble_prob, SSAStepper(), trajectories=num_sim)
	totals = [0., 0.]
	for j in 1:num_sim
		totals.+= calculate_time_fraction(sim[j], fixed_points[2])
		num_steps += length(sim[j])
	end
	totals, num_steps
end

# ╔═╡ 0e38a5c3-d970-4844-b560-fe4ed5452d85
totals, num_steps = big_sim()

# ╔═╡ 573a4914-b37e-4c2b-bd23-c10b58a6d4db
begin
	sim_frac = totals./sum(totals)
end

# ╔═╡ c62dd4a3-80ce-4b1f-b5a1-6142d30d0880
md"Time fractions from simulations is $(sim_frac)"

# ╔═╡ 86ee8326-51e1-453d-8d83-fd7021613c76
begin
	p_low = quadgk(p_normal, n_min, fixed_points[2])[1]
	p_high = quadgk(p_normal, fixed_points[2], n_max)[1]
	comp_frac = p_high, p_low
end

# ╔═╡ 5cabf565-b573-4446-b34d-8812986b25fe
md"Time fractions from integration of probability is $(comp_frac)"

# ╔═╡ Cell order:
# ╠═ad96c6b4-add2-11eb-1a72-8993033e1259
# ╟─5168971b-2b60-494c-a8d8-cdfa8e1b8102
# ╟─261580d9-f1a5-442c-8e7f-3479f993f9b2
# ╠═02d0fcf2-ca87-49ae-a8f3-f14310d0182a
# ╠═13ddc727-e9bd-44f2-ba5e-c6c311b41723
# ╟─868ba091-d0d2-42a2-bca7-f9e996b3870d
# ╠═348718a0-8e19-4911-8998-5de9689bce1d
# ╟─51d6af76-5ed3-41f8-9351-91e683c7bb57
# ╠═acb0adcf-fc19-45cf-bf0f-eb5532743b1a
# ╠═b4c511ea-fc96-475e-a5fc-1c9e282ca0e5
# ╠═3373f26d-a6a4-4378-a8e1-c011c2e9f7a9
# ╟─e147802a-2cc5-4610-821b-bf351ed62f9f
# ╠═178564ff-da53-4dd5-ba1f-b3e5db24b2ea
# ╟─33cf16fa-679d-4447-aa59-b6d48e8fd15a
# ╠═a7e9be12-a1d4-4f1a-bbfc-d2642e5a9dd5
# ╟─f5b8a081-222f-4dd6-ae4c-af7060aa4223
# ╠═467386e3-2ee8-46d4-9b40-831ae2778a57
# ╠═6ae4ed84-80c2-4c06-b69a-c81e6485b78b
# ╠═f32b2ab8-a941-463b-bb27-968c09e24777
# ╠═50c911b1-57cf-4de4-9d65-054318c445fe
# ╟─4547d20c-dab1-4df2-aa54-b8c52b1b5eeb
# ╠═456090be-e1c0-452e-b0ad-98fbc9f0576f
# ╠═23494ad1-82e3-4eee-86e3-695511de502e
# ╠═0e38a5c3-d970-4844-b560-fe4ed5452d85
# ╟─c62dd4a3-80ce-4b1f-b5a1-6142d30d0880
# ╟─5cabf565-b573-4446-b34d-8812986b25fe
# ╠═573a4914-b37e-4c2b-bd23-c10b58a6d4db
# ╠═86ee8326-51e1-453d-8d83-fd7021613c76


"""
The bistable model of mutual antagonism. To perform the bistability, the parameters are defined symmetrically, contributing to a stable system with steady-states determined by the initial state.

In this section, we are going to examine wheter this package can successfully find two steady states by grid searching and predefined ranges.

Reference
---------
1. p.82-p.83, Figure 4.8. Ingalls, B. P. (2013). Mathematical modeling in systems biology: an introduction. MIT press.
"""
function bistable_ode!(du, u, p ,t)
	s1, s2 = u
	K1, K2, k1, k2, k3, k4, n1 , n2  = p

	du[1] = k1 / (1 + (s2/K2)^n1) - k3*s1
	du[2] = k2/  (1 + (s1/K1)^n2) - k4*s2
end

p_ = LVector(K1=1., K2=1., k1=20., k2=20., k3=5., k4=5., n1= 4., n2=4.)

u_1 = LVector(s1=3., s2=1.)
u_2 = LVector(s1=1., s2=3.)