# This file is a part of InverseFunctions.jl, licensed under the MIT License (MIT).

using InverseFunctions
using Test

foo(x) = inv(exp(-x) + 1)
inv_foo(y) = log(y / (1 - y))

InverseFunctions.inverse(::typeof(foo)) = inv_foo
InverseFunctions.inverse(::typeof(inv_foo)) = foo

@testset "inverse" begin
    @test inverse(inverse)(inverse(log)) == log

    x = 4.2
    X = rand(10)
    A = rand(5, 5)

    @test @inferred(inverse(foo)(foo(x))) ≈ x
    @test @inferred(inverse(inverse(foo))) == foo

    @static if VERSION >= v"1.6"
        log_foo = log ∘ foo
        @test inverse(log_foo)(log_foo(x)) ≈ x
        @test @inferred(inverse(log_foo)) == inv_foo ∘ exp
    end

    broadcasted_foo = Base.Fix1(broadcast, foo)
    @test @inferred(inverse(inverse(broadcasted_foo))) == broadcasted_foo
    @test @inferred(inverse(broadcasted_foo)(broadcasted_foo(x))) ≈ x
    @test @inferred(inverse(broadcasted_foo)(broadcasted_foo(fill(x)))) ≈ x
    @test @inferred(inverse(broadcasted_foo)(broadcasted_foo(Ref(x)))) ≈ x
    @test @inferred(inverse(broadcasted_foo)(broadcasted_foo((x,))))[1] ≈ x
    @test @inferred(inverse(broadcasted_foo)(broadcasted_foo(X))) ≈ X

    mapped_foo = Base.Fix1(map, foo)
    @test @inferred(inverse(inverse(mapped_foo))) == mapped_foo
    @test @inferred(inverse(mapped_foo)(mapped_foo(x))) ≈ x
    @test @inferred(inverse(mapped_foo)(mapped_foo(fill(x)))) ≈ fill(x)
    @test @inferred(inverse(mapped_foo)(mapped_foo(Ref(x)))) ≈ fill(x)
    @test @inferred(inverse(mapped_foo)(mapped_foo((x,))))[1] ≈ x
    @test @inferred(inverse(mapped_foo)(mapped_foo(X))) ≈ X

    mapped_foo = Base.Fix1(broadcast, foo)
    Y = mapped_foo(X)
    @test @inferred(inverse(mapped_foo)(Y)) ≈ X

    for f in (identity, inv, adjoint, transpose)
        @test @inferred(inverse(f)(f(A))) ≈ A
        @test @inferred(inverse(f)) == f
    end

    for f in (exp, log, exp2, log2, exp10, log10, expm1, log1p)
        @test @inferred(inverse(f)(f(x))) ≈ x
        @test @inferred(inverse(inverse(f))) == f
    end
end


"""
Create linear grayscale gradient image of type `Gray`.
"""
function gradient_image(height, width)
    row = reshape(range(0; stop=1, length=width), 1, width)
    grad = Gray.(vcat(repeat(row, height))) # Linear gradient
    img = srgb2linear.(grad) # For printing, compensate for SRGB colorspace
    return grad, img
end

"""
Test dithering algorithm `alg` on linear grayscale gradient from `gradient_image`
and show stacked plot.
"""
function test_on_gradient(alg::AbstractDither)
    srgb, linear = gradient_image(100, 800)
    dither_srgb = dither(srgb, alg)
    dither_linear = dither(linear, alg)

    return mosaicview([srgb, dither_srgb, linear, dither_linear]; ncol=1)
end


function hess_approx(h, L, D, η; ϵ = sqrt(eps()))
    n = size(D)[1]
    θ = Vector{Float64}(undef, n)
    ω = Vector{Float64}(undef, n)
    ν = Vector{Float64}(undef, n)

    θ[n] = 0
    ν[1] = h[1, 1]

    # ξ = maximum(abs.(LDLt.L))
    ξ = maximum(abs.(UnitLowerTriangular(h)- Matrix(Diagonal(ones(n)))))
    γ = maximum(abs.(diag(h)))
    β² = max(ϵ, γ, ξ/n)
    β = sqrt(β²)
    # println("ξ/n = $(ξ/n) γ = $γ β = $β")

    ω[1] = max(β, abs(ν[1]), (θ[1]/β)^2)

    good_approx = true; j = 1

    # println("avant le while")

    while (good_approx) && (j <= n)
        # println("dans le while j = $j")
        if j > 1
            ν[j] = h[j, j] - sum(L[j, s]^2 * ω[s] for s = 1:j-1)
        end
        # println("ν[j] = $(ν[j])")

        for i=j+1:n
            # println("dans le for i = $i")
            # println(" θ[j] = $(θ[j])")
            # println(" maximum( abs.( h[i,j])) = $(maximum( abs.( h[i,j])))")
            if j > 1
                # println("on est dans le if qui fait la somme")
                sum_ljs_lis = sum(L[j, s] * L[i, s] * ω[s] for s=1:j-1)
                # println("sum_ljs_lis = $sum_ljs_lis")
            else
                # println("on est dans le if qui ne fait pas la somme")
                sum_ljs_lis = 0.0
                # println("sum_ljs_lis = $sum_ljs_lis")
            end
            θ[j] = max(θ[j],maximum( abs.( h[i,j] .- sum_ljs_lis)))
        end

        if (θ[j]/β)^2 >= η * ν[j]
            good_approx = false
        else
            good_approx = true
        end
        # println("j = $j good_approx = $good_approx")
        # println("ν[j] = $(ν[j]) θ[j] = $(θ[j])")
        j += 1
    end

    return good_approx
end


using Logging

logger = Logging.SimpleLogger(stdout, Logging.Info)
global_logger(logger)

include("../src/ExactODEReduction.jl")
using .ExactODEReduction: load_ODEs, construct_jacobians, many_invariant_subspaces, polynormalize, invariant_subspace_1

# e2.ode
fname = "./data/ODEs/testing/fceri_ji/fceri_ji.ode"

equations = load_ODEs(fname)
As = construct_jacobians(equations)

@info "Loaded system of dimension: " size(As[1])

Vs = many_invariant_subspaces(As, backend_algorithm=invariant_subspace_1)
@info "The number of reductions is $(length(Vs))"

println(equations)

for v in Vs
    @info "-----------------------"
    polyform = polynormalize(v, parent(first(keys(equations))))
    @info "Dimenson $(length(polyform))"
    for p in polyform
        @info p
    end
end


using BenchmarkTools, TaylorSeries

const SUITE = BenchmarkGroup()

S = SUITE["Constructors"] = BenchmarkGroup()
for i in (05,10,20,50)
    x = rand(i)
    t = tuple(x...)
    S["STaylor1(x::Array[len = $i])"] = @benchmarkable STaylor1($x)
    S["STaylor1(x::Float64, Val($i))"] = @benchmarkable STaylor1($t)
end

function f(g, x, y, n)
    t = x
    for i=1:n
        t = g(x,y)
    end
    t
end
function f(g, x, n)
    t = x
    for i=1:n
        t = g(t)
    end
    t
end
n = 100
dims = (5,20)

S = SUITE["arithmetic"] = BenchmarkGroup()
for i in dims
    r = rand(i)
    x = Taylor1(r)
    x2 = Taylor1(r)
    q = STaylor1(r)
    q2 = STaylor1(r)
    y = rand()
    for g in (+, -, /, *)
        S["Taylor1{$i,Float64} $(Symbol(g)) Float64"] = @benchmarkable f($g, $x, $y, $n)
        S["STaylor1{$i,Float64} $(Symbol(g)) Float64"] = @benchmarkable f($g, $q, $y, $n)
        S["Taylor1{$i,Float64} $(Symbol(g)) Taylor1{$i,Float64}"] = @benchmarkable f($g, $x, $x2, $n)
        S["STaylor1{$i,Float64} $(Symbol(g)) STaylor1{$i,Float64}"] = @benchmarkable f($g, $q, $q2, $n)
    end
end

S = SUITE["functions"] = BenchmarkGroup()
for i in dims
    r = rand(i)
    x = Taylor1(r)
    q = STaylor1(r)
    y = rand()
    for g in (exp, abs, zero, one, real, imag, conj, adjoint, iszero, isnan,
              isinf, deg2rad, rad2deg)
        S["$(Symbol(g))(Taylor1{Float64}), len = $i"] = @benchmarkable f($g, $x, $n)
        S["$(Symbol(g))(STaylor1{$i,Float64}),"] = @benchmarkable f($g, $q, $n)
    end
end


### ndft functions ###

function ndft(plan::NFFTPlan{D}, f::AbstractArray{T,D}) where {D,T}
    plan.N == size(f) || throw(DimensionMismatch("Data is not consistent with NFFTPlan"))

    g = zeros(T, plan.M)

    for l=1:prod(plan.N)
        idx = CartesianIndices(plan.N)[l]

        for k=1:plan.M
            arg = zero(T)
            for d=1:D
                arg += plan.x[d,k] * ( idx[d] - 1 - plan.N[d] / 2 )
            end
            g[k] += f[l] * cis(-2*pi*arg)
        end
    end

    return g
end

function ndft(x, f::AbstractArray{T,D}, rest...; kwargs...) where {D,T}
    p = NFFTPlan(x, size(f), rest...; kwargs...)
    return ndft(p, f)
end

function ndft_adjoint(plan::NFFTPlan{D}, fHat::AbstractArray{T,1}) where {D,T}
    plan.M == length(fHat) || throw(DimensionMismatch("Data is not consistent with NFFTPlan"))

    g = zeros(T, plan.N)

    for l=1:prod(plan.N)
        idx = CartesianIndices(plan.N)[l]

        for k=1:plan.M
            arg = zero(T)
            for d=1:D
                arg += plan.x[d,k] * ( idx[d] - 1 - plan.N[d] / 2 )
            end
            g[l] += fHat[k] * cis(2*pi*arg)
        end
    end

    return g
end

function ndft_adjoint(x, N, fHat::AbstractVector{T}, rest...; kwargs...) where T
    p = NFFTPlan(x, N, rest...; kwargs...)
    return ndft_adjoint(p, fHat)
end


# Scale-Location Student-t Stein Distribution
#
# Represents a mu + sigma * X where X is a univariate t-distribution with nu
# degrees of freedom
using Distributions: TDist
import Distributions

type SteinScaleLocationStudentT <: SteinDistribution
    nu::Float64
    mu::Float64
    sigma::Float64
    c1::Float64
    c2::Float64
    c3::Float64
end

# Constructor with canonical setting of (c1,c2,c3) = (1,1,1)
SteinScaleLocationStudentT(nu::Float64, mu::Float64, sigma::Float64) =
SteinScaleLocationStudentT(nu, mu, sigma, 1.0, 1.0, 1.0);

# Constructor for standard t-distribution with canonical constants c1, c2, c3
SteinScaleLocationStudentT(nu::Float64) =
SteinScaleLocationStudentT(nu, 0.0, 1.0, 1.0, 1.0, 1.0);

# Draw n independent samples from distribution
function rand(d::SteinScaleLocationStudentT, n::Int64)
    d.mu + d.sigma * Distributions.rand(TDist(d.nu), n)'';
end

function supportlowerbound(d::SteinScaleLocationStudentT, j::Int64)
    -Inf
end

function supportupperbound(d::SteinScaleLocationStudentT, j::Int64)
    Inf
end

# Gradient of log density of distribution evaluated at each
# entry of n x 1 matrix X
function gradlogdensity(d::SteinScaleLocationStudentT, X::Array{Float64,2})
    -(d.nu + 1) * (X - d.mu) ./ (d.nu * d.sigma^2 + (X - d.mu).^2);
end

# Cumulative distribution function
function cdf(d::SteinScaleLocationStudentT, t)
    Distributions.cdf(TDist(d.nu),(t-d.mu)/d.sigma);
end

function numdimensions(d::SteinScaleLocationStudentT)
    1
end


@testset "Util" begin
    @testset "Perpendicular vector" begin
        for i in 1 : 100
            vec = randn(SVector{3, Float64})
            perp = Rotations.perpendicular_vector(vec)
            @test norm(perp) >= maximum(abs.(vec))
            @test isapprox(dot(vec, perp), 0.; atol = 1e-10)
        end
        let vec = randn(SVector{3, Float64})
            allocs = @allocated Rotations.perpendicular_vector(vec)
            @test allocs == 0
        end
    end
end


"""
    solve(FDDE::FDDEProblem, h, DelayABM())

Use the Adams-Bashforth-Moulton method to solve fractional delayed differential equations.

### References

```tex
@inproceedings{Bhalekar2011APS,
  title={A PREDICTOR-CORRECTOR SCHEME FOR SOLVING NONLINEAR DELAY DIFFERENTIAL EQUATIONS OF FRACTIONAL ORDER},
  author={Sachin Bhalekar and Varsha Daftardar-Gejji},
  year={2011}
}
```
"""
struct DelayABM <: FractionalDiffEqAlgorithm end
#FIXME: There are still some improvments about initial condition
#FIXME: Fix DelayABM method for FDDESystem : https://www.researchgate.net/publication/245538900_A_Predictor-Corrector_Scheme_For_Solving_Nonlinear_Delay_Differential_Equations_Of_Fractional_Order
#FIXME: Also the problem definition f(t, ϕ, y) or f(t, y, ϕ)?
function solve(FDDE::FDDEProblem, h, ::DelayABM)
    @unpack f, ϕ, α, τ, tspan = FDDE
    N::Int = round(Int, tspan/h)
    Ndelay::Int = round(Int, τ/h)
    x1 = zeros(Ndelay+N+1)
    x = zeros(Ndelay+N+1)
    #x1[Ndelay+N+1] = 0

    #x[Ndelay+N+1] = 0

    # History function handling
    #FIXME: When the value of history function ϕ is different with the initial value?
    if typeof(ϕ) <: Number
        x[1:Ndelay] = ϕ*ones(Ndelay)
    elseif typeof(ϕ) <: Function
        x[Ndelay] = ϕ(0)
        x[1:Ndelay-1] .= ϕ(collect(-h*(Ndelay-1):h:(-h)))
    end

    x0 = copy(x[Ndelay])

    x1[Ndelay+1] = x0 + h^α*f(0, x[1], x0)/(gamma(α)*α)

    x[Ndelay+1] = x0 + h^α*(f(0, x[1], x[Ndelay+1]) + α*f(0, x[1], x0))/gamma(α+2)

    @fastmath @inbounds @simd for n=1:N
        M1=(n^(α+1)-(n-α)*(n+1)^α)*f(0, x[1], x0)

        N1=((n+1)^α-n^α)*f(0, x[1], x0)

        @fastmath @inbounds @simd for j=1:n
            M1 = M1+((n-j+2)^(α+1)+(n-j)^(α+1)-2*(n-j+1)^(α+1))*f(0, x[j], x[Ndelay+j])
            N1 = N1+((n-j+1)^α-(n-j)^α)*f(0, x[j], x[Ndelay+j])
        end
        x1[Ndelay+n+1] = x0+h^α*N1/(gamma(α)*α)
        x[Ndelay+n+1] = x0+h^α*(f(0, x[n+1], x[Ndelay+n+1])+M1)/gamma(α+2)
    end

    xresult = zeros(N-Ndelay+1)
    yresult = zeros(N-Ndelay+1)

    xresult[N-Ndelay+1]=0
    yresult[N-Ndelay+1]=0

    @fastmath @inbounds @simd for n=2*Ndelay+1:N+Ndelay+1
       xresult[n-2*Ndelay] = x[n]
       yresult[n-2*Ndelay] = x[n-Ndelay]
    end

    return xresult, yresult
end


using Statistics

include("update.jl")

function randomwalk(num::Integer, nsteps::Integer, prob::Real=0.5)
    means, vars = zeros(nsteps+1), zeros(nsteps+1)

    xs = zeros(num)
    means[1] = mean(xs)
    vars[1] = var(xs)
    for t in 1:nsteps
        map!(x -> update(x, prob), xs, xs)
        means[t+1] = mean(xs)
        vars[t+1] = var(xs)
    end
    return means, vars
end

function main(num, nsteps, prob, filename)
    if isempty(filename)
        io = stdout
    else
        io = open(filename, "w")
    end

    means, vars = randomwalk(num, nsteps, prob)

    for (i, (m, v)) in enumerate(zip(means, vars))
        println(io, "$(i-1) $m $v")
    end

    if io != stdout
        close(io)
    end
end

if abspath(PROGRAM_FILE) == @__FILE__
    num = length(ARGS)>0 ? parse(Int, ARGS[1]) : 1000
    nsteps = length(ARGS)>1 ? parse(Int, ARGS[2]) : 1000
    prob = length(ARGS)>2 ? parse(Float64, ARGS[3]) : 0.5
    filename = length(ARGS)>3 ? ARGS[4] : ""

    main(num, nsteps, prob, filename)
end


# Usage instruction:
# julia quantum_dot.jl groundstate # calculates groundstate at t0 = 0
# julia quantum_dot.jl ref [tol] # tol::Float64, calc ref solution with specified tol for AdaptiveAdamsLawson
# julia quantum_dot.jl {conv | stab} [method] [num_steps] # run experiment for equidistant method as listed below
# conv compares the result with a reference solution and prints the norm distance function
# stab calculates until t = 70 without external potential
#
# Parameters are changed as global variables otherwise
# Experiment results are being saved to subdirectories, please review those lines and mkdir before running!
# Saved data includes the wave_function as .h5 for ref and conv,
# the time, norm, energy (, stepsize) for every timestep in a .jld file.
# For conv, the error and the number of B_Calls is additionally saved.
using TSSM
using JLD
include("mctdhf1d.jl")
include("check.jl")
include("../examples/time_propagators.jl")

V1(x) = 1. / 32 * (x^2)
V2(x,y) = 1/sqrt((x-y)^2+1/16)
V(x,y) = V1(x) + V1(y) + V2(x,y)

const N = 4
const interval_bound = 20
const nx = Int(512*interval_bound/10)
const t0 = 0
const groundstate_tol=1e-9

println("N = ", N, ", nx = ", nx, " on [-", interval_bound, ",", interval_bound, "]")

function method_from_string(variant::String)
    if variant == "yoshida"
        g = [1.351207191959657634, -1.702414383919315268, 1.351207191959657634] # Yoshida
        a, b = get_coeffs_composition(g)
        method = SplittingRK4BMethod(a,b)
    end
    if variant == "suzuki"
        g = [1/(4-4^(1/3)),1/(4-4^(1/3)),-4^(1/3)/(4-4^(1/3)), 1/(4-4^(1/3)), 1/(4-4^(1/3))] # Suzuki
        a, b = get_coeffs_composition(g)
        method = SplittingRK4BMethod(a,b)
    end
    if variant == "strang"
        a, b = [0.5, 0.5], [1.0, 0.0]
        method = SplittingRK4BMethod(a, b, secondorder=true)
    end
    if variant == "krogstad"
        method = ExponentialRungeKutta(:krogstad)
    end
    if variant == "rk4"
        method = ExponentialRungeKutta(:rk4)
    end
    if variant == "rklawson"
        method = ExponentialRungeKutta(:lawson)
    end
    if variant == "strehmel_weiner"
        method = ExponentialRungeKutta(:strehmel_weiner)
    end
    if variant == "adamslawson3"
        method = ExponentialMultistep(3, version=2, iters=1, starting_method=ExponentialRungeKutta(:lawson), starting_subdivision=50)
    end
    if variant == "expmultistep3"
        method = ExponentialMultistep(3, version=1, iters=1, starting_method=ExponentialRungeKutta(:lawson), starting_subdivision=50)
    end
    if variant == "adamslawson5"
        method = ExponentialMultistep(5, version=2, iters=1, starting_method=ExponentialRungeKutta(:lawson), starting_subdivision=50)
        println("adamslawson5")
    end
    if variant == "expmultistep5"
        method = ExponentialMultistep(5, version=1, iters=1, starting_method=ExponentialRungeKutta(:lawson), starting_subdivision=50)
        println("exponential multistep5")
    end
    if variant == "adamslawson3_nocorr"
        method = ExponentialMultistep(3, version=2, iters=0, starting_method=ExponentialRungeKutta(:lawson), starting_subdivision=50)
        println("adamslawson3 without correction step")
    end
    if variant == "expmultistep3_nocorr"
        method = ExponentialMultistep(3, version=1, iters=0, starting_method=ExponentialRungeKutta(:lawson), starting_subdivision=50)
        println("exponential multistep 3 without correction step")
    end
    return method
end

function ref(tol::Float64=1e-5)
    E(t) = sin(2*t)
    tend = 12

    V1_t(x, t) = E(t)*x
    V_t(x,y,t) = E(t)*(x+y)

    m = MCTDHF1D(2, N, nx, -interval_bound, interval_bound, potential1=V1, potential1_t=V1_t, potential2=V2, spin_restricted=true);
    psi = wave_function(m);
    TSSM.load!(psi, "wfquantumdot_f2_n"*string(N)*"_nx"*string(nx)*"_t0_1e-9.h5")
    set_time!(psi, t0)

    times, energies, norms, stepsizes = propagate_adaptive!(psi, t0, tend, tol)

    TSSM.save(psi, "results_tolerance/wfquantumdot_f2_n"*string(N)*"_nx"*string(nx)*"_t"*string(tend)*"_"*string(tol)*".h5")
    JLD.save("results_tolerance/wfquantumdot_f2_n"*string(N)*"_nx"*string(nx)*"_"*string(tol)*".jld", "times", times, "energies", energies, "norms", norms, "stepsizes", stepsizes)
end

function conv(variant::String, steps::Int)
    #laser field
    E(t) = sin(2*t)
    tend = 12
    dt=tend/steps

    V1_t(x, t) = E(t)*x
    V_t(x,y,t) = E(t)*(x+y)

    m = MCTDHF1D(2, N, nx, -interval_bound, interval_bound, potential1=V1, potential1_t=V1_t, potential2=V2, spin_restricted=true)
    psi = wave_function(m)

    TSSM.load!(psi, "wfquantumdot_f2_n"*string(N)*"_nx"*string(nx)*"_t0_1e-9.h5")
    set_time!(psi, t0)

    psi_ref = wave_function(m);
    TSSM.load!(psi_ref, "results_tolerance/wfquantumdot_f2_n"*string(N)*"_nx"*string(nx)*"_t"*string(tend)*"_1.0e-9.h5")

    method = method_from_string(variant)

    times, energies, norms = propagate_equidistant!(method, psi, t0, dt, steps)

    err = distance(psi, psi_ref)
    println("err:", err)
    TSSM.save(psi, "results_conv/wfquantumdot_f2_n"*string(N)*"_nx"*string(nx)*"_t"*string(tend)*"_"*variant*"_"*string(steps)*".h5")
    JLD.save("results_conv/wfquantumdot_conv_f2_n"*string(N)*"_nx"*string(nx)*"_t"*string(tend)*"_"*variant*"_"*string(steps)*".jld", "times", times, "energies", energies, "dt", dt, "err", err, "B_calls", COUNT_B)

end

function stab(variant::String, steps::Int)
    #laser field

    E(t) = 0
    tend = 70
    dt=tend/steps

    V1_t(x, t) = E(t)*x
    V_t(x,y,t) = E(t)*(x+y)

    m = MCTDHF1D(2, N, nx, -interval_bound, interval_bound, potential1=V1, potential1_t=V1_t, potential2=V2, spin_restricted=true);
    psi = wave_function(m);

    TSSM.load!(psi, "wfquantumdot_f2_n"*string(N)*"_nx"*string(nx)*"_t0_1e-9.h5")
    set_time!(psi, t0)

    method = method_from_string(variant)

    times, energies, norms = propagate_equidistant!(method, psi, t0, dt, steps)

    JLD.save("results_stab/wfquantumdot_stab_f2_n"*string(N)*"_nx"*string(nx)*"_t"*string(tend)*"_"*variant*"_"*string(steps)*".jld", "times", times, "energies", energies, "dt", dt, "norms", norms)
end

function propagate_equidistant!(method, psi, t0, dt, steps)
    times = Vector{Float64}()
    energies = Vector{Float64}()
    norms = Vector{Float64}()
    step_counter = 1
    time0 = time()
    for (step, tsi) in EquidistantTimeStepper(method, psi, t0, dt, steps)
        n = norm(psi)
        E_pot = potential_energy(psi)
        E_kin = kinetic_energy(psi)
        E_tot = E_pot+E_kin
        if step_counter % 50 == 0 || step == steps
            @printf("%5i  %14.10f  %14.10f  %14.10f  %14.10f  %14.10f  %14.10f %6i %10.2f\n",
                step_counter, get_time(psi), E_pot, E_kin, E_tot, dt, n, COUNT_B, time()-time0)
        end
        append!(times, get_time(psi))
        append!(energies, E_tot)
        append!(norms, n)
        step_counter += 1
    end
    return times, energies, norms
end

function propagate_adaptive!(psi, t0, tend, tol)
    method = AdaptiveAdamsLawson(6)
    times = Vector{Float64}()
    energies = Vector{Float64}()
    stepsizes = Vector{Float64}()
    norms = Vector{Float64}()
    step_counter = 1
    time0 = time()
    told = t0
    for (t, tsi) in AdaptiveTimeStepper(method, psi, t0, tend, tol, 0.00001)
        n = norm(psi)
        E_pot = potential_energy(psi)
        E_kin = kinetic_energy(psi)
        E_tot = E_pot+E_kin
        stepsize = t - told
        if step_counter % 50 == 0
            @printf("%5i  %14.10f  %14.10f  %14.10f  %14.10f  %14.10f  %14.10f  %10.2f\n",
                step_counter, get_time(psi), n, E_pot, E_kin, E_tot, stepsize, time()-time0)
        end
        append!(times, get_time(psi))
        append!(energies, E_tot)
        append!(stepsizes, stepsize)
        append!(norms, n)
        told = t
        step_counter += 1
    end
    return times, energies, norms, stepsizes
end

if ARGS[1] == "groundstate"
    println("calculate groundstate with tolerance ", groundstate_tol)
    include("propagators.jl")
    m = MCTDHF1D(2, N, nx, -interval_bound, interval_bound, potential1=V1, potential2=V2, spin_restricted=true)
    psi = wave_function(m)
    groundstate!(psi, dt=0.1, max_iter=500000, output_step=100, tol=groundstate_tol)
    TSSM.save(psi, "wfquantumdot_f2_n"*string(N)*"_nx"*string(nx)*"_t0_1e-9.h5")
elseif ARGS[1] == "ref"
    println("calculate reference solution with tolerance ", ARGS[2])
    ref(parse(Float64, ARGS[2]))
elseif ARGS[1] == "conv"
    println("calculate solution using method ", ARGS[2], " with ", ARGS[3], " steps")
    conv(ARGS[2], parse(Int, ARGS[3]))
elseif ARGS[1] == "stab"
    stab(ARGS[2], parse(Int, ARGS[3]))
else
    throw(ErrorException("first cmdline argument is not 'ref', 'conv' or 'stab'"))
end


using IterativeSolvers
using Test
using Random
using SparseArrays
using LinearAlgebra
using LinearMaps

@testset "MINRES" begin

function hermitian_problem(T, n)
    B = rand(T, n, n) + n * I
    A = B + B'
    x = ones(T, n)
    b = B * x
    A, x, b
end

function skew_hermitian_problem(T, n)
    B = rand(T, n, n) + n * I
    A = B - B'
    x = ones(T, n)
    b = A * x
    A, x, b
end

srand(123)
n = 15

@testset "Hermitian Matrix{$T}" for T in (Float32, Float64, ComplexF32, ComplexF64)
    A, x, b = hermitian_problem(T, n)
    tol = sqrt(eps(real(T)))
    x0 = rand(T, n)

    x1, hist1 = minres(A, b, maxiter = 10n, tol = tol, log = true)
    x2, hist2 = minres!(x0, A, b, maxiter = 10n, tol = tol, log = true)

    @test isa(hist1, ConvergenceHistory)
    @test norm(b - A * x1) / norm(b) ≤ tol
    @test hist1.isconverged
    @test norm(b - A * x2) / norm(b) ≤ tol
    @test x2 == x0
end

@testset "Skew-Hermitian Matrix{$T}" for T in (Float32, Float64, ComplexF32, ComplexF64)
    A, x, b = skew_hermitian_problem(T, n)
    tol = sqrt(eps(real(T)))
    x_approx, hist = minres(A, b, skew_hermitian = true, maxiter = 10n, tol = tol, log = true)

    @test norm(b - A * x_approx) / norm(b) ≤ tol
    @test hist.isconverged
end

@testset "SparseMatrixCSC{$T}" for T in (Float32, Float64, ComplexF32, ComplexF64)
    A = let
        B = sprand(n, n, 2 / n)
        B + B' + I
    end

    x = ones(T, n)
    b = A * x
    tol = sqrt(eps(real(T)))

    x_approx, hist = minres(A, b, maxiter = 10n, tol = tol, log = true)

    @test norm(b - A * x_approx) / norm(b) ≤ tol
    @test hist.isconverged
end

end


include("metrics/seed.jl")
include("metrics/pricer.jl")
include("metrics/pricer_cv.jl")
include("metrics/distribution.jl")
include("metrics/delta.jl")
include("metrics/rho.jl")
include("metrics/confinterval.jl")
include("metrics/variance.jl")


push!(LOAD_PATH,"../src")

using Test
using TypesParticles
using TypesBasis
using BuildBasis
using InputParser
using NuclearAttraction

"""
		run_buildnuclrattract_test()

""" function run_buildnuclrattract_test()
    @testset "Nuclear Attraction" begin
        system,basisset = getcalcsetup("../examples/HatreeFock.in")
        basisfunc,numelec = buildbasisfunc(system,basisset);
        nuclrattractcalc = buildnuclrattract(system,basisfunc,basisset);
        nuclrattracttest = [-2.7494e0 -1.3415e0 -2.1471e0 -1.2642e0;
                  -1.3415e0 -1.2431e0 -1.2642e0 -1.2027e0;
                  -2.1471e0 -1.2642e0 -2.7494e0 -1.3415e0;
                  -1.2642e0 -1.2027e0 -1.3415e0 -1.2431e0];

        tolerance = 1.0e-4;
        for i=1:4
            for j=1:4
                @test sign(nuclrattractcalc[i,j]) == sign(nuclrattracttest[i,j]);
                diff = abs(nuclrattractcalc[i,j]-nuclrattracttest[i,j]);
                @test diff <= tolerance;
	    end
        end #i

    end #testset
end #run_nuclrattract_test()


using Test, ArgParse
using SignalingDimension

try print_test_results catch err
    include("../utilities.jl")
    using .ScriptUtilities: print_test_results
end

# parsing command line arguments
arg_table = ArgParseSettings()
@add_arg_table! arg_table begin
    "-N"
        help = "max number of inputs/outputs to consider"
        arg_type = Int64
        default = 35
    "--dir"
        help = "directory to print results .txt to"
        arg_type = String
        default = "./script/facet_verifications/results/"
    "--stdout"
        help = "Print output to STDOUT. If not flagged, output is printed to `.txt` file."
        action = :store_true
end
args = parse_args(ARGS, arg_table)

# verification script: verifies all anti-guessing facets with N ∈ [4, N_max]
function verify_anti_guessing_facet(N_max :: Int64)
    @testset "Testing all anti-guessing facets of size `($N_max, $N_max)` or less." begin
        @testset "Scanning over all `d ∈ [2, $N - 2]`" for N in 4:N_max
            @testset "Scanning over all `ε ∈ [3, $N - $d + 1]`" for d in 2:N-2
                @testset "Verifying N =  $N, d = $d, ε = $ε" for ε in 3:N-d+1
                    vertices = aff_ind_anti_guessing_vertices(N, d, ε)
                    facet = anti_guessing_facet(N, d, ε)

                    @test verify_facet(d, facet, vertices)
                end
            end
        end
    end
end

# outputting result to file or stdout
print_test_results(
    verify_anti_guessing_facet,
    params=[args["N"]],
    stdout=args["stdout"],
    dir=args["dir"]
)


using NBInclude

@nbinclude("GroundState(QOptics).ipynb")

using ITensors

using PyPlot

function HHeisdmrg(N, Jxy, Jz, Nsweep, Bonddim, truncerr)
  sites = siteinds("S=1/2",N)

  ampo = OpSum()
  for j=1:N-1
    ampo += Jz , "Sz",j,"Sz",j+1
    ampo += Jxy, "Sx",j,"Sx",j+1
    ampo += Jxy, "Sy",j,"Sy",j+1
  end
H = MPO(ampo,sites)
psi0 = randomMPS(sites,10)

  sweeps = Sweeps(Nsweep)
  setmaxdim!(sweeps, Bonddim)
  setcutoff!(sweeps, truncerr)

  energy, psi = dmrg(H,psi0, sweeps)

  return energy, psi
end

Jxy=1
Jz=1
truncerr=1E-12
nsweep = 5
Bonddim= 5
bondlist = 1:2:10
e0s = []

Nlist = 7:10;
E0sIT=[]
for l in Nlist
    e0, psi = HHeisdmrg(l, Jxy, Jz, nsweep, Bonddim, truncerr)
    push!(E0sIT,real(e0)/l)
end
E0p=[E0sIT[i][1] for i=1:length(Nlist)];

plot(Nlist,E0pQO,color="blue",linestyle = ":",linewidth = 3)
plot(Nlist,E0p,color="red",linewidth = 3)
scatter(Nlist,E0pQO,facecolor = "blue", edgecolors="blue", s=35)
scatter(Nlist,E0p,facecolor = "red", edgecolors="red", s=35)
legend(["ED Data(QOptics)", "ED Data(ITensors)","E0(QOptics)", "E0(ITensors)"])
xlabel("N")
ylabel("E0/N")
title("QOptics/ITensors-GS Energies(N)")
savefig( "GS(QOIT).pdf", bbox_inches = "tight", pad_inches = 0.1 )

Nlist = 7:10;
E0s=[];
for N in Nlist
    for bonddim in bondlist
        e0, psi = HHeisdmrg(N, Jxy, Jz, nsweep, bonddim, truncerr)
        push!(E0s,real(e0)/N)
    end

end
E0pIT=[E0s[i][1] for i=1:length(Nlist)]

e07=E0s[1:5]
e08=E0s[6:10]
e09=E0s[11:15]
e01=E0s[16:20]

plot(bondlist,e07,color = "black",linewidth = 0.6)
plot(bondlist,e08,color = "green",linewidth = 0.6)
plot(bondlist,e09,color = "blue",linewidth = 0.6)
plot(bondlist,e01,color = "red",linewidth = 0.6)
legend(["E07","E08","E09","E010"])
xlabel("Bond Dim")
ylabel("E0/N")
savefig( "ITensorE0(Bondim).pdf", bbox_inches = "tight", pad_inches = 0.1 )


# Содержание
# Функция вычисления правой части прямой задачи     :   |directRP|
# Функция вычисления трехдиагонального
#                       якобиана правой части       :   |DRP_y|
# Обертка `DRP_y`                                   :   |∂DRP_∂y|
# Функция вычисления якобиана правой части          :   |∂directRP_∂y|
# Функция нахождения решения прямой задачи          :   |solve|

@doc raw"""
    directRP(y::Vector, m::Int,
             Xₙ::Vector, N::Int,
             ε::Real,
             ulₘ::Vector, urₘ::Vector,
             qₙ::Vector)

Вычисляет вектор правой части с помощью конечно-разностной
аппроксимации пространственных производных.

# Arguments
- `y::Vector`:          Вектор размера `N-1`, решение в текущий момент без ГТ.
- `m::Int`:             Номер шага в сетке по времени.
- `Xₙ::Vector`:         Вектор размера `N+1` сетки по ``X``, вместе с ГТ.
- `N::Int`:             Число **интервалов** в полной сетке по ``X``.
- `ε::Real`:            Малый параметр при старшей производной.
- `ulₘ::Vector`:        Вектор сеточных значений левого ГУ.
- `urₘ::Vector`:        Вектор сеточных значений правого ГУ.
- `qₙ::Vector`:         Вектор размера `N-1` сеточные значений  неоднородности без ГТ.

!!! warn
    Функция не входит в публичный API, поэтому размерность
    входных векторов отличается от аналогичных в [`solve`](@ref).
     - `qₙ`, `y` размера `N-1`!
     - `Xₙ` размера `N+1`.

"""
function directRP(y::Vector, m::Int,
                  Xₙ::Vector, N::Int,
                  ε::Real,
                  ulₘ::Vector, urₘ::Vector,
                  qₙ::Vector)

    @assert length(Xₙ) == N+1
    @assert length(qₙ) == N-1
    @assert length(y)  == N-1
    @assert m < length(ulₘ)

    RP = zero(y)                    # Создаем нулевой вектор того же типа и размера

    # Здесь нужно применить сдвиг индексов в `Xₙ` на +1!
    # Xₙ[1] соответствует первому узлу нашей сетки — ``x_0``
    # Xₙ[2] — ``x_1``
    # Xₙ[3] — ``x_2``
    #
    # Для удобства, формулы записаны в следующем виде
    # d[1] = ... Xₙ[1+1+1] ...,
    # первая единица соответствует единице в d[1],
    # вторая — берем следующий элемент,
    # третья — совершаем сдвиг индексов для Xₙ
    RP[1]     = 2ε / (Xₙ[1+1+1] - Xₙ[1-1+1]) * ( (y[2] - y[1]) /  (Xₙ[1+1+1] - Xₙ[1+1]) - (y[1] - ulₘ[m])/(Xₙ[1+1] - Xₙ[1-1+1])) +
    + y[1]   * (y[2]   - ulₘ[m])/(Xₙ[1+1+1] - Xₙ[1-1+1]) - y[1] * qₙ[1]

    # Xₙ[n  ] — соответствует ``x_{n-1}``
    # Xₙ[n+1] — соответствует ``x_{n  }``
    # Xₙ[n+2] — соответствует ``x_{n+1}``
    #
    # Для удобства, формулы записаны в следующем виде
    # d[n] = ... Xₙ[n-1+1],
    # первое слагаемое соответствует d[n],
    # второе — берем предыдущий элемент,
    # третье — совершаем сдвиг индексов для Xₙ
    for n in 2:N-2
        RP[n] = 2ε / (Xₙ[n+1+1] - Xₙ[n-1+1]) * ( (y[n+1] - y[n]) /  (Xₙ[n+1+1] - Xₙ[n+1]) - (y[n] - y[n-1])/(Xₙ[n+1] - Xₙ[n-1+1])) +
        + y[n]   * (y[n+1] - y[n-1])/(Xₙ[n+1+1] - Xₙ[n-1+1]) - y[n]   * qₙ[n]
    end

    RP[N-1]   =  2ε / (Xₙ[N+1] - Xₙ[N-2+1]) * ( (urₘ[m] - y[N-1]) /  (Xₙ[N+1] - Xₙ[N-1+1]) - (y[N-1] - y[N-2])/(Xₙ[N-1+1] - Xₙ[N-2+1])) +
    + y[N-1] * (urₘ[m] - y[N-2])/(Xₙ[N+1] - Xₙ[N-2+1]) - y[N-1] * qₙ[N-1]

    return RP
end

@doc raw""""
    DRP(y::Int, m::Int,
        Xₙ::Vector, N::Int,
        ε::Real,
        ulₘ::Vector, urₘ::Vector,
        qₙ::Vector) -> (::Vector, ::Vector, ::Vector)

Возращает три диагонали якоибана правой части прямой задачи,
чтобы после сформировать трехдигональную матрицу `Tridiagonal`.

!!! warn
    Функция не входит в публичный API, поэтому размерность
    входных векторов отличается от аналогичных в [`solve`](@ref).
     - `qₙ`, `y` размера `N-1`!
     - `Xₙ` размера `N+1`.

# Return
Возвращает три вектора `dl`, `d`, `dl` элементов якоибана.
Нижняя и верхняя диагонали длины ``N-2``, главная — ``N-1``.
```
    [ d[1]  du[1]                         ]
    [ dl[1] d[2]  du[2]                   ]
    [ 0     dl[2] d[3] du[3]              ]
    [           ...  ...  ...             ]
    [                ...  ...     du[N-2] ]
    [                     dl[N-2] d[N-1]  ]
```

# Example
```
julia> # Данные необохдимо подготовить, например, как в examples/example_direct.jl

julia> # Подготовим массивы, выбросив граничные точки

julia> # ведь тестируемая функция — для внутреннего использования

julia> qq = qₙ[2:N];

julia> y = y₀[2:N];

julia> dl, d, du = NonLinearReactionAdvectionDiffusionWithFrontData.DRP_y(y, 1, Xₙ, N, ε, ulₘ, urₘ, qq);

julia> Tridiagonal( dl, d, du )
49×49 Tridiagonal{Float64,Array{Float64,1}}:
 -991.038   305.462      ⋅         ⋅         ⋅         ⋅         ⋅     …       ⋅         ⋅         ⋅         ⋅         ⋅        ⋅
[...]
```
"""
function DRP_y(y::Vector, m::Int,
             Xₙ::Vector, N::Int,
             ε::Real,
             ulₘ::Vector, urₘ::Vector,
             qₙ::Vector)

    @assert length(Xₙ) == N+1
    @assert length(qₙ) == N-1
    @assert length(y) == N-1
    @assert m < length(ulₘ)

    dl = zeros(N-2);        # Поддиагональные элементы
    d = zeros(N-1);         #    Диагональные элементы
    du = zeros(N-2);        # Наддиагональные элементы

    # Здесь нужно применить сдвиг индексов на +1 для Xₙ!
    # Xₙ[1] соответствует ПЕРВОМУ узлу нашей сетки — ``x_0``
    # Xₙ[2] — ``x_1``
    # Xₙ[3] — ``x_2``
    #
    # Для удобства, формулы записаны в следующем виде
    # d[1] = ... Xₙ[1+1+1] ...,
    # первая единица соответствует единице в d[1],
    # вторая — берем следующий элемент,
    # третья — совершаем сдвиг индексов для Xₙ
    d[1] = 2ε / ( Xₙ[1+1+1] - Xₙ[1-1+1]) * ( -1 / (Xₙ[1+1+1] - Xₙ[1+1]) - 1 / (Xₙ[1+1] - Xₙ[1-1+1])) +
            + (y[2] - ulₘ[m]) / ( Xₙ[1+1+1] - Xₙ[1-1+1])  - qₙ[1]

    du[1] = 2ε / ( Xₙ[1+1+1] - Xₙ[1-1+1]) * ( 1/ (Xₙ[1+1+1] - Xₙ[1+1]) ) +
            + y[1] / (Xₙ[1+1+1] - Xₙ[1-1+1])

    # Xₙ[n  ] — соответствует ``x_{n-1}``
    # Xₙ[n+1] — соответствует ``x_{n  }``
    # Xₙ[n+2] — соответствует ``x_{n+1}``
    #
    # Для удобства, формулы записаны в следующем виде
    # d[n] = ... Xₙ[n-1+1],
    # первое слагаемое соответствует d[n],
    # второе — берем предыдущий элемент,
    # третье — совершаем сдвиг индексов для Xₙ
    for n in 2:N-2
        # Сдвиг индексов для поддиагональных элементов `dl`
        # см # Return документации функции
        dl[n - 1] = 2ε / ( Xₙ[n+1+1] - Xₙ[n-1+1]) * ( 1 / (Xₙ[n+1] - Xₙ[n-1+1])) -
                    y[n] / ( Xₙ[n+1+1] - Xₙ[n-1+1])

        d[n]      = 2ε / ( Xₙ[n+1+1] - Xₙ[n-1+1]) * ( -1 / (Xₙ[n+1+1] - Xₙ[n+1]) - 1 / (Xₙ[n+1] - Xₙ[n-1+1])) +
                + (y[n+1] - y[n-1]) / ( Xₙ[n+1+1] - Xₙ[n-1+1])  - qₙ[n]

        du[n]     = 2ε / ( Xₙ[n+1+1] - Xₙ[n-1+1]) * ( 1/ (Xₙ[n+1+1] - Xₙ[n+1]) ) +
                + y[n] / (Xₙ[n+1+1] - Xₙ[n-1+1])
    end

    dl[N-2] = 2ε / ( Xₙ[N+1] - Xₙ[N-2+1]) * ( 1 / (Xₙ[N-1+1] - Xₙ[N-2+1])) -
                 y[N-1] / ( Xₙ[N+1] - Xₙ[N-2+1])
    d[N-1] =  2ε / ( Xₙ[N+1] - Xₙ[N-2+1]) * ( -1 / (Xₙ[N+1] - Xₙ[N-1+1]) - 1 / (Xₙ[N-1+1] - Xₙ[N-2+1]))             +
                + (urₘ[m] - y[N-2]) / ( Xₙ[N+1] - Xₙ[N-2+1])  - qₙ[N-1]

    return dl, d, du
end

@doc raw"""
    ∂DRP_∂y(y::Vector, m::Int,
            Xₙ::Vector, N::Int,
            ε::Real,
            ulₘ::Vector, urₘ::Vector,
            qₙ::Vector) -> Tridiagonal

Обертка фнукции [`DRP_y`](@ref), которая возвращает `Tridiagonal( DRP_y(...))`
трехдиагональную матрицу из векторов, которые возвращает `DRP_y(...)`.

!!! warn
    Функция не входит в публичный API, поэтому размерность
    входных векторов отличается от аналогичных в [`solve`](@ref).
     - `qₙ`, `y` размера `N-1`!
     - `Xₙ` размера `N+1`.
"""
function ∂DRP_∂y(y::Vector, m::Int,
               Xₙ::Vector, N::Int,
               ε::Real,
               ulₘ::Vector, urₘ::Vector,
               qₙ::Vector)
    return Tridiagonal( (DRP_y(y, m, Xₙ, N, ε, ulₘ, urₘ, qₙ))... )
end

@doc raw"""
    ∂directRP_∂y(y::Vector, m::Int,
                  Xₙ::Vector, N::Int,
                  ε::Real,
                  ulₘ::Vector, urₘ::Vector,
                  qₙ::Vector)

Функция якобиана для `adjointRP`.
Размерности входных векторов такие же, как и у [`directRP`](@ref).

!!! warn
    Функция не входит в публичный API, поэтому размерность
    входных векторов отличается от аналогичных в [`solve`](@ref).
     - `qₙ`, `y` размера `N-1`!
     - `Xₙ` размера `N+1`.
"""
function ∂directRP_∂y(y::Vector, m::Int,
                  Xₙ::Vector, N::Int,
                  ε::Real,
                  ulₘ::Vector, urₘ::Vector,
                  qₙ::Vector)
    return ForwardDiff.jacobian( z -> directRP(z, m, Xₙ, N, ε, ulₘ, urₘ, qₙ), y)
end

@doc raw"""
    solve(y₀::Vector, Xₙ::Vector, N::Int,
          Tₘ::Vector, M::Int,
          ε::Real, ulₘ::Vector, urₘ::Vector,
          qₙ::Vector,
          RP::Function = directRP,
          jac::Function = ∂directRP_∂y;
          α::Complex = complex(0.5, 0.5),
          create_mesh::Function = x -> [NaN, NaN]) -> Matrix, Matrix, Vector

Функция, которая находит решение прямой задачи с помощью
одностадийной схемы Розенброка с комплексным коэффициентом.

# Arguments
- `y₀::Vector`:         Сеточные значения начального условия.
- `Xₙ::Vector`:         Пространственная сетка по ``X``.
- `N::Int`:             Число **интервалов** сетки по ``X``.
- `Tₘ::Vector`:         Пространственная сетка по ``t``.
- `M::Int`:             Число **интервалов** сетки по ``t``.
- `ε::Real`:            Малый параметр при старшей производной.
- `ulₘ::Function`:      Сеточные значения левого ГУ.
- `urₘ::Function`:      Сеточные значения правого ГУ.
- `qₙ::Vector`:         Сеточные значения "неоднородности", см. постановку задачи.
- `RP::Function`:       Функция вычисления вектора правой части — `directRP`.
- `jac::Function`:      Якобиан правой части по вектору `y` — `∂DRP_∂y`.
- `α::Complex`:         Коэффициент схемы. При `α = 0` — схема Эйлера, при `α = complex(0.5, 0.5)` — схема Розенброка с комплексным коэффициентом.
- `create_mesh::Function`: Принимает один аргумент `x_tp` положение переходного слоя и формирует подходяющую сетку.

# Return
Тройку:
    - Матрицу размера ``(N+1, M+1)``, содержащую значения искомой функции на сетках ``X\_n, T\_m``,.
    - Матрицу размера ``(N+1, M+1)``, содержащую значения стеки ``X\_n`` на каждом моменте времени.
    - Вектора размера ``M+1``, содержащий координаты положения переходного слоя в каждый момент времени.

!!! info
     - `Xₙ`, `qₙ`, `y₀`     векторы размера ``N+1``!
     - `Tₘ`, `ulₘ`, `urₘ`   векторы размера ``M+1``!
"""
function solve(y₀::Vector, Xₙ::Vector, N::Int,
               Tₘ::Vector, M::Int,
               ε::Real, ulₘ::Vector, urₘ::Vector,
               qₙ::Vector,
               RP::Function = directRP,
               jac::Function = ∂DRP_∂y;
               α::Complex = complex(0.5, 0.5),
               create_mesh::Function = x -> [NaN, NaN])

    if length(Xₙ) != N+1
        throw(ArgumentError("""length(Xₙ) == $(length(Xₙ)), N == $(N)
        Массив Xₙ должен иметь размерность N+1."""))
    end
    if length(qₙ) != N+1
        throw(ArgumentError("""length(qₙ) == $(length(qₙ)), N == $(N)
        Массив qₙ должен иметь размерность N+1."""))
    end
    if length(y₀) != N+1
        throw(ArgumentError("""length(y) == $(length(y)), N == $(N)
        Массив `y` должен иметь размерность N+1."""))
    end

    if length(Tₘ) != M+1
        throw(ArgumentError("""length(Tₘ) == $(length(Tₘ)), M == $(M)
        Массив `Tₘ` должен иметь размерность M+1"""))
    end
    if length(ulₘ) != M+1
        throw(ArgumentError("""length(ulₘ) == $(length(ulₘ)), M == $(M)
        Массив `ulₘ` должен иметь размерность M+1"""))
    end
    if length(urₘ) != M+1
        throw(ArgumentError("""length(y) == $(length(y)), M == $(M)
        Массив `ulₘ` должен иметь размерность M+1"""))
    end

    u = zeros(N+1, M+1);                # Решение на всей сетке ``Xₙ x Tₘ``
    u[1  , :]   = ulₘ;                  # Запись левого  ГУ на каждом шаге
    u[N+1, :]   = urₘ;                  # Запись правого ГУ на каждом шаге
    u[:, 1] = y₀;                       # Запись начального условия, это перепишет ГУ.
                                        #
    Xₜₚ = zeros(M+1);                   # Массив для хранения положения переходного слоя
                                        # Определяется апостриорным способом
    XX = zeros(N+1, M+1);               # Для хранения сеток: переходный слой определяется
                                        # на каждом шаге и под него формируется сетка
    XX[:, 1] = Xₙ;                      # Сетка на первом шаге задается автоматом.

    # Векторы, которые подлежат изменению внутри цикла
    # Дальше, в вычислениях должны использоваться только они!
    y = strip_borderPoints(y₀, N);      # Вектор содержащий решение на текущем шаге
    q = strip_borderPoints(qₙ, N);      # Сетка без граничных точек
    X = XX[:, 1];                       # Сетка по X на первом временном шаге

    # Для определения положения переходного слоя
    # Найдем полусумму вырожденных корней на всех шагах по времени
    # используя оригинальную сетку.
    ϕl = phidetermination(qₙ, ulₘ, Xₙ, N, Tₘ, M);                   # Левый вырожденный корень
    ϕr = phidetermination(qₙ, urₘ, Xₙ, N, Tₘ, M, reverseX = true);  # Правый вырожденный корень
    ϕ  = Φ(ϕl, ϕr, N, M);                                           # Серединный корень

    x_tp = find_f_zeros(u[:, 1] - ϕ[:, 1], X);         # Найдем положение переходного слоя
    Xₜₚ[1] = x_tp;

    shouldupdate_mesh = all(!, isnan.(create_mesh(x_tp)));  # Проверяем, передали ли нам функцию

    for m in 1:M
        X = zeros(N+1);                         # Создаем новый массив, чтобы точно все обнулилось
        X = XX[:, m];                           # Используем соответствующую текущему шагу по времени сетку
        τ = (Tₘ[m+1] - Tₘ[m]);                  # Шаг по времени

        j = jac(y, m, X, N, ε, ulₘ, urₘ, q);    # Якобиан от текущего решения
        rp = RP(y, m, X, N, ε, ulₘ, urₘ, q);    # Правая часть от текущего решения

        W = (I - α * τ * j) \ rp;
        y = y .+ τ * real(W);

        u[2:N, m+1] = y       # Сохраним вектор решения на следующем шаге

        if shouldupdate_mesh
            y1 = u[:, m+1];       # Решение на следующем шаге вместе с Граничными Точками

            # Теперь нам нужно определить положение переходного слоя в **следующий** момент времени.
            # Напоминаю, что оно определяет как пересечение решения с полусуммой вырожденных решений.
            # Их мы нашли на оригинальной сетке для всех моментов времени.
            # Найдем `ϕ[:, m+1]` на текущей сетке.
            ϕsp = Spline1D(X, ϕ[:, m+1]);
            ϕₘ =  ϕsp(X);

            try
                x_tp = find_f_zeros(y1 - ϕₘ, X);        # Найдем положение переходного слоя
            catch e
                @error "Прерываем вычисления на шаге $(m), но возвращаем уже посчитанное." e
                return u, XX, Xₜₚ;
            end
            Xₜₚ[m+1] = x_tp;                        # Сохраним его

            XX[:, m+1] = create_mesh(x_tp);         # Обновим сетку

            spl = Spline1D(X, y1)                   # Задаем аппроксимирующий объект: решение на следующем шаге на текущей сетке
            y = spl(XX[2:end-1, m+1])               # Аппроксимируем решением на следующем шаге на сетку следующего шага

            qsp = Spline1D(X[2:end-1], q)           # Задаем аппроксимирующий объект: `q` на текущей сетке
            q = qsp(XX[2:end-1, m+1])               # Аппроксимируем `q` на сетку следующего шага

        else
            XX[:, m+1] = X                          # Если сетку обновлять не нужно, примем за следующую — текущую
        end

    end

    return u, XX, Xₜₚ;
end


module VortexDistributions

using Test
using JLD2
using Parameters
using SpecialFunctions
using Interpolations
using LinearAlgebra
using ToeplitzMatrices
using SparseArrays
using FFTW
using FileIO
using ProgressMeter
using LightGraphs
using SimpleWeightedGraphs
import Plots:stroke,scatter!,plot,plot!

const Λ = 0.8249

# topology
export Field, Torus, Sphere,

# vortex groups
VortexGroup, Dipole, Cluster,

# cores
Vortex, CoreShape, Ansatz, Exact, ScalarVortex, PointVortex,

# detection
findvortices, found_near, phase_jumps, phase_jumps!, unwrap, unwrap!, Δ,
find_where, findvortices_jumps, findvortices_grid,
remove_vortices_edge, zoom_interp, zoom_grid,
circ_mask, keep_vortices,

# construction
scalar_ansatz, vortex_array, uniform,
vortex!, dipole_phase, periodic_dipole!,
rand_charge, rand_pointvortex, rand_scalarvortex, rand_vortex, rand_vortexfield,
thetad,

# convenient access
charge, xpos, ypos, pos,

# RCA
distances, periodic_distances, sparse_distances,
spanning_tree, vortex_marker, plot_vortices!,
plot_cluster!, vortex_marker, grow_plus_clusters,
grow_minus_clusers, get_dipoles, seed_clusters

include("types.jl")
include("pointvortex.jl")
include("detection.jl")
include("creation.jl")

# RCA
include("get_dipoles.jl")
include("seed_clusters.jl")
include("grow_minus_clusters.jl")
include("grow_plus_clusters.jl")
# include("get_spanning_trees.jl")
# include("get_negative_spanning_trees.jl")
# include("get_positive_spanning_trees.jl")
# include("find_smallest_spanning_trees.jl")
# include("sort_RCA_structs.jl")
# include("RCA.jl")

# utils
include("utils.jl")

@load joinpath(@__DIR__,"exactcore.jld2") ψi
@load joinpath(@__DIR__,"ansatzcore.jld2") ψa

end


@testset "Faro" begin
    @test Faro === Weave

    @test infaro === inweave === Faro{:in}()
    @test outfaro === outweave === Faro{:out}()

    x = [1, 2, 3, 4, 5, 6]
    shuffle!(x, Faro{:in}()) == x == [4, 1, 5, 2, 6, 3]

    y = [1, 2, 3, 4, 5, 6]
    shuffle([1, 2, 3, 4, 5, 6], Faro{:out}()) == [1, 4, 2, 5, 3, 6] != y

    arr = rand(Int, 1000)
    inshuffled = shuffle(arr, Faro{:in}())
    outshuffled = shuffle(arr, Faro{:out}())

    @test inshuffled[1:2:end] == arr[501:end]
    @test inshuffled[2:2:end] == arr[1:500]

    @test outshuffled[2:2:end] == arr[501:end]
    @test outshuffled[1:2:end] == arr[1:500]

    arr = nshuffle(collect(1:52), 26, Faro{:in}())
    @test arr == collect(52:-1:1)

    revarr = collect(52:-1:1)
    nshuffle!(revarr, 26, Faro{:in}())
    @test revarr == collect(1:52)

    @test nshuffle(collect(1:52), 8, Faro{:out}()) == collect(1:52)

    OA = OffsetArray(1:6, 6:11)
    @test parent(shuffle(OA, Faro{:out}())::OffsetArray) == [1, 4, 2, 5, 3, 6]
    @test parent(shuffle(OA, Faro{:in}())::OffsetArray) == [4, 1, 5, 2, 6, 3]
end

@testset "Cut" begin
    @test shuffle([1, 2, 3, 4, 5], Cut(2)) == [3, 4, 5, 1, 2]

    @test shuffle([1, 2, 3, 4, 5], Cut(0)) == [1, 2, 3, 4, 5]

    @test shuffle([1, 2, 3, 4, 5], Cut(5)) == [1, 2, 3, 4, 5]

    @test_throws DomainError shuffle([1, 2, 3, 4, 5], Cut(7))

    @test nshuffle(collect(1:100), 7, Cut(10)) == vcat(71:100, 1:70)
end

@testset "RandomShuffle" begin

    @test randshuffle === RandomShuffle()

    let mt1 = MersenneTwister(132), mt2 = MersenneTwister(132), x = rand(1000)
        @test shuffle(mt1, x, RandomShuffle()) == Random.shuffle(mt2, x)
    end

    # probably not the same after shuffling
    arr = rand(1000)
    arr_copy = copy(arr)
    @test shuffle!(arr, RandomShuffle()) == arr != arr_copy

    narr = rand(1000)
    narr_copy = copy(narr)
    @test nshuffle!(narr, 5, RandomShuffle()) == narr != narr_copy

    let mt1 = MersenneTwister(42), mt2 = MersenneTwister(42), x = rand(1000)
        @test shuffle(mt1, x, RandomShuffle()) ==
              shuffle!(mt2, x, RandomShuffle())
    end

    let mt1 = MersenneTwister(12), mt2 = MersenneTwister(12), x = rand(1000)
        @test shuffle(mt1, x, RandomShuffle()) ==
              shuffle!(mt2, x, RandomShuffle())
    end

    let mt1 = MersenneTwister(12), mt2 = MersenneTwister(12),
            mt3 = MersenneTwister(12), x = rand(1000)

        y = shuffle(mt1, x, RandomShuffle())
        shuffle!(mt1, y, RandomShuffle())
        shuffle!(mt1, y, RandomShuffle())

        @test nshuffle(mt2, x, 3, RandomShuffle()) == y

        @test nshuffle!(mt3, x, 3, RandomShuffle()) == y
    end
end

@testset "GilbertShannonReeds" begin

    @test gsrshuffle === GilbertShannonReeds()

    # probably not the same after shuffling
    arr = rand(1000)
    @test shuffle(arr, GilbertShannonReeds()) != arr

    narr = rand(1000)
    narr_copy = copy(narr)
    @test nshuffle!(narr, 10, GilbertShannonReeds()) == narr != narr_copy

    let mt1 = MersenneTwister(32), mt2 = MersenneTwister(32), x = rand(1000)
        @test shuffle(mt1, x, GilbertShannonReeds()) ==
              shuffle(mt2, x, GilbertShannonReeds())
    end

    let mt1 = MersenneTwister(153), mt2 = MersenneTwister(153), x = rand(1000)
        @test nshuffle(mt1, x, 7, GilbertShannonReeds()) ==
              nshuffle!(mt2, x, 7, GilbertShannonReeds())
    end
end


@time using Plots
@time using LsqFit
@time using Statistics
# %%
@inline function getindexbc(grid, i)
    if checkbounds(Bool, grid, i)
        @inbounds return grid[i]
    else
        return 0
    end
end
@inline iscritical(i::Int, hc::Int) = i >= hc
getindexbc(grid) = i -> getindexbc(grid)
function evolveat(
        grid::Array{Int,2},
        hood::Array{CartesianIndex{2},1},
        index::CartesianIndex,
        hc::Int,
        topple::Array{Int},
        step::Int
    )
    res = grid[index]
    if res >= hc
        res -= 4
        @inbounds topple[step] += 1
    end
    c = count(h -> iscritical(getindexbc(grid, h + index), hc), hood)
    res = res + c
    return res
end
function evolve!(
        grid::Array{Int,2},
        simgrid::Array{Int,2},
        hood::Array{CartesianIndex{2},1},
        hc::Int,
        topple::Array{Int,1},
        step::Int
    )
    for index in CartesianIndices(grid)
        @inbounds simgrid[index] = evolveat(grid, hood, index, hc, topple, step)
    end
end
function ready_next!(grid::Array{Int,2}, hc::Int, topple::Array{Int,1}, step::Int, max_steps::Int)
    if count(iscritical.(grid, hc)) == 0
        if step == max_steps
            return false, step
        else
            xrand, yrand = rand.(Base.OneTo.(size(grid)))
            grid[xrand, yrand] += 1
            step += 1
        end
    end
    return true, step
end

function simulate(grid::Array{Int}, hc::Int, max_steps::Int)
    hood = CartesianIndex.([(-1, 0), (1, 0), (0, -1), (0, 1)])
    topple = fill(0, max_steps)
    step = 1
    st = true
    simgrid = similar(grid)
    results = Array{Int}[]
    while st
        evolve!(grid, simgrid, hood, hc, topple, step)
        grid, simgrid = simgrid, grid
        st, step = ready_next!(grid, hc, topple, step, max_steps)
    end
    return results, topple
end
# %%

hc = 4
results, topple = simulate(rand(1:hc - 1, 20, 20), 4, 10000);

# %%

function freqtable(k::Array{T}) where T
    d = Dict{T,Int}()
    for i ∈ k
        if i != 0
            d[i] = get(d, i, 0) + 1
        end
    end
    return d
end
function getlogdata(topple_freq)
    x = log.(keys(topple_freq))
    perm = sortperm(x)
    y = log.(values(topple_freq))[perm]
    x = x[perm]
    return x, y
end
function bin(xs, ys, length)
    binx = range(x[1], stop=x[end], length=length)
    _step = step(binx)
    biny = [
        mean(ys[findall(x -> val <= x < (val + _step), xs)])
        for val in binx
    ]
    binx = (binx .+ _step)[1:end - 1]
    return binx, biny
end

x, y = getlogdata(freqtable(topple))
binx, biny = bin(x, y, 100)
plot(binx, biny, st=:scatter)
# %%
@. model(x, p) = p[1] * x + p[2]
fit = curve_fit(model, x[1:45], y[1:45], [0.,0.])
println(fit.param)
xp = 0:0.01:maximum(x)
yp = model(xp, fit.param)
plot!(xp, yp)


search_range = [(-10.0,10.0), (0.0,2.0), (25.0,40.0)]
n_samples = 6
plan = SurrogateModelOptim.scaled_LHC_sampling_plan(search_range,n_samples,1000;trace=:silent)

@testset "scaled LHC plan" begin
    @test vec(extrema(plan,dims=2)) == search_range
end

@testset "function eval" begin
    @test size(SurrogateModelOptim.f_opt_eval(minimum,plan)) == (1,n_samples)
    samples = mapslices(minimum,plan,dims=1)
    @test size(SurrogateModelOptim.f_opt_eval(minimum,plan,samples)) == (1,n_samples)
end


mutable struct ObjectiveSurface
    param1_name::String
    param1_values::Array{Float64,1}
    param2_name::String
    param2_values::Array{Float64,1}
    surface_points::Int64
    replications::Int64
    params::Dict{String, Any}
    weight_matrix::Any
end

function objective_function(parameter_dict::Dict, os::ObjectiveSurface, seed, ind)
    try
        parameter_dict[os.param1_name] = os.param1_values[ind]
        parameter_dict[os.param2_name] = os.param2_values[ind]
        log_price_paths = log.(SLOB(parameter_dict)(seed))
        log_price_paths = vcat(log_price_paths[1:1,:], log_price_paths)
        log_returns = diff(log_price_paths, dims=1)
        objective_value = weighted_moment_distance(os.weight_matrix,
            log_returns)
        return objective_value
    catch e
        return NaN
    end
end

function (os::ObjectiveSurface)(seed, parallel=false)
    iterations = os.surface_points^2

    seeds = Int.(rand(MersenneTwister(seed), UInt32, iterations))
    parameter_vector = fill(os.params, iterations)
    if parallel==true
        os_values = SharedArray{Float64,1}(iterations)
        @sync @distributed for i in 1:iterations
            os_values[i] = objective_function(parameter_vector[i], os, seeds[i], i)
        end
        return Array(os_values)
    else
        os_values = Array{Float64,1}(undef, iterations)
        @showprogress 1 "Iteration: " for i in 1:iterations
            os_values[i] = objective_function(parameter_vector[i], os, seeds[i], i)
        end
        return os_values
    end
end


### A Pluto.jl notebook ###
# v0.19.5

using Markdown
using InteractiveUtils

# ╔═╡ 77b330b5-9fb7-4f74-8306-b147bf052dae
begin
	using CSV, DataFrames
	using MortalityTables, ActuaryUtilities
	using Dates
	using ThreadsX
	using BenchmarkTools
end

# ╔═╡ a7b52cf6-b03d-47e9-818c-22ea8f82f158
using Random

# ╔═╡ 8b86fb4e-2835-4282-8345-6708033423f1
using UnicodePlots

# ╔═╡ c1cacc0f-a789-4fd6-8f1e-a822dd17a66e
md" # Stochastic claims projections demo"

# ╔═╡ 9abe410a-8f6b-4d66-ac0e-570df1e34b47
md" Define a datatype. Not strictly necessary, but will make extending the program with more functions easier."

# ╔═╡ 11f171f8-9137-4e53-9384-f302ad3a773c
md"Type annotations are optional, but providing them is able to coerce the values to be all plain bits (i.e. simple, non-referenced values like arrays are) when the type is constructed. This makes the whole data be stored in the stack and is an example of data-oriented design. It's much slower (~0.5 million policies per second, ~50x slower)"

# ╔═╡ 4310ba76-2a79-49e9-b9c4-78e82871184f
begin
	@enum Sex Female = 1 Male = 2
	@enum Risk Standard = 1 Preferred = 2
end

# ╔═╡ f4d57736-ad94-4139-a372-dc281be9303a
struct Policy
    id::Int
	sex::Sex
    benefit_base::Float64
    COLA::Float64
    mode::Int
    issue_date::Date
    issue_age::Int
    risk::Risk
end

# ╔═╡ be3ae4ef-661c-40b1-acce-4591cc7e6e5f
md"Load the data:"

# ╔═╡ 14901845-d99b-4a26-947d-63f4c8dd8334
sample_csv_data =
IOBuffer(
	raw"id,sex,benefit_base,COLA,mode,issue_date,issue_age,risk
		1,M,100000.0,0.03,12,1999-12-05,30,Std
		2,F,200000.0,0.03,12,1999-12-05,30,Pref"
)

# ╔═╡ 5ee1ca24-45aa-47e7-a948-26b4cc819cdb
policies = let

	# read CSV directly into a dataframe
    # df = CSV.read("sample_inforce.csv",DataFrame) # use local string for notebook
	df = CSV.read(sample_csv_data,DataFrame)

	# map over each row and construct an array of Policy objects
    map(eachrow(df)) do row
        Policy(
            row.id,
			row.sex == "M" ? Male : Female,
            row.benefit_base,
            row.COLA,
            row.mode,
            row.issue_date,
            row.issue_age,
            row.risk == "Std" ? Standard : Preferred,
        )
    end

end

# ╔═╡ 89997227-faa5-45fa-aabb-304dee864fd3
md" Define what mortality gets used:"

# ╔═╡ e8d3578b-560b-491e-8ceb-85057f18373e
mort = Dict(
	Male => MortalityTables.table(988).ultimate,
	Female => MortalityTables.table(992).ultimate,
)

# ╔═╡ febebebd-48e6-4645-9c21-699bfb50eb3a
function mortality(pol::Policy,params)
	return params.mortality[pol.sex]
end

# ╔═╡ 23dec098-dda1-4153-bc3f-e1816354888b
md" This defines the core logic of the policy projection and will write the results to the given `out` container (here, a named tuple of arrays).

This is using a threaded approach where it could be operating on any of the computer's available threads, thus acheiving thread-based parallelism (as opposed to multi-processor (multi-machine) or GPU-based computation which requires formulating the problem a bit differently (array/matrix based). For the scale of computation here, I think I'd apply this model of parallelism."

# ╔═╡ ef0b652f-4f73-463a-be25-2e0466286ce1
function pol_project!(out,policy,params)
	# some starting values for the given policy
	dur = duration(policy.issue_date,params.val_date)
	start_age = policy.issue_age + dur - 1
	COLA_factor = (1+policy.COLA)
	cur_benefit = policy.benefit_base * COLA_factor ^ (dur-1)

	# get the right mortality vector
	qs = mortality(policy,params)

	# grab the current thread's id to write to results container without conflicting with other threads
	tid = Threads.threadid()

	ω = lastindex(qs)

	# inbounds turns off bounds-checking, which makes hot loops faster but first write loop without it to ensure you don't create an error (will crash if you have the error without bounds checking)
    @inbounds for t in 1:min(params.proj_length,ω - start_age)

		q = qs[start_age + t] # get current mortality

		if (rand() < q)
			return # if dead then just return and don't increment the results anymore
		else
			# pay benefit, add a life to the output count, and increment the benefit for next year
			out.benefits[t,tid] += cur_benefit
			out.lives[t,tid] += 1
			cur_benefit *= COLA_factor
		end
    end
end

# ╔═╡ c3e16d9b-af03-4608-b982-c1c99e638e2d
md" Parameters for our projection:"

# ╔═╡ 8ebc2609-576a-4105-aabe-84f1fe51e8de
params = (
    val_date = Date(2021,12,31),
	proj_length = 100,
	mortality = mort,
)

# ╔═╡ b2f183d5-c739-467f-8e77-2546fff33c0c
md" Check the number of threads we're using:"

# ╔═╡ 84b2c368-c1ba-41ed-ab7c-9677a7293f1a
Threads.nthreads()

# ╔═╡ 055c5cc1-0cf3-407a-af4f-057bc2b8d0e8
function project(policies,params)
	threads = Threads.nthreads()
	benefits = zeros(params.proj_length,threads)
	lives = zeros(Int,params.proj_length,threads)
	out = (;benefits,lives)
	ThreadsX.foreach(policies) do pol
		pol_project!(out,pol,params)
	end
	map(x->vec(reduce(+,x,dims=2)),out)
end

# ╔═╡ 51bdd873-32db-494e-a2f2-1975124f8008
md" Example of single projection:"

# ╔═╡ cfe6a86d-a48e-497c-8a7a-837b16a41931
project(repeat(policies,100_000),params)

# ╔═╡ 8c87a09a-c46f-4143-b2c7-87c51a54332c
md" ## Benchmarking"

# ╔═╡ bde3cee6-8b59-42f8-b450-10044636a563
md"

Using a Macbook Air laptop, about 30 million policies able to be stochastically projected per second:
"

# ╔═╡ e087efbc-9a9f-4e09-ac14-2e4067de458b
let
	policies_to_benchmark = 3_000_000
	# adjust the `repeat` depending on how many policies are already in the array
	# to match the target number for the benchmark
	n = policies_to_benchmark ÷ length(policies)

	@benchmark project(p,r) setup=(p=repeat($policies,$n);r=$params)
end

# ╔═╡ 9e45b261-4e29-48d7-99fe-1fc623b5a757
md""" ## Stochastic Set

Loop through and calculate the reults `n` times (this is only running the two policies in the sample data" `n` times).
"""

# ╔═╡ 778f030b-aab1-4191-8902-3f10f402b82e
function stochastic_proj(policies,params,n)

	ThreadsX.map(1:n) do i
		project(policies,params)
	end
end

# ╔═╡ 73ead2b9-733d-470c-a863-9aa4397c35cd
stoch = stochastic_proj(policies,params,1000)

# ╔═╡ 0e8d3fe0-5df3-4db9-88a6-fe4f738211c0
let
	v = [pv(0.03,s.benefits) for s in stoch]
	histogram(v,nbins=15)
end

# ╔═╡ 786312c8-2a40-44db-96cd-a1edc2f29e9a
md" # Further Optimization

In no particular order:
- the RNG could be made faster: https://bkamins.github.io/julialang/2020/11/20/rand.html
- Could make the stochastic set distributed, but at the current speed the overhead of distributed computing is probably more time than it would save. Same thing with GPU projections
- ...

"

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ActuaryUtilities = "bdd23359-8b1c-4f88-b89b-d11982a786f4"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
MortalityTables = "4780e19d-04b9-53dc-86c2-9e9aa59b5a12"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ThreadsX = "ac1d9e8a-700a-412c-b207-f0111f4b6c0d"
UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"

[compat]
ActuaryUtilities = "~3.2.0"
BenchmarkTools = "~1.3.1"
CSV = "~0.10.4"
DataFrames = "~1.3.4"
MortalityTables = "~2.3.0"
ThreadsX = "~0.1.10"
UnicodePlots = "~2.11.2"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.8.0-beta2.85"
manifest_format = "2.0"
project_hash = "00d9a3edb00c3fbe2b994ae8ff9046994b313c67"

[[deps.ActuaryUtilities]]
deps = ["Dates", "Distributions", "ForwardDiff", "QuadGK", "Roots", "StatsBase", "Yields"]
git-tree-sha1 = "59a8e9632a0a69d9931e6e08fa1c5843f24f683a"
uuid = "bdd23359-8b1c-4f88-b89b-d11982a786f4"
version = "3.2.0"

[[deps.Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "af92965fb30777147966f58acb05da51c5616b5f"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.3"

[[deps.ArgCheck]]
git-tree-sha1 = "a3a402a35a2f7e0b87828ccabbd5ebfbebe356b4"
uuid = "dce04be8-c92d-5529-be00-80e4d2c0e197"
version = "2.3.0"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
version = "1.1.1"

[[deps.ArrayLayouts]]
deps = ["FillArrays", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "c23473c60476e62579c077534b9643ec400f792b"
uuid = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
version = "0.8.6"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.AxisAlgorithms]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
git-tree-sha1 = "66771c8d21c8ff5e3a93379480a2307ac36863f7"
uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950"
version = "1.0.1"

[[deps.BSplineKit]]
deps = ["ArrayLayouts", "BandedMatrices", "FastGaussQuadrature", "Interpolations", "LazyArrays", "LinearAlgebra", "Random", "Reexport", "SparseArrays", "StaticArrays"]
git-tree-sha1 = "651e79138b30580c5a1586b67c3298be6c7b8afe"
uuid = "093aae92-e908-43d7-9660-e50ee39d5a0a"
version = "0.8.4"

[[deps.BandedMatrices]]
deps = ["ArrayLayouts", "FillArrays", "LinearAlgebra", "Random", "SparseArrays"]
git-tree-sha1 = "019aa88766e2493c59cbd0a9955e1bac683ffbcd"
uuid = "aae01518-5342-5314-be14-df237901396f"
version = "0.16.13"

[[deps.BangBang]]
deps = ["Compat", "ConstructionBase", "Future", "InitialValues", "LinearAlgebra", "Requires", "Setfield", "Tables", "ZygoteRules"]
git-tree-sha1 = "b15a6bc52594f5e4a3b825858d1089618871bf9d"
uuid = "198e06fe-97b7-11e9-32a5-e1d131e6ad66"
version = "0.3.36"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.Baselet]]
git-tree-sha1 = "aebf55e6d7795e02ca500a689d326ac979aaf89e"
uuid = "9718e550-a3fa-408a-8086-8db961cd8217"
version = "0.1.1"

[[deps.BenchmarkTools]]
deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"]
git-tree-sha1 = "4c10eee4af024676200bc7752e536f858c6b8f93"
uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
version = "1.3.1"

[[deps.Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"

[[deps.CEnum]]
git-tree-sha1 = "eb4cb44a499229b3b8426dcfb5dd85333951ff90"
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
version = "0.4.2"

[[deps.CSV]]
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"]
git-tree-sha1 = "873fb188a4b9d76549b81465b1f75c82aaf59238"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.10.4"

[[deps.Calculus]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad"
uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
version = "0.5.1"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "9950387274246d08af38f6eef8cb5480862a435f"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.14.0"

[[deps.ChangesOfVariables]]
deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
git-tree-sha1 = "1e315e3f4b0b7ce40feded39c73049692126cf53"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.3"

[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "a985dc37e357a3b22b260a5def99f3530fb415d3"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.2"

[[deps.ColorVectorSpace]]
deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"]
git-tree-sha1 = "3f1f500312161f1ae067abe07d13b40f78f32e07"
uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
version = "0.9.8"

[[deps.Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[deps.CommonSolve]]
git-tree-sha1 = "68a0743f578349ada8bc911a5cbd5a2ef6ed6d1f"
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
version = "0.2.0"

[[deps.CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "b153278a25dd42c65abbf4e62344f9d22e59191b"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.43.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "0.5.2+0"

[[deps.CompositionsBase]]
git-tree-sha1 = "455419f7e328a1a2493cabc6428d79e951349769"
uuid = "a33af91c-f02d-484b-be07-31d278c5ca2b"
version = "0.1.1"

[[deps.ConstructionBase]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f74e9d5388b8620b4cee35d4c5a618dd4dc547f4"
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
version = "1.3.0"

[[deps.Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"

[[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1"

[[deps.DataAPI]]
git-tree-sha1 = "fb5f5316dd3fd4c5e7c30a24d50643b73e37cd40"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.10.0"

[[deps.DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "daa21eb85147f72e41f6352a57fccea377e310a9"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.3.4"

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "cc1a8e22627f33c789ab60b36a9132ac050bbf75"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.12"

[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DefineSingletons]]
git-tree-sha1 = "0fba8b706d0178b4dc7fd44a96a92382c9065c2c"
uuid = "244e2a9f-e319-4986-a169-4d1fe445cd52"
version = "0.1.2"

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.DensityInterface]]
deps = ["InverseFunctions", "Test"]
git-tree-sha1 = "80c3e8639e3353e5d2912fb3a1916b8455e2494b"
uuid = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
version = "0.4.0"

[[deps.DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"

[[deps.DiffRules]]
deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "28d605d9a0ac17118fe2c5e9ce0fbb76c3ceb120"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.11.0"

[[deps.Distances]]
deps = ["LinearAlgebra", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "3258d0659f812acde79e8a74b11f17ac06d0ca04"
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
version = "0.10.7"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[deps.Distributions]]
deps = ["ChainRulesCore", "DensityInterface", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SparseArrays", "SpecialFunctions", "Statistics", "StatsBase", "StatsFuns", "Test"]
git-tree-sha1 = "8a6b49396a4058771c5c072239b2e0a76e2e898c"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.58"

[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"

[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
version = "1.6.0"

[[deps.DualNumbers]]
deps = ["Calculus", "NaNMath", "SpecialFunctions"]
git-tree-sha1 = "5837a837389fccf076445fce071c8ddaea35a566"
uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
version = "0.6.8"

[[deps.EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[deps.EzXML]]
deps = ["Printf", "XML2_jll"]
git-tree-sha1 = "0fa3b52a04a4e210aeb1626def9c90df3ae65268"
uuid = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615"
version = "1.1.0"

[[deps.FastGaussQuadrature]]
deps = ["LinearAlgebra", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "58d83dd5a78a36205bdfddb82b1bb67682e64487"
uuid = "442a2c76-b920-505d-bb47-c5924d526838"
version = "0.4.9"

[[deps.FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "9267e5f50b0e12fdfd5a2455534345c4cf2c7f7a"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.14.0"

[[deps.FilePathsBase]]
deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"]
git-tree-sha1 = "129b104185df66e408edd6625d480b7f9e9823a0"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.18"

[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"

[[deps.FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "246621d23d1f43e3b9c368bf3b72b2331a27c286"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.13.2"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[deps.ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "89cc49bf5819f0a10a7a3c38885e7c7ee048de57"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.29"

[[deps.FreeType]]
deps = ["CEnum", "FreeType2_jll"]
git-tree-sha1 = "cabd77ab6a6fdff49bfd24af2ebe76e6e018a2b4"
uuid = "b38be410-82b0-50bf-ab77-7b57e271db43"
version = "4.0.0"

[[deps.FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "87eb71354d8ec1a96d4a7636bd57a7347dde3ef9"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.4+0"

[[deps.FreeTypeAbstraction]]
deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"]
git-tree-sha1 = "b5c7fe9cea653443736d264b85466bad8c574f4a"
uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
version = "0.9.9"

[[deps.Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[deps.GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "83ea630384a13fc4f002b77690bc0afeb4255ac9"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.2"

[[deps.HypergeometricFunctions]]
deps = ["DualNumbers", "LinearAlgebra", "SpecialFunctions", "Test"]
git-tree-sha1 = "cb7099a0109939f16a4d3b572ba8396b1f6c7c31"
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
version = "0.3.10"

[[deps.InitialValues]]
git-tree-sha1 = "4da0f88e9a39111c2fa3add390ab15f3a44f3ca3"
uuid = "22cec73e-a1b8-11e9-2c92-598750a2cf9c"
version = "0.3.1"

[[deps.InlineStrings]]
deps = ["Parsers"]
git-tree-sha1 = "61feba885fac3a407465726d0c330b3055df897f"
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
version = "1.1.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.Interpolations]]
deps = ["AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"]
git-tree-sha1 = "b7bc05649af456efc75d178846f47006c2c4c3c7"
uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
version = "0.13.6"

[[deps.InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "336cc738f03e069ef2cac55a104eb823455dca75"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.4"

[[deps.InvertedIndices]]
git-tree-sha1 = "bee5f1ef5bf65df56bdd2e40447590b272a5471f"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.1.0"

[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[deps.IterTools]]
git-tree-sha1 = "fa6287a4469f5e048d763df38279ee729fbd44e5"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.4.0"

[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.4.1"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.3"

[[deps.LazyArrays]]
deps = ["ArrayLayouts", "FillArrays", "LinearAlgebra", "MacroTools", "MatrixFactorizations", "SparseArrays", "StaticArrays"]
git-tree-sha1 = "721bebe4d0f8581c18fccf272c62000e22a80a2d"
uuid = "5078a376-72f3-5289-bfd5-ec5146d43c02"
version = "0.22.10"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
version = "0.6.3"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
version = "7.81.0+0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.10.2+0"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "09e4b894ce6a976c354a69041a04748180d43637"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.15"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"

[[deps.MarchingCubes]]
deps = ["StaticArrays"]
git-tree-sha1 = "5f768e0a0c3875df386be4c036f78c8bd4b1a9b6"
uuid = "299715c1-40a9-479a-aaf9-4a633d36f717"
version = "0.1.2"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MatrixFactorizations]]
deps = ["ArrayLayouts", "LinearAlgebra", "Printf", "Random"]
git-tree-sha1 = "2212d36f97e01347adb1460a6914e20f2feee853"
uuid = "a3b82374-2e81-5b9e-98ce-41277c0e4c87"
version = "0.9.1"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.0+0"

[[deps.Memoize]]
deps = ["MacroTools"]
git-tree-sha1 = "2b1dfcba103de714d31c033b5dacc2e4a12c7caa"
uuid = "c03570c3-d221-55d1-a50c-7939bbd78826"
version = "0.4.4"

[[deps.MicroCollections]]
deps = ["BangBang", "InitialValues", "Setfield"]
git-tree-sha1 = "6bb7786e4f24d44b4e29df03c69add1b63d88f01"
uuid = "128add7d-3638-4c79-886c-908ea0c25c34"
version = "0.1.2"

[[deps.Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MortalityTables]]
deps = ["Memoize", "OffsetArrays", "Parsers", "Pkg", "QuadGK", "Requires", "StringDistances", "UnPack", "XMLDict"]
git-tree-sha1 = "8ae1af8a53147c592a95fbce9fd37dda2a50355f"
uuid = "4780e19d-04b9-53dc-86c2-9e9aa59b5a12"
version = "2.3.0"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2022.2.1"

[[deps.NaNMath]]
git-tree-sha1 = "737a5957f387b17e74d4ad2f440eb330b39a62c5"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "1.0.0"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"

[[deps.OffsetArrays]]
deps = ["Adapt"]
git-tree-sha1 = "aee446d0b3d5764e35289762f6a18e8ea041a592"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.11.0"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.20+0"

[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
version = "0.8.1+0"

[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[deps.PDMats]]
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
git-tree-sha1 = "027185efff6be268abbaf30cfd53ca9b59e3c857"
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
version = "0.11.10"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "1285416549ccfcdf0c50d4997a94331e88d68413"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.3.1"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.8.0"

[[deps.PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "a6062fe4063cdafe78f4a0a81cfffb89721b30e7"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.4.2"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "47e5f437cc0e7ef2ce8406ce1e7e24d44915f88d"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.3.0"

[[deps.PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "dfb54c4e414caa595a1f2ed759b160f5a3ddcba5"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.3.1"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.Profile]]
deps = ["Printf"]
uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"

[[deps.QuadGK]]
deps = ["DataStructures", "LinearAlgebra"]
git-tree-sha1 = "78aadffb3efd2155af139781b8a8df1ef279ea39"
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
version = "2.4.2"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.Ratios]]
deps = ["Requires"]
git-tree-sha1 = "dc84268fe0e3335a62e315a3a7cf2afa7178a734"
uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
version = "0.4.3"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.Referenceables]]
deps = ["Adapt"]
git-tree-sha1 = "e681d3bfa49cd46c3c161505caddf20f0e62aaa9"
uuid = "42d2dcc6-99eb-4e98-b66c-637b7d73030e"
version = "0.1.2"

[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"

[[deps.Rmath]]
deps = ["Random", "Rmath_jll"]
git-tree-sha1 = "bf3188feca147ce108c76ad82c2792c57abe7b1f"
uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
version = "0.7.0"

[[deps.Rmath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "68db32dff12bb6127bac73c209881191bf0efbb7"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.3.0+0"

[[deps.Roots]]
deps = ["CommonSolve", "Printf", "Setfield"]
git-tree-sha1 = "838b60ee62bebc794864c880a47e331e00c47505"
uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
version = "1.4.1"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"

[[deps.SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "6a2f7d70512d205ca8c7ee31bfa9f142fe74310c"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.3.12"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.Setfield]]
deps = ["ConstructionBase", "Future", "MacroTools", "Requires"]
git-tree-sha1 = "38d88503f695eb0301479bc9b0d4320b378bafe5"
uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46"
version = "0.8.2"

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "5ba658aeecaaf96923dce0da9e703bd1fe7666f9"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "2.1.4"

[[deps.SplittablesBase]]
deps = ["Setfield", "Test"]
git-tree-sha1 = "39c9f91521de844bad65049efd4f9223e7ed43f9"
uuid = "171d559e-b47b-412a-8079-5efa626c420e"
version = "0.1.14"

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "cd56bf18ed715e8b09f06ef8c6b781e6cdc49911"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.4.4"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.StatsAPI]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "c82aaa13b44ea00134f8c9c89819477bd3986ecd"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.3.0"

[[deps.StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "8977b17906b0a1cc74ab2e3a05faa16cf08a8291"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.16"

[[deps.StatsFuns]]
deps = ["ChainRulesCore", "HypergeometricFunctions", "InverseFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "5783b877201a82fc0014cbf381e7e6eb130473a4"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "1.0.1"

[[deps.StringDistances]]
deps = ["Distances", "StatsAPI"]
git-tree-sha1 = "ceeef74797d961aee825aabf71446d6aba898acb"
uuid = "88034a9c-02f8-509d-84a9-84ec65e18404"
version = "0.11.2"

[[deps.StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "e75d82493681dfd884a357952bbd7ab0608e1dc3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.7"

[[deps.SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.0"

[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"]
git-tree-sha1 = "5ce79ce186cc678bbb5c5681ca3379d1ddae11a1"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.7.0"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"

[[deps.TensorCore]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6"
uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50"
version = "0.1.1"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.ThreadsX]]
deps = ["ArgCheck", "BangBang", "ConstructionBase", "InitialValues", "MicroCollections", "Referenceables", "Setfield", "SplittablesBase", "Transducers"]
git-tree-sha1 = "d223de97c948636a4f34d1f84d92fd7602dc555b"
uuid = "ac1d9e8a-700a-412c-b207-f0111f4b6c0d"
version = "0.1.10"

[[deps.TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[deps.Transducers]]
deps = ["Adapt", "ArgCheck", "BangBang", "Baselet", "CompositionsBase", "DefineSingletons", "Distributed", "InitialValues", "Logging", "Markdown", "MicroCollections", "Requires", "Setfield", "SplittablesBase", "Tables"]
git-tree-sha1 = "c76399a3bbe6f5a88faa33c8f8a65aa631d95013"
uuid = "28d57a85-8fef-5791-bfe6-a80928e7c999"
version = "0.4.73"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.UnPack]]
git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b"
uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
version = "1.0.2"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.UnicodePlots]]
deps = ["ColorTypes", "Contour", "Crayons", "Dates", "FileIO", "FreeTypeAbstraction", "LinearAlgebra", "MarchingCubes", "NaNMath", "Printf", "SparseArrays", "StaticArrays", "StatsBase", "Unitful"]
git-tree-sha1 = "4b18663db312f47f9729935066a377e5c6a9bdfb"
uuid = "b8865327-cd53-5732-bb35-84acbb429228"
version = "2.11.2"

[[deps.Unitful]]
deps = ["ConstructionBase", "Dates", "LinearAlgebra", "Random"]
git-tree-sha1 = "b649200e887a487468b71821e2644382699f1b0f"
uuid = "1986cc42-f94f-5a68-af5c-568840ba703d"
version = "1.11.0"

[[deps.WeakRefStrings]]
deps = ["DataAPI", "InlineStrings", "Parsers"]
git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23"
uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5"
version = "1.4.2"

[[deps.WoodburyMatrices]]
deps = ["LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "de67fa59e33ad156a590055375a30b23c40299d3"
uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
version = "0.5.5"

[[deps.XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"

[[deps.XMLDict]]
deps = ["EzXML", "IterTools", "OrderedCollections"]
git-tree-sha1 = "d9a3faf078210e477b291c79117676fca54da9dd"
uuid = "228000da-037f-5747-90a9-8195ccbf91a5"
version = "0.4.1"

[[deps.Yields]]
deps = ["BSplineKit", "ForwardDiff", "LinearAlgebra", "Roots", "UnicodePlots"]
git-tree-sha1 = "dc6d9a1c5e315bca48846095c4b969869735f38b"
uuid = "d7e99b2f-e7f3-4d9e-9f01-2338fc023ad3"
version = "1.1.1"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.12+3"

[[deps.ZygoteRules]]
deps = ["MacroTools"]
git-tree-sha1 = "8c1a8e4dfacb1fd631745552c8db35d0deb09ea0"
uuid = "700de1a5-db45-46bc-99cf-38207098b444"
version = "0.2.2"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.1.0+0"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
version = "1.41.0+1"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
version = "16.2.1+1"
"""

# ╔═╡ Cell order:
# ╟─c1cacc0f-a789-4fd6-8f1e-a822dd17a66e
# ╠═77b330b5-9fb7-4f74-8306-b147bf052dae
# ╟─9abe410a-8f6b-4d66-ac0e-570df1e34b47
# ╟─11f171f8-9137-4e53-9384-f302ad3a773c
# ╠═4310ba76-2a79-49e9-b9c4-78e82871184f
# ╠═f4d57736-ad94-4139-a372-dc281be9303a
# ╟─be3ae4ef-661c-40b1-acce-4591cc7e6e5f
# ╠═14901845-d99b-4a26-947d-63f4c8dd8334
# ╠═5ee1ca24-45aa-47e7-a948-26b4cc819cdb
# ╟─89997227-faa5-45fa-aabb-304dee864fd3
# ╠═e8d3578b-560b-491e-8ceb-85057f18373e
# ╠═febebebd-48e6-4645-9c21-699bfb50eb3a
# ╟─23dec098-dda1-4153-bc3f-e1816354888b
# ╠═ef0b652f-4f73-463a-be25-2e0466286ce1
# ╟─c3e16d9b-af03-4608-b982-c1c99e638e2d
# ╠═a7b52cf6-b03d-47e9-818c-22ea8f82f158
# ╠═8ebc2609-576a-4105-aabe-84f1fe51e8de
# ╟─b2f183d5-c739-467f-8e77-2546fff33c0c
# ╠═84b2c368-c1ba-41ed-ab7c-9677a7293f1a
# ╠═055c5cc1-0cf3-407a-af4f-057bc2b8d0e8
# ╟─51bdd873-32db-494e-a2f2-1975124f8008
# ╠═cfe6a86d-a48e-497c-8a7a-837b16a41931
# ╟─8c87a09a-c46f-4143-b2c7-87c51a54332c
# ╟─bde3cee6-8b59-42f8-b450-10044636a563
# ╠═e087efbc-9a9f-4e09-ac14-2e4067de458b
# ╟─9e45b261-4e29-48d7-99fe-1fc623b5a757
# ╠═778f030b-aab1-4191-8902-3f10f402b82e
# ╠═73ead2b9-733d-470c-a863-9aa4397c35cd
# ╠═8b86fb4e-2835-4282-8345-6708033423f1
# ╠═0e8d3fe0-5df3-4db9-88a6-fe4f738211c0
# ╟─786312c8-2a40-44db-96cd-a1edc2f29e9a
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


using Soss

gps = @model speed,n,t begin
    x ~ For(1:n) do i
        Normal(μ = speed * t[i])
    end
    return x
end

radar = @model speed,n begin
    v ~ For(1:n) do i
        Normal(speed, 5.0)
    end
end

fullmodel = @model n,t begin
    speed ~ Normal(0.0, 100.0)
    x ~ gps(;speed, n, t)
    v ~ radar(;speed, n)
end

n = 10
t = 1:10

truth = rand(fullmodel(;n, t))
v = truth.v

post = fullmodel(;n, t) |(v=truth.v,)

dynamicHMC(post)

logdensity(post, truth)

speed = truth.speed
x = truth.x
v = truth.v

_ℓ = 0.0
_ℓ += logdensity(Normal(0.0, 100.0), speed)
speed = predict(Normal(0.0, 100.0), speed)
_ℓ += logdensity(gps(; speed, n, t), x)
x = predict(gps(; speed, n, t), x)
_ℓ += logdensity(radar(; speed, n), v)
v = predict(radar(; speed, n), v)
_ℓ


using SymmetricFormats, Test, LinearAlgebra

n=10
for elty in (Float32, Float64)
    @testset "spr! $elty" begin
        α = rand(elty)
        M = rand(elty, n, n)
        AL = Symmetric(M, :L)
        AU = Symmetric(M, :U)
        x = rand(elty, n)

        ALP_result_julia_lower = SymmetricFormats.pack(α*x*x' + AL, :L)
        ALP_result_blas_lower = SymmetricFormats.pack(AL, :L)
        SymmetricFormats.spr!('L', α, x, ALP_result_blas_lower)
        @test ALP_result_julia_lower ≈ ALP_result_blas_lower

        AUP_result_julia_upper = SymmetricFormats.pack(α*x*x' + AU, :U)
        AUP_result_blas_upper = SymmetricFormats.pack(AU, :U)
        SymmetricFormats.spr!('U', α, x, AUP_result_blas_upper)
        @test AUP_result_julia_upper ≈ AUP_result_blas_upper
    end
end


#=

  Set partition problem in Julia ConstraintSolver.jl

  Problem formulation from
    http://www.koalog.com/resources/samples/PartitionProblem.java.html
  """
   This is a partition problem.
   Given the set S = {1, 2, ..., n},
   it consists in finding two sets A and B such that:

    *  A U B = S
    *  |A| = |B|
    * sum(A) = sum(B)
    * sum_squares(A) = sum_squares(B)
  """

  This model was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#

using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
const CS = ConstraintSolver
include("constraints_utils.jl")

function set_partition(n=8,num_sets=2,print_solutions=true,all_solutions=true)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   "all_solutions"=> all_solutions,
                                                            # "all_optimal_solutions"=>true,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                                            # "branch_strategy" => :IMPS, # default
                                                            "branch_strategy" => :ABS, # Activity Based Search
                                                            "activity.decay" => 0.999, # default 0.999
                                                            "activity.max_probes" => 100, # default, 10
                                                            "activity.max_confidence_deviation" => 1, # default 20

                                                            # "simplify"=>false,
                                                            # "simplify"=>true, # default

                                                            "time_limit"=>16,

                                                            # "backtrack" => false, # default true
                                                            # "backtrack_sorting" => false, # default true

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            # "lp_optimizer" => glpk_optimizer,
                                                            # "lp_optimizer" => ipopt_optimizer,
                                        ))

    println("n:$n num_sets:$num_sets")
    n2 = round(Int, n / num_sets)
    if n % 4 != 0
        println("n ($n) must be a multiple of 4")
        return
    end

    # Which set (1..num_sets) is  1..n assigned to
    @variable(model, 1 <= a[1:n] <= num_sets, Int)

    # number of integers in each set
    @variable(model, n2 <= counts[1:num_sets] <= n2, Int)

    @variable(model, 0 <= sums[1:num_sets] <= n*n, Int)
    @variable(model, 0 <= sum_squared[1:num_sets] <= n*n*n, Int)

    for s in 1:num_sets
        count_ctr(model, a, :(==), s, counts[s])
        b = @variable(model, [1:n],Bin)
        for i in 1:n
            @constraint(model, b[i] := {a[i] == s})
        end
        @constraint(model,sums[s] == sum(i*b[i] for i in 1:n))
        @constraint(model,sum_squared[s] == sum(i*i*b[i] for i in 1:n))
    end

    for s in 1:num_sets-1
        @constraint(model,sums[s] == sums[s+1])
        @constraint(model,sum_squared[s] == sum_squared[s+1])
    end

    # Symmetry breaking
    @constraint(model,a[1] == 1)

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                println("solution #$sol")
                a_val = convert.(Integer,JuMP.value.(a; result=sol))
                sums_val = convert.(Integer,JuMP.value.(sums; result=sol))
                sum_squared_val = convert.(Integer,JuMP.value.(sum_squared; result=sol))
                counts_val = convert.(Integer,JuMP.value.(counts; result=sol))
                println("a:$a_val")
                for s in 1:num_sets
                    println("set $s: ", [i for i in 1:n if a_val[i] == s])
                end
                println("sums:$sums_val")
                println("sum_squared:$sum_squared_val")
                println("counts:$counts_val")
                println()
            end
        end
    else
        println("status:$status")
    end

    return status
end

# @time set_partition(16,2,true,false)
function testit()
    for i in 2:10
        n = 4*i
        println("\nn:$n")
        @time status = set_partition(n,2,true,false)
        if status != MOI.OPTIMAL
            break
        end
    end
end

testit()


module MPSLabXD

# Imports
using TensorLabXD
const TX = TensorLabXD

using TupleLabXD
const TU = TupleLabXD

using TensorContractionsXD
const TC = TensorContractionsXD

using KrylovKit

using LinearAlgebra

# Export
export AbstractMPS, FiniteMPS
export field, spacetype, sectortype, get_chi, entanglement_entropy

export make_right_canonical, make_left_canonical, norm

export MPO, TransverseFieldIsing

export FiniteEnvironment, initialize_FiniteEnvironment, update_left_env, update_right_env

export finiteDMRG, one_sweep, one_update, build_two_site_H_eff
# source codes
include("MPS/abstractmps.jl")
include("MPS/finitemps.jl")
include("MPO/mpo.jl")
include("Environment/finiteEnvironment.jl")
include("GroundState/finiteDMRG.jl")

end