"""
    solve_adbc(equation, u, tlist, n, dt = (tlist[2] - tlist[1]) / 200)

Solve filtered equation using approximate deconvolution boundary conditions (ADBC). This
approach was proposed in [Borggaard 2006].

J. Borggaard, T. Iliescu, Approximate deconvolution boundary conditions for large eddy
simulation, Applied Mathematics Letters 19 (8) (2006) 735–740.
doi: https://doi.org/10.1016/j.aml.2005.08.022.
URL: https://www.sciencedirect.com/science/article/pii/S0893965905003319
"""
function solve_adbc(
    equation::DiffusionEquation{ClosedIntervalDomain{T},TopHatFilter},
    u,
    tlist,
    n,
    Δt = (tlist[2] - tlist[1]) / 1000,
) where {T}
    @unpack domain, filter, f, g_a, g_b = equation
    x = discretize(domain, n)
    Δx = x[2] - x[1]
    h = filter.width
    all(h.(x) .≈ h(x[1])) || error("ADBC requires constant filter width")
    h₀ = h(x[1])
    a, b = domain.left, domain.right

    # Get matrices
    D = diffusion_matrix(domain, n)

    # Filter matrix
    inds = [-1, 0, 1]
    stencil = [1 / 24, 11 / 12, 1 / 24]
    diags = [i => fill(s, n + 1 - abs(i)) for (i, s) ∈ zip(inds, stencil)]
    W = spdiagm(diags...)
    W[1, [1, 2]] = [23 / 24, 1 / 24]
    W[end, [end, end - 1]] = [23 / 24, 1 / 24]
    w₀ = W[1, :]
    wₙ = W[end, :]

    # Reconstruction matrix
    inds = [-1, 0, 1]
    stencil = [-1 / 24, 13 / 12, -1 / 24]
    diags = [i => fill(s, n + 1 - abs(i)) for (i, s) ∈ zip(inds, stencil)]
    R = spdiagm(diags...)
    R[1, [1, 2]] = [25 / 24, -1 / 24]
    R[end, [end, end - 1]] = [25 / 24, -1 / 24]

    function perform_step!(ūᵏ, tᵏ, Δt, p)
        uᵏ, uᵏ⁺¹, ūᵏ⁺¹ = p

        # Current approximate unfiltered solution
        mul!(uᵏ, R, ūᵏ)

        # Next approximate unfiltered solution
        uᵏ⁺¹ .= uᵏ
        uᵏ⁺¹[1] = g_a(tᵏ + Δt)
        uᵏ⁺¹[end] = g_b(tᵏ + Δt)

        # Filtered boundary conditions
        ūᵏ⁺¹[1] = w₀'uᵏ⁺¹
        ūᵏ⁺¹[end] = wₙ'uᵏ⁺¹

        # Next inner points for filtered solution
        ūᵏ⁺¹[2:end-1] .+=
            Δt .* D[2:end-1, :]ūᵏ .+ Δt .* W[2:end-1, :]f.(x, tᵏ) .+
            Δt .* (abs.(x[2:end-1] .- b) .≤ h₀) ./ 2h₀ .* (g_b(tᵏ) - uᵏ[end-1]) / Δx .-
            Δt .* (abs.(x[2:end-1] .- a) .≤ h₀) ./ 2h₀ .* (uᵏ[2] - g_a(tᵏ)) / Δx

        # Advance by Δt
        ūᵏ .= ūᵏ⁺¹
    end

    ūᵏ = W * u.(x)
    p = (copy(ūᵏ), copy(ūᵏ), copy(ūᵏ))
    tᵏ = tlist[1]
    while tᵏ + Δt < tlist[2]
        perform_step!(ūᵏ, tᵏ, Δt, p)
        tᵏ += Δt
    end

    # Perform last time step (with adapted step size)
    Δt_last = tlist[2] - tᵏ
    perform_step!(ūᵏ, tᵏ, Δt_last, p)
    tᵏ += Δt_last

    ūᵏ
end

function solve_adbc(
    equation::DiffusionEquation{ClosedIntervalDomain{T},ConvolutionalFilter},
    u,
    tlist,
    n,
    Δt = (tlist[2] - tlist[1]) / 1000;
    solver = :euler,
    δ = √(6 / π) / equation.filter.kernel(0),
    ū_ext = nothing,
    uₓ = nothing,
) where {T}
    @unpack domain, filter, f, g_a, g_b = equation
    x = discretize(domain, n)
    Δx = x[2] - x[1]
    h₀ = filter.width(0)
    G = filter.kernel
    a, b = domain.left, domain.right

    # Get matrices
    D = diffusion_matrix(domain, n)

    # Filter stencil
    normalize(w) = w / sum(w)
    nw = floor(Int, h₀ / 2Δx)
    inds = -nw:nw
    w = normalize(G.(Δx .* inds))

    # Filter matrix
    diags = [i => fill(w, n + 1 - abs(i)) for (i, w) ∈ zip(inds, w)]
    W = spdiagm(diags...)

    # Move weights of outside points to endpoint
    for i = 1:nw
        W[i, 1] += sum(w[1:(nw+1-i)])
        W[end+1-i, end] += sum(w[(end-nw+i):end])
    end

    # Boundary stencils
    w₀ = W[1, :]
    wₙ = W[end, :]

    G_a = G.(x[2:end-1] .- a)
    G_b = G.(b .- x[2:end-1])

    f̄ᵏ_constant = apply_filter_extend(x -> f(x, tlist[1]), filter, domain).(x[2:end-1])

    function du!(du, ūᵏ, tᵏ, Δt, p)
        @unpack uᵏ, uᵏ⁺¹, Dūᵏ, fᵏ, f̄ᵏ, δ = p

        # Diffusion term
        mul!(Dūᵏ, D, ūᵏ)

        # Source term
        # fᵏ .= f.(x, tᵏ)
        # mul!(f̄ᵏ, W[2:end-1, :], fᵏ)
        # f̄ᵏ .= apply_filter_extend(x -> f(x, tᵏ), filter, domain).(x[2:end-1])
        f̄ᵏ = f̄ᵏ_constant

        # Current approximate unfiltered solution
        @. uᵏ = ūᵏ - δ^2 / 24 * Dūᵏ

        # Next approximate unfiltered solution
        uᵏ⁺¹ .= uᵏ
        uᵏ⁺¹[1] = g_a(tᵏ + Δt)
        uᵏ⁺¹[end] = g_b(tᵏ + Δt)

        # Filtered boundary conditions
        if isnothing(ū_ext)
            # Approximate deconvolution boundary conditions
            du[1] = (w₀'uᵏ⁺¹ - ūᵏ[1]) / Δt
            du[end] = (wₙ'uᵏ⁺¹ - ūᵏ[end]) / Δt
        else
            # Exact filtered boundary conditions
            du[1] = (ū_ext(tᵏ + Δt)(a) - ūᵏ[1]) / Δt
            du[end] = (ū_ext(tᵏ + Δt)(b) - ūᵏ[end]) / Δt
        end

        if isnothing(uₓ)
            # Approximate boundary terms
            uₓ_a = (uᵏ[2] - g_a(tᵏ)) / Δx
            uₓ_b = (g_b(tᵏ) - uᵏ[end-1]) / Δx
        else
            # Exact boundary terms
            uₓ_a = uₓ(a, tᵏ)
            uₓ_b = uₓ(b, tᵏ)
        end

        # Next inner points for filtered solution
        du_in = @view du[2:end-1]
        Dūᵏ_in = @view Dūᵏ[2:end-1]
        @. du_in = Dūᵏ_in + f̄ᵏ + G_b * uₓ_b - G_a * uₓ_a
    end

    function perform_step_euler!(ūᵏ, tᵏ, Δt, p)
        @unpack du = p
        du!(du, ūᵏ, tᵏ, Δt, p)
        ūᵏ .+= Δt .* du
    end

    function perform_step_RK4!(ūᵏ, tᵏ, Δt, p)
        @unpack k₁, k₂, k₃, k₄ = p

        # Compute change
        du!(k₁, ūᵏ, tᵏ, Δt / 2, p)
        du!(k₂, ūᵏ + Δt / 2 * k₁, tᵏ + Δt / 2, Δt / 2, p)
        du!(k₃, ūᵏ + Δt / 2 * k₂, tᵏ + Δt / 2, Δt / 2, p)
        du!(k₄, ūᵏ + Δt * k₃, tᵏ + Δt, Δt / 2, p)

        # Advance by Δt
        @. ūᵏ += Δt * (k₁ / 6 + k₂ / 3 + k₃ / 3 + k₄ / 6)
    end

    if solver == :euler
        perform_step! = perform_step_euler!
    elseif solver == :RK4
        perform_step! = perform_step_RK4!
    else
        error("Solver is not supported")
    end

    # ūᵏ = W * u.(x)
    ūᵏ = apply_filter_extend(u, filter, domain).(x)
    p = (;
        du = copy(ūᵏ),
        k₁ = copy(ūᵏ),
        k₂ = copy(ūᵏ),
        k₃ = copy(ūᵏ),
        k₄ = copy(ūᵏ),
        uᵏ = copy(ūᵏ),
        uᵏ⁺¹ = copy(ūᵏ),
        Dūᵏ = copy(ūᵏ),
        fᵏ = copy(ūᵏ),
        f̄ᵏ = copy(ūᵏ[2:end-1]),
        δ,
    )
    tᵏ = tlist[1]
    while tᵏ + Δt < tlist[2]
        # @show tᵏ
        perform_step!(ūᵏ, tᵏ, Δt, p)
        tᵏ += Δt
    end

    # Perform last time step (with adapted step size)
    Δt_last = tlist[2] - tᵏ
    perform_step!(ūᵏ, tᵏ, Δt_last, p)
    tᵏ += Δt_last

    # nT = round(Int, (tlist[2] - tlist[1]) / Δt)
    # uu = zeros(n + 1, nT + 1)
    # uu[:, 1] = ūᵏ
    # for i = 1:nT
    #     perform_step!(ūᵏ, tᵏ, Δt, p)
    #     uu[:, i+1] = ūᵏ
    #     tᵏ += Δt
    # end

    ūᵏ
    # uu
end


using MomentClosure
using MomentClosure: define_M, define_μ
using ModelingToolkit: value
using SymbolicUtils: expand
using Test
using Catalyst

@parameters t, c₁, c₂, c₃, c₄, Ω
@variables X(t), Y(t)

S_mat = [ 1 -1  1 -1;
         -1  1  0  0]
a = [c₁*X*Y*(X-1)/Ω^2, c₂*X, c₃*Ω, c₄*X]
rn = ReactionSystemMod(t, [X, Y], [c₁, c₂, c₃, c₄, Ω], a, S_mat)

μ = define_μ(2,4)
M = define_M(2,4)
sys = generate_central_moment_eqs(rn, 2, 4)
expr1 = sys.odes.eqs[1].rhs

closed_eqs = moment_closure(sys, "zero")
@test closed_eqs.closure[M[2,2]] == 0
expr1 = closed_eqs.odes.eqs[1].rhs
expr2 = c₃*Ω + M[1,1]*c₁*μ[1,0]*(Ω^-2) + M[1,1]*c₁*(Ω^-2)*(μ[1,0]- 1) + c₁*M[2,0]*μ[0,1]*(Ω^-2) +
        c₁*μ[0,1]*μ[1,0]*(Ω^-2)*(μ[1,0] - 1) - c₂*μ[1,0] - c₄*μ[1,0]
expr2 = simplify(value.(expr2))
@test isequal(expand(expr1), expand(expr2))

# check that deterministic_IC is working with central moments
ic_values = Dict(deterministic_IC([2, 5], closed_eqs))
@test length(ic_values) == 5
@test ic_values[μ[1,0]] == 2 && ic_values[μ[0,1]] == 5 && ic_values[M[1,1]] == 0

closed_eqs = moment_closure(sys, "normal")
@test isequal(closed_eqs.closure[M[0,4]], 3*M[0,2]^2)

closed_eqs= moment_closure(sys, "log-normal")
expr1 = closed_eqs.closure[M[1,2]]
expr2 = μ[1,0]*μ[0,1]^2*(1.0+M[0,2]*μ[0,1]^-2)*(1.0 + M[1,1]*(μ[0,1]^-1)*(μ[1,0]^-1))^2 -
        M[0,2]*μ[1,0] - μ[1,0]*μ[0,1]^2 - 2*M[1,1]*μ[0,1]
@test isequal(expr1, expr2)

closed_eqs = moment_closure(sys, "poisson")
@test isequal(closed_eqs.closure[M[3,0]], μ[1,0])

closed_eqs = moment_closure(sys, "gamma")
expr1 = closed_eqs.closure[M[2,1]]
expr2 = M[2,0]*μ[0,1] + μ[0,1]*μ[1,0]^2 + 2*M[1,1]*μ[1,0] + 2*M[1,1]*M[2,0]*μ[1,0]^-1 -
    M[2,0]*μ[0,1] - μ[0,1]*μ[1,0]^2 - 2*M[1,1]*μ[1,0]
@test isequal(expr1, expr2)
expr1

closed_eqs = moment_closure(sys, "derivative matching")
expr1 = closed_eqs.closure[sys.M[0,4]]
expr2 = μ[0,1]^4*(M[0,2]+μ[0,1]^2)^-6*(M[0,3]+μ[0,1]^3+3*M[0,2]*μ[0,1])^4 - μ[0,1]^4 -
    6*M[0,2]*μ[0,1]^2 - 4*M[0,3]*μ[0,1]
@test isequal(expr1, expr2)

sys = generate_raw_moment_eqs(rn, 2)
closed_eqs = moment_closure(sys, "zero")
expr1 = closed_eqs.closure[μ[3,0]]
expr2 = -2*μ[1,0]^3 + 3*μ[1,0]*μ[2,0]
@test isequal(expr1, expr2)

closed_eqs = moment_closure(sys, "normal")
expr1 = closed_eqs.closure[sys.μ[1,2]]
expr2 = 2*μ[0,1]*μ[1,1] + μ[0,2]*μ[1,0] - 2*μ[1,0]*μ[0,1]^2
@test isequal(expr1, expr2)
expr1 = closed_eqs.odes.eqs[5].rhs
expr2 = c₂*μ[1,0] + 2*c₂*μ[1,1] + c₁*μ[0,1]*μ[2,0]/Ω^2 - c₁*μ[1,1]/Ω^2 - 4*c₁*μ[1,1]^2/Ω^2 + 4*c₁*μ[0,1]*μ[1,1]/Ω^2 -
    2*c₁*μ[0,1]*μ[1,0]^2/Ω^2 + 2*c₁*μ[0,2]*μ[1,0]/Ω^2 -2*c₁*μ[0,2]*μ[2,0]/Ω^2 + 2*c₁*μ[1,0]*μ[1,1]/Ω^2 -
    4*c₁*μ[1,0]*μ[0,1]^2/Ω^2 + 4*c₁*μ[0,1]^2*μ[1,0]^2/Ω^2
expr2 = simplify(value.(expr2))
@test isequal(expand(expr1), expr2)

# check that deterministic_IC is working with raw moments
ic_values = Dict(deterministic_IC([2, 5], closed_eqs))
@test length(ic_values) == 5
@test ic_values[μ[1,0]] == 2 && ic_values[μ[0,1]] == 5 && ic_values[μ[1,1]] == 10

closed_eqs = moment_closure(sys, "log-normal")
expr1 = closed_eqs.closure[μ[1,3]]
expr2 = μ[0,1]^-6 * μ[0,2]^3 * μ[1,0]^-2 * μ[1,1]^3
@test isequal(expr1, expr2)

closed_eqs = moment_closure(sys, "poisson")
expr1 = closed_eqs.closure[μ[4,0]]
expr2 = μ[1,0] + 6 * μ[1,0]^4 + 3*μ[2,0]^2 + 4*μ[1,0]*μ[3,0] -12*μ[2,0]*μ[1,0]^2
@test isequal(expr1, expr2)

closed_eqs = moment_closure(sys, "gamma")
expr1 = closed_eqs.closure[μ[0,3]]
expr2 = μ[0,1]^3 + 3*μ[0,1]*(μ[0,2]-μ[0,1]^2) + 2*μ[0,1]^-1*(μ[0,2]-μ[0,1]^2)^2
@test isequal(expr1, expr2)

closed_eqs = moment_closure(sys, "derivative matching")
expr1 = closed_eqs.closure[sys.μ[0,3]]
expr2 = μ[0,1]^-3*μ[0,2]^3
@test isequal(expr1, expr2)


using Test
using LogicCircuits
using ProbabilisticCircuits
using DiscriminativeCircuits
using DataFrames: DataFrame

# This tests are supposed to test queries on the circuits
@testset "Logistic Circuit Class Conditional" begin
    # Uses a Logistic Circuit with 4 variables, and tests 3 of the configurations to
    # match with python version.

    EPS = 1e-5; # TODO; Cannot do more with Float32
    logistic_circuit = zoo_dc("little_4var.circuit");
    @test logistic_circuit isa LogisticCircuit;

    # Step 1. Check Probabilities for 3 samples
    data = DataFrame(Bool.([0 0 0 0; 0 1 1 0; 0 0 1 1]), :auto);

    # Testing values before taking sigmoid, so will compare with output of `class_weights_per_instance`
    true_prob = [3.43147972 4.66740416;
                4.27595352 2.83503504;
                3.67415087 4.93793472]
    true_prob

    CLASSES = 2
    calc_prob = class_weights_per_instance(logistic_circuit, data)

    for i = 1:3
        for j = 1:2
            @test true_prob[i,j] ≈ calc_prob[i,j] atol= EPS;
        end
    end
end


using DiffEqSensitivity
using OrdinaryDiffEq, Calculus, Test

function f(du,u,p,t)
  du[1] = -p[1]
  du[2] = p[2]
end

cb = ContinuousCallback((u,t,i) -> u[1], (integrator)->(println("Stopped.");integrator.p[2]=0.0))
p = [2.0, 1.0]

function test_f(p)
  prob = ODEProblem(f,eltype(p).([1.0,0.0]),eltype(p).((0.0,1.0)),copy(p))
  solve(prob,Tsit5(),abstol=1e-14,reltol=1e-14,callback=cb,save_everystep=false)[end]
end
p = [2.0, 1.0]
findiff = Calculus.finite_difference_jacobian(test_f,p)
findiff

using ForwardDiff
ad = ForwardDiff.jacobian(test_f,p)
ad

@test ad ≈ findiff


struct ModulationFilterbank{F<:FilterCoefficients,T<:Real,U<:Real} <: Filterbank
    filters::Vector{F}
    center_frequencies::Vector{T}
    fs::U
end

function make_modulation_filter(w0, Q)
    W0 = tan(w0/2)
    B0 = W0/Q
    b = [B0, 0, -B0]
    a = [(1 + B0 + W0^2), (2*W0^2 - 2), (1 - B0 + W0^2)]
    BiquadFilter(b[1], b[2], b[3], a[1], a[2], a[3])
end

function modulation_filterbank(mf, fs, q)
    ModulationFilterbank([make_modulation_filter(w0, q) for w0 in 2*pi*mf/fs], mf, fs)
end

function compute_modulation_cfs(min_cf, max_cf, n)
    spacing_factor = (max_cf/min_cf)^(1/(n-1))
    cfs = zeros(n)
    cfs[1] = min_cf
    for k=2:n
        cfs[k] = cfs[k-1]*spacing_factor
    end
    return cfs
end


using Push
using Base.Test

cfg_path = joinpath(dirname(@__FILE__), "configuration/integer.cfg")
cfg = Push.load_configuration(cfg_path)

# INTEGER.*
s = Push.run("(1 INTEGER.*)", cfg)
@test s.integer == [1]
s = Push.run("(7 3 2 10 INTEGER.*)", cfg)
@test s.integer == [7, 3, 20]

# INTEGER.+
s = Push.run("(1 INTEGER.+)", cfg)
@test s.integer == [1]
s = Push.run("(7 3 2 10 INTEGER.+)", cfg)
@test s.integer == [7, 3, 12]
s = Push.run("(7 3 2 -10 INTEGER.+)", cfg)
@test s.integer == [7, 3, -8]

# INTEGER.-
s = Push.run("(1 INTEGER.-)", cfg)
@test s.integer == [1]
s = Push.run("(7 3 10 2 INTEGER.-)", cfg)
@test s.integer == [7, 3, 8]
s = Push.run("(7 3 2 10 INTEGER.-)", cfg)
@test s.integer == [7, 3, -8]

# INTEGER./
s = Push.run("(1 INTEGER./)", cfg)
@test s.integer == [1]
s = Push.run("(7 3 10 2 INTEGER./)", cfg)
@test s.integer == [7, 3, 5]
s = Push.run("(7 3 -10 4 INTEGER./)", cfg)
@test s.integer == [7, 3, -2]
s = Push.run("(7 3 -10 0 INTEGER./)", cfg)
@test s.integer == [7, 3, -10, 0]

# INTEGER.<
s = Push.run("(1 INTEGER.<)", cfg)
@test s.integer == [1] && isempty(s.boolean)
s = Push.run("(100 900 2 3 INTEGER.<)", cfg)
@test s.integer == [100, 900] && s.boolean == [true]
s = Push.run("(100 900 5 -10 INTEGER.<)", cfg)
@test s.integer == [100, 900] && s.boolean == [false]
s = Push.run("(100 900 5 5 INTEGER.<)", cfg)
@test s.integer == [100, 900] && s.boolean == [false]

# INTEGER.=
s = Push.run("(1 INTEGER.=)", cfg)
@test s.integer == [1] && isempty(s.boolean)
s = Push.run("(100 900 -10 90 INTEGER.=)", cfg)
@test s.integer == [100, 900] && s.boolean == [false]
s = Push.run("(100 900 -10 -10 INTEGER.=)", cfg)
@test s.integer == [100, 900] && s.boolean == [true]

# INTEGER.>
s = Push.run("(1 INTEGER.>)", cfg)
@test s.integer == [1] && isempty(s.boolean)
s = Push.run("(100 900 2 3 INTEGER.>)", cfg)
@test s.integer == [100, 900] && s.boolean == [false]
s = Push.run("(100 900 5 -10 INTEGER.>)", cfg)
@test s.integer == [100, 900] && s.boolean == [true]
s = Push.run("(100 900 5 5 INTEGER.>)", cfg)
@test s.integer == [100, 900] && s.boolean == [false]

# INTEGER.DUP
s = Push.run("(10 20 30 INTEGER.DUP)", cfg)
@test s.integer == [10, 20, 30, 30]

# INTEGER.FLUSH
s = Push.run("(10 20 30 INTEGER.FLUSH)", cfg)
@test s.integer == []

# INTEGER.FROMBOOLEAN
s = Push.run("(10 20 TRUE FALSE INTEGER.FROMBOOLEAN)", cfg)
@test s.integer == [10, 20, 0] && s.boolean == [true]
s = Push.run("(10 20 FALSE TRUE INTEGER.FROMBOOLEAN)", cfg)
@test s.integer == [10, 20, 1] && s.boolean == [false]

# INTEGER.FROMFLOAT
s = Push.run("(1.0 2.0 3.0 INTEGER.FROMFLOAT)", cfg)
@test s.integer == [3] && s.float == [1.0, 2.0]

# INTEGER.MAX
s = Push.run("(10 10 INTEGER.MAX)", cfg)
@test s.integer == [10]
s = Push.run("(20 10 INTEGER.MAX)", cfg)
@test s.integer == [20]
s = Push.run("(10 20 INTEGER.MAX)", cfg)
@test s.integer == [20]

# INTEGER.MIN
s = Push.run("(10 10 INTEGER.MIN)", cfg)
@test s.integer == [10]
s = Push.run("(20 10 INTEGER.MIN)", cfg)
@test s.integer == [10]
s = Push.run("(10 20 INTEGER.MIN)", cfg)
@test s.integer == [10]

# INTEGER.POP
s = Push.run("(10 20 30 INTEGER.POP)", cfg)
@test s.integer == [10, 20]

# INTEGER.ROT
s = Push.run("(1 INTEGER.ROT)", cfg)
@test s.integer == [1]
s = Push.run("(0 10 20 30 INTEGER.ROT)", cfg)
@test s.integer == [0, 30, 10, 20]

# INTEGER.SHOVE
s = Push.run("(1 INTEGER.SHOVE)", cfg)
@test s.integer == [1]
s = Push.run("(2 0 INTEGER.SHOVE)", cfg)
@test s.integer == [2]
s = Push.run("(2 9000 INTEGER.SHOVE)", cfg)
@test s.integer == [2]
s = Push.run("(1 2 3 4 5 6 7 8 900 -20 INTEGER.SHOVE)", cfg)
@test s.integer == [1,2,3,4,5,6,7,8,900]
s = Push.run("(1 2 3 4 5 6 7 8 900 0 INTEGER.SHOVE)", cfg)
@test s.integer == [1,2,3,4,5,6,7,8,900]
s = Push.run("(1 2 3 4 5 6 7 8 900 1 INTEGER.SHOVE)", cfg)
@test s.integer == [1,2,3,4,5,6,7,900,8]
s = Push.run("(1 2 3 4 5 6 7 8 900 2 INTEGER.SHOVE)", cfg)
@test s.integer == [1,2,3,4,5,6,900,7,8]
s = Push.run("(1 2 3 4 5 6 7 8 900 9 INTEGER.SHOVE)", cfg)
@test s.integer == [900,1,2,3,4,5,6,7,8]
s = Push.run("(1 2 3 4 5 6 7 8 900 1000 INTEGER.SHOVE)", cfg)
@test s.integer == [900,1,2,3,4,5,6,7,8]

# INTEGER.STACKDEPTH
s = Push.run("(INTEGER.STACKDEPTH)", cfg)
@test s.integer == [0]
s = Push.run("(10 20 30 40 INTEGER.STACKDEPTH)", cfg)
@test s.integer == [10,20,30,40, 4]

# INTEGER.SWAP
s = Push.run("(1 INTEGER.SWAP)", cfg)
@test s.integer == [1]
s = Push.run("(1 2 3 4 5 6 INTEGER.SWAP)", cfg)
@test s.integer == [1,2,3,4,6,5]

# INTEGER.YANK
s = Push.run("(1 INTEGER.YANK)", cfg)
@test s.integer == [1]
s = Push.run("(10 0 INTEGER.YANK)", cfg)
@test s.integer == [10]
s = Push.run("(10 20 30 40 50 -90 INTEGER.YANK)", cfg)
@test s.integer == [10, 20, 30, 40, 50]
s = Push.run("(10 20 30 40 50 0 INTEGER.YANK)", cfg)
@test s.integer == [10, 20, 30, 40, 50]
s = Push.run("(10 20 30 40 50 1 INTEGER.YANK)", cfg)
@test s.integer == [10, 20, 30, 50, 40]
s = Push.run("(10 20 30 40 50 2 INTEGER.YANK)", cfg)
@test s.integer == [10, 20, 40, 50, 30]
s = Push.run("(10 20 30 40 50 5 INTEGER.YANK)", cfg)
@test s.integer == [20, 30, 40, 50, 10]
s = Push.run("(10 20 30 40 50 987 INTEGER.YANK)", cfg)
@test s.integer == [20, 30, 40, 50, 10]

# INTEGER.YANKDUP
s = Push.run("(1 INTEGER.YANKDUP)", cfg)
@test s.integer == [1]
s = Push.run("(10 20 30 40 50 0 INTEGER.YANKDUP)", cfg)
@test s.integer == [10, 20, 30, 40, 50, 50]
s = Push.run("(10 20 30 40 50 1 INTEGER.YANKDUP)", cfg)
@test s.integer == [10, 20, 30, 40, 50, 40]
s = Push.run("(10 20 30 40 50 2 INTEGER.YANKDUP)", cfg)
@test s.integer == [10, 20, 30, 40, 50, 30]
s = Push.run("(10 20 30 40 50 3 INTEGER.YANKDUP)", cfg)
@test s.integer == [10, 20, 30, 40, 50, 20]
s = Push.run("(10 20 30 40 50 4 INTEGER.YANKDUP)", cfg)
@test s.integer == [10, 20, 30, 40, 50, 10]
s = Push.run("(10 20 30 40 50 987 INTEGER.YANKDUP)", cfg)
@test s.integer == [10, 20, 30, 40, 50, 10]
s = Push.run("(10 20 30 40 50 -987 INTEGER.YANKDUP)", cfg)
@test s.integer == [10, 20, 30, 40, 50, 50]

# INTEGER.DEFINE
s = Push.run("(X INTEGER.DEFINE X)", cfg)
@test isempty(s.integer) && s.name == [:X, :X]
s = Push.run("(3 X INTEGER.DEFINE X)", cfg)
@test isempty(s.name) && s.integer == [3]

# INTEGER.RAND
s = Push.run("(INTEGER.RAND)", cfg)
@test length(s.integer) == 1

# INTEGER.%


# Routines to calculate the azimuth, backazimuth and great circle distance
# from headers in traces.

"Earth elliposoid semi-axes in WGS84"
const earth_r_major_WGS84 = 6378137.0000
const earth_r_minor_WGS84 = 6356752.3142
"Flattening of the Earth in WGS84"
const f_WGS84 = (earth_r_major_WGS84 - earth_r_minor_WGS84)/earth_r_major_WGS84

"""
    _great_circle(lon0, lat0, lon1, lat1, f=f_WGS84) -> gcarc, az, baz

Return the great-circle distance, `gcarc`, forward azimuth `az` and backazimuth `baz`
between two points, all specified in degrees.
"""
function _great_circle(lon0, lat0, lon1, lat1, f=f_WGS84)
    lon0, lat0, lon1, lat1 = Float64(lon0), Float64(lat0), Float64(lon1), Float64(lat1)
    gcarc, az, baz = vincentydist(f, 1.0, deg2rad(lat0), deg2rad(lon0),
        deg2rad(lat1), deg2rad(lon1))
    rad2deg(gcarc), rad2deg(az), rad2deg(baz)
end

"""
    update_great_circle!(s::SACtr)

If all headers `evlo`, `evla`, `stlo` and `stla` are set, update the values of
`az`, `baz` and `gcarc`.
"""
function update_great_circle!(s::SACtr)
    any([s.evlo, s.evla, s.stlo, s.stla] .== sac_rnull) && return
    s.gcarc, s.az, s.baz = _great_circle(s.evlo, s.evla, s.stlo, s.stla)
end

# The following functions have been taken from
#   https://github.com/acrosby/GreatCircle.jl
# commit
#   c4860071103c1895a86561c0a0a772bf89705fba
#
# See the file LICENSE.md for details of the licence.
#
# -----------------------------------------------------------------------
# | Algorithms from Geocentric Datum of Australia Technical Manual |
# | |
# | http://www.anzlic.org.au/icsm/gdatum/chapter4.html |
# | |
# | This page last updated 11 May 1999 |
# | |
# | Computations on the Ellipsoid |
# | |
# | There are a number of formulae that are available |
# | to calculate accurate geodetic positions, |
# | azimuths and distances on the ellipsoid. |
# | |
# | Vincenty's formulae (Vincenty, 1975) may be used |
# | for lines ranging from a few cm to nearly 20,000 km, |
# | with millimetre accuracy. |
# | The formulae have been extensively tested |
# | for the Australian region, by comparison with results |
# | from other formulae (Rainsford, 1955 & Sodano, 1965). |
# | |
# | * Inverse problem: azimuth and distance from known |
# | latitudes and longitudes |
# | * Direct problem: Latitude and longitude from known |
# | position, azimuth and distance. |
# | * Sample data |
# | * Excel spreadsheet |
# | |
# | Vincenty's Inverse formulae |
# | Given: latitude and longitude of two points |
# | (phi1, lembda1 and phi2, lembda2), |
# | Calculate: the ellipsoidal distance (s) and |
# | forward and reverse azimuths between the points (alpha12, alpha21). |
# | |
# -----------------------------------------------------------------------
"""
    vincentypt(flattening, semimajor_radius, lat, lon, azimuth, distance) -> lat, lon, backazimuth

Return lat and long of projected point and backazimuth,
given a reference point and a distance and azimuth to project.

Coordinates and azimuth are in radians.
"""
function vincentypt(f::T, a::T, phi1::T, lembda1::T, alpha12::T, s::T) where {T<:AbstractFloat}
    two_pi = 2π
    if alpha12 < 0.0
        alpha12 = alpha12 + two_pi
    end
    if alpha12 > two_pi
        alpha12 = alpha12 - two_pi
    end

    b = a .* (1.0 - f)

    TanU1 = (1 - f) .* tan(phi1)
    U1 = atan( TanU1 )
    sigma1 = atan( TanU1, cos(alpha12) )
    Sinalpha = cos(U1) .* sin(alpha12)
    cosalpha_sq = 1.0 - Sinalpha .* Sinalpha

    u2 = cosalpha_sq .* (a .* a - b .* b ) ./ (b .* b)
    A = 1.0 + (u2 ./ 16384) .* (4096 + u2 .* (-768 + u2 .* (320 - 175 .* u2) ) )
    B = (u2 ./ 1024) .* (256 + u2 .* (-128 + u2 .* (74 - 47 .* u2) ) )

    # Starting with the approximation
    sigma = (s ./ (b .* A))

    # Not moving anywhere. We can return the location that was passed in.
    if sigma == 0
        return phi1, lembda1, alpha12
    end

    last_sigma = 2.0 .* sigma + 2.0 # something impossible

    # Iterate the following three equations
    # until there is no significant change in sigma
    # two_sigma_m , delta_sigma
    while ( abs( (last_sigma - sigma) ./ sigma) > 1.0e-9 )
        global two_sigma_m = 2 .* sigma1 + sigma
        delta_sigma = B .* sin(sigma) .* ( cos(two_sigma_m) + (B./4) .* (cos(sigma) .* (-1 + 2 .* cos(two_sigma_m).^2 - (B./6) .* cos(two_sigma_m) .* (-3 + 4 .* sin(sigma).^2) .* (-3 + 4 .* cos(two_sigma_m).^2 ))))
        last_sigma = sigma
        sigma = (s ./ (b .* A)) + delta_sigma
    end

    phi2 = atan( (sin(U1) .* cos(sigma) + cos(U1) .* sin(sigma) .* cos(alpha12) ), ((1-f) .* sqrt( Sinalpha.^2 + (sin(U1) .* sin(sigma) - cos(U1) .* cos(sigma) .* cos(alpha12)).^2)))

    lembda = atan( (sin(sigma) .* sin(alpha12 )), (cos(U1) .* cos(sigma) - sin(U1) .* sin(sigma) .* cos(alpha12)))

    C = (f./16) .* cosalpha_sq .* (4 + f .* (4 - 3 .* cosalpha_sq ))

    omega = lembda - (1-C) .* f .* Sinalpha .* (sigma + C .* sin(sigma) .* (cos(two_sigma_m) + C .* cos(sigma) .* (-1 + 2 .* cos(two_sigma_m).^2 )))

    lembda2 = lembda1 + omega

    alpha21 = atan( Sinalpha, (-sin(U1) .* sin(sigma) + cos(U1) .* cos(sigma) .* cos(alpha12)))
    alpha21 = alpha21 + pi

    if alpha21 < 0.0
        alpha21 = alpha21 + two_pi
    end
    if alpha21 > two_pi
        alpha21 = alpha21 - two_pi
    end
    return phi2, lembda2, alpha21

end

function vincentypt(f::T, a::T, phi1::Array{T,1}, lembda1::Array{T,1}, alpha12::Array{T,1}, s::Array{T,1}) where {T<:AbstractFloat}
    lenphi = length(phi1)
    @assert lenphi == length(lembda1)
    @assert lenphi == length(alpha12)
    @assert lenphi == length(s)
    phi2 = zeros(phi1)
    lembda2 = zeros(phi1)
    alpha21 = zeros(phi1)
    for i=1:lenphi
        @inbounds phi2[i], lembda2[i], alpha21[i] = vincentypt(f, a, phi1[i], lembda1[i], alpha12[i], s[i])
    end
    return phi2, lembda2, alpha21
end

vincentypt(f::T, a::T, phi1::Array{T,1}, lembda1::Array{T,1}, alpha12::Array{T,1}, s::T) where {T<:AbstractFloat} = vincentypt(f, a, phi1, lembda1, alpha12, ones(length(phi1)) * s)
vincentypt(f::T, a::T, phi1::Array{T,1}, lembda1::Array{T,1}, alpha12::T, s::Array{T,1}) where {T<:AbstractFloat} = vincentypt(f, a, phi1, lembda1, ones(length(phi1)) * alpha12, s)
vincentypt(f::T, a::T, phi1::Array{T,1}, lembda1::Array{T,1}, alpha12::T, s::T) where {T<:AbstractFloat} = vincentypt(f, a, phi1, lembda1, ones(length(phi1)) * alpha12, ones(length(phi1)) * s)

"""
    vincentydist(flattening, semimajor_radius, lat1, lon1, lat2, lon2) -> dist, azimuth, backazimuth

Returns the distance between two geographic points on the ellipsoid,
and the forward and reverse azimuths between these points.

Latitude, longitude and (back)azimuth are in radians; distances are in the units
provided for the semimajor axis length.
"""
function vincentydist(f::T, a::T, phi1::T, lembda1::T, phi2::T, lembda2::T) where {T<:AbstractFloat}
    two_pi = 2π
    if (abs( phi2 - phi1 ) < 1e-8) & ( abs( lembda2 - lembda1) < 1e-8 )
        return 0.0, 0.0, 0.0
    end

    b = a .* (1.0 - f)

    TanU1 = (1 - f) .* tan( phi1 )
    TanU2 = (1 - f) .* tan( phi2 )

    U1 = atan(TanU1)
    U2 = atan(TanU2)

    lembda = lembda2 - lembda1
    last_lembda = -4000000.0 # an impossibe value
    omega = lembda

    # Iterate the following equations until there is no significant change in lembda
    alpha, sigma, Sin_sigma, Cos2sigma_m, Cos_sigma, sqr_sin_sigma = -999999., -999999., -999999., -999999., -999999., -999999.
    while ( (last_lembda < -3000000.0) | (lembda != 0) ) & ( abs( (last_lembda - lembda)./lembda) > 1.0e-9 )
        sqr_sin_sigma = (cos(U2) .* sin(lembda)).^2 + ( (cos(U1) .* sin(U2) - sin(U1) .* cos(U2) .* cos(lembda) )).^2
        Sin_sigma = sqrt( sqr_sin_sigma )
        Cos_sigma = sin(U1) .* sin(U2) + cos(U1) .* cos(U2) .* cos(lembda)
        sigma = atan( Sin_sigma, Cos_sigma )

        Sin_alpha = cos(U1) .* cos(U2) .* sin(lembda) ./ sin(sigma)

        if (Sin_alpha >= 1)# & (Sin_alpha .== 1.0)
            Sin_alpha = 1.0
        elseif (Sin_alpha <= -1)# & (Sin_alpha .== -1.0)
            Sin_alpha = -1.0
        end

        alpha = asin( Sin_alpha )
        Cos2sigma_m = cos(sigma) - (2 .* sin(U1) .* sin(U2) ./ cos(alpha).^2 )
        C = (f./16) .* cos(alpha).^2 .* (4 + f .* (4 - 3 .* cos(alpha).^2))
        last_lembda = lembda
        lembda = omega + (1-C) .* f .* sin(alpha) .* (sigma + C .* sin(sigma) .* (Cos2sigma_m + C .* cos(sigma) .* (-1 + 2 .* Cos2sigma_m.^2 )))
    end

    u2 = cos(alpha).^2 .* (a.*a-b.*b) ./ (b.*b)

    A = 1 + (u2./16384) .* (4096 + u2 .* (-768 + u2 .* (320 - 175 .* u2)))

    B = (u2./1024) .* (256 + u2 .* (-128+ u2 .* (74 - 47 .* u2)))

    delta_sigma = B .* Sin_sigma .* (Cos2sigma_m + (B./4) .* (Cos_sigma .* (-1 + 2 .* Cos2sigma_m.^2 ) - (B./6) .* Cos2sigma_m .* (-3 + 4 .* sqr_sin_sigma) .* (-3 + 4 .* Cos2sigma_m.^2 )))

    s = b .* A .* (sigma - delta_sigma)

    alpha12 = atan( (cos(U2) .* sin(lembda)), (cos(U1) .* sin(U2) - sin(U1) .* cos(U2) .* cos(lembda)))
    alpha21 = atan( (cos(U1) .* sin(lembda)), (-sin(U1) .* cos(U2) + cos(U1) .* sin(U2) .* cos(lembda)))

    if alpha12 < 0.0
        alpha12 = alpha12 + two_pi
    end
    if alpha12 > two_pi
        alpha12 = alpha12 - two_pi
    end
    alpha21 = alpha21 + two_pi ./ 2.0

    if alpha21 < 0.0
        alpha21 = alpha21 + two_pi
    end
    if alpha21 > two_pi
        alpha21 = alpha21 - two_pi
    end
    return s, alpha12, alpha21
end

function vincentydist(f::T, a::T, phi1::Array{T,1}, lembda1::Array{T,1}, phi2::Array{T,1}, lembda2::Array{T,1}) where {T<:AbstractFloat}
    lenphi = length(phi1)
    @assert lenphi == length(lembda1)
    @assert lenphi == length(phi2)
    @assert lenphi == length(lembda2)
    s = zeros(phi1)
    alpha12 = zeros(phi1)
    alpha21 = zeros(phi1)
    for i=1:lenphi
        @inbounds s[i], alpha12[i], alpha21[i] = vincentydist(f, a, phi1[i], lembda1[i], phi2[i], lembda2[i])
    end
    return s, alpha12, alpha21
end

vincentydist(f::T, a::T, phi1::T, lembda1::Array{T,1}, phi2::T, lembda2::Array{T,1}) where {T<:AbstractFloat} = vincentydist(f, a, phi1 * ones(lembda1), lembda1, phi2 * ones(lembda1), lembda2)
vincentydist(f::T, a::T, phi1::Array{T,1}, lembda1::T, phi2::Array{T,1}, lembda2::T) where {T<:AbstractFloat} = vincentydist(f, a, phi1, lembda1 * ones(phi1), phi2, lembda2 * ones(phi1))


########## functions ##########

function start_fishes(data)
    fishes = zeros(BigInt, 9)
    for day = 1:9
        fishes[day] = sum(data .== (day - 1))
    end
    return fishes
end

function live_another_day!(fishes)
    n_spawns = fishes[1]
    fishes[1:8] = fishes[2:9]
    fishes[9] = n_spawns
    fishes[7] += n_spawns
    return nothing
end

########## Script ##########

function main()

    # open file and store content into an input string
    input = open("inputs//day6.txt", "r") do io
        read(io, String)
    end

    # split input into a data vector of integers
    data = parse.(Int, String.(split(input, ",")))

    # initialize fishes table
    fishes = start_fishes(data)

    # part 1
    for _ = 1:80
        live_another_day!(fishes)
    end
    println("part 1: ", sum(fishes))

    # part 2
    for _ = 81:256
        live_another_day!(fishes)
    end
    println("part 1: ", sum(fishes))

end

main()


"""
    psaverage(psysc) -> sys::DescriptorStateSpace

Compute for the continuous-time periodic system `psysc = (A(t),B(t),C(t),D(t))`
the corresponding time averaged LTI system `sys = (Am,Bm,Cm,Dm)` over one period.
"""
function psaverage(psysc::PeriodicStateSpace{PM}) where {T,PM <: AbstractPeriodicArray{:c,T}}
    return dss(pmaverage(psysc.A), pmaverage(psysc.B), pmaverage(psysc.C), pmaverage(psysc.D))
end
"""
    psteval(psysc,tval) -> sys::DescriptorStateSpace

Compute for the continuous-time periodic system `psysc = (A(t),B(t),C(t),D(t))` and a time value `tval`,
the LTI system `sys = (A(tval),B(tval),C(tval),D(tval))`.
"""
function psteval(psysc::PeriodicStateSpace{PM}, tval::Real = 0) where {T,PM <: AbstractPeriodicArray{:c,T}}
    return dss(tvmeval(psysc.A, tval)[1], tvmeval(psysc.B, tval)[1], tvmeval(psysc.C, tval)[1], tvmeval(psysc.D, tval)[1])
end
"""
     psmrc2d(sys, Ts; ki, ko) -> psys::PeriodicStateSpace{PeriodicMatrix}

Perform the multirate discretization of a linear time-invariant system.

For a continuous-time state-space system `sys = (A,B,C,D)`, a basic sampling time `Ts`,
and the integer vectors `ki` and `ko` containing, respectively, the numbers of
input and output sampling subperiods, the corresponding discretized
periodic system `psys = (Ap,Bp,Cp,Dp)` of period `T = n*Ts` is determined,
where `n` is the least common multiple
of the integers contained in `ki` and `ko`. For a continuous-time system `sys`
a zero-order hold based discretization method is used, such that the `i`-th input
is constant during intervals of length `ki[i]*Ts`.
An output hold device is used to provide constant intersample outputs, such that the `i`-th output
is constant during intervals of length `ko[i]*Ts`.
For a discrete-time system with a defined sample time `Δ`,
an input and output resampling is performed using `Δ` as basic
sample time and the value of `Ts` is ignored.
If the system sample time is not defined, then the value of
`Ts` is used as the basic sample time.
"""
function psmrc2d(sys::DescriptorStateSpace{T}, Ts::Real; ki::Vector{Int} = ones(Int,size(sys,2)), ko::Vector{Int}= ones(Int,size(sys,1))) where T
    Ts > 0 || error("the sampling time Ts must be positive")
    (all(ki .>= 1) && all(ko .>= 1)) || error("the numbers of subperiods must be at least one")
    p, m = size(sys)
    length(ki) == m || error("the number of input components of ki must be $m")
    length(ko) == p || error("the number of ouput components of ko must be $p")
    A, E, B, C, D = dssdata(sys)
    E == I || error("only standard state-space systems supported")
    Δ = sys.Ts
    T1 = T <: BlasFloat ? T : promote_type(Float64,T)
    n = sys.nx
    if Δ != 0
       i1 = 1:n; i2 = n+1:n+m
       Δ > 0 && (Ts = Δ)
       G = exp([ rmul!(A,Ts) rmul!(B,Ts); zeros(T1,m,n+m)])
       A =  G[i1,i1]
       B =  G[i1,i2]
    end

    K = lcm(lcm(ki),lcm(ko))
    Ap = similar(Vector{Matrix},K)
    Bp = similar(Vector{Matrix},K)
    Cp = similar(Vector{Matrix},K)
    Dp = similar(Vector{Matrix},K)
    for i = 1:K
        si = mod.(i-1,ki) .== 0
        Si = Diagonal(si)
        Sineg = Diagonal(.!si)
        Ti = Diagonal(mod.(i-1,ko) .== 0)
        Ap[i] = [A  B*Sineg; zeros(T1,m,n) Sineg]
        Bp[i] = [B*Si; Si]
        Cp[i] = [Ti*C Ti*D*Sineg]
        Dp[i] = Ti*D
    end
    PMT = PeriodicMatrix
    period = K*Ts
    return ps(PMT(Ap,period),PMT(Bp,period),PMT(Cp,period),PMT(Dp,period))
end
"""
     psc2d(psysc, Ts; solver, reltol, abstol, dt) -> psys::PeriodicStateSpace{PeriodicMatrix}

Compute for the continuous-time periodic system `psysc = (A(t),B(t),C(t),D(t))` of period `T` and
for a sampling time `Ts`, the corresponding discretized
periodic system `psys = (Ad,Bd,Cd,Dd)` using a zero-order hold based discretization method.

The discretization is performed by determining the monodromy matrix as a product of
`K = T/Ts` state transition matrices of the extended state-space matrix [A(t) B(t); 0 0]
by integrating numerically the corresponding homogeneous linear ODE.
The ODE solver to be employed can be
specified using the keyword argument `solver`, together with
the required relative accuracy `reltol` (default: `reltol = 1.e-3`),
absolute accuracy `abstol` (default: `abstol = 1.e-7`) and/or
the fixed step length `dt` (default: `dt = Ts/10`) (see [`tvstm`](@ref)).
For large values of `K`, parallel computation of factors can be alternatively performed
by starting Julia with several execution threads.
The number of execution threads is controlled either by using the `-t/--threads` command line argument
or by using the `JULIA_NUM_THREADS` environment variable.
"""
function psc2d(psysc::PeriodicStateSpace{PM}, Ts::Real; solver::String  = "", reltol = 1e-3, abstol = 1e-7, dt = Ts/10) where {T,PM <: AbstractPeriodicArray{:c,T}}

    Ts > 0 || error("the sampling time Ts must be positive")
    period = psysc.period
    r = rationalize(period/Ts)
    denominator(r) == 1 || error("incommensurate period and sample time")
    K = numerator(r)

    T1 = T <: BlasFloat ? T : (T <: Num ? Float64 : promote_type(Float64,T))
    ONE = one(T1)
    n, m = size(psysc.B)

    # quick exit in constant case
    islti(psysc) && (return ps(c2d(psaverage(psysc),Ts)[1],psysc.period))

    ka, na = isconstant(psysc.A) ? (1,K) : (K,1)
    kb, nb = isconstant(psysc.A) && isconstant(psysc.B) ? (1,K) : (K,1)
    kc, nc = isconstant(psysc.C) ? (1,K) : (K,1)
    kd, nd = isconstant(psysc.D) ? (1,K) : (K,1)
    Ap = similar(Vector{Matrix},ka)
    Bp = similar(Vector{Matrix},kb)
    Cp = similar(Vector{Matrix},kc)
    Dp = similar(Vector{Matrix},kd)

    psys1 = typeof(psysc.A) <: PeriodicFunctionMatrix ? psysc :
               convert(PeriodicStateSpace{PeriodicFunctionMatrix},psysc)
    i1 = 1:n; i2 = n+1:n+m

    if isconstant(psysc.A) && isconstant(psysc.B)
        G = exp([ rmul!(psys1.A.f(0),Ts) rmul!(psys1.B.f(0),Ts); zeros(T1,m,n+m)])
        Ap[1] = G[i1,i1]
        Bp[1] = G[i1,i2]
        ts = zero(T1)
        for i = 1:kc
            Cp[i] = psys1.C.f(ts)
            ts += Ts
        end
        ts = zero(T1)
        for i = 1:kd
            Dp[i] = psys1.D.f(ts)
            ts += Ts
        end
    else
        Gfun = PeriodicFunctionMatrix(t -> [psys1.A.f(t) psys1.B.f(t); zeros(T1,m,n+m)], period)
        G = monodromy(Gfun, K; solver, reltol, abstol, dt)
        [Ap[i] = G.M[i1,i1,i] for i in 1:ka]
        [Bp[i] = G.M[i1,i2,i] for i in 1:kb]
        ts = zero(T1)
        for i = 1:kc
            Cp[i] = psys1.C.f(ts)
            ts += Ts
        end
        ts = zero(T1)
        for i = 1:kd
            Dp[i] = psys1.D.f(ts)
            ts += Ts
        end
    end
    PMT = PeriodicMatrix
    return ps(PMT(Ap,period; nperiod = na),PMT(Bp,period; nperiod = nb),PMT(Cp,period; nperiod = nc),PMT(Dp,period; nperiod = nd))
    # end PSC2D
end


using NNFEM
using ADCME
using LinearAlgebra
using SparseArrays
using PyPlot
using Test
include("fem.jl")


AP = CSV.read("../examples/AirPassengers.csv")
logAP = log.(Vector{Float64}(AP.Passengers))

@testset "Air passengers with Kalman filter" begin
    model1 = structural(logAP, 12)

    @test isa(model1, StateSpaceModel)
    @test model1.mode == "time-invariant"

    ss1 = statespace(model1)

    @test ss1.filter_type <: KalmanFilter
    @test isa(ss1, StateSpace)
    compare_forecast_simulation(ss1, 20, 1000, 1e-2)

    model2 = structural(logAP, 12)

    ss2 = statespace(model2; filter_type = SquareRootFilter{Float64})

    @test ss2.filter_type <: SquareRootFilter
    @test isa(ss2, StateSpace)
    compare_forecast_simulation(ss2, 20, 1000, 1e-2)

    model3 = structural(logAP, 12)

    ss3 = statespace(model3; filter_type = UnivariateKalmanFilter{Float64})

    @test ss3.filter_type <: UnivariateKalmanFilter
    @test isa(ss3, StateSpace)
    compare_forecast_simulation(ss3, 20, 1000, 1e-2)

    @test ss2.smoother.alpha ≈ ss1.smoother.alpha rtol = 1e-2
    @test ss3.smoother.alpha ≈ ss1.smoother.alpha rtol = 1e-3
    @test ss2.filter.a ≈ ss1.filter.a rtol = 1e-3
    @test ss3.filter.a ≈ ss1.filter.a rtol = 1e-3

    diags1 = diagnostics(ss1)
    diags2 = diagnostics(ss2)
    diags3 = diagnostics(ss3)

    @test diags1.p_jarquebera ≈ diags2.p_jarquebera atol = 1e-2
    @test diags1.p_jarquebera ≈ diags3.p_jarquebera atol = 1e-2
    @test diags1.p_ljungbox ≈ diags2.p_ljungbox atol = 1e-2
    @test diags1.p_ljungbox ≈ diags3.p_ljungbox atol = 1e-2
    @test diags1.p_homo ≈ diags2.p_homo atol = 1e-2
    @test diags1.p_homo ≈ diags3.p_homo atol = 1e-2
end


#= to check : Raviart-Thomas assembling =#

function getBaseRT0(E::Float64, xE::Array{Float64, 2})
    return [
        x -> (x - xE[:, 3])/(2*E),
        x -> (x - xE[:, 1])/(2*E),
        x -> (x - xE[:, 2])/(2*E)
    ]
end

function melemRT0(vtx::Array{Float64, 2})
    @assert size(vtx, 2) == 3
    # initialize
    E = polygonArea(vtx)
    # div part
    DIV = ones(Float64, 3, 3) / E
    # mass part
    RT0 = getBaseRT0(E, vtx)
    MASS = zeros(Float64, 3, 3)
    quad = quadGL2d(3, vtx)
    for j=1:3
        for i=1:3
            MASS[i,j] = integrate(quad, x -> dot(RT0[i](x),RT0[j](x)))
        end
    end
    return DIV, MASS
end

function buildRT0matrices(msh::mesh, conn::Connectivity)
    e2dof = conn.e2dof
    ndof  = conn.ndof
    # 1st: preallocate
    A = zeros(Float64, ndof, ndof)
    B = zeros(Float64, ndof, ndof)
    # 2nd: main loop
    for e=1:length(msh.elt)
        @inbounds verts  = msh.vtx[:, msh.elt[e]]
        AE, BE = melemRT0(verts)
        nloc = length(e2dof[e])
        for i = 1:nloc
            @inbounds absI = abs(e2dof[e][i])
            @inbounds sgnI = sign(e2dof[e][i])
            for j=1:nloc
                @inbounds absJ = abs(e2dof[e][j])
                @inbounds sgnJ = sign(e2dof[e][j])
                @inbounds A[absI, absJ] += sgnI*sgnJ*(AE[i,j] + BE[i,j])
                @inbounds B[absI, absJ] += sgnI*sgnJ*BE[i,j]
                #
            end
        end
    end
    return A, B
end


const Vec2 = SVector{2,Float64}

abstract type AbstractLD2 <: POMDP{Vec2, Vec2, Vec2} end

"""
2-Dimensional light-dark problem

    min_noise::Float64
        minimum standard deviation of observation noise

    min_noise_loc::Float64
        x location of minimum observation noise

    Q::Matrix{Float64}
    R::Matrix{Float64}
        cost function matrices: reward = -s'Qs -a'Ra

    term_radius::Float64
        problem will terminate if the state is within this radius of zero

    init_dist::Any
        initial state distribution

    discount::Float64
        discount factor
"""
@with_kw type LightDark2D <: AbstractLD2
    min_noise::Float64      = 0.0
    min_noise_loc::Float64  = 5.0
    Q::Matrix{Float64}      = diagm([0.5, 0.5])
    R::Matrix{Float64}      = diagm([0.5, 0.5])
    term_radius::Float64    = 1e-5
    init_dist::Any          = SymmetricNormal2([2.0, 2.0], 0.5)
    discount::Float64       = 1.0
    count::Int              = 0
end

# POMDPs.jl API functions:
generate_s(p::AbstractLD2, s::Vec2, a::Vec2, rng::AbstractRNG) = generate_s(p, s, a)
generate_o(p::AbstractLD2, s::Vec2, a::Vec2, sp::Vec2, rng::AbstractRNG) = generate_o(p, sp, rng)
observation(p::AbstractLD2, a::Vec2, sp::Vec2) = observation(p, sp)
isterminal(p::AbstractLD2, s::Vec2) = norm(s) <= p.term_radius
initial_state_distribution(p::AbstractLD2) = p.init_dist
reward(p::AbstractLD2, s::Vec2, a::Vec2, sp::Vec2) = -(dot(s, p.Q*s) + dot(a, p.R*a))
discount(p::AbstractLD2) = p.discount

immutable SymmetricNormal2
    mean::Vec2
    std::Float64
end
rand(rng::AbstractRNG, d::SymmetricNormal2) = d.mean + d.std*Vec2(randn(rng, 2))
pdf(d::SymmetricNormal2, s::Vec2) = exp(-0.5*sum((s-d.mean).^2)/d.std^2)/(2*pi*d.std^2)
mean(d::SymmetricNormal2) = d.mean
mode(d::SymmetricNormal2) = d.mean
Base.eltype(::Type{SymmetricNormal2}) = Vec2

# chose this on 2/6/17 because I like the bowtie particle patterns it produces
# unclear which one was actually used in the paper
# Masters Thesis by Pas assumes the sqrt version
obs_std(p::AbstractLD2, x::Float64) = sqrt(0.5*(p.min_noise_loc-x)^2 + p.min_noise)
# obs_std(p::AbstractLD2, x::Float64) = 0.5*(p.min_noise_loc-x)^2 + p.min_noise

function generate_s(p::AbstractLD2, s::Vec2, a::Vec2)
    p.count += 1
    return s+a
end
observation(p::AbstractLD2, sp::Vec2) = SymmetricNormal2(sp, obs_std(p, sp[1]))
generate_o(p::AbstractLD2, sp::Vec2, rng::AbstractRNG) = rand(rng, observation(p, sp))


# exp(-t)*(2-x)*x*(1-y)
using Revise
using ADCME
using AdFem
using PyPlot

n = 20
m = 2n
NT = 200
ρ = 0.0
Δt = 1/NT
h = 1/n
x = zeros((m+1)*(n+1))
y = zeros((m+1)*(n+1))
for i = 1:m+1
    for j = 1:n+1
        idx = (j-1)*(m+1)+i
        x[idx] = (i-1)*h
        y[idx] = (j-1)*h
    end
end
bd = bcnode("all", m, n, h)

u1 = (x,y,t)->exp(-t)*(0.5-x)^2*(2-y)^2
u2 = (x,y,t)->exp(-t)*(0.5-x^2)^2*(2-sin(y))^2

ts = Δt * ones(NT)
dt = αscheme_time(ts, ρ = ρ )
F = zeros(NT, 2(m+1)*(n+1))
for i = 1:NT
    t = dt[i]
    f1 = (x,y)->-9.87654320987654*x*(0.5 - x^2)*(2 - sin(y))*exp(-t)*cos(y) + (0.5 - x)^2*(2 - y)^2*exp(-t) - 0.740740740740741*(0.5 - x)^2*exp(-t) - 3.20987654320988*(2 - y)^2*exp(-t)
  	f2 = (x,y)->-2.96296296296296*x^2*(2 - sin(y))^2*exp(-t) + (0.5 - x^2)^2*(2 - sin(y))^2*exp(-t) - 3.20987654320988*(0.5 - x^2)^2*(2 - sin(y))*exp(-t)*sin(y) - 3.20987654320988*(0.5 - x^2)^2*exp(-t)*cos(y)^2 + 1.48148148148148*(0.5 - x^2)*(2 - sin(y))^2*exp(-t) - (0.740740740740741*x - 0.37037037037037)*(2*y - 4)*exp(-t) - (2*x - 1.0)*(1.72839506172839*y - 3.45679012345679)*exp(-t)
    fval1 = eval_f_on_gauss_pts(f1, m, n, h)
  	fval2 = eval_f_on_gauss_pts(f2, m, n, h)
    F[i,:] = compute_fem_source_term(fval1, fval2, m, n, h)
end

abd = zeros(NT, (m+1)*(n+1)*2)
for i = 1:NT
    t = dt[i]
    abd[i,:] = [(@. u1(x, y, t)); (@. u2(x, y, t))]
end
abd = constant(abd[:, [bd; bd .+ (m+1)*(n+1)]])

E = 1.0
ν = 0.35
H = E/(1+ν)/(1-2ν)*[
  1-ν ν 0
  ν 1-ν 0
  0 0 (1-2ν)/2
]
M = constant(compute_fem_mass_matrix(m, n, h))
K = constant(compute_fem_stiffness_matrix(H, m, n, h))

a0 = [(@. u1(x, y, 0.0)); (@. u2(x, y, 0.0))]
u0 = -[(@. u1(x, y, 0.0)); (@. u2(x, y, 0.0))]
d0 = [(@. u1(x, y, 0.0)); (@. u2(x, y, 0.0))]

function solver(A, rhs, i)
    A, Abd = fem_impose_Dirichlet_boundary_condition_experimental(A, bd, m, n, h)
    rhs = rhs - Abd * abd[i]
    rhs = scatter_update(rhs, [bd; bd .+ (m+1)*(n+1)], abd[i])
    return A\rhs
end
d, u, a = αscheme(M, spzero(2(m+1)*(n+1)), K, F, d0, u0, a0, ts; extsolve=solver, ρ = ρ  )

sess = Session()
d_, u_, a_ = run(sess, [d, u, a])

function plot_traj(idx)
    figure(figsize=(12,3))
    subplot(131)
    plot((0:NT)*Δt, u1.(x[idx], y[idx],(0:NT)*Δt), "b-", label="x-Acceleration")
    plot((0:NT)*Δt, a_[:,idx], "y--", markersize=2)
  	plot((0:NT)*Δt, u2.(x[idx], y[idx],(0:NT)*Δt), "r-", label="y-Acceleration")
    plot((0:NT)*Δt, a_[:,idx+(m+1)*(n+1)], "c--", markersize=2)
    legend()
    xlabel("Time")
    ylabel("Value")

    subplot(132)
    plot((0:NT)*Δt, u1.(x[idx], y[idx],(0:NT)*Δt), "b-", label="x-Displacement")
    plot((0:NT)*Δt, d_[:,idx], "y--", markersize=2)
  	plot((0:NT)*Δt, u2.(x[idx], y[idx],(0:NT)*Δt), "r-", label="y-Displacement")
    plot((0:NT)*Δt, d_[:,idx+(m+1)*(n+1)], "c--", markersize=2)
    legend()
    xlabel("Time")
    ylabel("Value")

    subplot(133)
    plot((0:NT)*Δt, -u1.(x[idx], y[idx],(0:NT)*Δt), "b-", label="x-Velocity")
    plot((0:NT)*Δt, u_[:,idx], "y--", markersize=2)
  	plot((0:NT)*Δt, -u2.(x[idx], y[idx],(0:NT)*Δt), "r-", label="y-Velocity")
    plot((0:NT)*Δt, u_[:,idx+(m+1)*(n+1)], "c--", markersize=2)
    legend()
    xlabel("Time")
    ylabel("Value")

    tight_layout()
end

idx2 = (n÷3)*(m+1) + m÷2
plot_traj(idx2)


function OilInflow(pr,J;pb=0,n=10)

    pres=zeros(n,size(pr,1))
    Qrate=zeros(n,size(pr,1)*size(J,1))
    Sat=pr.<=pb

    leg=Array{String,1}(undef,size(pr,1)*size(J,1))
    count=0
    for p=1:size(pr,1)

        pres[:,p]=range(0,stop=pr[p]-50,length=n)

        for j=1:size(J,1)
             count=count+1
             leg[count]="Res. Pressure=$(pr[p]) psi; J=$(J[j]) bbl/d*psi"
            if Sat[p]==1
               Qrate[:,count]=map(pwf->(pr[p]*J[j]/1.8).*(1-0.2*(pwf./pr[p])-0.8*(pwf./pr[p]).^2),pres[:,p])
            else
                Qrate[pres[:,p].>=pb,count]=map(pwf->J[j]*(pr[p]-pwf),pres[:,p][pres[:,p].>=pb])
                Qrate[pres[:,p].<pb,count]=map(pwf->(J[j]*(pr[p]-pb))+(J[j]*pb/1.8)*(1-0.2*(pwf./pb)-0.8*(pwf./pb).^2),pres[:,p][pres[:,p].<pb])

            end

        end

    end
    return pres, Qrate, leg
end


using SMC, Base.Test

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

srand(125)

## Linear Gaussian

dx, dy = 5, 3
A = randn(dx,dx); A /= 0.9norm(A)
B = randn(dy,dx); B /= 1.1norm(B)
Q = randn(dx,dx); Q *= Q'; Q += 0.5*eye(dx); Q += Q'; Q /= 5
R = randn(dy,dy); R *= R'; R += 0.5*eye(dy); R += R'; R /= 20

lg  = LinearGaussian(A,B,Q,R)
hmm = HMM(lg)
x0  = randn(dx)

### LinearGaussian -- testing the generation

srand(123)
(states, observations) = generate(lg, x0, 3)

srand(123)
noisex = chol(Q)' * randn(dx, 3)
noisey = chol(R)' * randn(dy, 3)
state1 = x0
state2 = A*state1+noisex[:,2] # we do not use the first noise
state3 = A*state2+noisex[:,3] # since x0 is given
obs1   = B*state1+noisey[:,1]
obs2   = B*state2+noisey[:,2]
obs3   = B*state3+noisey[:,3]

@test state1==states[:,1] &&
      state2==states[:,2] &&
      state3==states[:,3]
@test obs1==observations[:,1] &&
      obs2==observations[:,2] &&
      obs3==observations[:,3]

@test isapprox( hmm.transmean(0,state1), A*state1)
@test isapprox( hmm.obsmean(0,state1), B*state1)
@test isapprox( hmm.transloglik(0,state1,state2),
        (-norm(chol(Q)'\(state2-A*state1))^2/2) )
@test isapprox( hmm.obsloglik(0,obs2,state2),
        (-norm(chol(R)'\(  obs2-B*state2))^2/2) )


#############################################################################
# abs.jl
# Absolute value of an expression
# All expressions and atoms are subtpyes of AbstractExpr.
# Please read expressions.jl first.
#############################################################################
import Base.abs, Base.abs2

### Absolute Value

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

    function AbsAtom(x::AbstractExpr)
        children = (x,)
        return new(:abs, hash(children), children, x.size)
    end
end

function sign(x::AbsAtom)
    return Positive()
end

function monotonicity(x::AbsAtom)
    return (Nondecreasing() * sign(x.children[1]),)
end

function curvature(x::AbsAtom)
    return ConvexVexity()
end

function evaluate(x::AbsAtom)
    return abs.(evaluate(x.children[1]))
end

function conic_form!(x::AbsAtom, unique_conic_forms::UniqueConicForms)
    if !has_conic_form(unique_conic_forms, x)
        c = x.children[1]
        t = Variable(size(c))
        objective = conic_form!(t, unique_conic_forms)
        if sign(x.children[1]) == ComplexSign()
            for i in 1:length(vec(t))
                conic_form!(
                    t[i] >= norm2([real(c[i]); imag(c[i])]),
                    unique_conic_forms,
                )
            end
        else
            conic_form!(c <= t, unique_conic_forms)
            conic_form!(c >= -t, unique_conic_forms)
        end
        cache_conic_form!(unique_conic_forms, x, objective)
    end
    return get_conic_form(unique_conic_forms, x)
end

abs(x::AbstractExpr) = AbsAtom(x)
abs2(x::AbstractExpr) = square(abs(x))


using Roots

println(find_zero(x -> x^3 - 3x^2 + 2x, (-100, 100)))


export kernelN2D, kernelN3D # haven't figured out how best to dispath for kernelN
export transmission_angle_wiener, transmission_angle, transmission_direction

function kernelN2D(n::Int,x::Union{T,Complex{T}},y::Union{T,Complex{T}}) where T<:AbstractFloat
        h = hankelh1(n,x); dh = diffhankelh1(n,x)
        j = besselj(n,y);  dj = diffbesselj(n,y)

    return x * dh * j - y * h * dj
end

function kernelN3D(n::Int,x::Union{T,Complex{T}},y::Union{T,Complex{T}}) where T<:AbstractFloat
    h = shankelh1(n,x); dh = diffshankelh1(n,x)
    j = sbesselj(n,y);  dj = diffsbesselj(n,y)

    return x * dh * j - y * h * dj
end

"""
    transmission_direction(k_eff::Complex, incident_wavevector::AbstractArray, surface_normal::AbstractArray)

Gives the effective direction `direction` where `sum(direction .^ 2) = 1.0` and the components of `k_eff .* direction` and `incident_wavevector` which are orthogonal to the surface are the same. Note `surface_normal` is the outward pointing normal and the incident medium's wavenumber `k = sqrt(sum(incident_wavevector .^2))`. Below we deduce the result.

Assume that `dot(v,w) = conj(v[i])w[i]` and `surface_normal[i]*surface_normal[i] = 1`. Let `wnp` be orthogonal to `surface_normal`

    wnp =  incident_wavevector -  dot(surface_normal,incident_wavevector) .* surface_normal

Then we know that the effective transmission wavevector has to equal

    k_eff_vec = wnp + α .* surface_normal

where α is determined so that

    sum(x^2 for x in wnp) + α^2  = sum(x^2 for x in k_eff_vec) = k_eff^2
"""
function transmission_direction(k_eff::Complex{T}, incident_wavevector::AbstractArray{CT} where CT <: Union{T,Complex{T}}, surface_normal::AbstractArray{T};
        tol::T = sqrt(eps(T))) where {T<:AbstractFloat,Dim}

    # Let us first make the surface normal a unit vector which is outward pointing
    surface_normal = surface_normal / norm(surface_normal)
    if real(dot(surface_normal,incident_wavevector)) > 0
        surface_normal = -surface_normal
    end

    wnp = incident_wavevector -  dot(surface_normal,incident_wavevector) .* surface_normal
    α = sqrt(k_eff^2 - sum(x^2 for x in wnp))

    # The conditions below guarantee (in order of priority) that either: 1) the wave attenuates when travelling into the material (imag(α) < 0), or 2) the wave does not attenuate, but travels into the material.
    if tol * real(α) > abs(imag(α))
        α = -α # leads to real(α) < 0
    elseif imag(α) > 0
        α = -α # leads to imag(α) < 0
    elseif isnan(α)
        α = -one(T) # covers the cases k_eff = Inf and k_eff = 0.0
        return wnp + α .* surface_normal
    end

    return (wnp + α .* surface_normal) ./ k_eff
end

function transmission_direction(k_eff::Complex{T},  psource::PlaneSource{T,Dim}, material::Material{Dim}; tol::T = sqrt(eps(T))) where {T,Dim}
    transmission_direction(k_eff, psource.direction, material.shape.normal; tol = tol)
end

transmission_angle(pwave::Union{PlaneSource,EffectivePlaneWaveMode}, material::Material) = transmission_angle(pwave, material.shape)

transmission_angle(pwave::Union{PlaneSource,EffectivePlaneWaveMode}, shape::Union{Plate,Halfspace}) = transmission_angle(pwave.direction, shape.normal)

"""
    transmission_angle(wavevector, surface_normal)

Calculates the transmission angle for a wave propagting inside a material which has the outward normal `surface_normal`. The wave propagates in the direction of `wavevector` with a wavenumber given by `k = sqrt(sum(wavevector .^2))`, where `k` should have a positive imaginary part.
"""
transmission_angle(wavevector::AbstractVector,surface_normal::AbstractVector) = transmission_angle(SVector(wavevector...),SVector(surface_normal...))

"""
    transmission_angle(wavevector::StaticVector{3}, surface_normal::StaticVector{3})

Some care is needed when `wavevector` is a complex vector in 3 dimensions.

## The maths

Let's define the normal projection `vn`:

    n = - surface_normal
    vn = dot(n,wavevector) .* n

remembering that in Julia, and mathematics, `dot(v,w) = sum(conj(v[i])*w[i] for i in eachindex(v))`.

We now define the orthogonal component `vo` and its direction `no`:

    vo = wavevector - vn
    no = vo / sqrt(sum(vo.^2))

Note this assumes that `vo` is a real vector, which it should be due to symmetry.

To determine the transmission angle `θT` we calculate the wavenumber

    k = ± sqrt(sum(wavevector .^2))

where we choose the sign in `±` such that `imag(k) >= 0`.

From the above we can write the `wavevector` in the form

    wavevector = k .* (n .* cos(θ) + no .* sin(θ))

which implies that

    k * cos(θ) = dot(conj(n), wavevector)
    k * sin(θ) = dot(conj(no),wavevector) = dot(conj(no),vo) = sqrt(sum(vo.^2))

From these two we can robustly calculate `θT` as

    θT = atan(sqrt(sum(vo.^2)),dot(n,wavevector))

"""
function transmission_angle(wavevector::SVector{3,CT} where CT <: Union{T,Complex{T}}, surface_normal::SVector{3,T}) where {T<:AbstractFloat}
    n = - surface_normal / norm(surface_normal)
    if real(dot(n,wavevector)) < 0
        n = -n
    end

    kcosθ = dot(n,wavevector)

    vo = wavevector - kcosθ .* n
    ksinθ = sqrt(sum(vo.^2))

    return θ = atan(ksinθ,kcosθ)
end

"""
    transmission_angle(wavevector::StaticVector{2}, surface_normal::StaticVector{2})

Specialised to 2 spatial dimensions.
"""
function transmission_angle(wavevector::SVector{2,CT} where CT <: Union{T,Complex{T}}, surface_normal::SVector{2,T}) where {T<:AbstractFloat}
    n = - surface_normal
    no = [-n[2], n[1]] # guarantee θ grows anti-clockwise

    θ = atan(dot(conj(no),wavevector),dot(n,wavevector))

    return θ
end

"""
calculate effective transmission angle θ_eff. We restrict -pi/2 < Re θ_eff < pi/2 for 2 dimensions.
"""
function transmission_angle_wiener(k::Union{T,Complex{T}}, k_eff::Union{T,Complex{T}}, θin) where T<:Number
    # snell(θ::Array{T}) = abs(k*sin(θin) - k_eff*sin(θ[1] + im*θ[2]))
    # result = optimize(snell, [θin,0.]; x_tol= tol, g_tol= tol^2.0)

    θ_eff = asin(k * sin(θin) / k_eff)

    # if !(abs(real(θ_eff - θin)) <= pi/T(2))
    if sign(real(k_eff * cos(θ_eff))) != sign(real(k_eff))
        θ_eff = pi - θ_eff
    end

    return θ_eff
end


# ------------------------- Theoretical -------------------------
"""
Estimates the theoretical peak performance of a CUDA device in TFLOP/s.

**Keyword arguments:**
* `tensorcores` (default: `hastensorcores()`): toggle usage of tensore cores. If `false`, cuda cores will be used.
* `verbose` (default: `true`): toggle printing of information
* `device` (default: `device()`): CUDA device to be analyzed
* `dtype` (default: `tensorcores ? Float16 : Float32`): element type of the matrices
* `io` (default: `stdout`): set the stream where the results should be printed.
"""
function theoretical_peakflops_gpu(;
    device=CUDA.device(),
    tensorcores=hastensorcores(),
    dtype=tensorcores ? Float16 : Float32,
    verbose=true,
    io::IO=stdout,
)
    if tensorcores
        max_peakflops = _theoretical_peakflops_gpu_tensorcores(; device, dtype)
    else
        max_peakflops = _theoretical_peakflops_gpu_cudacores(; device, dtype)
    end

    if verbose
        printstyled(
            io, "Theoretical Peakflops ($(Symbol(dtype) == :Int8 ? "TOP" : "TFLOP")/s):\n";
            bold=true,
        )
        if hastensorcores()
            print(io, " ├ tensorcores: ")
            printstyled(io, tensorcores, "\n"; color=:magenta, bold=true)
        end
        print(io, " ├ dtype: ")
        printstyled(io, Symbol(dtype), "\n"; color=:yellow, bold=true)
        print(io, " └ max: ")
        printstyled(io, round(max_peakflops; digits=1), "\n"; color=:green, bold=true)
    end
    return max_peakflops
end

function _theoretical_peakflops_gpu_cudacores(; device, dtype)
    max_clock_rate = CUDA.attribute(device, CUDA.CU_DEVICE_ATTRIBUTE_CLOCK_RATE) # in kHz
    num_cuda_cores = ncudacores(device)
    max_peakflops = max_clock_rate * num_cuda_cores * 1e-9 # in TFLOP/s
    if dtype == Float32
        max_peakflops *= 2
    elseif dtype == Float64
        max_peakflops *= 1
    else
        throw(ArgumentError("Unsupported dtype."))
    end
    return max_peakflops
end

function _theoretical_peakflops_gpu_tensorcores(;
    device=CUDA.device(), dtype=Float16, verbose=true
)
    cap = CUDA.capability(device)
    if cap == v"8.0.0"
        devtype = :A100
    elseif cap == v"7.0.0"
        devtype = :V100
    else
        error("Unsupported compute capability / device generation.")
    end
    max_clock_rate = CUDA.attribute(device, CUDA.CU_DEVICE_ATTRIBUTE_CLOCK_RATE) # in kHz
    num_tensor_cores = ntensorcores(device)
    max_peakflops = max_clock_rate * num_tensor_cores * 1e-9 # in TFLOP/s
    if devtype == :A100
        if Symbol(dtype) == :Float16
            # matrix dimensions 8x8x4, factor 2 for nflops in A*B+C
            # see e.g. https://peerj.com/articles/cs-330.pdf
            max_peakflops *= 2 * 8 * 8 * 4
        elseif Symbol(dtype) in (:Float32, :TensorFloat32, :TF32)
            max_peakflops *= 2 * 4 * 8 * 4
        elseif Symbol(dtype) == :Float64
            max_peakflops *= 2 * 4 * 2 * 2
        elseif Symbol(dtype) == :Int8
            max_peakflops *= 2 * 2 * 8 * 8 * 4
        else
            throw(ArgumentError("Unsupported dtype."))
        end
    elseif devtype == :V100
        if Symbol(dtype) == :Float16
            max_peakflops *= 2 * 4 * 4 * 4
        else
            throw(ArgumentError("Unsupported dtype."))
        end
    end
    return max_peakflops
end

# ------------------------- Empirical -------------------------
"""
    peakflops_gpu(; tensorcores=hastensorcores(), kwargs...)
Tries to estimate the peak performance of a GPU in TFLOP/s by measuring the time
it takes to perform

* `_kernel_fma_nfmas() * size` many FMAs on CUDA cores (if `tensorcores == false`)
* `_kernel_wmma_nwmmas()` many WMMAs on Tensor Cores (if `tensorcores == true`)

For more keyword argument options see [`peakflops_gpu_fmas`](@ref) and [`peakflops_gpu_wmmas`](@ref).
"""
function peakflops_gpu(;
    tensorcores=hastensorcores(),
    verbose=true,
    dtype=tensorcores ? Float16 : Float32,
    io::IO=stdout,
    kwargs...,
)
    if tensorcores
        flops = peakflops_gpu_wmmas(; verbose=false, dtype, kwargs...)
    else
        flops = peakflops_gpu_fmas(; verbose=false, dtype, kwargs...)
    end
    if verbose
        printstyled(
            io, "Peakflops ($(Symbol(dtype) == :Int8 ? "TOP" : "TFLOP")/s):\n"; bold=true
        )
        if hastensorcores()
            print(io, " ├ tensorcores: ")
            printstyled(io, tensorcores, "\n"; color=:magenta, bold=true)
        end
        print(io, " ├ dtype: ")
        printstyled(io, Symbol(dtype), "\n"; color=:yellow, bold=true)
        print(io, " └ max: ")
        printstyled(io, round(flops; digits=1), "\n"; color=:green, bold=true)
    end
    return flops
end


using JuliaWeBWorK
using Plots

## --------------------------------------------------

meta=(Project="Plotting  questions",
     Question="3"
      )

qs = JuliaWeBWorK.QUESTIONS()

intro = jmt"""

[Details for students](https://webwork.maa.org/wiki/Student_Information)

----

# Including plots

This shows  how  fixed and randomized plots can be included; it also shows how to use [JSXGraph](https://jsxgraph.uni-bayreuth.de/) to create dynamic graphics (using JavaScript commands).

For randomized plots, as each  *possible*  plot is precomputed and serialized,
this should not be  used  over large event spaces.

"""

## --------------------------------------------------

## fixed plot, using string interpolation
## This is the simplest and suggested way to include a plot
p = plot(sinpi, -1/2,  5/2)
plot!(p, zero, -1/2, 5/2)
numericq(jmt"""

# Include using `(Plot(p))`

![Figure]($(Plot(p)))

(1) In the figure above, what are the zeros of the function?
""",
         () -> List((0,1,2)),
         ()
         ) |> qs

## reading a plt in from a file, `pngfile`
## here we create `pngfile` and fill it with an image; this isn't generally needed
pngfile = tempname() * ".png"
savefig(p, pngfile)

numericq(jmt"""

----
# Include file through `(File(pngfile))`

![Figure]($(File(pngfile)))

""",()->1,()) |> qs

## Reading a plot from a url
label(jmt"""

----
# Using Markdown to include from a url

![some image](https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Tangent_to_a_curve.svg/200px-Tangent_to_a_curve.svg.png)
""") |> qs

## Randomized plot for students using the `plot` "question" type (in quotes
## as `plotq` only prings a randomized plot, the question comes after, hence
## the use of a `randomizer` to share the randomization.

r = randomizer(2:3) |> qs

label(jmt"""
----
# Randomized plots

Plot of a periodic function
""") |> qs

plotq("", (a) -> Plot(plot(sin, 0,2a*pi)), r) |> qs

numericq("(2) How many periods of the periodic function are shown in the above figure?", (a)  -> a, r) |> qs

## This shows use of [jsxgraph](https://jsxgraph.uni-bayreuth.de/) graphics for
## interactive graphics. This is not as integrated into WeBWorK as GeoGebra, but
## are easy enough to author and quite illustrative.
## To load from a file, the `JuliaWeBWorK.INCLUDE` is available to manage relative directories

label(jmt"""
----
# Using jsxgraph for interactive plots
""") |> qs

jsxgraph("""
var brd = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-10, 10, 10, -10]});
var a = brd.create('point', [-2, 1]);
var b = brd.create('point', [-4, -5]);
var c = brd.create('point', [3, -6]);
var d = brd.create('point', [2, 3]);
var p = brd.create('polygon', [a, b, c, d], {hasInnerPoints: true});
"""; domid="jxgbox") |> qs

p = Page(intro, qs; meta...)


# ---
# title: 453. Minimum Moves to Equal Array Elements
# id: problem453
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Math
# link: <https://leetcode.com/problems/minimum-moves-to-equal-array-elements/description/>
# hidden: true
# ---
#
# Given a **non-empty** integer array of size _n_ , find the minimum number of
# moves required to make all array elements equal, where a move is incrementing
# _n_ \- 1 elements by 1.
#
# **Example:**
#
#
#
#     Input:
#     [1,2,3]
#
#     Output:
#     3
#
#     Explanation:
#     Only three moves are needed (remember each move increments two elements):
#
#     [1,2,3]  =>  [2,3,3]  =>  [3,4,3]  =>  [4,4,4]
#
#
#
## @lc code=start
using LeetCode

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


g = IntAdjacencyList(4,3)
add_edge!(g, 1, 2, 1)
add_edge!(g, 2, 3, 1)
add_edge!(g, 1, 4, 2)

@test num_edges(g) == 3

@test num_timestamps(g) == 3

@test num_nodes(g) == 4

@test forward_neighbors(g, 1, 1) == [(2,1), (1,2)]

@test forward_neighbors(g, 2, 1) == [(3,1)]

@test adjacency_matrix(g, 1) ≈ [0 1 0 0; 0 0 1 0; 0 0 0 0; 0 0 0 0]

@test adjacency_matrix(g, 2) ≈ [0 0 0 1; 0 0 0 0; 0 0 0 0; 0 0 0 0]

@test full(sparse_adjacency_matrix(g, 1)) ≈ adjacency_matrix(g, 1)
@test full(sparse_adjacency_matrix(g, 2)) ≈ adjacency_matrix(g, 2)

add_edge!(g, 2, 4, 3)

@test num_edges(g) == 4

@test forward_neighbors(g, 2, 1) == [(3,1), (2,3)]

@test backward_neighbors(g, 4, 3) == [(4,2), (2,3)]

A = block_adjacency_matrix(g)

@test full(A)[1:4,1:4] ≈ adjacency_matrix(g, 1)
@test full(A)[5:8,5:8] ≈ adjacency_matrix(g, 2)
@test A[1, 5] ≈ 1.
@test A[2, 10] ≈ 0.5
@test A[8, 12] ≈ 1.


##################################################
## Classical derivative-based, iterative, root-finding algorithms
##
## If ri = f^(i-1)/f^(i), then these have an update step `xn - delta` where:
##
## * Newton: delta = r1  # order 2 if simple root (multiplicity 1)
## * Halley: delta = 2*r2/(2r2 - r1) * r1 # order 3 if simple root
## * Schroder: delta = r2  / (r2 - r1) * r1  # order 2
## * Thukral(3): delta =  (-2*r3)*(r2 - r1)/(r1^2 - 3*r1*r3 + 2*r2*r3) * r1 # order 3
## * Thukral(4): delta =  3*r1*r2*r4*(r1^2 - 3*r1*r3 + 2*r2*r3)/(-r1^3*r2 + 4*r1^2*r2*r4 + 3*r1^2*r3*r4 - 12*r1*r2*r3*r4 + 6*r2^2*r3*r4) # order 4
##
## The latter two come from
## [Thukral](http://article.sapub.org/10.5923.j.ajcam.20170702.05.html). They are not implemented.

## Newton
abstract type AbstractNewtonLikeMethod <: AbstractUnivariateZeroMethod end
struct Newton <: AbstractNewtonLikeMethod end
"""

    Roots.Newton()

Implements Newton's [method](http://tinyurl.com/b4d7vls):
`xᵢ₊₁ =  xᵢ - f(xᵢ)/f'(xᵢ)`.  This is a quadratically convergent method requiring
one derivative. Two function calls per step.

Example
```
find_zero((sin,cos), 3.0, Roots.Newton())
```

If function evaluations are expensive one can pass in a function which returns (f, f/f') as follows

```
find_zero(x -> (sin(x), sin(x)/cos(x)), 3.0, Roots.Newton())
```

This can be advantageous if the derivative is easily computed from the
value of f, but otherwise would be expensive to compute.

The error, `eᵢ = xᵢ - α`, can be expressed as `eᵢ₊₁ = f[xᵢ,xᵢ,α]/(2f[xᵢ,xᵢ])eᵢ²` (Sidi, Unified treatment of regula falsi, Newton-Raphson, secant, and Steffensen methods for nonlinear equations).

"""
Newton

function init_state(method::AbstractNewtonLikeMethod, fs, x)

    x1 = float(x)
    T = eltype(x1)
    tmp = fΔx(fs, x1)
    fx1, Δ::T = tmp[1], tmp[2] # faster to pass in fx, fx/f'(x) than (fx, f'(x)) and compute

    fnevals = 1
    S = eltype(fx1)

    zT, zS = oneunit(x1) * (0*x1)/(0*x1), oneunit(fx1) * (0*fx1)/(0*fx1)
    state = UnivariateZeroState(x1, zT, zT/zT*oneunit(x1), [Δ],
                                fx1, zS, zS, S[],
                                0, fnevals,
                                false, false, false, false,
                                "")
    state
end

function init_state!(state::UnivariateZeroState{T,S}, M::Newton, fs, x) where {T,S}
    x1::T = float(x)
    tmp = fΔx(fs, x)
    fx1::S, Δ::T = tmp[1], tmp[2]

     init_state!(state, x1, oneunit(x1) * (0*x1)/(0*x1), [Δ],
                fx1, oneunit(fx1) * (0*fx1)/(0*fx1), S[])
end

function update_state(method::Newton, fs, o::UnivariateZeroState{T,S}, options) where {T, S}
    xn = o.xn1
    fxn = o.fxn1
    r1 = o.m[1]

    if isissue(r1)
        o.stopped=true
        return
    end

    xn1 = xn - r1

    tmp = fΔx(fs, xn1)
    fxn1::S, r1::T = tmp[1], tmp[2]
    incfn(o,2)

    o.xn0, o.xn1 = xn, xn1
    o.fxn0, o.fxn1 = fxn, fxn1
    empty!(o.m); push!(o.m, r1)

    nothing

end

"""
    Roots.newton(f, fp, x0; kwargs...)

Implementation of Newton's method: `x_n1 = x_n - f(x_n)/ f'(x_n)`

Arguments:

* `f::Function` -- function to find zero of

* `fp::Function` -- the derivative of `f`.

* `x0::Number` -- initial guess. For Newton's method this may be complex.

With the `FowardDiff` package derivatives may be computed automatically. For example,  defining
`D(f) = x -> ForwardDiff.derivative(f, float(x))` allows `D(f)` to be used for the first derivative.

Keyword arguments are passed to `find_zero` using the `Roots.Newton()` method.

See also `Roots.newton((f,fp), x0) and `Roots.newton(fΔf, x0)` for simpler implementations.

"""
newton(f, fp, x0; kwargs...) = find_zero((f, fp), x0, Newton(); kwargs...)

## Halley
abstract type AbstractHalleyLikeMethod <: AbstractUnivariateZeroMethod end

"""
    Roots.Halley()

Implements Halley's [method](http://tinyurl.com/yd83eytb),
`xᵢ₊₁ = xᵢ - (f/f')(xᵢ) * (1 - (f/f')(xᵢ) * (f''/f')(xᵢ) * 1/2)^(-1)`
This method is cubically converging, but requires more function calls per step (3) than
other methods.

Example
```
find_zero((sin, cos, x->-sin(x)), 3.0, Roots.Halley())
```

If function evaluations are expensive one can pass in a function which
returns (f, f/f',f'/f'') as follows

```
find_zero(x -> (sin(x), sin(x)/cos(x), -cos(x)/sin(x)), 3.0, Roots.Halley())
```

This can be advantageous if the derivatives are easily computed from
the value of f, but otherwise would be expensive to compute.

The error, `eᵢ = xᵢ - α`, satisfies
`eᵢ₊₁ ≈ -(2f'⋅f''' -3⋅(f'')²)/(12⋅(f'')²) ⋅ eᵢ³` (all evaluated at `α`).

"""
struct Halley <: AbstractHalleyLikeMethod
end

function init_state(method::AbstractHalleyLikeMethod, fs, x)

    x1 = float(x)
    T = eltype(x1)
    tmp = fΔxΔΔx(fs, x1)
    fx1, Δ::T, ΔΔ::T = tmp[1], tmp[2], tmp[3]
    S = eltype(fx1)
    fnevals = 3

    zT, zS =  oneunit(x1) * (0*x1)/(0*x1), oneunit(fx1) * (0*fx1)/(0*fx1)
    state = UnivariateZeroState(x1, zT, zT/zT*oneunit(x1), [Δ,ΔΔ],
                                fx1, zS, zS, S[],
                                0, fnevals,
                                false, false, false, false,
                                "")
    state
end

function init_state!(state::UnivariateZeroState{T,S}, M::AbstractHalleyLikeMethod, fs, x) where {T,S}
    x1::T = float(x)
    tmp = fΔxΔΔx(fs, x)
    fx1::S, Δ::T, ΔΔ::T = tmp[1], tmp[2], tmp[3]

     init_state!(state, x1, oneunit(x1) * (0*x1)/(0*x1), [Δ, ΔΔ],
                fx1, oneunit(fx1) * (0*fx1)/(0*fx1), S[])
end

function update_state(method::Halley, fs, o::UnivariateZeroState{T,S}, options::UnivariateZeroOptions) where {T,S}
    xn = o.xn1
    fxn = o.fxn1
    r1, r2 = o.m

    xn1::T = xn - 2*r2/(2r2 - r1) * r1

    tmp = fΔxΔΔx(fs, xn1)
    fxn1::S, r1::T, r2::T = tmp[1], tmp[2], tmp[3]
    incfn(o,3)

    o.xn0, o.xn1 = xn, xn1
    o.fxn0, o.fxn1 = fxn, fxn1
    empty!(o.m); append!(o.m, (r1, r2))
end

"""
    Roots.halley(f, fp, fpp, x0; kwargs...)

Implementation of Halley's method (cf `?Roots.Halley()`).

Arguments:

* `f::Function` -- function to find zero of

* `fp::Function` -- derivative of `f`.

* `fpp:Function` -- second derivative of `f`.

* `x0::Number` -- initial guess

With the `FowardDiff` package derivatives may be computed automatically. For example,  defining
`D(f) = x -> ForwardDiff.derivative(f, float(x))` allows `D(f)` and `D(D(f))` to be used for the first and second
derivatives, respectively.

Keyword arguments are passed to `find_zero` using the `Roots.Halley()` method.

"""
halley(f, fp, fpp, x0; kwargs...) = find_zero((f, fp, fpp), x0, Halley(); kwargs...)
#halley(f,  x0; kwargs...) = find_zero(f, x0, Halley(); kwargs...) # deprecated
#halley(f, fp, x0; kwargs...) = find_zero((f, fp), x0, Halley(); kwargs...) # deprecated
@deprecate halley(f,  x0; kwargs...)    halley(f, fp, fpp, x0; kwargs...)
@deprecate halley(f, fp, x0; kwargs...) halley(f, fp, fpp, x0; kwargs...)

## Shroder-like methods

"""
    Roots.Schroder()

Schröder's method, like Halley's method, utilizes `f`, `f'`, and
`f''`. Unlike Halley it is quadratically converging, but this is
independent of the multiplicity of the zero (cf. Schröder, E. "Über
unendlich viele Algorithmen zur Auflösung der Gleichungen."
Math. Ann. 2, 317-365, 1870;
[mathworld](http://mathworld.wolfram.com/SchroedersMethod.html)). Schröder's
method applies Newton's method to `f/f'`, a function with all
simple zeros.

Example
```
m = 2
f(x) = (cos(x)-x)^m
fp(x) = (-x + cos(x))*(-2*sin(x) - 2)
fpp(x) = 2*((x - cos(x))*cos(x) + (sin(x) + 1)^2)
find_zero((f, fp, fpp), pi/4, Roots.Halley())    # 14 steps
find_zero((f, fp, fpp), 1.0, Roots.Schroder())    # 3 steps
```

(Whereas, when `m=1`, Halley is 2 steps to Schröder's 3.)

If function evaluations are expensive one can pass in a function which
returns `(f, f/f',f'/f'')` as follows

```
find_zero(x -> (sin(x), sin(x)/cos(x), -cos(x)/sin(x)), 3.0, Roots.Schroder())
```

This can be advantageous if the derivatives are easily computed from
the value of `f`, but otherwise would be expensive to compute.

The error, `eᵢ = xᵢ - α`, is the same as `Newton` with `f` replaced by `f/f'`.

"""
struct Schroder <: AbstractHalleyLikeMethod
end
const Schroeder = Schroder # either spelling
const Schröder = Schroder

function update_state(method::Schroder, fs, o::UnivariateZeroState{T,S}, options::UnivariateZeroOptions) where {T,S}
    xn = o.xn1
    fxn = o.fxn1
    r1, r2 = o.m[1], o.m[2]

    delta =  r2 / (r2 - r1) * r1  # m * r1

    if isissue(delta)
        o.stopped=true
        return
    end

    xn1::T = xn - delta

    tmp = fΔxΔΔx(fs, xn1)
    fxn1::S, r1::T, r2::T = tmp[1], tmp[2], tmp[3]
    incfn(o,3)

    o.xn0, o.xn1 = xn, xn1
    o.fxn0, o.fxn1 = fxn, fxn1
    o.m[1], o.m[2] = r1, r2
end


export find_path, dist

"""
`find_path(G::ImplicitGraph,s,t)` finds a shortest path from `s` to `t`.
"""
function find_path(G::ImplicitGraph{T}, s::T, t::T) where {T}

    if !has(G, s) || !has(G, t)
        error("Source and/or target vertex is not in this graph")
    end
    if s == t
        return [s]
    end

    # set up a queue for vertex exploration
    Q = Queue{T}()
    enqueue!(Q, s)

    # set up trace-back dictionary
    tracer = Dict{T,T}()
    tracer[s] = s

    while length(Q) > 0
        v = dequeue!(Q)
        Nv = G[v]
        for w in Nv
            if haskey(tracer, w)
                continue
            end
            tracer[w] = v
            enqueue!(Q, w)

            if w == t  # success!
                path = Array{T}(undef, 1)
                path[1] = t
                while path[1] != s
                    v = tracer[path[1]]
                    pushfirst!(path, v)
                end
                return path

            end
        end
    end
    return T[]   # return empty array if no path found
end

dist(G::ImplicitGraph{T}, s::T, t::T) where {T} = length(find_path(G, s, t)) - 1


using JuMP
import Gurobi

M = 6
N = 3

# Build model indication, and set the solver.
m = Model(solver = Gurobi.GurobiSolver(Method=2))

# Set Variables
@variable(m, x[1:N] >= 0)
@variable(m, y[1:M] >= 0)

# Set Parameters
Xind = [1 2 3]
yind = [2 3 4 5 6 7]

# define objective
@objective(m, Min,(Xind * x + yind * y)[1])

Xind1 = [2 -1 5]
Yind1 = [9 2 -3 1 -7 12]

@constraint(m, (Xind1 * x + Yind1 * y)[1] <= 6)

Yind2 = [9 8 7 9 7 6]

Xind2 = [3 2 1; 2 5 7; 9 -1 4]
Zind2 = [4 3 2]'

@constraint(m, Xind2 * x .>= Zind2)

status = solve(m)

print(getvalue(x))
print(getvalue(y))


### routines for checking proximity or intervals for floating-point numbers.

function isnumericallyclose(x::T, y::T, tol = eps(T)*2) where T
    if abs(x-y) < tol
        return true
    end

    return false
end

function isnumericallyin(x::T, a::T, b::T, tol = eps(x)*2)::Bool where T
    if x > a-tol && x < b+tol
        return true
    end

    return false
end

# remove points that are too close to the entry before it.
# also sorts the collect A.
function makenotclose!(A, tol)::Nothing
    remove_list = Vector{Int}(undef,0)
    sort!(A)

    for i = 2:length(A)
        if isnumericallyclose(A[i-1], A[i], tol)
            push!(remove_list, i)
        end
    end

    deleteat!(A,remove_list)

    return nothing
end


@testset "Sparse logistic small ($T)" for T in [Float32, Float64]
    using ProximalOperators
    using ProximalAlgorithms
    using LinearAlgebra

    A = T[
        1.0 -2.0 3.0 -4.0 5.0
        2.0 -1.0 0.0 -1.0 3.0
        -1.0 0.0 4.0 -3.0 2.0
        -1.0 -1.0 -1.0 1.0 3.0
    ]
    b = T[1.0, 2.0, 3.0, 4.0]

    m, n = size(A)

    R = real(T)

    f = Translate(LogisticLoss(ones(R, m), R(1)), -b)
    lam = R(0.1)
    g = NormL1(lam)

    x_star = T[0, 0, 2.114635341704963e-01, 0, 2.845881348733116e+00]

    TOL = R(1e-6)

    # Nonfast/Adaptive

    x0 = zeros(T, n)
    x0_backup = copy(x0)
    solver = ProximalAlgorithms.ForwardBackward(tol = TOL, adaptive = true, fast = false)
    x, it = solver(x0, f = f, A = A, g = g)
    @test eltype(x) == T
    @test norm(x - x_star, Inf) <= 1e-4
    @test it < 1100
    @test x0 == x0_backup

    # Fast/Adaptive

    x0 = zeros(T, n)
    x0_backup = copy(x0)
    solver = ProximalAlgorithms.ForwardBackward(tol = TOL, adaptive = true, fast = true)
    x, it = solver(x0, f = f, A = A, g = g)
    @test eltype(x) == T
    @test norm(x - x_star, Inf) <= 1e-4
    @test it < 500
    @test x0 == x0_backup

    # ZeroFPR/Adaptive

    x0 = zeros(T, n)
    x0_backup = copy(x0)
    solver = ProximalAlgorithms.ZeroFPR(adaptive = true, tol = TOL)
    x, it = solver(x0, f = f, A = A, g = g)
    @test eltype(x) == T
    @test norm(x - x_star, Inf) <= 1e-4
    @test it < 25
    @test x0 == x0_backup

    # PANOC/Adaptive

    x0 = zeros(T, n)
    x0_backup = copy(x0)
    solver = ProximalAlgorithms.PANOC(adaptive = true, tol = TOL)
    x, it = solver(x0, f = f, A = A, g = g)
    @test eltype(x) == T
    @test norm(x - x_star, Inf) <= 1e-4
    @test it < 50
    @test x0 == x0_backup

end


using IncDataStats
using Base.Test

grb = GroupBy([:a, :b, :c, :d], [:a, :c])
update!(grb, [1, 2, 3, 4])
update!(grb, [1, 3.2, 3, 7])
update!(grb, [2, 2, 1, 4])
update!(grb, [2, 2, 7, 4])

@test mean(grb, :a) == 1.5
@test mean(grb, :b) == 2.3
@test sum(grb, :c) == 14
@test count(grb, :d) == 4

bins, h = hist(grb, :c)
@test bins == [1.0,3.0,7.0]
@test h == [1,2,1]

@test quantile(grb, :c, .25) == 1
@test quantile(grb, :c, .5) == 3
@test quantile(grb, :c, .75) == 3
@test quantile(grb, :c, .9) == 7

# groupers test

grb = GroupBy([:a, :b, :c, :d], [:a => identity, :c => stepgrouper(3)])
update!(grb, [1, 2, 3, 4])
update!(grb, [1, 3.2, 3, 7])
update!(grb, [2, 2, 1, 4])
update!(grb, [2, 2, 7, 4])


test_gage_principal_axes(
    Array[
     [1., 0., 0.],
     [0., 1., 0.],
     [1., 1., 0.]],
    [sqrt(2)/2. -sqrt(2)/2. 0.;
     sqrt(2)/2.  sqrt(2)/2. 0.;
     0.          0.         1.], "test using array of 3 vectors as input")


import ViscousFlow: Fields, TimeMarching
import ViscousFlow.TimeMarching:RK31

using LinearAlgebra

@testset "Time Marching" begin

  @testset "Basic Unconstrained" begin

  ω = 4
  u₀ = 1.0
  uex(t) = u₀ + sin(ω*t)/ω

  Δt = 0.005
  T = 0:Δt:10
  u = [u₀]
  TimeMarching.r₁(u::Vector{Float64},t::Float64) = cos(ω*t)
  rk = RK(u,Δt,r₁,rk=TimeMarching.RK31)

  u = [u₀]
  uhist = Float64[]
  for t in T
    push!(uhist,u[1])
    t,u = rk(t,u)
  end

  @test norm(uhist-uex.(T)) ≈ 0.000494857145025

  end

  @testset "Unconstrained with integrating factor" begin

  α = 0.5
  ω = 4
  u₀ = 1.0
  uex(t) = u₀*exp(-α*t) + (α*(cos(ω*t)-exp(-α*t))+ω*sin(ω*t))/(α^2+ω^2)

  Fields.plan_intfact(t::Float64,u::Vector{Float64}) = exp(-α*t)

  Δt = 0.005
  T = 0:Δt:10
  u = [u₀]
  TimeMarching.r₁(u::Vector{Float64},t::Float64) = cos(ω*t)
  ifrk = IFRK(u,Δt,plan_intfact,r₁,rk=TimeMarching.RK31)

  u = [u₀]
  uhist = Float64[]
  for t in T
    push!(uhist,u[1])
    t,u = ifrk(t,u)
  end
  @test norm(uhist-uex.(T)) ≈ 0.0005014844449

  end

  @testset "Basic unconstrained using IFHERK" begin

  ω = 4
  u₀ = 1.0
  uex(t) = u₀ + sin(ω*t)/ω

  Δt = 0.005
  T = 0:Δt:10
  u = [u₀]
  f = Vector{Float64}()
  TimeMarching.r₁(u::Vector{Float64},t::Float64) = cos(ω*t)
  TimeMarching.r₂(u::Vector{Float64},t::Float64) = Vector{Float64}()
  TimeMarching.plan_constraints(u::Vector{Float64},t::Float64) = f -> zeros(Float64,1), u -> Vector{Float64}()
  Fields.plan_intfact(t::Float64,u::Vector{Float64}) = Matrix(1.0I,1,1)
  ifherk = IFHERK(u,f,Δt,plan_intfact,plan_constraints,(r₁,r₂),rk=TimeMarching.RK31)

  u = [u₀]
  uhist = Float64[]
  for t in T
    push!(uhist,u[1])
    t,u,_ = ifherk(t,u)
  end

  @test norm(uhist-uex.(T)) ≈ 0.0004948571450253

  end

end


# This file was generated, do not modify it.

using MLJ
using PyPlot
ioff() # hide
MLJ.color_off() # hide
using StableRNGs

linear = @load LinearRegressor pkg=MLJLinearModels
knn = @load KNNRegressor; knn.K = 4
tree_booster = @load EvoTreeRegressor; tree_booster.nrounds = 100
forest = @load RandomForestRegressor pkg=DecisionTree; forest.n_trees = 500
svm = @load SVMRegressor;

X = source()
y = source()

model1 = linear
model2 = knn

m1 = machine(model1, X, y)
y1 = predict(m1, X)

m2 = machine(model2, X, y)
y2 = predict(m2, X)

yhat = 0.5*y1 + 0.5*y2

mach = machine(Deterministic(), X, y; predict=yhat)

@from_network mach begin
    mutable struct MyAverageTwo
        regressor1=model1
        regressor2=model2
    end
end

average_two = MyAverageTwo()

function print_performance(model, data...)
    e = evaluate(model, data...;
                 resampling=CV(rng=StableRNG(1234), nfolds=8),
                 measure=rms,
                 verbosity=0)
    μ = round(e.measurement[1], sigdigits=5)
    ste = round(std(e.per_fold[1])/sqrt(8), digits=5)
    println("$model = $μ ± $(2*ste)")
end;

X, y = @load_boston

print_performance(linear, X, y)
print_performance(knn, X, y)
print_performance(average_two, X, y)

folds(data, nfolds) =
    partition(1:nrows(data), (1/nfolds for i in 1:(nfolds-1))...);

f = folds(1:10, 3)

corestrict(string.(1:10), f, 2)

figure(figsize=(8,6))
steps(x) = x < -3/2 ? -1 : (x < 3/2 ? 0 : 1)
x = Float64[-4, -1, 2, -3, 0, 3, -2, 1, 4]
Xraw = (x = x, )
yraw = steps.(x);
idxsort = sortperm(x)
xsort = x[idxsort]
ysort = yraw[idxsort]
step(xsort, ysort, label="truth", where="mid")
plot(x, yraw, ls="none", marker="o", label="data")
xlim(-4.5, 4.5)
legend()

savefig(joinpath(@OUTPUT, "s1.svg")) # hide

model1 = linear
model2 = knn

judge = linear

X = source(Xraw)
y = source(yraw)

f = @node folds(X, 3)

f()

folds(X::AbstractNode, nfolds) = node(XX->folds(XX, nfolds), X)
f = folds(X, 3)
f()

MLJ.restrict(X::AbstractNode, f::AbstractNode, i) =
    node((XX, ff) -> restrict(XX, ff, i), X, f);
MLJ.corestrict(X::AbstractNode, f::AbstractNode, i) =
    node((XX, ff) -> corestrict(XX, ff, i), X, f);

m11 = machine(model1, corestrict(X, f, 1), corestrict(y, f, 1))
m12 = machine(model1, corestrict(X, f, 2), corestrict(y, f, 2))
m13 = machine(model1, corestrict(X, f, 3), corestrict(y, f, 3))

y11 = predict(m11, restrict(X, f, 1));
y12 = predict(m12, restrict(X, f, 2));
y13 = predict(m13, restrict(X, f, 3));

y1_oos = vcat(y11, y12, y13);

fit!(y1_oos, verbosity=0)

figure(figsize=(8,6))
step(xsort, ysort, label="truth", where="mid")
plot(x, y1_oos(), ls="none", marker="o", label="linear oos")
legend()

savefig(joinpath(@OUTPUT, "s2.svg")) # hide

m21 = machine(model2, corestrict(X, f, 1), corestrict(y, f, 1))
m22 = machine(model2, corestrict(X, f, 2), corestrict(y, f, 2))
m23 = machine(model2, corestrict(X, f, 3), corestrict(y, f, 3))
y21 = predict(m21, restrict(X, f, 1));
y22 = predict(m22, restrict(X, f, 2));
y23 = predict(m23, restrict(X, f, 3));

y2_oos = vcat(y21, y22, y23);
fit!(y2_oos, verbosity=0)

figure(figsize=(8,6))
step(xsort, ysort, label="truth", where="mid")
plot(x, y2_oos(), ls="none", marker="o", label="knn oos")
legend()

savefig(joinpath(@OUTPUT, "s3.svg")) # hide

X_oos = MLJ.table(hcat(y1_oos, y2_oos))
m_judge = machine(judge, X_oos, y)

m1 = machine(model1, X, y)
m2 = machine(model2, X, y)

y1 = predict(m1, X);
y2 = predict(m2, X);
X_judge = MLJ.table(hcat(y1, y2))
yhat = predict(m_judge, X_judge)

fit!(yhat, verbosity=0)

figure(figsize=(8,6))
step(xsort, ysort, label="truth", where="mid")
plot(x, yhat(), ls="none", marker="o", label="yhat")
legend()

savefig(joinpath(@OUTPUT, "s4.svg")) # hide

e1 = rms(y1(), y())
e2 = rms(y2(), y())
emean = rms(0.5*y1() + 0.5*y2(), y())
estack = rms(yhat(), y())
@show e1 e2 emean estack;

@from_network machine(Deterministic(), X, y; predict=yhat) begin
    mutable struct MyTwoModelStack
        regressor1=model1
        regressor2=model2
        judge=judge
    end
end

my_two_model_stack = MyTwoModelStack()

X, y = make_regression(1000, 20; sparse=0.75, noise=0.1, rng=123);

avg = MyAverageTwo(regressor1=tree_booster,
                   regressor2=svm)

stack = MyTwoModelStack(regressor1=tree_booster,
                        regressor2=svm,
                        judge=forest) # judge=linear

all_models = [tree_booster, svm, forest, avg, stack];

for model in all_models
    print_performance(model, X, y)
end

r = range(stack, :(regressor2.C), lower = 0.01, upper = 10, scale=:log)
tuned_stack = TunedModel(model=stack,
                         ranges=r,
                         tuning=Grid(shuffle=false),
                         measure=rms,
                         resampling=Holdout())

mach = fit!(machine(tuned_stack,  X, y), verbosity=0)
best_stack = fitted_params(mach).best_model
best_stack.regressor2.C

print_performance(best_stack, X, y)

PyPlot.close_figs() # hide