struct FdHullWhiteSwaptionEngine{Y <: TermStructure, F <: FdmSchemeDescType} <: PricingEngine
  model::HullWhite{AffineModelType, Y}
  tGrid::Int
  xGrid::Int
  dampingSteps::Int
  invEps::Float64
  schemeDesc::FdmSchemeDesc{F}
  ts::Y
end

FdHullWhiteSwaptionEngine(model::HullWhite{AffineModelType, Y},
                          tGrid::Int = 100,
                          xGrid::Int = 100,
                          dampingSteps::Int = 0,
                          invEps::Float64 = 1e-5,
                          schemeDesc::FdmSchemeDesc{F} = FdmSchemeDesc(Douglas())) where {F <: FdmSchemeDescType, Y <: TermStructure} =
                  FdHullWhiteSwaptionEngine{Y, F}(model, tGrid, xGrid, dampingSteps, invEps, schemeDesc, model.ts)
# methods #
function _calculate!(pe::FdHullWhiteSwaptionEngine, swaption::Swaption)
  # 1. Term structure
  ts = pe.ts

  # 2. Mesher
  dc = ts.dc
  refDate = reference_date(ts)
  maturity = year_fraction(dc, refDate, swaption.exercise.dates[end])

  process = OrnsteinUhlenbeckProcess(get_a(pe.model), get_sigma(pe.model))
  shortRateMesher = FdmSimpleProcess1dMesher(pe.xGrid, process, maturity, 1, pe.invEps)
  mesher = FdmMesherComposite(shortRateMesher)

  # 3. Inner Value calculator
  exerciseDates = swaption.exercise.dates
  t2d = Dict{Float64, Date}()
  @simd for i = 1:length(exerciseDates)
    @inbounds t = year_fraction(dc, refDate, exerciseDates[i])
    @inbounds t2d[t] = exerciseDates[i]
  end

  disTs = pe.model.ts
  fwdTs = swaption.swap.iborIndex.ts

  # TODO check that day counts and ref dates match btwn the two term structures
  fwdModel = HullWhite(fwdTs, get_a(pe.model), get_sigma(pe.model))
  calculator = FdmAffineModelSwapInnerValue(pe.model, fwdModel, swaption.swap, t2d, mesher, 1)

  # 4. Step Conditions
  conditions = vanilla_FdmStepConditionComposite(DividendSchedule(), swaption.exercise, mesher, calculator, refDate, dc)

  # 5. Boundary conditions
  boundaries = FdmBoundaryConditionSet()

  # 6. Solver
  solverDesc = FdmSolverDesc(mesher, boundaries, conditions, calculator, maturity, pe.tGrid, pe.dampingSteps)

  solver = FdmHullWhiteSolver(pe.model, solverDesc, pe.schemeDesc)
  swaption.results.value = value_at(solver, 0.0)
end


type MALPDeployment <: DeploymentModel
    m::JuMP.Model
    x::Vector{JuMP.Variable}
end
deployment(m::MALPDeployment) = Int[round(Int,x) for x in JuMP.getValue(m.x)]

function MALPDeployment(p::DeploymentProblem,
                        q::Float64; # busy fraction;
                        α::Float64 = 0.99, # reliability level
                        tol = params.δ,
                        solver = GurobiSolver(OutputFlag=0))
    demand = vec(mean(p.demand[p.train,:],1))
    @assert length(demand) == p.nregions

    I = 1:p.nlocations
    J = 1:p.nregions
    b = ceil(Int, log(1-α)/log(q))

    m = JuMP.Model(solver=solver)
    JuMP.@variable(m, x[1:p.nlocations] >= 0, Int)
    JuMP.@variable(m, z[1:p.nregions, 1:b], Bin)

    JuMP.@objective(m, Max, sum(demand[j]*z[j,b], j in J))

    JuMP.@constraint(m, sum(x[i] for i in I) <= p.nambulances)
    for j in J # coverage over all regions
        JuMP.@constraint(m, sum(x[i] for i in filter(i->p.coverage[j,i], I)) >= 1)
        JuMP.@constraint(m, sum(x[i] for i in filter(i->p.coverage[j,i], I)) >= sum(z[j,k] for k in 1:b))
        for k in 2:b
            JuMP.@constraint(m, z[j,k] <= z[j,k-1])
        end
    end

    MALPDeployment(m, x)
end

solve(model::MALPDeployment) = JuMP.solve(model.m)
solve(model::MALPDeployment, p::DeploymentProblem) = JuMP.solve(model.m)


using DifferentialEquations
using StochasticDiffEq
using DiffEqCallbacks
using Random
using SparseArrays
using DataFrames
using StatsPlots
using BenchmarkTools

function sir_mbp!(du,u,p,t)
    (Ctilde,Rtilde) = u
    (β,c,γ,S₀,I₀,N) = p
    C = exp(Ctilde)-1.0
    R = exp(Rtilde)-1.0
    S = S₀-C
    I = I₀+C-R
    @inbounds begin
        du[1] = (exp(-Ctilde)-0.5*exp(-2.0*Ctilde))*(β*c*I/N*S)
        du[2] = (exp(-Rtilde)-0.5*exp(-2.0*Rtilde))*(γ*I)
    end
    nothing
end;

# Define a sparse matrix by making a dense matrix and setting some values as not zero
A = zeros(2,2)
A[1,1] = 1
A[2,2] = 1
A = SparseArrays.sparse(A);

# Make `g` write the sparse matrix values
function sir_noise!(du,u,p,t)
    (Ctilde,Rtilde) = u
    (β,c,γ,S₀,I₀,N) = p
    C = exp(Ctilde)-1.0
    R = exp(Rtilde)-1.0
    S = S₀-C
    I = I₀+C-R
    du[1,1] = exp(-Ctilde)*sqrt(β*c*I/N*S)
    du[2,2] = exp(-Rtilde)*sqrt(γ*I)
end;

δt = 0.1
tmax = 40.0
tspan = (0.0,tmax)
t = 0.0:δt:tmax;

u0 = [0.0,0.0]; # C,R

p = [0.05,10.0,0.25,990.0,10.0,1000.0]; # β,c,γ,S₀,I₀,N

Random.seed!(1234);

function condition(u,t,integrator,p) # Event when event_f(u,t) == 0
    (Ctilde,Rtilde) = u
    (β,c,γ,S₀,I₀,N) = p
    C = exp(Ctilde)-1.0
    R = exp(Rtilde)-1.0
    S = S₀-C
    I = I₀+C-R
    I
end

function affect!(integrator)
    terminate!(integrator)
end

cb = ContinuousCallback(
        (u,t,integrator)->condition(u,t,integrator,p),
        affect!)

prob_mbp = SDEProblem(sir_mbp!,sir_noise!,u0,tspan,p,noise_rate_prototype=A)

sol_mbp = solve(prob_mbp,
            SRA1(),
            callback=cb,
            saveat=δt);

df_mbp = DataFrame(sol_mbp(sol_mbp.t)')
df_mbp[!,:C] = exp.(df_mbp[!,:x1]) .- 1.0
df_mbp[!,:R] = exp.(df_mbp[!,:x2]) .- 1.0
df_mbp[!,:S] = p[4] .- df_mbp[!,:C]
df_mbp[!,:I] = p[5] .+ df_mbp[!,:C] .- df_mbp[!,:R]
df_mbp[!,:t] = sol_mbp.t

@df df_mbp plot(:t,
    [:S :I :R],
    label=["S" "I" "R"],
    xlabel="Time",
    ylabel="Number")

@benchmark solve(prob_mbp,SRA1(),callback=cb)

include(joinpath(@__DIR__,"tutorials","appendix.jl"))
appendix()


#Define functions for the calculation and formation of the covariance matrix S

#Compute the element of the S-matrix and store them into a vector
function S_gen(dim::Int64)

       S_vec = Complex{Float64}[]

       for i=1:dim
           for j=i:dim
      @views   fr = cov(ENWfDer[:,j+1],ENWfDer[:,i+1],corrected=false)
               push!(S_vec,fr)
             end
       end

     return S_vec

end

#Form and return the S-matrix
function Gen_S(S_vec::Array{Complex{Float64},1})

    S_mat = zeros(Complex{Float64},dim,dim)

    k = 1
    for i=1:dim
        for j=i:dim
            @views S_mat[i,j] = S_vec[k]
            @views S_mat[j,i] = conj(S_vec[k])
            k +=1
        end
    end

    return S_mat

end


using NLopt
count = 0;
function myfunc(x::Vector, grad::Vector)
    if length(grad) > 0
         grad[1] = 0
         grad[2] = 0.5/sqrt(x[2])
    end
    global count;
    count::Int += 1;
    sqrt(x[2]);
end

function mycons(x::Vector, grad::Vector, a, b)
    if length(grad) > 0
        grad[1] = 3*a * (a*x[1] + b)^2
        grad[2] = -1
    end
    (a*x[1] + b)^3 - x[2]
end

opt = Opt(:LD_MMA, 2);
lower_bounds!(opt, [-Inf, 0.]);
xtol_rel!(opt,1e-4);
min_objective!(opt, myfunc);

inequality_constraint!(opt, (x,g) -> mycons(x,g,2,0), 1e-8);
inequality_constraint!(opt, (x,g) -> mycons(x,g,-1,1), 1e-8);

(minf,minx,ret) = optimize(opt, [1.2, 5.6]);
@printf "Ans => %6.3f at (%6.3f,%6.3f)" minf minx[1] minx[2];


using GeodesicRendering
using AccretionGeometry
using CarterBoyerLindquist
using AccretionFormulae
using StaticArrays
using Printf

using Plots
gr()

function temperature(m, gp, max_time; kwargs...)
    g = AccretionFormulae.redshift(m, gp, max_time; kwargs...)

    M = m.M
    a_star = m.a
    temperature = AccretionFormulae.observed_temperature(gp.u[2], a_star, M, g)

end

function radius(m, gp, max_time; kwargs...)
    gp.u[2]
end

function temperature_render(;
    mass = 1,
    spin = 0.998,
    obs_angle = 85.0,
    disc_angle = 90.0,
    tolerance = 1e-8,
    dtmax = 1000.0,
    size_multiplier::Int64 = 1,
    resolution = 400,
    fov = 3.0,
    η = 0.1,
    η_phys = 0.1,
    edd_ratio = 0.1,
    edd_ratio_phys = 0.1,
)

    m = CarterMethodBL(M = 1.0, a = spin)
    M = m.M

    # observer position
    u = [0.0, 1000.0, deg2rad(obs_angle), 0.0]
    R_isco = AccretionFormulae.r_isco(m.a, m.M)

    # disc
    d = GeometricThinDisc(R_isco + 1, 50.0, deg2rad(disc_angle))

    # cache the render
    cache = @time prerendergeodesics(
        m,
        u,
        2000.0,
        d,
        fov_factor = fov * size_multiplier,
        abstol = tolerance,
        reltol = tolerance,
        image_width = 350 * size_multiplier,
        image_height = 350 * size_multiplier,
        dtmax = dtmax,
    )

    # create and compose the PointFunctions
    temperature_vf = (
        PointFunction(temperature) ∘
        FilterPointFunction((m, gp, max_time; kwargs...) -> gp.u[2] > R_isco, NaN) ∘
        ConstPointFunctions.filter_early_term
    )

    radius_vf = (
        PointFunction(radius) ∘
        FilterPointFunction((m, gp, max_time; kwargs...) -> gp.u[2] > R_isco, NaN) ∘
        ConstPointFunctions.filter_early_term
    )

    radius_img = GeodesicRendering.apply(radius_vf, cache)
    temperature_img = GeodesicRendering.apply(temperature_vf, cache)

    # correcting for physical mass
    M_phys = mass * 1.99e30
    r_isco_phys = AccretionFormulae.r_isco(M_phys, 0.998)
    r_g_phys = M_phys

    numerators =
        AccretionFormulae.mass_scale_fraction.(
            M_phys,
            η_phys,
            edd_ratio_phys,
            r_isco_phys,
            r_g_phys,
            radius_img * M_phys,
        )
    denominators =
        AccretionFormulae.mass_scale_fraction.(M, η, edd_ratio, R_isco, M, radius_img)

    fractions = numerators ./ denominators
    temperature_img .*= fractions

    # # autoscale
    # scale = maximum(filter(!isnan,temperature_img))
    # scale = floor(log(10, scale))
    # scale = 10^scale

    # scaling image
    scale = 1e7
    scalestr = @sprintf "%.E" scale
    exponent =  Int32(floor(log(10, scale)))
    new_img = reverse(temperature_img, dims = 1)
    new_img ./= scale

    hmap = heatmap(
        new_img,
        aspect_ratio = 1.0,
        size = (resolution * 3 / 2, resolution),
        clim = (0, 3),
    )
    # contour(new_img, aspect_ratio=1.0, size=(resolution*3/2, resolution), clim=(0,3))
    # title!("Temperature Scale = $scalestr, Mass = $mass M_☼, Obs Angle = $obs_angle")
    # title = "Temperature Scale = $scalestr K, Mass = $mass M_☼, Obs Angle = $obs_angle"
    title = "Temperature Scale = \$10^{$exponent}\$ K, Mass = $mass \$\\mathrm{M}_{\\odot}\$, Obs Angle = $obs_angle\$^{\\circ}\$"
    # "")
    return hmap, cache, title, new_img
end

# hmap, cache, title = temperature_render(obs_angle = 62.5, mass = 10, resolution=1080)
# # hmap, cache, title = temperature_render(
# #                                         mass=10,
# #                                         spin=0.998,
# #                                         obs_angle=0.01,
# #                                         tolerance=1e-12,
# #                                         size_multiplier=6,
# #                                         dtmax=1,
# #                                         resolution=2000
# #                                         )
# title!(title)
# display(hmap)


#############################################################################
# matrixfrac.jl
# implements the atom for x^T*P^{-1}*x, where P is a positive semidefinite
# matrix.
# All expressions and atoms are subtypes of AbstractExpr.
# Please read expressions.jl first.
#############################################################################

struct MatrixFracAtom <: AbstractExpr
    head::Symbol
    id_hash::UInt64
    children::Tuple{AbstractExpr, AbstractExpr}
    size::Tuple{Int, Int}

    function MatrixFracAtom(x::AbstractExpr, P::AbstractExpr)
        if x.size[2] != 1
            error("first argument of matrix frac must be a vector")
        elseif P.size[1] != P.size[2]
            error("second argument of matrix frac must be square")
        elseif x.size[1] != P.size[1]
            error("sizes must agree for arguments of matrix frac")
        end
        children = (x, P)
        return new(:matrixfrac, hash(children), children, (1,1))
    end
end

function sign(m::MatrixFracAtom)
    return Positive()
end

function monotonicity(m::MatrixFracAtom)
    return (NoMonotonicity(), NoMonotonicity())
end

function curvature(m::MatrixFracAtom)
    return ConvexVexity()
end

function evaluate(m::MatrixFracAtom)
    x = evaluate(m.children[1])
    return x'*inv(evaluate(m.children[2]))*x
end

matrixfrac(x::AbstractExpr, P::AbstractExpr) = MatrixFracAtom(x, P)
matrixfrac(x::Value, P::AbstractExpr) = MatrixFracAtom(Constant(x), P)
matrixfrac(x::AbstractExpr, P::Value) = MatrixFracAtom(x, Constant(P))

function conic_form!(m::MatrixFracAtom, unique_conic_forms::UniqueConicForms)
    if !has_conic_form(unique_conic_forms, m)
        x = m.children[1]
        P = m.children[2]
        t = Variable()
        # the matrix [t x'; x P] has Schur complement t - x'*P^{-1}*x
        # this matrix is PSD <=> t >= x'*P^{-1}*x
        p = minimize(t, [t x'; x P] ⪰ 0)
        cache_conic_form!(unique_conic_forms, m, p)
    end
    return get_conic_form(unique_conic_forms, m)
end


using NeuralPDE, Flux, ModelingToolkit, GalacticOptim, Optim, DiffEqFlux
using Quadrature,Cubature,Cuba
using Plots

t_ref = 1.0       # s
x_ref = 0.38      # dm
C_ref = 0.16      # mol/dm^3
Phi_ref = 1.0     # V

epsilon = 78.5    # K
F = 96485.3415    # A s mol^-1
R = 831.0         # kg dm^2 s^-2 K^-1 mol^-1
T = 298.0         # K

z_Na = 1.0        # non-dim
z_Cl = -1.0       # non-dim

D_Na = 0.89e-7    # dm^2 s^−1
D_Cl = 1.36e-7    # dm^2 s^−1

u_Na = D_Na * abs(z_Na) * F / (R * T)
u_Cl = D_Cl * abs(z_Cl) * F / (R * T)

t_max = 0.01 / t_ref    # non-dim
x_max = 0.38 / x_ref    # non-dim
Na_0 = 0.16 / C_ref     # non-dim
Cl_0 = 0.16 / C_ref     # non-dim
Phi_0 = 4.0 / Phi_ref   # non-dim

Na_anode = 0.0            # non-dim
Na_cathode = 2.0 * Na_0   # non-dim
Cl_anode = 1.37 * Cl_0    # non-dim
Cl_cathode = 0.0          # non-dim

Pe_Na = x_ref^2 / ( t_ref * D_Na )  # non-dim
Pe_Cl = x_ref^2 / ( t_ref * D_Cl )  # non-dim

M_Na = x_ref^2 / ( t_ref * Phi_ref * u_Na )  # non-dim
M_Cl = x_ref^2 / ( t_ref * Phi_ref * u_Cl )  # non-dim

Po_1 = (epsilon * Phi_ref) / (F * x_ref * C_ref)  # non-dim

dx = 0.01 # non-dim

function solve(opt)
    strategy = QuadratureTraining()

    @parameters t,x
    @variables Phi(..),Na(..),Cl(..)
    Dt = Differential(t)
    Dx = Differential(x)
    Dxx = Differential(x)^2

    eqs = [
            ( Dxx(Phi(t,x)) ~ ( 1.0 / Po_1 ) *
                              ( z_Na * Na(t,x) + z_Cl * Cl(t,x) ) )
            ,
            ( Dt(Na(t,x)) ~ ( 1.0 / Pe_Na ) * Dxx(Na(t,x))
                          +   z_Na / ( abs(z_Na) * M_Na )
                          * ( Dx(Na(t,x)) * Dx(Phi(t,x)) + Na(t,x) * Dxx(Phi(t,x)) ) )
            ,
            ( Dt(Cl(t,x)) ~ ( 1.0 / Pe_Cl ) * Dxx(Cl(t,x))
                          +   z_Cl / ( abs(z_Cl) * M_Cl )
                          * ( Dx(Cl(t,x)) * Dx(Phi(t,x)) + Cl(t,x) * Dxx(Phi(t,x)) ) )
          ]

    bcs = [
            Phi(t,0.0) ~ Phi_0,
            Phi(t,x_max) ~ 0.0
            ,
            Na(0.0,x) ~ Na_0,
            Na(t,0.0) ~ Na_anode,
            Na(t,x_max) ~ Na_cathode
            ,
            Cl(0.0,x) ~ Cl_0,
            Cl(t,0.0) ~ Cl_anode,
            Cl(t,x_max) ~ Cl_cathode
          ]

    # Space and time domains ###################################################

    domains = [
                t ∈ IntervalDomain(0.0, t_max),
                x ∈ IntervalDomain(0.0, x_max)
              ]

    # Neural network, Discretization ###########################################

    dim = length(domains)
    output = length(eqs)
    neurons = 16
    chain1 = FastChain( FastDense(dim, neurons, Flux.σ),
                        FastDense(neurons, neurons, Flux.σ),
                        FastDense(neurons, neurons, Flux.σ),
                        FastDense(neurons, 1))
    chain2 = FastChain( FastDense(dim, neurons, Flux.σ),
                        FastDense(neurons, neurons, Flux.σ),
                        FastDense(neurons, neurons, Flux.σ),
                        FastDense(neurons, 1))
    chain3 = FastChain( FastDense(dim, neurons, Flux.σ),
                        FastDense(neurons, neurons, Flux.σ),
                        FastDense(neurons, neurons, Flux.σ),
                        FastDense(neurons, 1))

    discretization = PhysicsInformedNN([chain1, chain2, chain3], strategy)

    indvars = [t, x]   #phisically independent variables
    depvars = [Phi, Na, Cl]       #dependent (target) variable

    loss = []
    initial_time = 0

    times = []

    cb = function (p,l)
        if initial_time == 0
            initial_time = time()
        end
        push!(times, time() - initial_time)
        #println("Current loss for $opt is: $l")
        push!(loss, l)
        return false
    end

    @named pde_system = PDESystem(eqs, bcs, domains, indvars, depvars)
    prob = discretize(pde_system, discretization)

    if opt == "both"
        res = GalacticOptim.solve(prob, ADAM(); cb = cb, maxiters=50)
        prob = remake(prob,u0=res.minimizer)
        res = GalacticOptim.solve(prob, BFGS(); cb = cb, maxiters=150)
    else
        res = GalacticOptim.solve(prob, opt; cb = cb, maxiters=200)
    end

    times[1] = 0.001

    return loss, times #add numeric solution
end

opt1 = ADAM()
opt2 = ADAM(0.005)
opt3 = ADAM(0.05)
opt4 = RMSProp()
opt5 = RMSProp(0.005)
opt6 = RMSProp(0.05)
opt7 = Optim.BFGS()
opt8 = Optim.LBFGS()

loss_1, times_1 = solve(opt1)
loss_2, times_2 = solve(opt2)
loss_3, times_3 = solve(opt3)
loss_4, times_4 = solve(opt4)
loss_5, times_5 = solve(opt5)
loss_6, times_6 = solve(opt6)
loss_7, times_7 = solve(opt7)
loss_8, times_8 = solve(opt8)
loss_9, times_9 = solve("both")

p = plot([times_1, times_2, times_3, times_4, times_5, times_6, times_7, times_8, times_9], [loss_1, loss_2, loss_3, loss_4, loss_5, loss_6, loss_7, loss_8, loss_9],xlabel="time (s)", ylabel="loss", xscale=:log10, yscale=:log10, labels=["ADAM(0.001)" "ADAM(0.005)" "ADAM(0.05)" "RMSProp(0.001)" "RMSProp(0.005)" "RMSProp(0.05)" "BFGS()" "LBFGS()" "ADAM + BFGS"], legend=:bottomleft, linecolor=["#2660A4" "#4CD0F4" "#FEC32F" "#F763CD" "#44BD79" "#831894" "#A6ED18" "#980000" "#FF912B"])

p = plot(1:201, [loss_1, loss_2, loss_3, loss_4, loss_5, loss_6, loss_7, loss_8, loss_9[2:end]], xlabel="iterations", ylabel="loss", yscale=:log10, labels=["ADAM(0.001)" "ADAM(0.005)" "ADAM(0.05)" "RMSProp(0.001)" "RMSProp(0.005)" "RMSProp(0.05)" "BFGS()" "LBFGS()" "ADAM + BFGS"], legend=:bottomleft, linecolor=["#2660A4" "#4CD0F4" "#FEC32F" "#F763CD" "#44BD79" "#831894" "#A6ED18" "#980000" "#FF912B"])

@show loss_1[end], loss_2[end], loss_3[end], loss_4[end], loss_5[end], loss_6[end], loss_7[end], loss_8[end], loss_9[end]

using SciMLBenchmarks
SciMLBenchmarks.bench_footer(WEAVE_ARGS[:folder],WEAVE_ARGS[:file])


function jacobian(m::RepDSGEGovDebt)
    endo = augment_model_states(m.endogenous_states, n_model_states(m))
    eq   = m.equilibrium_conditions

    # Load in parameters/steady-state parameters
    c_scalar   = m[:cstar].value
    ell_scalar = m[:lstar].value
    ω          = m[:ωstar].value
    H          = m[:H].value
    ϕh         = m[:ϕh].value
    lam        = 1/c_scalar
    ϕ          = lam*ω/(H^ϕh)
    βrank      = exp(m[:γ].value)/(1+m[:r])
    y          = m[:ystar].value
    g          = m[:g].value
    Rk         = m[:Rkstar].value
    T          = m[:Tstar].value
    k          = m[:kstar].value
    x          = m[:xstar].value
    Tg         = m[:Tg].value
    spp        = m[:spp].value
    R          = m[:r].scaledvalue + 1
    γ          = m[:γ].scaledvalue
    δ          = m[:δ].value
    κw         = m[:κ_w].value
    κp         = m[:κ_p].value
    α          = m[:α].value
    ρR         = m[:ρR].value
    ψπ         = m[:ψπ].value
    ψy         = m[:ψy].value
    δb         = m[:δb].value
    bg         = m[:bg].value
    ρB         = m[:ρ_b].value
    ρG         = m[:ρ_g].value
    ρZ         = m[:ρ_z].value
    ρμ         = m[:ρ_μ].value
    ρlamw      = m[:ρ_λ_w].value
    ρlamf      = m[:ρ_λ_f].value
    ρmon       = m[:ρ_rm].value

    # make the Jacobian
    JJ = zeros(n_model_states(m), 2*n_model_states(m))

    # Euler equation
    JJ[eq[:eq_euler],endo[:l′_t]] = 1.
    JJ[eq[:eq_euler],endo[:z′_t]] = -1.
    JJ[eq[:eq_euler],endo[:b_t]]  = ell_scalar
    JJ[eq[:eq_euler],endo[:l_t]]  = -1.
    JJ[eq[:eq_euler],endo[:R_t]]  = 1.

    # mkt clearing
    JJ[eq[:eq_market_clearing],endo[:y_t]]   = y/g
    JJ[eq[:eq_market_clearing],endo[:g_t]]   = -y/g
    JJ[eq[:eq_market_clearing],endo[:I_t]]   = -x
    JJ[eq[:eq_market_clearing],endo[:l_t]] = c_scalar

    # lambda = average marginal utility
    JJ[eq[:eq_lambda],endo[:margutil_t]] = lam
    JJ[eq[:eq_lambda],endo[:l_t]] = -1/c_scalar

    # transfer
    JJ[eq[:eq_transfers],endo[:t_t]]  = T
    JJ[eq[:eq_transfers],endo[:capreturn_t]]  = -Rk*k
    JJ[eq[:eq_transfers],endo[:k_t]]  = -Rk*k
    JJ[eq[:eq_transfers],endo[:z_t]]   = Rk*k
    JJ[eq[:eq_transfers],endo[:I_t]]   = x
    JJ[eq[:eq_transfers],endo[:mc_t]]  = y
    JJ[eq[:eq_transfers],endo[:tg_t]]  = Tg

    # investment
    JJ[eq[:eq_investment],endo[:Q_t]]  = 1.
    JJ[eq[:eq_investment],endo[:μ_t]]  = 1.
    JJ[eq[:eq_investment],endo[:I′_t]] = spp*(exp(3*γ))/R
    JJ[eq[:eq_investment],endo[:z′_t]] = spp*(exp(3*γ))/R
    JJ[eq[:eq_investment],endo[:I_t]]  = -spp*(exp(3*γ))/R - spp*exp(2*γ)
    JJ[eq[:eq_investment],endo[:I_t1]] = spp*exp(2*γ)
    JJ[eq[:eq_investment],endo[:z_t]]  = -spp*exp(2*γ)

    # tobin's q
    JJ[eq[:eq_tobin_q],endo[:margutil_t]]  = 1.
    JJ[eq[:eq_tobin_q],endo[:margutil′_t]] = -1.
    JJ[eq[:eq_tobin_q],endo[:Q_t]]    = 1.
    JJ[eq[:eq_tobin_q],endo[:z′_t]]   = 1.
    JJ[eq[:eq_tobin_q],endo[:capreturn′_t]]  = -Rk/R
    JJ[eq[:eq_tobin_q],endo[:Q′_t]]   = -(1-δ)/R

    # capital accumulation
    JJ[eq[:eq_capital_accumulation],endo[:k′_t]] = 1.
    JJ[eq[:eq_capital_accumulation],endo[:k_t]]  = -(1-δ)
    JJ[eq[:eq_capital_accumulation],endo[:z_t]]   = (1-δ)
    JJ[eq[:eq_capital_accumulation],endo[:μ_t]]  = -x/k
    JJ[eq[:eq_capital_accumulation],endo[:I_t]]   = -x/k

    # wage phillips curve
    JJ[eq[:eq_wage_phillips],endo[:π_w_t]]  = -1.
    JJ[eq[:eq_wage_phillips],endo[:λ_w_t]] = 1.
    JJ[eq[:eq_wage_phillips],endo[:L_t]]   = κw*ϕh
    JJ[eq[:eq_wage_phillips],endo[:w_t]]    = -κw
    JJ[eq[:eq_wage_phillips],endo[:π_w′_t]]  = βrank
    JJ[eq[:eq_wage_phillips],endo[:margutil_t]]  = -κw

    # price phillips curve
    JJ[eq[:eq_price_phillips],endo[:π_t]]   = -1.
    JJ[eq[:eq_price_phillips],endo[:mc_t]]   = κp
    JJ[eq[:eq_price_phillips],endo[:λ_f_t]] = 1.
    JJ[eq[:eq_price_phillips],endo[:π′_t]]  = 1/R

    # marginal cost
    JJ[eq[:eq_marginal_cost],endo[:mc_t]] = 1.
    JJ[eq[:eq_marginal_cost],endo[:w_t]]  = -(1-α)
    JJ[eq[:eq_marginal_cost],endo[:capreturn_t]] = -α

    # gdp
    JJ[eq[:eq_gdp],endo[:y_t]]  = 1.
    JJ[eq[:eq_gdp],endo[:z_t]]  = α
    JJ[eq[:eq_gdp],endo[:k_t]] = -α
    JJ[eq[:eq_gdp],endo[:L_t]] = -(1-α)

    # optimal k/l ratio
    JJ[eq[:eq_optimal_kl],endo[:capreturn_t]] = 1.
    JJ[eq[:eq_optimal_kl],endo[:w_t]]  = -1.
    JJ[eq[:eq_optimal_kl],endo[:L_t]] = -1.
    JJ[eq[:eq_optimal_kl],endo[:k_t]] = 1.
    JJ[eq[:eq_optimal_kl],endo[:z_t]]  = -1.

    # taylor rule
    JJ[eq[:eq_taylor],endo[:i_t]]   = -1.
    JJ[eq[:eq_taylor],endo[:i_t1]]  = ρR
    JJ[eq[:eq_taylor],endo[:π_t]]   = (1-ρR)*ψπ
    JJ[eq[:eq_taylor],endo[:y_t]]    = (1-ρR)*ψy
    JJ[eq[:eq_taylor],endo[:y_t1]]  = -(1-ρR)*ψy
    JJ[eq[:eq_taylor],endo[:z_t]]    = (1-ρR)*ψy
    JJ[eq[:eq_taylor],endo[:rm_t]] = 1.

    # fisher eqn
    JJ[eq[:eq_fisher],endo[:R_t]]  = 1.
    JJ[eq[:eq_fisher],endo[:π′_t]] = 1.
    JJ[eq[:eq_fisher],endo[:i_t]]  = -1.

    # wage inflation
    JJ[eq[:eq_nominal_wage_inflation],endo[:π_w_t]] = 1.
    JJ[eq[:eq_nominal_wage_inflation],endo[:π_t]]  = -1.
    JJ[eq[:eq_nominal_wage_inflation],endo[:z_t]]   = -1.
    JJ[eq[:eq_nominal_wage_inflation],endo[:w_t]]   = -1.
    JJ[eq[:eq_nominal_wage_inflation],endo[:w_t1]]  = 1.

    # fiscal rule
    JJ[eq[:eq_fiscal_rule],endo[:tg_t]]  = -Tg
    JJ[eq[:eq_fiscal_rule],endo[:R_t]]   = δb*bg/R
    JJ[eq[:eq_fiscal_rule],endo[:bg_t]]  = δb*bg*ℯ^(-γ)
    JJ[eq[:eq_fiscal_rule],endo[:z_t]]   = -δb*bg*ℯ^(-γ)
    JJ[eq[:eq_fiscal_rule],endo[:y_t]]   = δb*(1-(1/g))*y
    JJ[eq[:eq_fiscal_rule],endo[:g_t]]   = δb*y/g

    # govt budget constraint
    JJ[eq[:eq_g_budget_constraint],endo[:bg′_t]] = -(bg/R)
    JJ[eq[:eq_g_budget_constraint],endo[:R_t]]   = bg/R
    JJ[eq[:eq_g_budget_constraint],endo[:bg_t]]  = bg*ℯ^(-γ)
    JJ[eq[:eq_g_budget_constraint],endo[:z_t]]   = -bg*ℯ^(-γ)
    JJ[eq[:eq_g_budget_constraint],endo[:y_t]]   = (1-(1/g))*y
    JJ[eq[:eq_g_budget_constraint],endo[:g_t]]   = y/g
    JJ[eq[:eq_g_budget_constraint],endo[:tg_t]]  = -Tg

    # update lagged variables
    JJ[eq[:LR],endo[:R′_t1]] = 1.
    JJ[eq[:LR],endo[:R_t]]   = -1.

    JJ[eq[:LI],endo[:i′_t1]] = 1.
    JJ[eq[:LI],endo[:i_t]]   = -1.

    JJ[eq[:LY],endo[:y′_t1]] = 1.
    JJ[eq[:LY],endo[:y_t]]   = -1.

    JJ[eq[:LW],endo[:w′_t1]] = 1.
    JJ[eq[:LW],endo[:w_t]]   = -1.

    JJ[eq[:LX],endo[:I′_t1]] = 1.
    JJ[eq[:LX],endo[:I_t]]   = -1.

    # discount factor shock
    JJ[eq[:eq_b],endo[:b′_t]] = 1.
    JJ[eq[:eq_b],endo[:b_t]]  = -ρB

    # g/y shock
    JJ[eq[:eq_g],endo[:g′_t]] = 1.
    JJ[eq[:eq_g],endo[:g_t]]  = -ρG

    # tfp growth shock
    JJ[eq[:eq_z],endo[:z′_t]] = 1.
    JJ[eq[:eq_z],endo[:z_t]]  = -ρZ

    # investment shock
    JJ[eq[:eq_μ],endo[:μ′_t]] = 1.
    JJ[eq[:eq_μ],endo[:μ_t]]  = -ρμ

    # wage mkup shock
    JJ[eq[:eq_λ_w],endo[:λ_w′_t]] = 1.
    JJ[eq[:eq_λ_w],endo[:λ_w_t]]  = -ρlamw

    # price mkup shock
    JJ[eq[:eq_λ_f],endo[:λ_f′_t]] = 1.
    JJ[eq[:eq_λ_f],endo[:λ_f_t]] = -ρlamf

    # monetary policy shock
    JJ[eq[:eq_rm],endo[:rm′_t]] = 1.
    JJ[eq[:eq_rm],endo[:rm_t]]  = -ρmon

    if !m.testing
        JJ  = normalize(m, JJ)
    end

    return JJ
end

function compose_normalization_matrices(m::RepDSGEGovDebt)
    Qleft     = eye(n_model_states(m))
    Qx        = eye(n_backward_looking_states(m))
    Qy        = eye(n_jumps(m))

    Qright    = cat(Qx', Qy', Qx', Qy', dims = [1,2])

    return Qx, Qy, Qleft, Qright
end

function normalize(m::RepDSGEGovDebt, JJ::Matrix{Float64})
    Qx, _, Qleft, Qright = compose_normalization_matrices(m)

    m <= Setting(:n_predetermined_variables, size(Qx, 1))

    Jac1 = Qleft*JJ*Qright

    return Jac1
end


module LogisticRegression
using LinearAlgebra
using Distributions
using Random
# using UnicodePlots
using SparseArrays
BLAS.set_num_threads(2)

"""
This structure implements the Sklearn.jl api for
the Logistic Regression algorithm
"""
mutable struct LogisticRegressor
    num_steps::Int64
    cutoff::Float64
    learning_rate::Float64
    add_intercept::Bool
    weights::Array{Float64, 1}
end
LogisticRegressor(;
    num_steps=300000,
    cutoff=1e-4,
    learning_rate=5e-5,
    add_intercept=false,
    weights=Float64[]
    ) = LogisticRegressor(  num_steps,
                            cutoff,
                            learning_rate,
                            add_intercept,
                            weights )

function fit!(self::LogisticRegressor, data, labels::Array{T, 1}) where T <: Number
    weights = logistic_regression(  data,
                                    labels,
                                    num_steps = self.num_steps,
                                    cutoff = self.cutoff,
                                    learning_rate = self.learning_rate,
                                    add_intercept = self.add_intercept)
    self.weights = weights
end
function predict(self::LogisticRegressor, data)
    if self.add_intercept
        data = [ones(size(data)[1]) data]
    end
    final_scores = data * self.weights
    preds = round.(par_sigmoid(final_scores))
    return preds
end
function sigmoid(score::T) where T <: Number
    return 1/(1 + exp(-score))
end
function par_sigmoid(scores::Array{T}) where T <: Number
    res = zeros(length(scores))
    Threads.@threads for i in eachindex(scores)
        @inbounds res[i] = sigmoid(scores[i])
    end
    return res
end
function log_likelihood(feats, label, weights)
    scores = feats * weights
    log_likely = sum(label .* scores - log.(1 .+ exp.(scores)))
    return log_likely
end
function logistic_regression(feats, labels; num_steps=20000, cutoff=1e-1000, learning_rate=1e-2, add_intercept=false)
    if add_intercept == true
        intercept = ones(size(feats)[1])
        feats = [intercept feats]
    end
    # should have a weight for each column
    weights = zeros(size(feats)[2])
    scores = zeros(size(feats)[2])
    error_signal = zeros(size(feats)[2])
    last_log_likelihood_delta = 100.0
    current_log_likelihood_delta = 100.0
    last_log_likelihood = 10000.0
    current_log_likelihood = 10000.0
    for step=1:num_steps
        scores = feats * weights
        predictions = par_sigmoid(scores)

        # update using the gradient
        error_signal = labels - predictions
        gradient = transpose(feats) * error_signal
        weights += learning_rate * gradient
        if step % 10000 == 0
            current_log_likelihood = log_likelihood( feats,
                                                     labels,
                                                     weights)
            current_log_likelihood_delta = abs(current_log_likelihood - last_log_likelihood)
            println("Change in log likelihood: " * string(current_log_likelihood_delta))
            if current_log_likelihood_delta < cutoff && last_log_likelihood_delta < cutoff
                break # stop training if we haven't exceeded cutoff in 20000 iterations
            end
            last_log_likelihood_delta = current_log_likelihood_delta
            last_log_likelihood = current_log_likelihood
        end
    end
    return weights
end
function test_log_reg()
    num_instances = 5000
    # generate dense training data
    #x1_v1 = rand(Normal(0, 2), num_instances)
    #x1_v2 = rand(Normal(0, .3), num_instances)
    #x2_v1 = rand(Normal(4, 1), num_instances)
    #x2_v2 = rand(Normal(2, .3), num_instances)

    # generate sparse training data
    x1_v1 = sparse(rand(Binomial(4, .2), num_instances))
    x1_v2 = sparse(rand(Binomial(8, .1),
    num_instances))
    x2_v1 = sparse(rand(Binomial(4, .8), num_instances))
    x2_v2 = sparse(rand(Binomial(8, .7), num_instances))

    x1 = [x1_v1 x1_v2]
    x2 = [x2_v1 x2_v2]
    # generate sparse test data
    x3_v1 = sparse(rand(Binomial(4, .2), num_instances))
    x3_v2 = sparse(rand(Binomial(8, .1),
    num_instances))
    x4_v1 = sparse(rand(Binomial(4, .8), num_instances))
    x4_v2 = sparse(rand(Binomial(8, .7), num_instances))
    x3 = [x3_v1 x3_v2]
    x4 = [x4_v1 x4_v2]
    test_data = [x3 ; x4]
    test_labels = [zeros(num_instances) ; ones(num_instances)]
    # display the dataset we just made
    # plt = scatterplot(x1[:, 1], x2[:, 2], ylim=[-2,3])
    # scatterplot!(plt, x2[:, 1], x2[:, 2], color=:blue)
    # combine these two separate
    # normal distributions
    data = [x1 ; x2]
    # data_with_intercept = [ones(size(data)[1]) data]
    labels = [zeros(num_instances) ; ones(num_instances)]
    # weights = logistic_regression(data, labels, num_steps=300000, learning_rate=5e-5, add_intercept=true)
    # final_scores = data_with_intercept * weights
    # preds = round.(par_sigmoid(final_scores))
    model = LogisticRegressor(add_intercept=true)
    fit!(model, data, labels)
    test_preds = predict(model, test_data)
    # println("Accuracy on training data: ")
    # acc = sum(preds .== labels)/length(labels)
    # println(acc)
    # test_data_with_intercept = [ones(size(test_data)[1]) test_data]
    # test_scores = test_data_with_intercept * weights
    # test_preds = round.(par_sigmoid(test_scores))
    println("Accuracy on test data: ")
    acc = sum(test_preds .== test_labels)/length(test_labels)
    println(acc)
    return acc
end
export LogisticRegressor, fit!, predict
end # module


"""
CV(data::AbstractVector)
CV(data::AbstractVector, TimeWindow::Int64, Start = data[1], End = data[end])
CV(data::AbstractVector, TimeWindow::Float64,Start = data[1], End = data[end])

CV function calculate coefficient of validation, which can be described with
$$
CV = \frac{\sqrt{\langle \tau \rangle}}{\langle \tau \rangle}.
$$
CV can be underestimated if the dataset is right censored.
"""
function CV(x::AbstractVector, tw::Float64 = nothing;
         data::AbstractVector = x, TimeWindow::Float64= tw, Start = data[1], End = data[end])
    if TimeWindow == nothing
        return std(data)/mean(data)
    else
        t::Vector{Float64} = [i for i in start:tw:End]
        CVs::Vector{Float64} = zeros(Float64,length(t))
        n::Int64 = 0
        for i in t
            a = data[ i .< data .<  i + tw ]
            if length(a) != 0
                CVs[n] = std(a)/mean(a)
            else
                CVs[n] = nothing
            end
        end
        return t,CVs
    end
end

function CV(x::AbstractVector, tw::Int64 = nothing;
                data::AbstractVector = x, TimeWindow::Int64= tw, Start = data[1], End = data[end])
    if TimeWindow == nothing
        return std(data)/mean(data)
    else
        t::Vector{Float64} = [i for i in start:tw:End]
        CVs::Vector{Float64} = zeros(Float64,length(t))
        n::Int64 = 0
        for i in t
            a = data[ i .< data .<  i + tw ]
            if length(a) != 0
                CVs[n] = std(a)/mean(a)
            else
                CVs[n] = nothing
            end
        end
        return t,CVs
    end
end

function CV(x::AbstractVector, tw::Float64 = nothing, s,e;
                data::AbstractVector = x, TimeWindow::Float64= tw, Start = s, End = e)
    if TimeWindow == nothing
        return std(data)/mean(data)
    else
        t::Vector{Float64} = [i for i in start:tw:End]
        CVs::Vector{Float64} = zeros(Float64,length(t))
        n::Int64 = 0
        for i in t
            a = data[ i .< data .<  i + tw ]
            if length(a) != 0
                CVs[n] = std(a)/mean(a)
            else
                CVs[n] = nothing
            end
        end
        return t,CVs
    end
end

function CV(x::AbstractVector, tw::Int64 = nothing, s,e;
                data::AbstractVector = x, TimeWindow::Int64= tw, Start = s, End = e)
    if TimeWindow == nothing
        return std(data)/mean(data)
    else
        t::Vector{Float64} = [i for i in start:tw:End]
        CVs::Vector{Float64} = zeros(Float64,length(t))
        n::Int64 = 0
        for i in t
            a = data[ i .< data .<  i + tw ]
            if length(a) != 0
                CVs[n] = std(a)/mean(a)
            else
                CVs[n] = nothing
            end
        end
        return t,CVs
    end
end


#=
The Computer Language Benchmarks Game
 https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
 direct transliteration of the swift#3 program by Ralph Ganszky and Daniel Muellenborn:
 https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-swift-3.html
 modified for Julia 1.0 by Simon Danisch
=#
const zerov8 = ntuple(x-> 0f0, 8)

@inline function step_mandel(Zr,Zi,Tr,Ti,cr,ci)
    Zi = 2f0 .* Zr .* Zi .+ ci
    Zr = Tr .- Ti .+ cr
    Tr = Zr .* Zr
    Ti = Zi .* Zi
    return Zr,Zi,Tr,Ti
end

# Calculate mandelbrot set for one Vec8 into one byte
Base.@propagate_inbounds function mand8(cr, ci)
    Zr = zerov8
    Zi = zerov8
    Tr = zerov8
    Ti = zerov8
    t = zerov8
    i = 0

    while i<50
        for _ in 1:5
            Zr,Zi,Tr,Ti = step_mandel(Zr,Zi,Tr,Ti,cr,ci)
            i += 1
        end
        t = Tr .+ Ti
        all(x-> x > 4f0, t) && (return 0x00)
    end
    byte = 0xff
    t[1] <= 4.0 || (byte &= 0b01111111)
    t[2] <= 4.0 || (byte &= 0b10111111)
    t[3] <= 4.0 || (byte &= 0b11011111)
    t[4] <= 4.0 || (byte &= 0b11101111)
    t[5] <= 4.0 || (byte &= 0b11110111)
    t[6] <= 4.0 || (byte &= 0b11111011)
    t[7] <= 4.0 || (byte &= 0b11111101)
    t[8] <= 4.0 || (byte &= 0b11111110)
    return byte
end

function mandel_inner(rows, ci, y, N, xvals)
    @simd for x in 1:8:N
        @inbounds begin
            cr = ntuple(i-> xvals[x + (i - 1)], 8)
            rows[((y-1)*N÷8+(x-1)÷8) + 1] = mand8(cr, ci)
        end
    end
end

function mandelbrot(n = 200)
    inv_ = 2.0 / n
    N = n
    xvals = zeros(Float32, n)
    yvals = zeros(Float32, n)
    Threads.@threads for i in 0:(N-1)
        @inbounds xvals[i + 1] = i * inv_ - 1.5
        @inbounds yvals[i + 1] = i * inv_ - 1.0
    end
    rows = zeros(UInt8, n*N÷8)
    Threads.@threads for y in 1:N
        @inbounds ci = yvals[y]
        mandel_inner(rows, ci, y, N, xvals)
    end
    write(stdout, "P4\n$n $n\n")
    write(stdout, rows)
end

mandelbrot(parse(Int, ARGS[1]))


using PyCall
using Distributions

@pyimport matplotlib.pyplot as plt
@pyimport matplotlib.figure as pyfigure
@pyimport matplotlib.pylab as pylab
@pyimport matplotlib.colors as colors

function main(keepinput=true)

  #
  # 1 means white, 0 means black
  #

  # number of lines in filter
  n = 500
  # number of columns in filter
  m = 500
  # radius of the filter circle
  r = 60
  # filter off if nothing, keep input
  x = keepinput ? nothing : 0
  # filter on
  y = 1

  filterfunc = f_cria_func_transf(n,m,r,x,y)

  # 1 is white, 0 is black
  inputmatrix = rand(n,m)

  filterfunc(inputmatrix)

end

function f_cria_func_transf(n,m,r,x,y)

  function applyfilter(inputmatrix)

    # input matrix must be at least as large as the requested filter
    @assert size(inputmatrix,1) >= n
    @assert size(inputmatrix,2) >= m
    # only square matrices
    @assert m == n

    totalsize = n*m

    outputmatrix = reshape(deepcopy(inputmatrix),1,totalsize)

    # add indices to elements so that we know where we are
    outputmatrix = collect(enumerate(outputmatrix))

    # apply the filter per se
    # each (index,element) tuple becomes a single filtered element

    filtered = map( tuple -> mapper(tuple[1],tuple[2],x,y,r,m,n),outputmatrix)

    # turn it back into a matrix
    filtered = reshape(filtered,n,m)

    # display
    plt.matshow(filtered,cmap = plt.get_cmap("hot") )
    plt.show()

  end
end

# returns a possibly filtered element
function mapper(index,elem,off,on,radius,numrows,numcols)

  if withinradius(numrows,numcols,radius,index)
    on
  else
    if off == nothing
      elem
    else
      off
    end
  end

end

function withinradius(numrows,numcols,radius,elemindex)

  # otherwise we can't find the center of the filter circle
  @assert numrows == numcols

  center = if numrows % 2 == 0
    (numrows/2,numcols/2)
  else
    ((numrows+1)/2,(numcols+1)/2)
  end

  # this is the position of the element we want to evaluate
  element_i = div(elemindex,numcols) +1
  element_j = elemindex % numcols +1

  # do you remember pythagoras' theorem?
  dist1 = abs(center[1]-element_i)
  dist2 = abs(center[2]-element_j)
  hyp = sqrt( (dist1^2) + (dist2^2) )

  if hyp <= radius
    true
  else
    false
  end

end

# main(true) sets the off state of the filter to be the original input
# main(false) sets the off state of the filter be 0 (i.e. black)
main(true)


"The empty domain with elements of type `T`."
struct EmptySpace{T} <: Domain{T}
end

const AnyEmptySpace = EmptySpace{Any}

EmptySpace() = EmptySpace{Float64}()
EmptySpace(::Type{T}) where {T} = EmptySpace{T}()

emptyspace(d::Domain{T}) where {T} = EmptySpace{T}()

indomain(x::T, d::EmptySpace{T}) where {T} = false

approx_indomain(x, d::EmptySpace, tolerance) = in(x, d)

isempty(d::EmptySpace) = true

# Arithmetic operations

union(d1::EmptySpace, d2::EmptySpace) = d1
union(d1::Domain, d2::EmptySpace) = d1
union(d1::EmptySpace, d2::Domain) = d2

intersect(d1::EmptySpace, d2::EmptySpace) = d1
intersect(d1::Domain, d2::EmptySpace) = d2
intersect(d1::EmptySpace, d2::Domain) = d1

setdiff(d1::EmptySpace, d2::EmptySpace) = d1
setdiff(d1::EmptySpace, d2::Domain) = d1
setdiff(d1::Domain, d2::EmptySpace) = d1

# TODO: verify these - should we restrict x?
(+)(d::EmptySpace, x::Number) = d
(*)(d::EmptySpace, x::Number) = d

==(::EmptySpace, ::EmptySpace) = true

show(io::IO, d::EmptySpace) = print(io, "{} (empty domain)")

"The full space of elements of type `T`."
struct FullSpace{T} <: Domain{T} end

const AnyFullSpace = FullSpace{Any}

FullSpace() = FullSpace{Float64}()
FullSpace(d) = FullSpace{eltype(d)}()

euclideanspace(n::Val{N}) where {N} = euclideanspace(n, Float64)
euclideanspace(::Val{N}, ::Type{T}) where {N,T} = FullSpace{SVector{N,T}}()

indomain(x::T, d::FullSpace{T}) where {T} = true
indomain(x::S, d::FullSpace{T}) where {T,S} = promotes_to(S,T) == Val{true}

approx_indomain(x, d::FullSpace, tolerance) = in(x, d)

# We choose the origin as a point in the full space
point_in_domain(d::FullSpace) = zero(eltype(d))

isempty(::FullSpace) = false # constains zero

# Arithmetic operations

union(d1::FullSpace, d2::FullSpace) = d1
union(d1::Domain, d2::FullSpace) = d2
union(d1::FullSpace, d2::Domain) = d1

intersect(d1::FullSpace, d2::FullSpace) = d1
intersect(d1::Domain, d2::FullSpace) = d1
intersect(d1::FullSpace, d2::Domain) = d2

(+)(d::FullSpace{T}, x::T) where {T} = d

(*)(d::FullSpace, x::Number) = d

show(io::IO, d::FullSpace) = print(io, "{x} (full space)")

convert(::Type{Domain}, ::Type{T}) where T = FullSpace{T}()
convert(::Type{Domain{S}}, ::Type{T}) where {T,S} = convert(Domain{S}, convert(Domain, T))

infimum(d::FullSpace{T}) where {T} = typemin(T)
supremum(d::FullSpace{T}) where {T} = typemax(T)


println("# Julia says welcome from node ",gethostname() )

# Functions for demo calculation
function calc_pi(N::Integer)
  piapprox = 0.0
  for i in 1:N
    x, y = rand(), rand()
    piapprox += (x*x+y*y <= 1.0) ? 1.0 : 0.0
  end
  piapprox *= 4.0/N
end

# Perform demo calculation
srand(42)        # seed random number generator for repeatability
calc_pi(10)      # a test run to make sure it compiles before we start timing it
@time piapprox = calc_pi(100000000)
@printf("pi is estimated to be %14.12f\n", piapprox)


using Printf, Distributions

struct Grid
    cells::BitArray{2}
    hwall::BitArray{2}
    vwall::BitArray{2}
end
function Grid(p::AbstractFloat, m::Integer=10, n::Integer=10)
    cells = fill(false, m, n)
    hwall = rand(Bernoulli(p), m + 1, n)
    vwall = rand(Bernoulli(p), m, n + 1)
    vwall[:, 1] = true
    vwall[:, end] = true
    return Grid(cells, hwall, vwall)
end

function Base.show(io::IO, g::Grid)
    H = (" .", " _")
    V = (":", "|")
    C = (" ", "#")
    ind = findfirst(g.cells[end, :] .& .!g.hwall[end, :])
    percolated = !iszero(ind)
    println(io, "$(size(g.cells, 1))×$(size(g.cells, 2)) $(percolated ? "Percolated" : "Not percolated") grid")
    for r in 1:size(g.cells, 1)
        println(io, "    ", join(H[w+1] for w in g.hwall[r, :]))
        println(io, " $(r % 10)) ", join(V[w+1] * C[c+1] for (w, c) in zip(g.vwall[r, :], g.cells[r, :])))
    end
    println(io, "    ", join(H[w+1] for w in g.hwall[end, :]))
    if percolated
        println(io, " !)  ", "  " ^ (ind - 1), '#')
    end
end

function floodfill!(m::Integer, n::Integer, cells::AbstractMatrix{<:Integer},
                    hwall::AbstractMatrix{<:Integer}, vwall::AbstractMatrix{<:Integer})
    # fill cells
    cells[m, n] = true
    percolated = false
    # bottom
    if m < size(cells, 1) && !hwall[m+1, n] && !cells[m+1, n]
        percolated = percolated || floodfill!(m + 1, n, cells, hwall, vwall)
    # The Bottom
    elseif m == size(cells, 1) && !hwall[m+1, n]
        return true
    end
    # left
    if n > 1 && !vwall[m, n] && !cells[m, n-1]
        percolated = percolated || floodfill!(m, n - 1, cells, hwall, vwall)
    end
    # right
    if n < size(cells, 2) && !vwall[m, n+1] && !cells[m, n+1]
        percolated = percolated || floodfill!(m, n + 1, cells, hwall, vwall)
    end
    # top
    if m > 1 && !hwall[m, n] && !cells[m-1, n]
        percolated = percolated || floodfill!(m - 1, n, cells, hwall, vwall)
    end
    return percolated
end
function pourontop!(g::Grid)
    m, n = 1, 1
    percolated = false
    while !percolated && n ≤ size(g.cells, 2)
        percolated = !g.hwall[m, n] && floodfill!(m, n, g.cells, g.hwall, g.vwall)
        n += 1
    end
    return percolated
end

function main(probs, nrep::Integer=1000)
    sampleprinted = false
    pcount = zeros(Int, size(probs))
    for (i, p) in enumerate(probs), _ in 1:nrep
        g = Grid(p)
        percolated = pourontop!(g)
        if percolated
            pcount[i] += 1
            if !sampleprinted
                println(g)
                sampleprinted = true
            end
        end
    end
    return pcount ./ nrep
end

probs = collect(10:-1:0) ./ 10
percprobs = main(probs)

println("Fraction of 1000 tries that percolate through:")
for (pr, pp) in zip(probs, percprobs)
    @printf("\tp = %.3f ⇒ freq. = %5.3f\n", pr, pp)
end


# Tests for Enhanced Russell Graph DEA Models
@testset "EnhancedRussellGraphDEAModel" begin

    # Data for ERG test
    X = [2; 4; 8; 12; 6; 14; 14; 9.412]
    Y = [1; 5; 8; 9; 3; 7; 9; 2.353]

    # Enhanced Russell Graph CRS
    deaergcrs = deaerg(X, Y, rts = :CRS)

    @test typeof(deaergcrs) == EnhancedRussellGraphDEAModel

    @test nobs(deaergcrs) == 8
    @test ninputs(deaergcrs) == 1
    @test noutputs(deaergcrs) == 1
    @test efficiency(deaergcrs) ≈ [0.4; 1.0; 0.8; 0.6; 0.4; 0.4; 0.514285714285650175; 0.2]
    @test efficiency(deaergcrs, :beta) ≈ [0.4; 1.0; 1.0; 0.6; 0.4; 0.4; 0.514285714285650175; 0.2]
    @test convert(Matrix, peers(deaergcrs)) ≈ [
        0 0.5     0    0    0    0    0    0
        0 1.0     0    0    0    0    0    0
        0 1.6     0    0    0    0    0    0
        0 3.0     0    0    0    0    0    0
        0 1.5     0    0    0    0    0    0
        0 3.5     0    0    0    0    0    0
        0 3.5     0    0    0    0    0    0
        0 2.353   0    0    0    0    0    0]

    @test slacks(deaergcrs, :X) ≈ [0; 0; 1.6; 0; 0; 0; 0; 0]
    @test slacks(deaergcrs, :Y) ≈ [1.5; 0; 0; 6.0; 4.5; 10.5; 8.5; 9.412]

    @test efficiency(deaerg(targets(deaergcrs, :X), targets(deaergcrs, :Y), rts = :CRS)) ≈ ones(8,1) atol = 1e-6

    # Test default is CRS
    @test efficiency(deaerg(X, Y)) ≈ efficiency(deaergcrs)

    # Enhanced Russell Graph VRS
    deaergvrs = deaerg(X, Y, rts = :VRS)

    @test efficiency(deaergvrs) ≈ [1.0; 1.0; 1.0; 1.0; 0.4; 0.4761904761904763; 0.8571428571428572; 0.2] atol = 1e-10
    @test efficiency(deaergvrs, :beta) ≈ [1.0; 1.0; 1.0; 1.0; 0.6; 1.0; 1.0; 0.4706]
    @test convert(Matrix, peers(deaergvrs)) ≈ [
        1.0  0     0    0    0    0    0    0
        0    1.0   0    0    0    0    0    0
        0    0     1.0  0    0    0    0    0
        0    0.0   0    1    0    0    0    0
        0    1.0   0    0    0    0    0    0
        0    1/3   2/3  0    0    0    0    0
        0    0     0    1    0    0    0    0
        0    1     0    0    0    0    0    0]

    @test slacks(deaergvrs, :X) ≈ [0; 0; 0; 0; 2.0; 22/3; 2; 5.412]
    @test slacks(deaergvrs, :Y) ≈ [0; 0; 0; 0; 2.0; 0; 0.0; 2.647]

    @test efficiency(deaerg(targets(deaergvrs, :X), targets(deaergvrs, :Y), rts = :VRS)) ≈ ones(8,1) atol = 1e-6

    ## Test if one-by-one DEA using evaluation and reference sets match initial results
    deaerg_crs_ref_eff = zeros(size(X, 1))
    deaerg_vrs_ref_eff = zeros(size(X, 1))

    Xref = X[:,:]
    Yref = Y[:,:]

    for i = 1:size(X, 1)
        Xeval = X[i:i,:]
        Xeval = Xeval[:,:]
        Yeval = Y[i:i,:]
        Yeval = Yeval[:,:]

        deaerg_crs_ref_eff[i] = efficiency(deaerg(Xeval, Yeval, rts = :CRS, Xref = Xref, Yref = Yref))[1]
        deaerg_vrs_ref_eff[i] = efficiency(deaerg(Xeval, Yeval, rts = :VRS, Xref = Xref, Yref = Yref))[1]
    end

    @test deaerg_crs_ref_eff ≈ efficiency(deaergcrs)
    @test deaerg_vrs_ref_eff ≈ efficiency(deaergvrs)

    # Print
    show(IOBuffer(), deaergcrs)
    show(IOBuffer(), deaergvrs)

    # ------------------
    # Test errors
    # ------------------
    @test_throws DimensionMismatch deaerg([1; 2 ; 3], [4 ; 5]) #  Different number of observations
    @test_throws DimensionMismatch deaerg([1; 2], [4 ; 5], Xref = [1; 2; 3; 4]) # Different number of observations in reference sets
    @test_throws DimensionMismatch deaerg([1 1; 2 2], [4 4; 5 5], Xref = [1 1 1; 2 2 2]) # Different number of inputs
    @test_throws DimensionMismatch deaerg([1 1; 2 2], [4 4; 5 5], Yref = [4 4 4; 5 5 5]) # Different number of inputs
    @test_throws ArgumentError deaerg([1; 2; 3], [4; 5; 6], rts = :Error) # Invalid returns to scale

    @test_throws ArgumentError efficiency(deaergcrs, :Error) # Invalid efficiency type

end


using Test
using MDCDL
using Random
using LinearAlgebra

@testset "Orthonormal matrices" begin
    Random.seed!(883290234387)

    @testset "factorization" begin
        for p = 2:32
            for n = 1:5
                # generate random orthonormal matrix
                A = qr(rand(p, p)).Q |> Matrix

                θ, μ = MDCDL.mat2rotations(A)

                @test length(θ) == fld(p * (p-1), 2)
                @test length(μ) == p
                @test isreal(θ)
                @test isreal(μ)
                @test all(abs.(μ) .≈ 1)
            end
        end
    end

    @testset "generation" begin
        for p = 2:32
            for n = 1:5
                θ = pi * (rand(fld(p * (p-1), 2)) .- 0.5)
                μ = rand([-1.0, 1.0], p)

                A = MDCDL.rotations2mat(θ, μ)

                @test all(size(A) .== (p, p))
                @test isreal(A)
                @test A' * A ≈ I
            end
        end
    end

    @testset "reconstruction" begin
        for p = 2:32
            for n = 1:5
                A = qr(rand(p, p)).Q |> Matrix

                θ, μ = MDCDL.mat2rotations(A)
                rA = MDCDL.rotations2mat(θ, μ)

                @test rA ≈ A
            end
        end
    end

    @testset "non-destructivity" begin
        for p = 2:32
            for n = 1:5
                A = qr(rand(p, p)).Q |> Matrix

                expctdA = copy(A)
                θ, μ = MDCDL.mat2rotations(A)
                @test A == expctdA

                expctdθ = copy(θ)
                expctdμ = copy(μ)
                rA = MDCDL.rotations2mat(θ, μ)
                @test θ == expctdθ
                @test μ == expctdμ
            end
        end
    end
end


function test_variable_degrees()
	for num_vars = 1:10
		for min_degree = 0:3
			for max_degree = 3:5
				degrees = shuffle(collect(min_degree:max_degree))
				for d in Mayday.variable_degrees(num_vars, degrees)
					@test length(d) == num_vars
					@test sum(d) >= min_degree
					@test sum(d) <= max_degree
				end
			end
		end
	end
end


using Pkg
Pkg.activate(".")
Pkg.instantiate()

using RadMod2D
using GLMakie

include("./models2D.jl")
include("./plot2D.jl")

#### calculation
elemsize = 0.01
n = 60

# m = model_circles_in_circle_rand_full(0.1, 1.8, elemsize)
# m = model_circles_in_circle_rand_half(0.1, 1.8, elemsize)
m = model_circles_in_circle_rand_quarter(0.1, 1.8, elemsize)
# m = model_circles_in_circle_cross(0.1, 1.8, 9, elemsize)

# tiles
dx, dy = create_tiles(m, n)
@time t_occ = check_tile_occupation(m, dx, dy, n, blockparts = 1:(m.npar-1))
tile_occ_analysis(t_occ, printit = true)

println("elem size / tile size: ", round(elemsize/((dx+dy)/2), digits = 2), " %")

# classic algo
vfmat = zeros(Float64, m.nelem, m.nelem)
existing_vf!(m, vfmat)
@time blocking_vf_with_tiles!(m, vfmat, dx, dy, n, t_occ)
calculating_vf!(m, vfmat, normit = false)
vfmatp = vfmat_to_parts(m, vfmat, normit = false)

# simplified algo
vfmat_simp = zeros(Float64, m.nelem, m.nelem)
existing_vf!(m, vfmat_simp)
@time blocking_vf_with_tiles_simplified!(m, vfmat_simp, dx, dy, n, t_occ, elem_in_t = 3, skipped_t = 3)
calculating_vf!(m, vfmat_simp, normit = false)
vfmatp_simp = vfmat_to_parts(m, vfmat_simp, normit = false)

#### 2D plot
fig = Figure(resolution = (900, 900))
ax = fig[1, 1] = Axis(fig)
ax.xlabel = "X in m"
ax.ylabel = "Y in m"
ax.aspect = DataAspect()
plot_empty_tiles(fig, ax, dx, dy, n)
plot_occupied_tiles(fig, ax, dx, dy, n, t_occ)
plot_model(fig, ax, m, shownvec = true, shownodes = false, showcom = false, showleg = false)
display(fig)

#### compare results of classic algo with simplified algo

control_classic = sum(vfmatp, dims=2)
control = zeros(size(control_classic,1),3)
control[:,1] = control_classic

control_simp = sum(vfmatp_simp, dims=2)
control[:,2] = control_simp

diff = control_classic - control_simp
control[:,3] = diff

println("classic algo / simplified algo / diff")
for i = 1:size(control,1)
    println(control[i,:])
end

sum_classic = sum(control_classic)
sum_simp = sum(control_simp)
println("error rate: ", round((1-sum_simp/sum_classic)*100, digits = 2), " %")

# include("./test/run_algo_vs_simpalgo.jl")


"""
```
nearest_spd(A::Matrix)
```
Calculates the nearest (in Frobenius norm) symmetric positive semidefinite matrix to A.

From Higham: The nearest symmetric positive semidefinite matrix in the
Frobenius norm to an arbitrary real matrix A is shown to be (B + H)/2,
 where H is the symmetric polar factor of B=(A + A')/2.

http://www.sciencedirect.com/science/article/pii/0024379588902236

### Inputs

- `A::Matrix`: A square matrix

### Outputs

- `Ahat::Matrix`: The symmetric, square positive semidefinite matrix closest to A
"""
function nearest_spd(A::Matrix)
    @show "WHAT ARE YOU DOING? WHY ARE YOU CALLING THIS?"
    return "WHAT ARE YOU DOING? WHY ARE YOU CALLING THIS?"
end
#=function nearest_spd(A::Matrix)

    if size(A,1) != size(A,2)
        error("A must be a square matrix")
    end
    # symmetrize A into B
    B = (A + A')/2;

    # Compute the symmetric polar factor of B. Call it H.
    # Clearly H is itself SPD.
    U, Σ, V = svd(B);
    H = V*diagm(Σ)*V';

    Ahat = (B+H)/2;
    # Ensure symmetry
    Ahat = (Ahat + Ahat')/2;

    # Test that Ahat is in fact PD. if it is not so, then tweak it just a bit.
    k = 0
    chol_success = false

    eig_matrix = eigvals(Ahat)
    while !chol_success
        try
            chol(Ahat)
            chol_success = true
        catch
        end
        k = k + 1
        if !chol_success
            # mineig_factor is used in the event that the matrix has very small, slightly
            # negative eigenvalues, then the identity scaled by the absolute value of the eigenvalue must
            # be added so as to make the matrix "more" PD and not less.
            # If the minimum eigenvalue is 0 then mineig_factor will permanently remain at 0
            # and the Ahat matrix will cease to change in the iteration, so the ternary
            # condition sets mineig_factor to be the machine epsilon for Float64 numbers
            # so that the algorithm will continue to perturb Ahat in the right direction and not
            # get stuck.

            eig_list = eigvals(Ahat)
            mineig = eig_list[1]
            mineig_factor = mineig != 0 ? abs(mineig) : eps(Float64)
            Ahat = Ahat + (mineig_factor*k^2 + eps(Float64))*eye(size(A)...)
            eig_matrix = hcat(eig_matrix, eigvals(Ahat))
        end
    end

    return Ahat
end =#


module rectangular_plate_examples
using Statistics
using FinEtools
using FinEtools.MeshImportModule
using FinEtools.MeshExportModule
using LinearAlgebra: dot
using Arpack
using LinearAlgebra
using SparseArrays
using PGFPlotsX
using Test

E = 210e3*phun("MPa");
nu = 0.3;
rho = 7850*phun("KG/M^3");
a=4.00*phun("M"); b=1.00*phun("M"); h= 0.1*phun("M");
na= 10; nb=  5; nh =2;
na= 2*10; nb=  2*5; nh =8;
neigvs = 20                   # how many eigenvalues
OmegaShift = (10.0*2*pi)^2;
# Fundamental frequency
f_analytical =[0 0 0  0 0 0 33.78 82.28  92.99 170.06];

function rectangular_plate_esnice()
    global a,b,h,na,nb,nh
    MR = DeforModelRed3D
    fens, fes = T4block(a,b,h,na,nb,nh);

    geom = NodalField(fens.xyz)
    u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

    numberdofs!(u)

    material = MatDeforElastIso(MR, rho, E, nu, 0.0)

    femm = FEMMDeforLinearESNICET4(MR, IntegDomain(fes, NodalSimplexRule(3)), material)
    associategeometry!(femm,  geom)
    @show minimum(vec(femm.nphis)), maximum(vec(femm.nphis))
    @pgf a = Axis({
            xlabel = "Entity",
            ylabel = "Stabilization factor",
            grid="major",
            legend_pos  = "north east"
        },
        Plot({"only marks", mark="+"}, Table([:x => vec(1:count(fes)), :y => vec(femm.ephis)]))
        )
    display(a)
    K  = stiffness(femm, geom, u)
    M = mass(femm, geom, u)
    d,v,nev,nconv = eigs(K+OmegaShift*M, M; nev=neigvs, which=:SM)
    d = d .- OmegaShift;
    fs = real(sqrt.(complex(d)))/(2*pi)
    println("Eigenvalues: $fs [Hz]")
    println("f/f_analytical = $(fs[7:10]  ./ f_analytical[7:10] .* 100) %")

    vectors = []
    for i = 7:length(fs)
        scattersysvec!(u, v[:, i])
        push!(vectors, ("Mode_$i", deepcopy(u.values)))
    end
    File  =   "rectangular_plate_esnice.vtk"
    vtkexportmesh(File, connasarray(fes), fens.xyz, FinEtools.MeshExportModule.T4; vectors = vectors)
    @async run(`"paraview.exe" $File`)

    true
end # function

function allrun()
    println("#####################################################")
    println("# rectangular_plate_esnice ")
    rectangular_plate_esnice()
    return true
end # function allrun

end # module LE11NAFEMS_examples


int_rules_7_2_1 = @theory begin

    #= ::Subsection::Closed:: =#
    #= 7.2.1*(a+b*arccosh(c*x))^n =#
    @apply_utils Antiderivative((~(a') + ~(b') * acosh(~(c') * ~x)) ^ ~(n'), ~x) => ~x * (~a + ~b * acosh(~c * ~x)) ^ ~n - ~b * ~c * ~n * Antiderivative((~x * (~a + ~b * acosh(~c * ~x)) ^ (~n - 1)) / (sqrt(1 + ~c * ~x) * sqrt(-1 + ~c * ~x)), ~x) <-- FreeQ([~a, ~b, ~c], ~x) && GtQ(~n, 0)
    @apply_utils Antiderivative((~(a') + ~(b') * acosh(~(c') * ~x)) ^ ~n, ~x) => (sqrt(1 + ~c * ~x) * sqrt(-1 + ~c * ~x) * (~a + ~b * acosh(~c * ~x)) ^ (~n + 1)) / (~b * ~c * (~n + 1)) - (~c / (~b * (~n + 1))) * Antiderivative((~x * (~a + ~b * acosh(~c * ~x)) ^ (~n + 1)) / (sqrt(1 + ~c * ~x) * sqrt(-1 + ~c * ~x)), ~x) <-- FreeQ([~a, ~b, ~c], ~x) && LtQ(~n, -1)
    @apply_utils Antiderivative((~(a') + ~(b') * acosh(~(c') * ~x)) ^ ~n, ~x) => (1 / (~b * ~c)) * Subst(Antiderivative((~x) ^ ~n * sinh(-(~a) / ~b + ~x / ~b), ~x), ~x, ~a + ~b * acosh(~c * ~x)) <-- FreeQ([~a, ~b, ~c, ~n], ~x)
end

src/rules/7 Inverse hyperbolic functions/7.2 Inverse hyperbolic cosine/7.2.1 (a+b arccosh(c x))^n.jl


using CubicHermiteSpline
using Test

@testset "basis function" begin
    h00, h10, h01, h11 = CubicHermiteSpline.basis(0)
    @test h00 == 1
    @test h10 == 0
    @test h01 == 0
    @test h11 == 0

    h00, h10, h01, h11 = CubicHermiteSpline.basis(1)
    @test h00 == 0
    @test h10 == 0
    @test h01 == 1
    @test h11 == 0

    t = 0.5
    h00, h10, h01, h11 = CubicHermiteSpline.basis(t)
    @test h00 ≈ (1 + 2t) * (t - 1)^2
    @test h10 ≈ t * (t - 1)^2
    @test h01 ≈ t^2 * (3 - 2t)
    @test h11 ≈ t^2 * (t - 1)
end

@testset "basis_derivative function" begin
    h00, h10, h01, h11 = CubicHermiteSpline.basis_derivative(0)
    @test h00 == 0
    @test h10 == 1
    @test h01 == 0
    @test h11 == 0

    h00, h10, h01, h11 = CubicHermiteSpline.basis_derivative(1)
    @test h00 == 0
    @test h10 == 0
    @test h01 == 0
    @test h11 == 1

    t = 0.5
    h00, h10, h01, h11 = CubicHermiteSpline.basis_derivative(t)
    @test h00 ≈ 6t * (t - 1)
    @test h10 ≈ (3t - 1) * (t - 1)
    @test h01 ≈ -6t * (t - 1)
    @test h11 ≈ t * (3t - 2)
end

@testset "Interval enclosing the data point" begin
    x = [0, 3, 5, 8, 10, 100]
    idx, x1, x2 = CubicHermiteSpline.findinterval(5, x)
    @test idx == 3
    @test x1 == 5
    @test x2 === nothing
    idx, x1, x2 = CubicHermiteSpline.findinterval(11, x)
    @test idx == 5
    @test x1 == 10
    @test x2 == 100

    x = [0.0, 0.3, 0.5, 0.8, 10.5, 100.1]
    idx, x1, x2 = CubicHermiteSpline.findinterval(0.5, x)
    @test idx == 3
    @test x1 == 0.5
    @test x2 === nothing
    idx, x1, x2 = CubicHermiteSpline.findinterval(0.99, x)
    @test idx == 4
    @test x1 == 0.8
    @test x2 == 10.5
end

@testset "Interpolation" begin
    # Cubic Hermite spline interpolation should exactly interpolate the 3rd order polynomial.
    f(x) = x^3 - 3x^2 + 2x - 5
    df(x) = 3x^2 - 6x + 2
    x = range(0, 2.5, step=0.5)
    y = f.(x)
    gradient = df.(x)
    spl = CubicHermiteSplineInterpolation(x, y, gradient)

    # Interpolate at the input data point, simply return input data
    xi = 0.5
    @test spl(xi) ≈ f(xi)
    @test interp(spl, xi) ≈ f(xi)
    # Interpolate at arbitrary position
    xi = 1.2
    @test spl(xi) ≈ f(xi)
    @test interp(spl, xi) ≈ f(xi)

    # Test array interpolation
    xi = [0.5, 1.2]
    yi = spl(xi)
    @test yi[1] ≈ f(xi[1])
    @test yi[2] ≈ f(xi[2])
    yi = interp(spl, xi)
    @test yi[1] ≈ f(xi[1])
    @test yi[2] ≈ f(xi[2])
end

@testset "Interpolation grad" begin
    # Cubic Hermite spline interpolation should exactly interpolate the 3rd order polynomial.
    f(x) = x^3 - 3x^2 + 2x - 5
    df(x) = 3x^2 - 6x + 2
    # x = range(0, 2.5, step=0.5)
    x = [0, 0.3, 0.8, 1.5, 2.7, 3]
    y = f.(x)
    gradient = df.(x)
    spl = CubicHermiteSplineInterpolation(x, y, gradient)

    # Interpolate at the input data point, simply return input data
    xi = 0.5
    @test spl(xi; grad=true) ≈ df(xi)
    @test grad(spl, xi) ≈ df(xi)
    # Interpolate at arbitrary position
    xi = 2.1
    @test spl(xi; grad=true) ≈ df(xi)
    @test grad(spl, xi) ≈ df(xi)

    # Test array interpolation
    xi = [0.5, 2.1]
    yi = spl(xi; grad=true)
    @test yi[1] ≈ df(xi[1])
    @test yi[2] ≈ df(xi[2])
    yi = grad(spl, xi)
    @test yi[1] ≈ df(xi[1])
    @test yi[2] ≈ df(xi[2])
end


#!/usr/bin/julia

# Simulate the toppling of sandpiles.

# See also:
#   https://en.wikipedia.org/wiki/Abelian_sandpile_model
#   https://www.youtube.com/watch?v=1MtEUErz7Gg -- Sandpiles - Numberphile
#   https://www.youtube.com/watch?v=diGjw5tghYU -- Coding Challenge #107: Sandpiles (by Daniel Shiffman)

using Images

function topple(plane, nextplane, width, height)

    for y in 1:height, x in 1:width
        pile = plane[y,x]

        if (pile < 4)
            nextplane[y,x] = pile
        end
    end

    for y in 2:height-1, x in 2:width-1
        pile = plane[y,x]

        if (pile >= 4)
            nextplane[y, x] += pile - 4
            nextplane[y - 1, x] += 1
            nextplane[y + 1, x] += 1
            nextplane[y, x - 1] += 1
            nextplane[y, x + 1] += 1
        end
    end

    return nextplane
end

function contains_four(plane, width, height)

    for y in 1:height, x in 1:width
        if (plane[y,x] >= 4)
            return true
        end
    end

    return false
end

function sandpiles()

    w, h = 240, 240

    plane = zeros(Int64, h, w)
    plane[div(h,2), div(w,2)] = 10^5

    while (contains_four(plane, w, h))
        nextplane = zeros(Int64, h, w)
        plane = topple(plane, nextplane, w, h)
    end

    colors = [
                RGB{Float64}(0, 0, 0),
                RGB{Float64}(0, 0, 255),
                RGB{Float64}(0, 255, 0),
                RGB{Float64}(255, 255, 255),
            ]

    background = RGB{Float64}(0, 0, 0)
    img = zeros(RGB{Float64}, h, w)

    for y in 1:h, x in 1:w
        pile = plane[y,x]
        col = background

        if (pile <= 3)
            col = colors[pile+1]
        end

        img[y,x] = col
    end

    img = map(clamp01nan, img)

    return img
end

img = sandpiles()
println("Saving image...")
save("sandpiles.png", img)


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

using Markdown
using InteractiveUtils

# ╔═╡ 193ee450-7b32-4394-9401-7b91bce21f31
using LaTeXStrings, DifferentialEquations, LinearAlgebra, Plots, SparseArrays

# ╔═╡ 385b2bda-24d8-464c-9257-c4393a0b1c29
using ForwardDiff

# ╔═╡ 13137d52-2ede-4a63-8eac-43b13def7466
md"# Self Activating Toggle Switch"

# ╔═╡ cf20c08b-5226-4c6c-8741-6857ede519a2
md"_Add required function to implement model_"

# ╔═╡ 1dc242f8-b0a1-11eb-2585-a121968b7921
begin
	mma(x) = 1/(1+x)
	mmr(x) = 1/(1+1/x)
	hfr(n) = x -> 1/(1 + x^n)
	hfa(n) = x -> 1/(1 + (1/x)^n)
	hs(λ, n) = x-> hfr(n)(x) + λ * hfa(n)(x)
	hs(λ, n, x₀) = x-> hs(λ, n)(x/x₀)
end

# ╔═╡ d7ef6de7-145c-4478-aa59-011562a5b8de
md"_Implement model piecewise (separate production and degradation terms) for simulations and computation_"

# ╔═╡ 812b4a91-8c80-4499-abd4-e65a464db31b
begin
	n = 4 # hill coefficient
	λ₁ = 3 # for bistable
	λ₂ = 10 # for tristable
	λₙ = 0.1 # repressor
	x₁ = 80 # cutoff count bistable
	x₂ = 25 # cutoff count tristable
	xₙ = 20 # cuttoff count repress
	p = 5 # unregulated production rate
	d = 0.1 # death rate
	proda(a,b) = p*hs(λ₂, n, x₂)(a)*hs(λₙ, n, xₙ)(b)
	dega(a, b) = d*a
	dadt(a, b) = proda(a, b) - dega(a, b)
	prodb(a, b) = proda(b, a)
	degb(a, b)= d*b
	dbdt(a, b) = prodb(a,b) - degb(a, b)
	dfx = dadt
	dfy = dbdt
end

# ╔═╡ 4d905902-e730-4b79-a516-94ec9e236d59
begin
	fx(x::Vector) = dfx(x[1], x[2])
    fy(x::Vector) = dfy(x[1], x[2])
    f(x::Vector) = [fx(x), fy(x)]
end

# ╔═╡ 8416fdf7-1d4a-4af7-ba12-30f609c5dcdf
md"_Run Helmholtz decomposition using ForwardDiff for the divergence_"

# ╔═╡ 3ffd11ce-5388-4dfa-81ec-535c633ff378
begin
	# savefig(p_pot, "ch4_comp_pot.pdf")
	# savefig(p_rhs, "ch4_comp_rhs.pdf")
end

# ╔═╡ c105a211-66fb-495d-83bc-8ecf5679c37a
md"_Compute Vector Field_"

# ╔═╡ 006d5d3c-1b9e-442e-8fca-d7aa3a3abd35
begin
	n_min = 0
	n_max = 550
end

# ╔═╡ da1ae333-aafb-4ca7-a332-5d17a287c3d9
begin
	dq = (n_max - n_min) / 10.
	xs = n_min:dq:n_max+1
	ys = n_min:dq:n_max+1

	dxs = n_min:.5:n_max+1
	dys = n_min:.5:n_max+1

	df(x, y) = normalize([dfx(x, y), dfy(x,y)]) .* 30

	xxs = [x for x in xs for y in ys]
	yys = [y for x in xs for y in ys]

	quiver(xxs, yys, quiver=df)
	contour!(dxs, dys, dfx, levels=[0], color=:orange, label="X Nullcline", colorbar = false)
	contour!(dxs, dys, dfy, levels=[0], color=:green, label = "Y Nullcline", colorbar = false, xlabel=L"X", ylabel=L"Y", legend=:topright,)
end

# ╔═╡ ee260481-d1ae-4a38-b922-6e10bbfe945e
begin
	random_count() = floor(Int, n_min + rand() * (n_max - n_min))
	rand_u0() = [random_count(), random_count()]
	u0 = rand_u0()
	u0 = [5, 500]
	t = (0.0, 30000.0)
end

# ╔═╡ c077f2a7-6dd8-41a6-88ce-56ee38707081
md"_Stochastic DiffEq of Langevin equation for this system_

Note: `abs` is not supposed to be there but the simulation fails without it."

# ╔═╡ 32a77522-b389-4435-b92f-d05449f8b8d1
begin
	function f(du, u, p, t)
		du[1] = dfx(u...)
		du[2] = dfy(u...)
		du[1], du[2]
	end

	g(du, u, p, t) = begin
	  du[1,1] = sqrt(abs(proda(u[1], u[2])))
	  du[1,2] = -sqrt(abs(degb(u[1], u[2])))
	  du[1,3] = 0
	  du[1,4] = 0
	  du[2,1] = 0
	  du[2,2] = 0
	  du[2,3] = sqrt(abs(prodb(u[1], u[2])))
	  du[2,4] = -sqrt(abs(degb(u[1], u[2])))
	end
end

# ╔═╡ 1cd42539-b8db-4e2b-91a3-2038f299cf43
begin

	minval = 1
	maxval =  505
	nsample = 256
	rhs_xs = range(minval, maxval, length=nsample)
	rhs_ys = range(minval, maxval, length=nsample)

	divergence(F::Function, pt) = sum(
    	diag(
        	ForwardDiff.jacobian(F, pt)
        	)
    	)

	rhs_new = [divergence(f, [x, y]) for x=rhs_xs, y=rhs_ys]
end

# ╔═╡ bfb2c556-d3ec-449e-9f09-30d253bf3938
begin
	p_rhs = heatmap(rhs_xs, rhs_ys, rhs_new, xlabel=L"X", ylabel=L"Y", colorbar=:none)
end

# ╔═╡ 5989e134-f90b-43f0-bb2f-b2ffc5c74cb8
begin
	sde_prob_ssa= SDEProblem(f, g, u0, t, noise_rate_prototype=zeros(2,4))
	sde_sol = solve(sde_prob_ssa)
end

# ╔═╡ 46b15beb-6ee0-405e-96ca-6c0330a3c461
plot(sde_sol)

# ╔═╡ 1e7f2f9f-b8e6-44f8-8259-e2423368679d
md"_Gillespie Jump Process formulation for the same problem_"

# ╔═╡ f173fe40-ee7d-4513-8511-a18ec553ef5f
begin
	proda!(integrator) = integrator.u[1] += 1
	prodb!(integrator) = integrator.u[2] += 1
	dega!(integrator) = integrator.u[1] -= 1
	degb!(integrator) = integrator.u[2] -= 1
	jumps = Dict(proda => proda!, prodb => prodb!, dega => dega!, degb => degb!)
	constJumps = []
	for (rate, jump) in jumps
		push!(constJumps, ConstantRateJump((u, p, t) -> rate(u...), jump))
	end
	constJumps
end

# ╔═╡ 5c2c49e7-1948-41bb-82cc-f6a2460956fc
begin
	dprob_ssa = DiscreteProblem(u0, t)
	jprob_ssa = JumpProblem(dprob_ssa, Direct(), constJumps...)
end

# ╔═╡ 83b082e0-643e-4606-9db9-2957f3c6a470
ssa_sol = solve(jprob_ssa, SSAStepper())

# ╔═╡ 68c0fa0f-ff2d-4208-9a7d-415d0fa0abd1
stoch_tri = plot(ssa_sol, labels=[L"X" L"Y"], xlabel=L"t", legend=(0.9, 0.5))

# ╔═╡ 0715324c-8698-4a22-a146-9bc4c29c3a63
stoch_tri3 = plot(ssa_sol, vars=[(1, 2)], xlabel=L"X", ylabel=L"Y",legend=:none)

# ╔═╡ 73482204-dc5d-4fd5-aabb-72f284ca4416
pstoch = plot(stoch_tri, stoch_tri3, layout=(1, 2), size=(900, 500), title = ["A" "B"], titlelocation = :left)

# ╔═╡ 98664d78-abc5-45ca-a750-6184e0c285a8
# savefig(pstoch, "ch4_tri_stoch.pdf")

# ╔═╡ 2545abfb-df64-4a56-b217-d11fb7f2e95f
md"_Setup helper functions for running longer simulations_"

# ╔═╡ 0276796e-4f61-4d86-95b3-11df1b0ae242
function get_u0(i)
	offset = 1.0
	new_u0 = [0.0, 0.0]
	if i % 3 == 0
		new_u0 = [55, 55]
	elseif i % 3 == 1
		new_u0 .+= [5, 500]
	elseif i % 3 == 2
		new_u0 .+= [500, 5]
	end
	new_u0
end

# ╔═╡ a5d2a26e-5d33-480a-8712-8a38b43de1c9
md"_Storage for unnormalized probabilities that sum up the `dt`s in each bin_"

# ╔═╡ 31ed699a-77e2-4388-88a1-1dd740d56e40
md"_Rerun the following cell to add more values to the SSA simulation_"

# ╔═╡ 5250b601-7647-41cc-861a-393ad916cace
md"_Rerun the following cell to have the SDE simulation catch up to the SSA_"

# ╔═╡ d42b00d6-6f3f-44f9-8e5a-0ed8c8cc25ce
md"_the SDE probability distribution is complete non-sense_"

# ╔═╡ 076d3e3f-e3cd-4b60-8440-61c497b186a0
# savefig(ps, "ch4_ad_hoc.pdf")

# ╔═╡ a0bbd0f6-24d8-4705-b142-c1544346161c
md"## Appendix"

# ╔═╡ 125f4bfb-04b4-4fb3-8f44-413e8d8ee66c
md"_Helper function to generate -∇ ² to solve the Helmhotz equation_"

# ╔═╡ 13f7e53e-d2c9-49ec-b5c0-64e32299e758
begin
	eye(N, M) = sparse(I, N, M)
	eye(M) = eye(M, M)
	diff1(M) = [ [1.0 zeros(1, M - 1)]; diagm(1 => ones(M - 1)) - eye(M) ]
	sdiff1(M) = sparse(diff1(M))

	# make the discrete -Laplacian in 2d, with Dirichlet boundaries
	function laplacian2d(Nx, Ny, Lx, Ly)
		dx = Lx / (Nx + 1)
		dy = Ly / (Ny + 1)
		Dx = sdiff1(Nx) / dx
		Dy = sdiff1(Ny) / dy
		Ax = Dx' * Dx
		Ay = Dy' * Dy
		return Dx, Dy, kron(eye(Ny), Ax) + kron(Ay, eye(Nx))
	end
end

# ╔═╡ bcc69011-ae43-452e-b5bd-5c5eecaaae19
begin
	_, _, Δ = laplacian2d(nsample, nsample, maxval - minval, maxval - minval)
	sol = reshape(Δ \ reshape(rhs_new, length(rhs_new)),  size(rhs_new)...)
end

# ╔═╡ fdac6046-4801-434a-9d03-01e4c2e9403c
p_pot = surface(rhs_xs, rhs_ys, sol, xlabel=L"X", ylabel=L"Y", colorbar=:none)

# ╔═╡ 60be1d36-ebfa-4ab7-ba68-5c35b6d41aa0
md"_Helper function to compute 2D histograms for Probability distributions_"

# ╔═╡ f245c60d-84f8-4f24-b2a0-c55abd991641
begin
	nbins = 701
	bin_min = 0
	bin_max = 700
	xbins = range(bin_min, bin_max, length=nbins)
	ybins = range(bin_min, bin_max, length=nbins)

	function bucket_idx(val, vec)
		idx = 1
		while val > vec[idx]
			idx += 1
		end
		idx
	end

	function althist(x, y, dt, xedges, yedges)
	   counts = spzeros(length(yedges)-1, length(xedges)-1)
		for i=1:length(x) - 1
			r = bucket_idx(y[i], yedges)
			c = bucket_idx(x[i], xedges)
			counts[r,c] += dt[i]
		end
 		counts
	end
end

# ╔═╡ 18b7a92a-57f7-4b43-9d59-65b24889b676
begin
	unnorm_ssa = spzeros(length(xbins)-1, length(ybins)-1)
	unnorm_sde = spzeros(length(xbins)-1, length(ybins)-1)
	tspan = (0., 5e4)
end

# ╔═╡ 049020d7-1d6f-4603-9827-c1b1f22f7de6
prob_ssa = unnorm_ssa ./ sum(unnorm_ssa)

# ╔═╡ 6800f1bc-793e-49df-a60a-57305af8251a
prob_sde = unnorm_sde ./ sum(unnorm_sde)

# ╔═╡ f6a2e316-462e-4d3a-a6b2-37f02896599e
begin
	plotly()
	pl = surface(prob_sde, xlabel="X", ylabel="Y")
	gr()
	pl
end

# ╔═╡ 862c3e0a-261a-4e31-b986-f44d1056f66f
begin
	global n_points_sim
	for i in 1:3*10
		long_jprob_ssa = JumpProblem(
			DiscreteProblem(get_u0(i), tspan), Direct(), constJumps...)
		long_ssa_sol = solve(long_jprob_ssa, SSAStepper())
		unnorm_ssa .+= althist(long_ssa_sol[1,:], long_ssa_sol[2, :], diff(long_ssa_sol.t[:]), xbins, ybins)
	end
	unnorm_ssa
end

# ╔═╡ fe812819-697c-4840-a5df-f8ea1dd5e59b
begin
	i = 45
	num_sde_run_failures = 44
	while sum(unnorm_sde) < sum(unnorm_ssa)
		global i
		global num_sde_run_failures

		sde_run = SDEProblem(f, g, get_u0(i), tspan, noise_rate_prototype=zeros(2,4))
		sol = solve(sde_prob_ssa)

		if sol.t[end] < tspan[end]
			# the simulation did not complete succesfully.
			# This is a sign of divergence and a failing approximation
			num_sde_run_failures += 1
		end
		unnorm_sde .+= althist(sol[1,:], sol[2, :], diff(sol.t[:]), xbins, ybins)
		i += 1
	end
	unnorm_sde, num_sde_run_failures, i
end

# ╔═╡ d9665278-3e2d-47b9-b955-1ddee1fe61e9
begin
	xplot = range(bin_min, bin_max, length=nbins-1)
	yplot = range(bin_min, bin_max, length=nbins-1)
end

# ╔═╡ a26b07ac-5b4d-40c0-b84c-7b86c19a5362
 begin
	pp1 = surface(xplot, yplot,-log.(prob_ssa), c = :thermal, title="ad hoc Quasi-potential")
	pp2 =  surface(xplot, yplot, prob_ssa, c = :grayC, title="Prob_ssaability Distribution", zaxis=(formatter=y->string(round(Int, y / 10^-4))*"e-4" ) )
	ps = plot(pp2, pp1, layout =(1, 2), size=(800, 600), xlabel=L"X", ylabel=L"Y", colorbar=:none)
end

# ╔═╡ Cell order:
# ╟─13137d52-2ede-4a63-8eac-43b13def7466
# ╠═193ee450-7b32-4394-9401-7b91bce21f31
# ╠═385b2bda-24d8-464c-9257-c4393a0b1c29
# ╟─cf20c08b-5226-4c6c-8741-6857ede519a2
# ╠═1dc242f8-b0a1-11eb-2585-a121968b7921
# ╟─d7ef6de7-145c-4478-aa59-011562a5b8de
# ╠═812b4a91-8c80-4499-abd4-e65a464db31b
# ╠═4d905902-e730-4b79-a516-94ec9e236d59
# ╟─8416fdf7-1d4a-4af7-ba12-30f609c5dcdf
# ╠═1cd42539-b8db-4e2b-91a3-2038f299cf43
# ╠═bfb2c556-d3ec-449e-9f09-30d253bf3938
# ╠═bcc69011-ae43-452e-b5bd-5c5eecaaae19
# ╠═fdac6046-4801-434a-9d03-01e4c2e9403c
# ╠═3ffd11ce-5388-4dfa-81ec-535c633ff378
# ╟─c105a211-66fb-495d-83bc-8ecf5679c37a
# ╠═006d5d3c-1b9e-442e-8fca-d7aa3a3abd35
# ╠═da1ae333-aafb-4ca7-a332-5d17a287c3d9
# ╠═ee260481-d1ae-4a38-b922-6e10bbfe945e
# ╟─c077f2a7-6dd8-41a6-88ce-56ee38707081
# ╠═32a77522-b389-4435-b92f-d05449f8b8d1
# ╠═5989e134-f90b-43f0-bb2f-b2ffc5c74cb8
# ╠═46b15beb-6ee0-405e-96ca-6c0330a3c461
# ╟─1e7f2f9f-b8e6-44f8-8259-e2423368679d
# ╠═f173fe40-ee7d-4513-8511-a18ec553ef5f
# ╠═5c2c49e7-1948-41bb-82cc-f6a2460956fc
# ╠═83b082e0-643e-4606-9db9-2957f3c6a470
# ╠═68c0fa0f-ff2d-4208-9a7d-415d0fa0abd1
# ╠═0715324c-8698-4a22-a146-9bc4c29c3a63
# ╠═73482204-dc5d-4fd5-aabb-72f284ca4416
# ╠═98664d78-abc5-45ca-a750-6184e0c285a8
# ╟─2545abfb-df64-4a56-b217-d11fb7f2e95f
# ╠═0276796e-4f61-4d86-95b3-11df1b0ae242
# ╟─a5d2a26e-5d33-480a-8712-8a38b43de1c9
# ╠═18b7a92a-57f7-4b43-9d59-65b24889b676
# ╟─31ed699a-77e2-4388-88a1-1dd740d56e40
# ╠═862c3e0a-261a-4e31-b986-f44d1056f66f
# ╟─5250b601-7647-41cc-861a-393ad916cace
# ╠═fe812819-697c-4840-a5df-f8ea1dd5e59b
# ╠═049020d7-1d6f-4603-9827-c1b1f22f7de6
# ╠═6800f1bc-793e-49df-a60a-57305af8251a
# ╟─d42b00d6-6f3f-44f9-8e5a-0ed8c8cc25ce
# ╠═f6a2e316-462e-4d3a-a6b2-37f02896599e
# ╠═d9665278-3e2d-47b9-b955-1ddee1fe61e9
# ╠═a26b07ac-5b4d-40c0-b84c-7b86c19a5362
# ╠═076d3e3f-e3cd-4b60-8440-61c497b186a0
# ╟─a0bbd0f6-24d8-4705-b142-c1544346161c
# ╟─125f4bfb-04b4-4fb3-8f44-413e8d8ee66c
# ╠═13f7e53e-d2c9-49ec-b5c0-64e32299e758
# ╟─60be1d36-ebfa-4ab7-ba68-5c35b6d41aa0
# ╠═f245c60d-84f8-4f24-b2a0-c55abd991641


# load module from local directory

using SurfaceHopping
using Base.Test

# -----------------------------------------------------------------------------
H_d = H_diabatic(2.6,2.5,0.1)
U, U_nk = diagonalise(H_d,true)

ε = 1e-2
@test 1.0 ≈ U[1] atol = ε

# -----------------------------------------------------------------------------
H_ad = H_adiabatic(H_d)
H = *(U_nk',*(H_d,U_nk))

@test H[1] ≈ H_ad[1] atol = ε

# -----------------------------------------------------------------------------
phi_l,phi_r = diabatic_states(0.7,0.1)
phi_l2(R) = phi_l(R)^2; phi_r2(R) = phi_r(R)^2;

ε = 1e-4
integral = riemann(phi_l2,-10,10,1000)
@test 1.0 ≈ integral atol = ε

integral = riemann(phi_r2,-10,10,1000)
@test 1.0 ≈ integral atol = ε

# -----------------------------------------------------------------------------
psi_g, psi_e = adiabatic_states(phi_l, phi_r, U_nk)
psi_g2(R) = psi_g(R)^2; psi_e2(R) = psi_e(R)^2

integral = riemann(psi_g2,-10,10,1000)
@test 1.0 ≈ integral atol = ε

integral = riemann(psi_e2,-10,10,1000)
@test 1.0 ≈ integral atol = ε

# -----------------------------------------------------------------------------
dphi = diabatic_derivative()

d_ge, d_eg, d_gg, d_ee = NACV(phi_l, phi_r, dphi, U_nk, 2.6, -0.1, 0.1, 0.1)
#println(d_ge, "  ", d_eg, "  ", d_gg, "  ", d_ee)

# -----------------------------------------------------------------------------
cl = 1.0; cr = 0.0
ag, ae = a_mn(U_nk, cl, cr)
coeffs_diabatic = abs(cl)^2 + abs(cr)^2
@test 1.0 ≈ coeffs_diabatic atol = ε

coeffs_adiabatic = abs(ag)^2 + abs(ae)^2
@test 1.0 ≈ coeffs_adiabatic atol = ε

# -----------------------------------------------------------------------------
dpl, dpr = diabatic_potential(1.0,2.6,0.0)
apg, ape = adiabatic_potential(dpl, dpr, 2.0)
agn, aen = new_a_mn(ag, ae, apg, ape, d_ge, d_eg, d_gg, d_ee, 2.6, 0.1)

coeffs_adiabatic = abs(agn)^2 + abs(aen)^2
@test 1.0 ≈ coeffs_adiabatic atol = ε

# -----------------------------------------------------------------------------
println("Tests finished succesfully.")


using OrdinaryDiffEq, DifferentialEquations

tspan = (0.0f0, 50.0f0)

"""
Van der Pol oscillator model from wikipedia
"""
function vdp_system(du, u, p, t;
                     U_func_time=U_func_time_trivial,
                     U_func_space=U_func_space_trivial)
    Ft = U_func_time(t)
    Fs = U_func_space(u)
    μ = p[1]
    x, y = u

    du[1] = μ*(x - (x^3)/3 - y)
    du[2] = x/μ

    # Add controllers in
    du .+= Ft .+ Fs
end

# Generate data, with a time component
p = [1.0]
u0 = [1.0, 1.0]
ts = range(tspan[1], tspan[2], length=5001)

function solve_vdp_system(;U_func_time=U_func_time_trivial,
                              U_func_space=U_func_space_trivial)
    prob = ODEProblem((du, u, p, t)->vdp_system(du, u, p, t,
                                U_func_time=U_func_time,
                                U_func_space=U_func_space),
                    u0, tspan, p)
    sol = solve(prob, Tsit5(), saveat=ts);
    return sol
end

#####
##### Turing function for Bayesian parameter estimation
#####
# Unforced version for use with Turing.jl
# function vdp_system(u, p, t)
#     u = convert.(eltype(p),u)
#     beta, gamma = p
#     S, I, R = u
#
#     N = sum([S,I,R])
#     tmp = beta*I*S/N
#     du = [-tmp; tmp - gamma*I; gamma*I]
#     return du
# end

#####
##### True model in SINDy syntax
#####
μ = p
#      x  y  c  xx xy yy xxx  xxy xyy yyy
 A = [[μ -μ  0  0  0  0  -μ/3 0   0   0];
      [1/μ 0 0  0  0  0  0    0   0   0]]
n = size(A, 1)
sindy_library = Dict(
    "cross_terms"=>[2,3],
    "constant"=>nothing
);
core_dyn_true = sindyModel(ts, A,
                            convert_string2function(sindy_library),
                            ["x", "y"])

# Actually export
export ts, solve_vdp_system, vdp_system, core_dyn_true


using Plots
gr(titlefontsize=12,size=(500,300),colorbar=false)

using FourierGPE

# Units: oscillator
L = (20.,20.,20.)
N = (64,64,64)
sim = Sim(L,N)
@unpack_Sim sim

import FourierGPE.V
V(x,y,z,t) = 0.5*(x^2 + y^2 + 4*z^2)

## set simulation parameters ====
γ = 0.5
μ = 15.0
tf = 0.3 # 1.5/γ
Nt = 150
t = LinRange(0.,tf,Nt)

## state functions
ψ0(x,y,z,μ,g) = sqrt(μ/g)*sqrt(max(1.0-V(x,y,z,0.0)/μ,0.0)+im*0.0)
healing(x,y,z,μ,g) = 1/sqrt(g*abs2(ψ0(x,y,z,μ,g)))

## initial state
x,y,z = X
y = y'; z = reshape(z,1,1,length(z))
ψi = ψ0.(x,y,z,μ,g)
ϕi = kspace(ψi,sim)

@pack_Sim! sim

plot(x,abs2.(ψ0.(x,0.,0.,μ,g)))
plot!(x,abs2.(ψi[:,32,32]))

## evolve in k space
sol = runsim(sim)

## ground state
ϕg = sol[end]
ψg = xspace(ϕg,sim)
showpsi(x,y',ψg[:,:,32])

R(w) = sqrt(2*μ/w^2)
R(1)
R(2)

plot(x,abs2.(ψ0.(x,0.,0.,μ,g)))
plot!(x,abs2.(ψg[:,32,32]))
μ/g

abs2.(ψg[32,32,32])

# animate a slice
anim = @animate for i=1:Nt
    ϕ = sol[i]
    zmid = findfirst(z .≈ 0)
    ψ = xspace(ϕ,sim)
    showpsi(x,y,ψ[:,:,zmid])
end
gif(anim,"./examples/3dquench.gif",fps=30)
#TODO debug
## animate isosurface in Makie
using Makie, AbstractPlotting

function dense(phi)
    ψm = xspace(phi,sim)
    density = abs2.(ψm)
    pmax = maximum(density)
    return density/pmax
end

function densityfilm(sol,Nt;file="3dquench.gif")
    saveto="examples/"*file
    scene = Scene()
    tindex = Node(1)
    scene = volume(lift(i -> dense(sol[i]), tindex),
    algorithm = :iso,
    color = (c3,0.25),
    show_axis=false,
    isovalue=3f0(.15))

    R = 70
    eyepos = Vec3f0(R,R,R)
    lookat = Vec3f0(18,18,0)

    record(scene, saveto, 1:Nt) do i
        update_cam!(scene, eyepos, lookat)
        rotate_cam!(scene, 0., -0.25, 0.)
        tindex[] = i
    end
    p = scene[end];
    return
end

p = densityfilm(sol,Nt,file="3dquench1.gif")

# p.attributes.attribute