function Bspline_text(x, ϕ, j, k, color)
    xⱼ = mean_position(x, ϕ)
    txt = text(xⱼ,0.7maximum(ϕ),
               latexstring("\\mathrm{B}_{$j,$k}"),
               horizontalalignment="center",
               color=color)
    gray = 1.0 - mean_color(color)
    outline = RGB(lerp(gray, (gray<0.5 ? 0 : 1), 0.6)*[1,1,1]...)
    txt.set_path_effects([PathEffects.Stroke(linewidth=1,
                                             foreground="#$(hex(outline))"),
                          PathEffects.Normal()])
end

function bsplines_cardinal_splines()
    a,b = 1.0,6.0
    x = range(a, stop=b, length=1001)
    cfigure("cardinal splines",figsize=(7,9)) do
        t = 0
        for k = 1:5
            t = ArbitraryKnotSet(k, a:b, 1, 1)
            R = BSpline(t)
            χ = R[x,:]
            csubplot(5,1,k,nox=k<5) do
                for j = 1:size(χ,2)
                    ϕ = view(χ, :, j)
                    l=plot(x, ϕ)[1]
                    Bspline_text(x, ϕ, j, k, l.get_color())
                end
                ylabel(latexstring("k = $k"))
                margins(0.1,0.1)
            end
        end
        xlabel(L"x")
    end
    savedocfig("bsplines/cardinal-splines")
end

function bsplines_discontinuous_splines()
    t = ArbitraryKnotSet(3, [0.0, 1, 1, 3, 4, 6], 1, 3)
    r = range(first(t), stop=last(t), length=301)
    R = BSpline(t)
    χ = R[r, :]

    cfigure("basis functions",figsize=(7,6)) do
        csubplot(211,nox=true) do
            plot(r, χ, length(r) < 30 ? ".-" : "-")
            margins(0.1, 0.1)
        end
        csubplot(212) do
            rplot(t)
            margins(0.1, 0.1)
            xlabel(L"r")
            ylabel("Multiplicity")
        end
    end
    savedocfig("bsplines/discontinuous-splines")
end

function bsplines_full_multiplicity_splines()
    a,b = 1.0,6.0
    x = range(a, stop=b, length=1001)
    cfigure("splines",figsize=(7,9)) do
        t = 0
        for k = 1:5
            t = ArbitraryKnotSet(k, a:b)
            R = BSpline(t)
            χ = R[x,:]
            csubplot(5,1,k,nox=k<5) do
                for j = 1:size(χ,2)
                    ϕ = view(χ, :, j)
                    l=plot(x, ϕ)[1]
                    Bspline_text(x, ϕ, j, k, l.get_color())
                end
                ylabel(latexstring("k = $k"))
                margins(0.1,0.1)
            end
        end
        xlabel(L"x")
    end
    savedocfig("bsplines/full-multiplicity-splines")
end

function bsplines_spline1d()
    k = 4
    t = LinearKnotSet(k, 0, 1, 5)
    B = BSpline(t)
    x = range(0, stop=1, length=301)
    χ = B[x, :]

    i = 1:size(B,2)
    c = sin.(i)
    s = χ*c

    cfigure("spline 1d", figsize=(7,9)) do
        csubplot(311, nox=true) do
            plot(x, s)
            plot([mean_position(x, view(χ, :, j)) for j in i], c, "s-")
            ylabel(L"s(x)")
            margins(0.1,0.1)
        end
        csubplot(312, nox=true) do
            for j = 1:size(χ,2)
                ϕ = view(χ, :, j)
                l=plot(x, ϕ)[1]
                Bspline_text(x, ϕ, j, k, l.get_color())
            end
            margins(0.1,0.1)
        end
        csubplot(313) do
            rplot(t)
            margins(0.1,0.1)
            xlabel(L"x")
        end
    end

    savedocfig("bsplines/spline-1d")
end

function bsplines_spline2d()
    k = 4
    t = LinearKnotSet(k, 0, 1, 5)
    B = BSpline(t)
    x = range(0, stop=1, length=301)
    χ = B[x, :]

    i = 1:size(B,2)
    c = [sin.(i) tan.(i)]
    s = χ*c

    cfigure("spline 2d", figsize=(6,6)) do
        plot(s[:,1], s[:,2])
        plot(c[:,1], c[:,2], "s-")
        xlabel(L"x")
        ylabel(L"y")
        margins(0.1,0.1)
    end

    savedocfig("bsplines/spline-2d")
end

function bsplines_quadrature_points()
    t = ArbitraryKnotSet(3, [0.0, 1, 1, 3, 4, 6], 1, 3)
    x = range(first(t), stop=last(t), length=301)
    B = BSpline(t)
    χ = B[x, :]

    xl = nothing
    cfigure("quadrature points", figsize=(7,9)) do
        csubplot(411, nox=true) do
            plot(x, χ, length(x) < 30 ? ".-" : "-")
            margins(0.1, 0.1)
        end
        csubplot(412, nox=true) do
            rplot(t)
            margins(0.1, 0.1)
            xl = xlim()
            ylabel("Multiplicity")
        end
        csubplot(413, nox=true) do
            plot(B.x, 1:length(B.x), ".-")
            margins(0.1,0.1)
            xlim(xl)
            ylabel(L"i")
        end
        csubplot(414) do
            plot(B.x, B.w, ".-")
            margins(0.1,0.1)
            xlim(xl)
            xlabel(L"x")
            ylabel(L"w_i")
        end
    end
    savedocfig("bsplines/quadrature-points")
end

function bsplines_function_interpolation()
    t = LinearKnotSet(7, 0, 7, 10)
    B = BSpline(t)
    x = range(0, stop=7, length=301)
    χ = B[x, :]
    c = B \ sin.(axes(B,1))
    i = 1:size(B,2)

    cfigure("function bsplines_interpolation",figsize=(7,9)) do
        csubplot(311, nox=true) do
            plot(x, χ*c)
            plot([mean_position(x, view(χ, :, j)) for j in i], c, "s-")
        end
        csubplot(312, nox=true) do
            plot(x, χ*c-sin.(x))
            ylabel("Error")
        end
        csubplot(313) do
            plot(x, χ)
            xlabel(L"x")
        end
    end
    savedocfig("bsplines/function-interpolation")
end

function bsplines_restricted_basis_interpolation()
    t = LinearKnotSet(7, 0.0, 1.0, 6)
    x = range(first(t), stop=last(t), length=300)[2:end-1]

    B = BSpline(t)
    B̃ = B[:,2:end-1]

    f1 = x -> sin(2π*x)
    f2 = x -> cos(2π*x)

    xx = axes(B, 1)
    c1 = B \ f1.(xx)
    c2 = B \ f2.(xx)

    c̃1 = B̃ \ f1.(xx)
    c̃2 = B̃ \ f2.(xx)

    χ = B[x, :]
    χ̃ = B̃[x, :]
    x_avg = [mean_position(x, view(χ, :, j)) for j in 1:size(B,2)]
    x̃_avg = [mean_position(x, view(χ̃, :, j)) for j in 1:size(B̃,2)]

    yl = nothing
    cfigure("restricted basis interpolation", figsize=(7,9)) do
        csubplot(321, nox=true) do
            l=plot(x, χ*c1)[1]
            plot(x_avg, c1, "s:", color=l.get_color())
            l=plot(x, χ*c2)[1]
            plot(x_avg, c2, "s:", color=l.get_color())
        end
        csubplot(322, nox=true) do
            l=plot(x, χ̃*c̃1)[1]
            plot(x̃_avg, c̃1, "s:", color=l.get_color())
            l=plot(x, χ̃*c̃2)[1]
            plot(x̃_avg, c̃2, "s:", color=l.get_color())
            axes_labels_opposite(:y)
        end
        csubplot(324, nox=true) do
            semilogy(x, abs.(χ̃*c̃1 - f1.(x)))
            semilogy(x, abs.(χ̃*c̃2 - f2.(x)))
            axes_labels_opposite(:y)
            yl = ylim()
            ylabel("Error")
        end
        csubplot(323, nox=true) do
            semilogy(x, abs.(χ*c1 - f1.(x)))
            semilogy(x, abs.(χ*c2 - f2.(x)))
            ylabel("Error")
            ylim(yl)
        end
        csubplot(325) do
            plot(x, χ)
            xlabel(L"x")
            yl = ylim()
        end
        csubplot(326) do
            plot(x, χ̃)
            axes_labels_opposite(:y)
            ylim(yl)
            xlabel(L"x")
        end
    end
    savedocfig("bsplines/restricted-basis-interpolation")
end

function bsplines_smooth_interpolation()
    f = x -> sin(2π*x)

    rng = MersenneTwister(123);

    N = 10
    x = clamp.(sort(range(0, stop=1, length=N) + 0.1(2rand(rng,N) .- 1)), 0, 1);
    y = f.(x) + 0.1(2rand(rng,N) .- 1);

    t3 = LinearKnotSet(3, 0.0, 1.0, 6);
    t4 = LinearKnotSet(4, 0.0, 1.0, 6);
    B3 = BSpline(t3,k′=1)
    B4 = BSpline(t4,k′=1)

    c3 = B3[x,:] \ y
    c4 = B4[x,:] \ y

    r = range(first(t3), stop=last(t3), length=300)
    χ3 = B3[r,:]
    χ4 = B4[r,:]

    cfigure("smooth interpolation") do
        plot(x, y, "s", label="Samples")
        plot(r, χ3*c3, label="3rd order spline")
        plot(r, χ4*c4, label="4th order spline")
        plot(r, f.(r), "--", label=L"\sin(2\pi x)")
        legend()
    end
    savedocfig("bsplines/smooth-interpolation")
end

function bsplines_diagonal_operators()
    k = 7
    N = 31

    a,b = 0,70

    coulomb(r) = -1/r

    cfigure("V(x)", figsize=(7,9)) do
        for (j,(t,x)) in enumerate([(LinearKnotSet(k, a, b, N),
                                     range(a, stop=b, length=500)[2:end-1]),
                                    (ExpKnotSet(k, -1.0, log10(b), N),
                                     10 .^ range(-1.0, stop=log10(b), length=500)[2:end-1])])
            B = BSpline(t)[:,2:end-1]
            S = B'B

            χ = B[x,:]

            xx = axes(B,1)

            f = B \ (x -> x^2*exp(-x)).(xx)
            g = B \ (x -> -x*exp(-x)).(xx)

            V = B'*QuasiDiagonal(coulomb.(xx))*B
            g̃ = S \ V*f

            csubplot(3,2,(j-1)+1, nox=true) do
                plot(x, χ*f, label=L"f(x)")
                plot(x, χ*g̃, label=L"\tilde{g}(x)")
                plot(x, χ*g, "--", label=L"g(x)")
                yl=ylim()
                plot(x, coulomb.(x), label=L"V(x)")
                ylim(yl)
                legend(framealpha=0.75)
                xscale("log")
                iseven(j) && axes_labels_opposite(:y)
            end
            csubplot(3,2,(j-1)+3, nox=true) do
                plot(x, χ*(g-g̃), label=L"g(x)-\tilde{g}(x)")
                legend(framealpha=0.75)
                xscale("log")
                ylabel("Error")
                iseven(j) && axes_labels_opposite(:y)
            end
            csubplot(3,2,(j-1)+5) do
                plot(x, χ)
                xscale("log")
                xlabel(L"x")
                iseven(j) && axes_labels_opposite(:y)
            end
        end
    end
    savedocfig("bsplines/diagonal-operators")
end

function find_second_derivative(B, f::Function)
    S = B'*B
    x = axes(B,1)
    D = Derivative(x)
    ∇² = B'*D'*D*B

    # Project function onto B-spline basis
    cf = B \ f.(x)
    # Find derivative
    cg = S \ ∇²*cf

    cf,cg
end

function bsplines_sine_derivative()
    t = LinearKnotSet(10, 0, 10, 30);
    B = BSpline(t)[:,2:end-1]

    f = x -> sin(2π*x)
    g = x -> -4π^2*sin(2π*x)

    cf,cg = find_second_derivative(B, f)

    x = range(first(t), stop=last(t), length=1001)
    χ = B[x, :]

    cfigure("derivatives", figsize=(7,9)) do
        csubplot(411,nox=true) do
            l=plot(x, χ*cf, label=L"\tilde{f}(x)")[1]
            plot(x, f.(x), ":", label=L"f(x)")
            legend(framealpha=0.75)
        end
        csubplot(412,nox=true) do
            l=plot(x, (χ*cg), label=L"\tilde{g}(x)")[1]
            plot(x, g.(x), ":", label=L"g(x)")
            legend(framealpha=0.75)
        end
        csubplot(413,nox=true) do
            semilogy(x, abs.(χ*cg-g.(x)), label=L"|\tilde{g}(x)-g(x)|")
            legend(framealpha=0.75)
            ylim(1e-5,1)
            ylabel("Error")
        end
        csubplot(414) do
            plot(x, χ)
            xlabel(L"x")
        end
    end
    savedocfig("bsplines/sine-derivative")
end

function bsplines_ode_hookes_law(xₘₐₓ, kspring, k, N)
    t = LinearKnotSet(k, 0, xₘₐₓ, N)
    # By omitting the first basis function, we enforce V(0) = 0
    B = BSpline(t,k′=1)[:,2:end]
    xx = axes(B, 1)
    S = B'B

    D = Derivative(xx)
    ∇ = B'*D*B

    # Hooke's law
    F = x -> -kspring*x
    # Exact potential
    V = x -> kspring*x^2/2

    # Expand Hooke's law on B-splines
    cF = B \ F.(xx)
    # Solve for expansion coefficients of potential
    cV = -∇ \ S*cF

    x = range(first(t), stop=last(t), length=500)
    χ = B[x,:]
    x_avg = [mean_position(x, view(χ, :, j)) for j in 1:size(B,2)]

    cfigure("Hooke's law",figsize=(7,9)) do
        csubplot(411,nox=true) do
            l=plot(x, χ*cF, label=L"\tilde{F}(x)")[1]
            plot(-x, -χ*cF, "--", color=l.get_color())
            plot(x_avg, cF, ".:", color=l.get_color(), label=L"c_F")
            plot(x, F.(x), ":", label=L"F(x)")
            legend(framealpha=0.75)
        end
        csubplot(412,nox=true) do
            l=plot(x, χ*cV, label=L"\tilde{V}(x)")[1]
            plot(-x, χ*cV, "--", color=l.get_color())
            plot(x_avg, cV, ".:", color=l.get_color(), label=L"c_V")
            plot(x, V.(x), ":", label=L"V(x)")
            legend(framealpha=0.75)
        end
        csubplot(413,nox=true) do
            plot(x, χ*cV - V.(x))
            xl = xlim()
            xlim(-xl[2],xl[2])
            ylabel("Error")
        end
        csubplot(414) do
            plot(x, χ)
            xl = xlim()
            xlim(-xl[2],xl[2])
            xlabel(L"x")
        end
    end
    savedocfig("bsplines/hookes-law-$(k)-$(N)")
end

struct ShiftAndInvert{TA,TB,TT}
    A⁻¹::TA
    B::TB
    temp::TT
end

Base.size(S::ShiftAndInvert, args...) = size(S.A⁻¹, args...)
Base.eltype(S::ShiftAndInvert) = eltype(S.A⁻¹)

function LinearAlgebra.mul!(y,M::ShiftAndInvert,x)
    mul!(M.temp, M.B, x)
    ldiv!(y, M.A⁻¹, M.temp)
end

construct_linear_map(A,B,σ=0) =
    ShiftAndInvert(factorize(A-σ*B),B,Vector{eltype(A)}(undef, size(A,1)))

function bsplines_hydrogen_eigenstates()

    k = 7
    N = 31

    a,b = 0,70

    coulomb(r) = -1/r

    nev = 5
    σ = -0.5

    n = 1:nev

    cfigure("Hydrogen", figsize=(7,9)) do
        for (j,(t,x,tol)) in enumerate([(LinearKnotSet(k, a, b, N),
                                         range(a, stop=b, length=500)[2:end-1],
                                         9e-3),
                                        (ExpKnotSet(k, -1.0, log10(b), N),
                                         10 .^ range(-1.0, stop=log10(b), length=500)[2:end-1],
                                         2e-7)])
            B = BSpline(t)[:,2:end-1]
            xx = axes(B, 1)
            S = B'B

            χ = B[x,:]

            D = Derivative(xx)

            ∇² = B'*D'*D*B

            T = -∇²/2

            V = B'*QuasiDiagonal(coulomb.(xx))*B

            H = T + V

            schurQR,history = partialschur(construct_linear_map(H, S, σ), nev=nev)
            println(history)

            θ = schurQR.eigenvalues
            E = real(σ .+ inv.(θ))

            csubplot(3,2,(j-1)+1, nox=true) do
                for i = 1:nev
                    ϕ = schurQR.Q[:,i]
                    plot(x, real(E[i]) .+ abs2.(χ*ϕ)/(ϕ'S*ϕ)[1])
                end
                # legend(framealpha=0.75)
                xscale("log")
                iseven(j) && axes_labels_opposite(:y)
            end
            csubplot(3,2,(j-1)+3, nox=true) do
                plot(x, coulomb.(x))
                xscale("log")
                iseven(j) && axes_labels_opposite(:y)
            end
            csubplot(3,2,(j-1)+5) do
                plot(x, χ)
                plot(x, χ)
                xscale("log")
                xlabel(L"x")
                iseven(j) && axes_labels_opposite(:y)
            end
        end
    end
    savedocfig("bsplines/hydrogen-eigenstates")
end

mkpath(joinpath(fig_dir, "bsplines"))
@echo bsplines_cardinal_splines()
@echo bsplines_discontinuous_splines()
@echo bsplines_full_multiplicity_splines()
@echo bsplines_spline1d()
@echo bsplines_spline2d()
@echo bsplines_quadrature_points()
@echo bsplines_function_interpolation()
@echo bsplines_restricted_basis_interpolation()
@echo bsplines_smooth_interpolation()
@echo bsplines_diagonal_operators()
@echo bsplines_sine_derivative()
@echo bsplines_ode_hookes_law(3, 0.1, 7, 30)
@echo bsplines_ode_hookes_law(3, 0.1, 3, 1)
@echo bsplines_hydrogen_eigenstates()


# ---
# jupyter:
#   jupytext:
#     formats: jl:light,ipynb
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.5.1
#   kernelspec:
#     display_name: Julia 1.4.2
#     language: julia
#     name: julia-1.4
# ---

# # MintsHelper: Generating 1- and 2-electron Integrals with  Psi4
#
# In all of quantum chemistry, one process which is common to nearly every method is the evaluation of one-
# and two-electron integrals.  Fortunately, we can leverage infrastructure in
# Psi4  to perform this task for us.  This tutorial will discuss the [``psi4.core.MintsHelper``](http://psicode.org/psi4manual/master/psi4api.html#psi4.core.MintsHelper "Go to API") class, which is an
# interface for the powerful Psi4 ``libmints`` library which wraps the `libint` library, where these integrals are actually computed.
#
# ## MintsHelper Overview
# In order to compute 1- and 2-electron integrals, we first need a molecule and basis set with which to work.  So,
# before diving into `MintsHelper`, we need to build these objects.  In the cell below, we have imported
#  Psi4  and NumPy, defined a water molecule, and set the basis to
# cc-pVDZ.  We've also set the memory available to  Psi4, as well as
# defined a variable `numpy_memory` which we will discuss later.

# +
# ==> Setup <==
# Import statements
using PyCall: pyimport
psi4 = pyimport("psi4")
np   = pyimport("numpy")
using Formatting: printfmt

# Memory & Output file
psi4.set_memory(Int(2e9))
numpy_memory = 2
psi4.core.set_output_file("output.dat", false)

# Molecule definition
h2o = psi4.geometry("""
O
H 1 0.96
H 1 0.96 2 104.5
""")

# Basis Set
psi4.set_options(Dict("basis" => "cc-pvdz"))
# -

# Now, we are ready to create an instance of the `MintsHelper` class.  To do this, we need to pass a `BasisSet`
# object to the `MintsHelper` initializer.  Fortunately, from the previous tutorial on the `Wavefunction` class, we know
# that we can obtain such an object from an existing wavefunction.  So, let's build a new wavefunction for our molecule,
# get the basis set object, and build an instance of `MintsHelper`:

# +
# ==> Build MintsHelper Instance <==
# Build new wavefunction
wfn = psi4.core.Wavefunction.build(h2o, psi4.core.get_global_option("basis"))

# Initialize MintsHelper with wavefunction's basis set
mints = psi4.core.MintsHelper(wfn.basisset())
# -

# Below are summarized several commonly computed quantities and how to obtain them using a `MintsHelper` class method:
#
# | Quantity | Function | Description |
# |----------|----------|-------------|
# | AO Overlap integrals | [mints.ao_overlap()](http://psicode.org/psi4manual/master/psi4api.html#psi4.core.MintsHelper.ao_overlap "Go to Documentation") | Returns AO overlap matrix as a `psi4.core.Matrix` object |
# | AO Kinetic Energy | [mints.ao_kinetic()](http://psicode.org/psi4manual/master/psi4api.html#psi4.core.MintsHelper.ao_kinetic "Go to Documentation") | Returns AO kinetic energy matrix as a `psi4.core.Matrix` object |
# | AO Potential Energy | [mints.ao_potential()](http://psicode.org/psi4manual/master/psi4api.html#psi4.core.MintsHelper.ao_potential "Go to Documentation") | Returns AO potential energy matrix as a `psi4.core.Matrix` object |
# | AO Electron Repulsion Integrals | [mints.ao_eri()](http://psicode.org/psi4manual/master/psi4api.html#psi4.core.MintsHelper.ao_eri "Go to Documentation") | Returns AO electron repulsion integrals as a `psi4.core.Matrix` object

# As discussed previously, any of these `psi4.core.Matrix` objects can be accessed as Julia arrays, which is the preferred
# method in Psi4Julia.  For a Psi4 matrix `A`, we can access a Julia view using `psi4view(A)`, or we can make a
# copy of the matrix using `np.array(A)`.  This works as one would expect, converting square matrices into arrays of Array{Float64,2} type, for the overlap (S), kinetic energy (T), and potential energy (V) matrices.  In Psi4, the electron repulsion integrals
# (ERIs) are handled somewhat differently; `mints.ao_eri()` returns the rank-4 ERI tensor packed into a 2D matrix.  If the
# four indices of the ERI are p, q, r, s, then this element of the Psi4 Matrix can be accessed by first computing composite
# indices `pq = p * nbf + q` and `rs = r * nbf + s`, and then accessing element `A.get(pq,rs)`.  However, for convenience,
# the Julia view is a rank-4 tensor, and a particular ERI is more simply accessed like this:
# ~~~julia
# I = mints.ao_eri()
# I = psi4view(I)
# val = I[p,q,r,s]
# ~~~

function psi4view(psi4matrix)
   # Assumes Float64 type, C ordering
   if !hasproperty(psi4matrix,:__array_interface__)
      throw(ArgumentError("Input matrix cannot be accessed. Cannot be an rvalue"))
   end
   array_interface = psi4matrix.__array_interface__
   array_interface["data"][2] == false   || @warn "Not writable"
   array_interface["strides"] == nothing || @warn "Different ordering than C"
   array_interface["typestr"] == "<f8"   || @warn "Not little-endian Float64 eltype"
   ptr   = array_interface["data"][1]
   shape = reverse(array_interface["shape"])
   ndims = length(shape)
   unsafe_wrap(Array{Float64,ndims}, Ptr{Float64}(ptr), shape)
end

# In addition to these methods, another which is worth mentioning is the `MintsHelper.mo_eri()` ([Go to documentation](http://psicode.org/psi4manual/master/psi4api.html#psi4.core.MintsHelper.mo_eri)) function, which can transform
# the four-index, two-electron repulsion integrals from the atomic orbital (AO) to the molecular orbital (MO) basis,
# which will be important in MP2 theory.

# ## Memory Considerations

# Before moving forward to computing any 1- or 2-electron integrals, we must first discuss the memory requirements of
# these objects.  Whenever these quantities are computed, they are stored directly in memory (a.k.a. RAM,
# *not* on the hard drive) which, for a typical laptop or personal computer, usually tops out at around 16 GB of
# space.  The storage space required by the two-index AO overlap integrals and four-index ERIs scales as ${\cal O}(N^2)$
# and ${\cal O}(N^4)$, respectively, where $N$ is the number of AO basis functions.  This means that for a
# system with 500 AO basis functions, while the AO overlap integrals will only require 1 MB of memory to store,
# the ERIs will require a staggering **500 GB** of memory!! This can be reduced to **62.5 GB** of memory if integral permutational symmetry is used.
# However, this complicates the bookkeeping, and is not used in the `mints` functions discussed above.  For this reason, as well as the steep computational
# scaling of many of the methods demonstrated here, we limit ourselves to small systems ($\sim50$ basis functions)
# which should not require such egregious amounts of memory.  Additionally, we will employ a "memory check" to catch
# any case which could potentially try to use more memory than is available:
# ~~~julia
# # Memory check for ERI tensor
# I_size = nbf^4 * 8.e-9
# printfmt("Size of the ERI tensor will be {:4.2f} GB.\n", I_size)
# memory_footprint = I_size * 1.5
# if I_size > numpy_memory
#     psi4.core.clean()
#     throw(OutOfMemoryError("Estimated memory utilization ($memory_footprint GB) exceeds allotted memory " *
#                            "limit of $numpy_memory GB."))
# ~~~
# In this example, we have somewhat arbitrarily assumed that whatever other matrices we may need, in total their memory
# requirement will not exceed 50% of the size of the ERIs (hence, the total memory footprint of `I_size * 1.5`)
# Using the `numpy_memory` variable, we are able to control whether the ERIs will be computed, based on the amount of
# memory required to store them.
#
# **NOTE: DO NOT EXCEED YOUR SYSTEM'S MEMORY.  THIS MAY RESULT IN YOUR PROGRAM AND/OR COMPUTER CRASHING!**

# ## Examples: AO Overlap, AO ERIs, Core Hamiltonian
# The cell below demonstrates obtaining the AO overlap integrals, conducting the
# above memory check, and computing the ERIs and core Hamiltonian matrix for our water molecule.

# +
# ==> Integrals galore! <==
# AO Overlap
S = mints.ao_overlap()
S = psi4view(S) # psi4view(mints.ao_overlap()) would fail, first assign to a variable

# Number of basis functions
nbf = size(S)[1]

# Memory check
I_size = nbf^4 * 8.e-9
printfmt("Size of the ERI tensor will be {:4.2f} GB.\n", I_size)
memory_footprint = I_size * 1.5
if I_size > numpy_memory
    psi4.core.clean()
    throw(OutOfMemoryError("Estimated memory utilization ($memory_footprint GB) exceeds allotted memory " *
                           "limit of $numpy_memory GB."))
end

# Compute AO-basis ERIs
I = mints.ao_eri()

# Compute AO Core Hamiltonian
T = mints.ao_kinetic()
V = mints.ao_potential()
T = psi4view(T)
V = psi4view(V)
H = T + V


################################################################################
# returns dense idenity matrix of eltype T, used in BFGS
identity(T::Type, n::Int) = Matrix(one(T) * I(n))
identity(x::AbstractVecOrMat) = identity(eltype(x), size(x, 1))

################################################################################
using DataStructures: CircularBuffer
# convenience for circular buffer
# does in place copy of x in front of buffer, important if cb is buffer of arrays
# used in LBFGS
function copyfirst!(cb::CircularBuffer, x)
    cb.first = (cb.first == 1 ? cb.capacity : cb.first - 1)
    if length(cb) < cb.capacity
        cb.length += 1
    end
    copy!(cb.buffer[cb.first], x)
end

# TODO: make negative shifts efficient
# TODO: if shifts > c.length ÷ 2,
# used in LBFGS
# Base.circshift
function _circshift!(cb::CircularBuffer, shifts::Int = 1)
    shifts = mod(shifts, cb.length) # since identity if shifts = cb.length
    if shifts > 0
        if cb.length < cb.capacity
            first, last = cb.first, cb.first + cb.length
            for i in 1:shifts
                src = mod1((i-1) + first, cb.capacity)
                dest = mod1(i + last, cb.capacity)
                copy!(cb.buffer[dest], cb.buffer[src])
            end
        end
        cb.first = mod1(cb.first + shifts, cb.capacity)
    end
    return cb
end

######################### Lazy Difference Vector ###############################
# IDEA: is it worth using the LazyArrays package?
# could be replaced by LazyVector(applied(-, x, y)), which is neat.
# lazy difference between two vectors, has no memory footprint
struct LazyDifference{T, U, V} <: AbstractVector{T}
    x::U
    y::V
    function LazyDifference(x, y)
        length(x) == length(y) || throw(DimensionMismatch("x and y do not have the same length: $(length(x)) and $(length(y))."))
        T = promote_type(eltype(x), eltype(y))
        new{T, typeof(x), typeof(y)}(x, y)
    end
end

difference(x::Number, y::Number) = x-y # avoid laziness for scalars
difference(x::AbstractVector, y::AbstractVector) = LazyDifference(x, y)
difference(x::Tuple, y::Tuple) = LazyDifference(x, y)
# this creates a type instability:
Base.size(d::LazyDifference) = (length(d.x),)
Base.getindex(d::LazyDifference, i::Integer) = d.x[i]-d.y[i]

######################### solves with eigen factorization, used for SaddleFreeNewton
function LinearAlgebra.ldiv!(y::AbstractVector, E::Eigen, x::AbstractVector)
     ldiv!!(y, E, copy(x))
end
# WARNING: uses x as a temporary
function ldiv!!(y::AbstractVector, E::Eigen, x::AbstractVector) # temporary
    mul!(y, E.vectors', x)
    @. x = y / E.values
    mul!(y, E.vectors, x)
end


using Omega
using Test

function testpw()
  x = normal(0.0, 1.0)
  f(x::Real, y::Real) = x * y
  y = pw() do
    f(x, x)
  end
  x_, y_ = rand((x, y))
  @test y_ == x_ * x_
end


using Soss

multiple_NB_regression = @model (traps, live_in_super, log_sq_foot) begin
    alpha ∼ Normal(log(4), 1)
    beta ∼ Normal(-0.25, 1)
    beta_super ∼ Normal(-0.5, 1)
    inv_phi ∼ Normal(0, 1)

    complaints ∼ For(eachindex(traps)) do i
        # TODO: Log parametrization
        r = exp(alpha + beta * traps[i] + beta_super * live_in_super[i] + log_sq_foot[i])
        p = inv(inv_phi)
        # NegativeBinomial(r, p)
    end
end


@model function linear_regression(x, y)
    σ ~ Exponential(1)
    α ~ Normal(50, 10)
    βₗ ~ LogNormal(-5, 2)

    N = length(y)
    for n ∈ 1:N
        μ[n] ~ α + βₗ * x
    end

    y ~ Normal(μ, σ)
end


using ADCME
using LinearAlgebra
using AdFem
using DelimitedFiles

A = readdlm("fenics/A.txt")
mesh = Mesh(8,8,1/8)
ρ = constant(ones(get_ngauss(mesh)))
B1 = compute_fem_laplace_matrix1(ones(get_ngauss(mesh)), mesh)
B = compute_fem_laplace_matrix1(ρ, mesh)
sess = Session(); init(sess)
B0 = run(sess, B)
B0 = Array(B0)
@show norm(A - B0)
@info norm(B1-B0)

A = readdlm("fenics/A2.txt")
mesh = Mesh(8, 8, 1.0 / 8,degree=2)
ρ = constant(ones(get_ngauss(mesh)))
B1 = compute_fem_laplace_matrix1(ones(get_ngauss(mesh)), mesh)

B = compute_fem_laplace_matrix1(ρ, mesh)
sess = Session(); init(sess)
B0 = run(sess, B)
B0 = Array(B0)
@info norm(B1-B0)

E = Int64.(readdlm("fenics/edges.txt"))
Edof = get_edge_dof(E, mesh)
DOF = [1:mesh.nnode; Edof .+ mesh.nnode]
B0 = B0[DOF, DOF]

@show norm(A - B0)


#    Copyright (c) 2021 J.A. Duffek
#    Copyright (c) 2000 D.M. Spink
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, see <http://www.gnu.org/licenses/>.

"""
    vecnorm_toolbox(vec::Union{Matrix{F}, Array{F,3}}
                   )::Union{Matrix{F}, Array{F,3}} where {F<:AbstractFloat}

or

    vecnorm_toolbox(vec::Vector{F})::Vector{F} where {F<:AbstractFloat}

Normalises the array of vectors, returning the unit vectors.

# Arguments:
- `vec`: An array of column vectors represented by a matrix of size `(dim,nv)`,
  where `dim` is the dimension of the vector and `nv` the number of vectors.

# Output:
- `nvec`: Normalised vectors, matrix the same size as `vec`.

# Examples:
```julia
julia> nvec = vecnorm_toolbox(vec)
```

Normalise the two vectors `[0.0;2.0;1.3]` and `[1.5;3.4;2.3]`.
```julia
julia> nvec = vecnorm_toolbox([0.0 1.5; 2.0 3.4; 1.3 2.3])
```
"""
function vecnorm_toolbox(vec::Union{Matrix{F}, Array{F,3}}
                        )::Union{Matrix{F},Array{F,3}} where {F<:AbstractFloat}
return vec ./ sqrt.(sum(vec.^2,dims=1));
end # vecnorm_toolbox

# TODO: look if this is even necessary for this toolbox (using LinearAlgebra)
# overloaded function for vector input to get a float instead of a vector
function vecnorm_toolbox(vec::Vector{F})::Vector{F} where {F<:AbstractFloat}
return vec ./ sqrt(vec' * vec);
end # vecnorm_toolbox


include("odemethods.jl")

"""
given some ode model with the initial condition "ic" and the time span "tspan"

```
function ode(u, t)
    du = f(u,t)
    ...
return du
```
you can solve the ode using
```
sol = odesolve(ode, ic, tspan,  stepsize=1e-5; method="RK4")
```

sol.u will give you the numerical solution to the ode

"""
function odesolve(odefunction::Function, ic, tspan,  stepsize=1e-5; method="RK4")
    h = stepsize
    ts = collect(tspan[1]:h:tspan[2])
    t0= tspan[1]
    u0 = ic

    sol = [u0]
    tsol = [t0]
    if method=="RK4"
        for i in 1:round((tspan[2]-tspan[1])/h)
            u1 = RK4(odefunction, u0, h, t0)
            push!(sol, u1)
            push!(tsol, t0+h)
            u0 = u1
            t0 = t0+h
        end
    elseif method=="RK2"
        for i in 1:round((tspan[2]-tspan[1])/h)
            u1 = RK2(odefunction, u0, h, t0)
            push!(sol, u1)
            push!(tsol, t0+h)
            u0 = u1
            t0 = t0+h
        end
    else
        println("undefined method")
    end
    return (u=sol, t=tsol)

end


"""
    ssrand(T::Type, ny::Int, nu::Int, nstates::Int; proper=false, stable=true, Ts=nothing)

Returns a random `StateSpace` model with `ny` outputs, `nu` inputs, and `nstates` states,
whose matrix elements are normally distributed.

It is possible to specify if the system should be `proper` or `stable`.

Specify a sample time `Ts` to obtain a discrete-time system.
"""
function ssrand(T::Type, ny::Int, nu::Int, nstates::Int; proper=false, stable=true, Ts=nothing)
    A = randn(T, nstates, nstates)
    if stable
        Λ = eigvals(A)
        A = isnothing(Ts) ? A - 1.1*maximum(real(Λ))*I : A*0.9/maximum(abs.(Λ))
    end

    B = randn(T, nstates, nu)
    C = randn(T, ny, nstates)
    D = (proper ? zeros(T, ny, nu) : randn(T, ny, nu))
    return isnothing(Ts) ? StateSpace(A, B, C, D) : StateSpace(A, B, C, D, Ts)
end
ssrand(ny::Int, nu::Int, nstates::Int; kwargs...) = ssrand(Float64, ny::Int, nu::Int, nstates::Int; kwargs...)
ssrand(n; kwargs...) = ssrand(n, n, 2*n; kwargs...)
ssrand(T::Type, n; kwargs...) = ssrand(T, n, n, 2*n; kwargs...)

"""
    DemoSystems

A module with standard test systems from the control literature.

The returned systems are of type `StateSpace` or `DelayLTISystem`.

Default parameter can be changed using keyword arguments.

SISO systems
============
* `DemoSystems.lag(;T=1)`     First-order system  `1/(sT+1)`
* `DemoSystems.fotd(;T=1, τ=1)`   First-order system with time delay `exp(-sτ)/(sT+1)`
* `DemoSystems.sotd(;T=1, T2=10, τ=1)`   Second-order non-resonant system with time delay `exp(-sτ)/(sT+1)/(sT2 + 1)`
* `DemoSystems.resonant(;ω0=1, ζ=0.25)`   Second-order resonant systems `ω0^2/(s^2 + 2ζ*ω0*s + ω0^2)`

MIMO systems
============
* `DemoSystems.woodberry()`     Wood--Berry distillation column
* `DemoSystems.doylesat(;a=10)`    The spinning body example by Doyle

Fore more information, see the help for the specific systems.
"""
module DemoSystems

using ControlSystems
using LinearAlgebra

"""
    lag(;T=1, τ=1)

Returns a first-order system `StateSpace` with transfer function `1/(sT+1)`.
"""
lag(;T=1) = ss(-1/T, 1, 1/T, 0)

"""
    fotd(;T=1, τ=1)

Returns a first-order system + a time delay `exp(-sτ)/(sT+1)` (`DelayLTISystem`).
"""
fotd(;T=1, τ=1) = ss(-1/T, 1, 1/T, 0) * delay(τ)

"""
    sotd(;T=1, T2=10, τ=1)

Returns a second-order system + a time delay `exp(-sτ)/(sT+1)/(sT2 + 1)` (`DelayLTISystem`).
"""
sotd(;T=1, T2=10, τ=1) = ss(-1/T, 1, 1/T, 0)*ss(-1/T2, 1, 1/T2, 0)*delay(τ)

"""
    resonant(;ω0=1, ζ=0.25)

Returns a second-order system + a time delay `exp(-sτ)/(sT+1)/(sT2 + 1)` (`DelayLTISystem`).
"""
resonant(;ω0=1, ζ=0.25) = ss([-ζ -ω0; ω0 -ζ], [ω0; 0], [0 ω0], 0) # QUESTION: Is this the form that we like? Perhhaps not.

"""
    `doylesat(;a=10) = ss([0 a; -a 0], I(2), [1 a; -a 1], 0)`

Returns a `StateSpace` model for the spinning body (satellite) example by Doyle,
which is a classic example that illustrates that robustness analysis of MIMO systems
is more involved than for SISO systems.

*References:*

**Zhou, K., J. C. Doyle, and K. Glover**, Robust and optimal control,
Prentice hall (NJ), 1996.

**Skogestad, S, and I. Postlethwaite**, Multivariable feedback control:
analysis and design, Wiley (NY), 2007.
"""
doylesat(;a=10) = ss([0 a; -a 0], I(2), [1 a; -a 1], 0)

"""
    `woodberry()`

Returns a `DelayLTISystem` model for the Wood--Berry distillation column,
which is a classic example from the literature on process control of MIMO process.

*References:*

**Wood, R. K., and M. W. Berry**, Terminal composition control of a binary
distillation column, Chemical Engineering Science, 28.9, 1973, pp. 1707-1717.

"""
function woodberry()
    s = tf("s")
    return [12.8/(1 + 16.7*s)*delay(1.0)   -18.9/(1 + 21*s) * delay(3.0)
            6.6/(1 + 10.9*s) * delay(7.0)  -19.4/(1 + 14.4*s) * delay(3.0)]
end

end


# ---
# title: 1464. Maximum Product of Two Elements in an Array
# id: problem1464
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array
# link: <https://leetcode.com/problems/maximum-product-of-two-elements-in-an-array/description/>
# hidden: true
# ---
#
# Given the array of integers `nums`, you will choose two different indices `i`
# and `j` of that array. _Return the maximum value of_
# `(nums[i]-1)*(nums[j]-1)`.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [3,4,5,2]
#     Output: 12
#     Explanation: If you choose the indices i=1 and j=2 (indexed from 0), you will get the maximum value, that is, (nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12.
#
#
# **Example 2:**
#
#
#
#     Input: nums = [1,5,4,5]
#     Output: 16
#     Explanation: Choosing the indices i=1 and j=3 (indexed from 0), you will get the maximum value of (5-1)*(5-1) = 16.
#
#
# **Example 3:**
#
#
#
#     Input: nums = [3,7]
#     Output: 12
#
#
#
#
# **Constraints:**
#
#   * `2 <= nums.length <= 500`
#   * `1 <= nums[i] <= 10^3`
#
#
## @lc code=start
using LeetCode

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


mutable struct Preamble
  ins::Int
  preceding::Array{Int,1}
  Preamble(i,p) = 1 <= i <= length(p) ? new(i,p) : error("Bad insert index")
end

function Base.push!(preamble::Preamble, value::Int)
  preamble.preceding[preamble.ins] = value
  preamble.ins = preamble.ins % length(preamble.preceding) + 1
  preamble
end

function follows(next::Int, preamble::Preamble)
  for i in 1:length(preamble.preceding), j in i+1:length(preamble.preceding)
      if preamble.preceding[i] + preamble.preceding[j] == next
        return true
      end
  end
  false
end

input = parse.(Int, readlines("a9.txt"))

function findnonfollowing()
  buffer = Preamble(1, input[1:25])
  for value in input[26:end]
    if !follows(value, buffer)
      return value
    end
    push!(buffer, value)
  end
end

part1 = findnonfollowing()
println("$part1")

function findcontinuoussum(target)
  first = 1
  last = 2
  cs = input[first] + input[last]
  while cs != target && (first < last || last < length(input))
    while cs < target
      last += 1
      cs += input[last]
    end
    while cs > target
      cs -= input[first]
      first += 1
    end
  end
  cs == target && sum(extrema(input[first:last]))
end

part2 = findcontinuoussum(part1)
println("$part2")


using RunTests
using Base.Test

@testmodule MultinomialBayesTest begin
  using MLLibrary

  function test_getCounts()
    A=[1. 2. 1. 5.;
       4. 2. 1. 10.;
       3. 3. 2. 6.;]

    @test MLLibrary.LearningAlgorithms.MultinomialBayes.getCounts(A)==[8. 7. 4. 21.];
  end

  function test_getDictionarySize()
    A=[1. 2. 1. 5.;
       4. 2. 1. 10.;
       3. 3. 2. 6.;]

    @test MLLibrary.LearningAlgorithms.MultinomialBayes.getDictionarySize(A)==4
  end

  function test_getClassiferCount()
    A=[1;2;2;1;0;2;3;1;5]
    @test MLLibrary.LearningAlgorithms.MultinomialBayes.getClassifierCount(A)==Dict(0=>1,1=>3,2=>3,3=>1,5=>1)

    A=[1;0;1;0;1;1;0;0;0;1;1]
    @test MLLibrary.LearningAlgorithms.MultinomialBayes.getClassifierCount(A)==Dict(0=>5,1=>6)
  end

  function test_getDictionary()
    A=[1. 2. 1. 5. 1.;
       4. 2. 1. 10. 0.;
       3. 3. 2. 6. 1.;]

    dict,dictProb = MLLibrary.LearningAlgorithms.MultinomialBayes.getDictionary(A[:,1:end-1],A[:,end])
    @test dict==Dict(1=>[4. 5. 3. 11.],0=>[4. 2. 1. 10.])
    for key in keys(dictProb)
      dictProb[key] = round(dictProb[key],2)
    end
    @test dictProb ==Dict(0=>[.24 .14 .10 .52],1=>[.19 .22 .15 .44])
  end

  function test_classifyDocuments()
    A=[1. 2. 1. 5. 1.;
       4. 2. 1. 10. 0.;
       3. 3. 2. 6. 1.;]

    B=[1. 1. 1. 3. 1;
       2. 2. 1. 4. 0.;
       4. 2. 1. 1. 0.;]
    dict,dictProb = MLLibrary.LearningAlgorithms.MultinomialBayes.getDictionary(A[:,1:end-1],A[:,end])
    @test MLLibrary.LearningAlgorithms.MultinomialBayes.classifyDocuments(B[:,1:end-1],dictProb) == [1.;1.;1.]''
  end

end


using Random, Test, CommonLH

function compare_test()
    @testset "Comparisons" begin
        rng = MersenneTwister(12);
        x = rand(rng, 4,3);
        xMin, xMax = extrema(x);
        @test all_at_least(x, xMin)
        @test !all_at_least(x, xMin + 1e-6)
        @test all_at_most(x, xMax)
        @test !all_at_most(x, xMax - 1e-6)

        @test all_at_least(x, x .- 1e-5)
        @test all_at_most(x, x .+ 1e-5)
    end
end

function scale_array_test()
    @testset "Scale array" begin
        x = rand(4,3,2);
        for d = 1 : 3
            totalV = collect(range(1.0, 2.0, length = size(x, d)));
            scale_array!(x, d, totalV);
            for j = 1 : size(x, d)
                @test isapprox(sum(selectdim(x, d, j)),  totalV[j])
            end
        end
	end
end

function bracket_array_test()
    @testset "Bracket array" begin
        x = [1.0 1.1 1.2; 2.0 2.1 2.2];
        ub = x .+ 0.01;
        lb = x .- 0.01;
        z = copy(x);
        bracket_array!(z, lb, ub);
        @test isequal(z, x)

        ub[3] = x[3] - 0.001;
        lb[4] = x[4] + 0.001;
        z = copy(x);
        bracket_array!(z, lb, ub);
        @test !isequal(z, x)
        @test all(z .<= ub)
        @test all(z .>= lb)
	end
end

function bracket_array_scalar_test()
    @testset "Bracket array in scalars" begin
        x = [1.0 1.1 1.2; 2.0 2.1 2.2];
        ub = 2.099;
        lb = 1.099;
        bracket_array!(x, lb, ub);
        @test all(x .<= ub);
        @test all(x .>= lb);
    end
end

@testset "Arrays" begin
    compare_test();
    scale_array_test()
    bracket_array_test();
    bracket_array_scalar_test();
end

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


function sample_serialmc(t::MCMCTask; rate::Bool=true)
  chain = MCMC.run_serialmc_exit(t)

  if isa(t.sampler, RWM) || isa(t.sampler, RAM)
    nsamples, npars = size(chain.samples)
    chain.gradients = Array(Float64, nsamples, npars)
    for i = 1:npars; chain.gradients[i, :] = t.model.evalg(chain.samples[:, i]); end
  end

  if rate; merge!(chain.diagnostics, ["rate"=>acceptance(chain)]); end

  ZVSample(chain)
end

function sample_rand(d::MultivariateDistribution, nsamples::Int64)
  samples, gradients = Array(Float64, dim(d), nsamples), Array(Float64, dim(d), nsamples)

  for i = 1:nsamples
    samples[:, i] = rand(d)
    gradients[:, i] = gradlogpdf(d, samples[:, i])
  end

  ZVSample(samples', gradients')
end

function sample_rand(d::UnivariateDistribution, nsamples::Int64)
  samples, gradients = Array(Float64, nsamples), Array(Float64, nsamples)

  for i = 1:nsamples
    samples[i] = rand(d)
    gradients[i] = gradlogpdf(d, samples[i])
  end

  ZVSample(reshape(samples, nsamples, 1), reshape(gradients, nsamples, 1))
end

function zvmean(samples::Matrix{Float64}, gradients::Matrix{Float64};
  transform::Union(Function, Nothing)=nothing, lzv::Bool=true, qzv::Bool=true)
  transformed_samples = transform == nothing ? samples : transform(samples)
  npars = size(transformed_samples, 2)

  means = Array(Float64, npars, 3)
  means[:, 1] = mean(transformed_samples, 1)
  means[:, 2] = lzv ? mean(linearZv(transformed_samples, gradients)[1], 1) : fill(NaN, npars)
  means[:, 3] = qzv ? mean(quadraticZv(transformed_samples, gradients)[1], 1) : fill(NaN, npars)

  ZVMean(means)
end

function zvmean(zvsample::ZVSample; transform::Union(Function, Nothing)=nothing, lzv::Bool=true, qzv::Bool=true)
  zvmeans = zvmean(zvsample.samples, zvsample.gradients; transform=transform, lzv=lzv, qzv=qzv)
  zvmeans.diagnostics = zvsample.diagnostics
  zvmeans
end

sample_serialmc_zvmean(t::MCMCTask;
  transform::Union(Function, Nothing)=nothing, rate::Bool=true, lzv::Bool=true, qzv::Bool=true) =
  zvmean(sample_serialmc(t; rate=rate); transform=transform, lzv=lzv, qzv=qzv)

sample_rand_zvmean(d::Distribution, nsamples::Int64;
  transform::Union(Function, Nothing)=nothing, lzv::Bool=true, qzv::Bool=true) =
  zvmean(sample_rand(d, nsamples); transform=transform, lzv=lzv, qzv=qzv)


using Test
#@startexample Generalised BPS (Truncated Gaussian)
#=

This example is identical to the Global Bouncy Particle Sampler example with
a truncated gaussian except that it uses the *Generalised* kernel. In
that case, there does not need to be a refreshment step.
See also the corresponding paper: [*Generalized Bouncy Particle Sampler*](https://arxiv.org/pdf/1706.04781.pdf) by Changye Wu and Christian
Robert.

=#
using PDSampler, Random
p     = 2
ns, a = diagm(0=>ones(p)), zeros(p)
geom  = Polygonal(ns, a)

nextbdG(x, v) = nextboundary(geom, x, v)

Random.seed!(12)
P1  = randn(p,p)
P1 *= P1'
P1 += norm(P1)/100*diagm(0=>ones(p))
C1  = inv(P1); C1 += C1'; C1/=2;
L1  = cholesky(C1)
mu  = zeros(p) .+ 1.
mvg = MvGaussianCanon(mu, P1)

gradllG(x) = gradloglik(mvg, x)

nextevG(x, v) = nextevent_bps(mvg, x, v)
#=
Note the specification of `algname="GBPS"`. Note also the refreshment rate
set to 0.0 (no refreshment).
=#
T    = 1000.0   # length of path generated
lref = 0.0      # rate of refreshment
x0   = mu+L1.L*randn(p) # sensible starting point
v0   = randn(p) # starting velocity
v0  /= norm(v0) # put it on the sphere (not necessary)
# Define a simulation
sim = Simulation( x0=x0, v0=v0, T=T, nextevent=nextevG, gradloglik=gradllG,
                  nextboundary=nextbdG, lambdaref=lref, maxgradeval = 10000,algname="GBPS")
#=
The rest is as before:
=#
(path, details) = simulate(sim)

sN = 1000
s  = broadcast(+, mu, L1.L*randn(p,sN))
mt = zeros(2)
np = 0
# Sum for all samples in the positive orthan
ss = [s; ones(sN)']
mt = sum(ss[:,i] for i in 1:sN if !any(e->e<0, ss[1:p,i]))
mt = mt[1:p]/mt[end]

#@endexample

# all that's below won't show in doc

@test norm(pathmean(path)-mt)/norm(mt) < 0.1
ess,ns = esspath(path; ns=100)
@test minimum(ess) > 150


using DayCounts, Dates
using Test

# based on tests from OpenGamma Strata
# https://github.com/OpenGamma/Strata/blob/efaa0be0d08dc61c23692e7b86df101ea0fb7223/modules/basics/src/test/java/com/opengamma/strata/basics/date/DayCountTest.java
@testset "Actual365Fixed" begin
    dc = DayCounts.Actual365Fixed()
    @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == (4 + 58) / 365
    @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == (4 + 59) / 365
    @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == (4 + 60) / 365
    @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == (4 + 366 + 365 + 365 + 365 + 58) / 365
    @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == (4 + 366 + 365 + 365 + 365 + 59) / 365
    @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == (4 + 366 + 365 + 365 + 365 + 60) / 365
    @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == 29 / 365
    @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == 28 / 365
    @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == 27 / 365

    # zeros
    @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
    @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
    @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
    @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

    # reflection
    @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
    @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
end

@testset "Actual360" begin
    dc = DayCounts.Actual360()
    @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == (4 + 58) / 360
    @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == (4 + 59) / 360
    @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == (4 + 60) / 360
    @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == (4 + 366 + 365 + 365 + 365 + 58) / 360
    @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == (4 + 366 + 365 + 365 + 365 + 59) / 360
    @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == (4 + 366 + 365 + 365 + 365 + 60) / 360
    @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == 29 / 360
    @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == 28 / 360
    @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == 27 / 360

    # zeros
    @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
    @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
    @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
    @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

    # reflection
    @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
    @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
end

@testset "ActualActualISDA" begin
    dc = DayCounts.ActualActualISDA()
    @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == 4 / 365 + 58 / 366
    @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == 4 / 365 + 59 / 366
    @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == 4 / 365 + 60 / 366
    @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == 4 / 365 + 58 / 366 + 4
    @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == 4 / 365 + 59 / 366 + 4
    @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == 4 / 365 + 60 / 366 + 4
    @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == 29 / 366
    @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == 28 / 366
    @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == 27 / 366

    # zeros
    @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
    @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
    @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
    @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

    # reflection
    @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
    @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
end

@testset "Thirty360" begin
    dc = DayCounts.Thirty360()
    # usual rule: ((d2-d1) + (m2-m1)*30 + (y2-y1)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == ((28-28) + (2-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == ((29-28) + (2-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == ((1-28) + (3-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == ((28-28) + (2-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == ((29-28) + (2-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == ((1-28) + (3-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == ((28-28) + (3-2)*30 + (2012-2012)*360)/360
    @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == ((28-29) + (3-2)*30 + (2012-2012)*360)/360
    @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == ((28-1) + (3-3)*30 + (2012-2012)*360)/360

    @test yearfrac(Date(2012, 5,29), Date(2013, 8,29), dc) == ((29-29) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,29), Date(2013, 8,30), dc) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,29), Date(2013, 8,31), dc) == ((31-29) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception

    # zeros
    @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
    @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
    @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
    @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

    # reflection
    @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
    @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
end

@testset "ThirtyE360" begin
    dc = DayCounts.ThirtyE360()
    # usual rule: ((d2-d1) + (m2-m1)*30 + (y2-y1)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == ((28-28) + (2-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == ((29-28) + (2-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == ((1-28) + (3-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == ((28-28) + (2-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == ((29-28) + (2-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == ((1-28) + (3-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == ((28-28) + (3-2)*30 + (2012-2012)*360)/360
    @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == ((28-29) + (3-2)*30 + (2012-2012)*360)/360
    @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == ((28-1) + (3-3)*30 + (2012-2012)*360)/360

    @test yearfrac(Date(2012, 5,29), Date(2013, 8,29), dc) == ((29-29) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,29), Date(2013, 8,30), dc) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,29), Date(2013, 8,31), dc) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception

    # zeros
    @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
    @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
    @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
    @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

    # reflection
    @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
    @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
end

@testset "ThirtyE360ISDA" begin
    @testset "maturity not end of Feb" begin
        dc = DayCounts.ThirtyE360ISDA(Date(2012, 8, 31))
        @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == ((28-28) + (2-12)*30 + (2012-2011)*360)/360
        @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == ((30-28) + (2-12)*30 + (2012-2011)*360)/360 # exception
        @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == ((1-28) + (3-12)*30 + (2012-2011)*360)/360
        @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == ((28-28) + (2-12)*30 + (2016-2011)*360)/360
        @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == ((30-28) + (2-12)*30 + (2016-2011)*360)/360 # exception
        @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == ((1-28) + (3-12)*30 + (2016-2011)*360)/360
        @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == ((28-28) + (3-2)*30 + (2012-2012)*360)/360
        @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == ((28-30) + (3-2)*30 + (2012-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == ((28-1) + (3-3)*30 + (2012-2012)*360)/360

        @test yearfrac(Date(2012, 5,29), Date(2013, 8,29), dc) == ((29-29) + (8-5)*30 + (2013-2012)*360)/360
        @test yearfrac(Date(2012, 5,29), Date(2013, 8,30), dc) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360
        @test yearfrac(Date(2012, 5,29), Date(2013, 8,31), dc) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 5,30), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360
        @test yearfrac(Date(2012, 5,30), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360
        @test yearfrac(Date(2012, 5,30), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 5,31), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 5,31), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 5,31), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception

        # zeros
        @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
        @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
        @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
        @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

        # reflection
        @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
        @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
    end

    @testset "maturity end of Feb" begin
        dc = DayCounts.ThirtyE360ISDA(Date(2012, 2,29)) # leap year, end of month
        @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == ((28-28) + (2-12)*30 + (2012-2011)*360)/360
        @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == ((29-28) + (2-12)*30 + (2012-2011)*360)/360
        @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == ((1-28) + (3-12)*30 + (2012-2011)*360)/360
        @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == ((28-28) + (2-12)*30 + (2016-2011)*360)/360
        @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == ((30-28) + (2-12)*30 + (2016-2011)*360)/360 # exception
        @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == ((1-28) + (3-12)*30 + (2016-2011)*360)/360
        @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == ((28-28) + (3-2)*30 + (2012-2012)*360)/360
        @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == ((28-30) + (3-2)*30 + (2012-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == ((28-1) + (3-3)*30 + (2012-2012)*360)/360

        @test yearfrac(Date(2012, 5,29), Date(2013, 8,29), dc) == ((29-29) + (8-5)*30 + (2013-2012)*360)/360
        @test yearfrac(Date(2012, 5,29), Date(2013, 8,30), dc) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360
        @test yearfrac(Date(2012, 5,29), Date(2013, 8,31), dc) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 5,30), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360
        @test yearfrac(Date(2012, 5,30), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360
        @test yearfrac(Date(2012, 5,30), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 5,31), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 5,31), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
        @test yearfrac(Date(2012, 5,31), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception

        # zeros
        @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
        @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
        @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
        @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

        # reflection
        @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
        @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
    end
end

@testset "ActualActualICMA" begin
    dc = DayCounts.ActualActualICMA(Date(2011,7,1):Month(6):Date(2012,7,1))

    # zeros
    @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
    @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
    @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
    @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

    # reflection
    @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
    @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
end

@testset "ActuaActual ISDA Memo" begin
    # these test cases are all derived from
    # The Actual/Actual Day Count Fraction, ISDA
    # Paper for use with the ISDA Market Conventions Survey - 3rd June 1999
    # https://www.isda.org/a/pIJEE/The-Actual-Actual-Day-Count-Fraction-1999.pdf

    @testset "Semi-annual payment" begin
        dc_isda = DayCounts.ActualActualISDA()
        dc_icma = DayCounts.ActualActualICMA(Date(2003,11,1):Month(6):Date(2004,5,1))

        @test yearfrac(Date(2003,11,1), Date(2004,5,1), dc_isda) == (61/365 + 121/366)
        @test yearfrac(Date(2003,11,1), Date(2004,5,1), dc_icma) == 182/(2*182)
    end

    @testset "short first" begin
        dc_isda = DayCounts.ActualActualISDA()
        dc_icma = DayCounts.ActualActualICMA(Date(1998,7,1):Month(12):Date(2000,7,1))

        @test yearfrac(Date(1999,2,1), Date(1999,7,1), dc_isda) == 150/365
        @test yearfrac(Date(1999,2,1), Date(1999,7,1), dc_icma) ≈ 150/365 atol=eps()

        @test yearfrac(Date(1999,7,1), Date(2000,7,1), dc_isda) == 184/365 + 182/366
        @test yearfrac(Date(1999,7,1), Date(2000,7,1), dc_icma) == 366/366

        @test yearfrac(Date(1999,2,1), Date(2000,7,1), dc_isda) == (150 + 184)/365 + 182/366
        @test yearfrac(Date(1999,2,1), Date(2000,7,1), dc_icma) ≈ 150/365 + 366/366 atol=eps()
    end

    @testset "long first" begin
        dc_isda = DayCounts.ActualActualISDA()
        dc_icma = DayCounts.ActualActualICMA(Date(2002,7,15):Month(6):Date(2004,1,15))

        @test yearfrac(Date(2002,8,15), Date(2003,7,15), dc_isda) == 334/365
        @test yearfrac(Date(2002,8,15), Date(2003,7,15), dc_icma) == 181/(181*2) + 153/(184*2)

        @test yearfrac(Date(2003,7,15),Date(2004,1,15), dc_isda) == 170/365 + 14/366
        @test yearfrac(Date(2003,7,15), Date(2004,1,15), dc_icma) == 184/(184*2)

        @test yearfrac(Date(2002,8,15), Date(2004,1,15), dc_isda) ≈ (334+170)/365 + 14/366 rtol=eps()
        @test yearfrac(Date(2002,8,15), Date(2004,1,15), dc_icma) == 181/(181*2) + (153+184)/(184*2)
    end

    @testset "short final" begin
        dc_isda = DayCounts.ActualActualISDA()
        dc_icma = DayCounts.ActualActualICMA(Date(1999,7,30):Month(6):Date(2000,7,30))

        @test yearfrac(Date(1999,7,30), Date(2000,1,30), dc_isda) == 155/365 + 29/366
        @test yearfrac(Date(1999,7,30), Date(2000,1,30), dc_icma) == 184/(184*2)

        @test yearfrac(Date(2000,1,30), Date(2000,6,30), dc_isda) == 152/366
        @test yearfrac(Date(2000,1,30), Date(2000,6,30), dc_icma) == 152/(182*2)

        @test yearfrac(Date(1999,7,30), Date(2000,6,30), dc_isda) == 155/365 + (29+152)/366
        @test yearfrac(Date(1999,7,30), Date(2000,6,30), dc_icma) == 184/(184*2) + 152/(182*2)
    end

    @testset "long final" begin
        dc_isda = DayCounts.ActualActualISDA()
        # TODO: need a way to express "last day of month" schedules.
        dc_icma = DayCounts.ActualActualICMA(Date(1999,5,31):Month(3):Date(2000,5,31))

        @test yearfrac(Date(1999,11,30), Date(2000,4,30), dc_isda) == 32/365 + 120/366
        @test yearfrac(Date(1999,11,30), Date(2000,4,30), dc_icma) == 91/(91*4) + 61/(92*4)
    end
end

@testset "Thirty360Excel" begin
    dc = DayCounts.Thirty360Excel()
    # usual rule: ((d2-d1) + (m2-m1)*30 + (y2-y1)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == ((28-28) + (2-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == ((29-28) + (2-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == ((1-28) + (3-12)*30 + (2012-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == ((28-28) + (2-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == ((29-28) + (2-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == ((1-28) + (3-12)*30 + (2016-2011)*360)/360
    @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == ((28-28) + (3-2)*30 + (2012-2012)*360)/360
    @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == ((28-30) + (3-2)*30 + (2012-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == ((28-1) + (3-3)*30 + (2012-2012)*360)/360

    @test yearfrac(Date(2012, 5,29), Date(2013, 8,29), dc) == ((29-29) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,29), Date(2013, 8,30), dc) == ((30-29) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,29), Date(2013, 8,31), dc) == ((31-29) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360
    @test yearfrac(Date(2012, 5,30), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,29), dc) == ((29-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,30), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception
    @test yearfrac(Date(2012, 5,31), Date(2013, 8,31), dc) == ((30-30) + (8-5)*30 + (2013-2012)*360)/360 # exception

    # zeros
    @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
    @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
    @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
    @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

    # reflection
    @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
    @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
end

@testset "ActualActualExcel" begin
    dc = DayCounts.ActualActualExcel()
    # calculated from Excel sheet
    @test yearfrac(Date(2011,12,28), Date(2012, 2,28), dc) == 62/365
    @test yearfrac(Date(2011,12,28), Date(2012, 2,29), dc) == 63/366
    @test yearfrac(Date(2011,12,28), Date(2012, 3, 1), dc) == 64/366
    @test yearfrac(Date(2011,12,28), Date(2016, 2,28), dc) == 1523/365.3333333333333
    @test yearfrac(Date(2011,12,28), Date(2016, 2,29), dc) == 1524/365.3333333333333
    @test yearfrac(Date(2011,12,28), Date(2016, 3, 1), dc) == 1525/365.3333333333333
    @test yearfrac(Date(2012, 2,28), Date(2012, 3,28), dc) == 29/366
    @test yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc) == 28/366
    @test yearfrac(Date(2012, 3, 1), Date(2012, 3,28), dc) == 27/366

    @test yearfrac(Date(2012, 2,28), Date(2013, 2,28), dc) == 366/366
    @test yearfrac(Date(2012, 2,29), Date(2013, 2,28), dc) == 365/366
    @test yearfrac(Date(2012, 3, 1), Date(2013, 2,28), dc) == 364/365
    @test yearfrac(Date(2012, 2,28), Date(2013, 3, 1), dc) == 367/365.5
    @test yearfrac(Date(2012, 2,29), Date(2013, 3, 1), dc) == 366/365.5
    @test yearfrac(Date(2012, 3, 1), Date(2013, 3, 1), dc) == 365/365

    @test yearfrac(Date(2011, 2,28), Date(2012, 2,28), dc) == 365/365
    @test yearfrac(Date(2011, 3, 1), Date(2012, 2,28), dc) == 364/365
    @test yearfrac(Date(2011, 2,28), Date(2012, 2,29), dc) == 366/365.5
    @test yearfrac(Date(2011, 3, 1), Date(2012, 2,29), dc) == 365/366
    @test yearfrac(Date(2011, 2,28), Date(2012, 3, 1), dc) == 367/365.5
    @test yearfrac(Date(2011, 3, 1), Date(2012, 3, 1), dc) == 366/366

    # zeros
    @test yearfrac(Date(2011,12,28), Date(2011,12,28), dc) == 0
    @test yearfrac(Date(2011,12,31), Date(2011,12,31), dc) == 0
    @test yearfrac(Date(2012, 2,28), Date(2012, 2,28), dc) == 0
    @test yearfrac(Date(2012, 2,29), Date(2012, 2,29), dc) == 0

    # reflection
    @test yearfrac(Date(2012, 2,28), Date(2011,12,28), dc) == -yearfrac(Date(2011,12,28), Date(2012, 2,28), dc)
    @test yearfrac(Date(2012, 3,28), Date(2012, 2,29), dc) == -yearfrac(Date(2012, 2,29), Date(2012, 3,28), dc)
end

include("excel.jl")


function assemble_cut_cell_displacement_bilinear_form!(
    sysmatrix,
    basis,
    cellquads,
    stiffness,
    mesh,
    cellsign,
    cellid,
    vectosymmconverter,
)

    dim = dimension(mesh)
    jac = jacobian(mesh)
    quad = cellquads[cellsign, cellid]
    cellmatrix = vec(
        displacement_bilinear_form(
            basis,
            quad,
            stiffness[cellsign],
            jac,
            dim,
            vectosymmconverter,
        ),
    )
    nodeids = nodal_connectivity(mesh, cellsign, cellid)
    assemble_cell_matrix!(sysmatrix, nodeids, dim, cellmatrix)
end

function assemble_displacement_bilinear_forms!(
    sysmatrix,
    basis,
    cellquads,
    stiffness,
    mesh,
)

    ncells = number_of_cells(mesh)
    jac = jacobian(mesh)
    dim = dimension(mesh)
    vectosymmconverter = vector_to_symmetric_matrix_converter()

    uniformquad = uniform_cell_quadrature(cellquads)
    uniformbf1 = vec(
        displacement_bilinear_form(
            basis,
            uniformquad,
            stiffness[+1],
            jac,
            dim,
            vectosymmconverter,
        ),
    )
    uniformbf2 = vec(
        displacement_bilinear_form(
            basis,
            uniformquad,
            stiffness[-1],
            jac,
            dim,
            vectosymmconverter,
        ),
    )

    uniformcellmatrices = [uniformbf1, uniformbf2]

    for cellid = 1:ncells
        cellsign = cell_sign(mesh, cellid)
        check_cellsign(cellsign)
        if cellsign == +1
            nodeids = nodal_connectivity(mesh, +1, cellid)
            assemble_cell_matrix!(sysmatrix, nodeids, dim, uniformbf1)
        elseif cellsign == -1
            nodeids = nodal_connectivity(mesh, -1, cellid)
            assemble_cell_matrix!(sysmatrix, nodeids, dim, uniformbf2)
        else
            assemble_cut_cell_displacement_bilinear_form!(
                sysmatrix,
                basis,
                cellquads,
                stiffness,
                mesh,
                +1,
                cellid,
                vectosymmconverter,
            )
            assemble_cut_cell_displacement_bilinear_form!(
                sysmatrix,
                basis,
                cellquads,
                stiffness,
                mesh,
                -1,
                cellid,
                vectosymmconverter,
            )
        end
    end
end


using PyPlot
using JLD

function main(code::AbstractString)
    log_path_record = "$(homedir())/open_loop/output-record-$(code).jld"
    log_path_record = "$(homedir())/simulations/output-record-$(code).jld"
    d_rec = load(log_path_record)

    imu_meas    = d_rec["imu_meas"]
    t0 = imu_meas.t[1]
    sz = size(imu_meas.t,1)
    a_norm = zeros(sz,3)
    for i=1:sz
        a_norm[i,:] = (rotMatrix('y',-imu_meas.z[i,5])*rotMatrix('x',-imu_meas.z[i,4])*imu_meas.z[i,7:9]')'
    end
    figure()
    plot(imu_meas.t-t0,imu_meas.z[:,7:8])
    grid("on")
    figure()
    plot(imu_meas.t-t0,a_norm[:,1:2])
    grid("on")
    println("Mean raw: $(mean(imu_meas.z[:,8])), std raw: $(std(imu_meas.z[:,8]))")
    println("Mean filtered: $(mean(a_norm[:,2])), std filtered: $(std(a_norm[:,2]))")
end

function rotMatrix(s::Char,deg::Float64)
    A = zeros(3,3)
    if s=='x'
        A = [1 0 0;
             0 cos(deg) sin(deg);
             0 -sin(deg) cos(deg)]
    elseif s=='y'
        A = [cos(deg) 0 -sin(deg);
             0 1 0;
             sin(deg) 0 cos(deg)]
    elseif s=='z'
        A = [cos(deg) sin(deg) 0
             -sin(deg) cos(deg) 0
             0 0 1]
    else
        warn("Wrong angle for rotation matrix")
    end
    return A
end


@testset "Quadratic equation" begin
    Base.info("Testing quadratic equation...")
    solver = Solver()
    @test isapprox(solver(1.0,-4.0,3.0), [1.0,3.0])
    @test isapprox(solver(1.0,-2.0,1.0), ones(2))
    @test isapprox(solver(1.0,0.0,1.0), [-im,im])
    @test_throws ErrorException solver(0.0, 1.0, 2.0)
    Base.info("Done testing quadratic equation.")
end


@with_kw struct CROWN <: Solver
    method = :DeepPolyRelax  # alternative is :ParallelRelax
end

struct SymbolicBound
    # B(x) ≤≥ Λx + γ
    Λ::Union{Matrix{Float64}, UniformScaling{Bool}}  # UniformScaling for I
    γ::Union{Vector{Float64}, Float64}  # Float64 for 0.
end

function backward_linear(solver::CROWN, L::Layer, input::SymbolicBound)
    Λ = input.Λ * L.weights
    γ = input.Λ * L.bias .+ input.γ
    return SymbolicBound(Λ, γ)
end

function backward_act(solver::CROWN, L::Layer{ReLU}, input::SymbolicBound, lbs, ubs; upper=false)
    flip = upper ? -1. : 1.  # Λ⁺ and Λ⁻ are flipped for upper bound vs lower bound
    Λ⁺ = max.(flip * input.Λ, 0)
    Λ⁻ = min.(flip * input.Λ, 0)

    if solver.method == :DeepPolyRelax
        λ_l = relaxed_relu_gradient_lower.(lbs, ubs)
        λ_u = relaxed_relu_gradient.(lbs, ubs)
    elseif solver.method == :ParallelRelax
        λ_l = relaxed_relu_gradient.(lbs, ubs)
        λ_u = λ_l
    else
        throw(ArgumentError("Don't know relaxation method $(solver.method)!"))
    end

    β_l = zero(lbs)
    β_u = λ_u .* max.(-lbs, 0)

    Λ = flip * (Λ⁻ .* λ_u' + Λ⁺ .* λ_l')
    γ = flip * (Λ⁻ * β_u + Λ⁺ * β_l) .+ input.γ

    return SymbolicBound(Λ, γ)
end

function backward_act(solver::CROWN, L::Layer{Id}, input::SymbolicBound, lbs, ubs; upper=false)
    return input
end

"""
Calculates parameters of linear bounding function of the network via symbolic
backward propagation, given pre-activation bounds of the intermediate neurons.
By default a lower bound is calculated, for upper bound, set upper=true

solver - the solver to use for symbolic backward propagation
net - the network to generate the bounding function for
lbs - lower bounds on the pre-activation values of the intermediate neurons
ubs - upper bounds on the pre-activation values of the intermediate neurons
input - not needed as of now
upper - whether to calculate upper bound, if false, we calculate lower bound
"""
function backward_network(solver, net, lbs, ubs, input; upper=false)
    # assumes that last layer is linear!
    Z = SymbolicBound(I, 0.)
    Z = backward_linear(solver, net.layers[end], Z)
    for i in reverse(1:length(net.layers)-1)
        layer = net.layers[i]

        Ẑ = backward_act(solver, layer, Z, lbs[i], ubs[i], upper=upper)
        Z = backward_linear(solver, layer, Ẑ)
    end

    return Z
end

"""
Calculates pre-activation bounds for each of the neurons in the network starting
at from_layer.

solver - the solver to use for calculating bounds
net - the network the bounds are calculated for
input_set - input constraints for the network (right now LazySet, Hyperrectangle)

from_layer::Int - calculate bounds from that layer onwards to the last layer
lbs - lower bounds to use. Need bounds for all layers from the start to from_layer-1
ubs - upper bounds to use. See lbs for more info
printing - whether to print progress of bounds computation
"""
function calc_bounds(solver::CROWN, net, input_set; from_layer=1, lbs=nothing, ubs=nothing, printing=false)
    layers = from_layer == 1 ? [] : net.layers[1:from_layer-1]
    lbs = isnothing(lbs) ? [] : lbs
    ubs = isnothing(ubs) ? [] : ubs

    for (i, l) in enumerate(net.layers[from_layer:end])
        if printing
            println("Layer ", from_layer + i-1)
        end

        push!(layers, l)
        nn_part = Network(layers)

        Zl = backward_network(solver, nn_part, lbs, ubs, input_set)
        Zu = backward_network(solver, nn_part, lbs, ubs, input_set, upper=true)

        sym = SymbolicInterval([Zl.Λ Zl.γ], [Zu.Λ Zu.γ], input_set)
        lb, ub = bounds(sym)
        push!(lbs, lb)
        push!(ubs, ub)
    end

    return lbs, ubs
end


module Davidson
using LinearAlgebra
export eigdav
function eigdav(A, eigs, k, kmax, tol)
    n = size(A, 1)
    V = zeros((n, n))
    theta = 0
    w = 0
    theta_old = 0
    I = Diagonal(ones(n, n))
    t = Diagonal(randn(n, n))
    for m = k:k:kmax
        if m <= k
            for j = 1:1:k
                V[:, j] = t[:, j]#/norm(t[:,j])
            end
            theta_old = ones(eigs)
        else
            theta_old = theta[1:eigs]
        end
        F = qr(V)
        V = Array{Float64}(F.Q)
        @views T = transpose(V[:, 1:(m+1)]) * A * V[:, 1:(m+1)]
        theta, s = eigen(T)

        #THETA = eigvals(T)
        #S = eigvecs(T)
        #idx = sortperm(THETA)
        #theta = THETA[idx]
        #s = S[:,idx]
        #println(theta)
        for j = 1:1:k
            @views w = (A - theta[j] * I) * V[:, 1:(m+1)] * s[:, j]
            println("DIFF ", theta[j] - A[j, j])
            q = w / (theta[j] - A[j, j])
            V[:, m + j + 1] = q
        end
        normm = norm(theta[1:eigs] - theta_old)
        println("NORM ", normm)
        if normm < tol
            return theta[1:eigs]
        end

    end
    return theta[1:eigs]
end
end #module Davidson


"A convolutional filter. One would normally fit many such filters as part of a convolutional layer."
immutable ConvFilter{T,N,P<:Params} <: Learnable
    sizein::NTuple{N,Int}
    sizeout::NTuple{N,Int}
    sizefilter::NTuple{N,Int}
    stride::NTuple{N,Int}
    input::SumNode{T,N}
    output::OutputNode{T,N}
    params::P
end
ConvFilter(args...) = ConvFilter(Float64, args...)

function ConvFilter{T,N}(::Type{T}, sizein::NTuple{N,Int}, sizefilter::NTuple{N,Int}, stride::NTuple{N,Int} = (1,1))
    input = InputNode(T, sizein...)
    nr_x,nc_x = sizein
    nr_f,nc_f = sizefilter
    sizeout = (nr_x-nr_f+1, nc_x-nc_f+1)
    output = OutputNode(T, sizeout...)
    nparams = prod(sizefilter) + 1
    params = Params(T, nparams, (sizefilter, (1,)))
    w, b = params.views
    initialize_weights!(w)
    initialize_bias!(b)
    ConvFilter(sizein, sizeout, sizefilter, stride, input, output, params)
end

# non-allocating sum(x .* y)
function summult{T}(x::AbstractMatrix{T}, y::AbstractMatrix{T})
    tot = zero(T)
    nr, nc = size(x)
    @assert size(x) == size(y)
    for c=1:nc, r=1:nr
        tot += x[r,c]*y[r,c]
    end
    tot
end

"allows reading out of bounds (returns `zero(T)`)"
immutable TileView{T,N,A<:AbstractArray} <: AbstractArray{T,N}
    a::A
    pos::NTuple{N,Int}
    sz::NTuple{N,Int}
end
Base.size(tile::TileView) = tile.sz
function Base.getindex{T}(tile::TileView{T,2}, i::Int, j::Int)
    ai = tile.pos[1] + i - 1
    aj = tile.pos[2] + j - 1
    if ai <= 0 || aj <= 0 || ai > size(tile.a,1) || aj > size(tile.a,2)
        zero(T)
    else
        tile.a[ai,aj]
    end
end

# specialized for matrix... TODO: make this a generated function
function transform!{T}(filter::ConvFilter{T,2})
    w, b = filter.params.views
    x = filter.input.val
    y = filter.output.val

    # start with the bias
    fill!(y, b[1])

    # now apply the filter, passing over the input
    nr_y, nc_y = filter.sizeout
    nr_f, nc_f = filter.sizefilter
    for r=1:nr_y, c=1:nc_y
        x_tile = view(x, r:r+nr_f-1, c:c+nc_f-1)
        # x_tile = TileView{T,2,typeof(x)}(x, (r,c), filter.sizefilter)
        y[r, c] = summult(w, x_tile) + b[1]
    end
    y
end

function grad!{T}(filter::ConvFilter{T,2})
    w, b = filter.params.views
    ∇w, ∇b = filter.params.∇_views
    x = filter.input.val
    ∇x = filter.input.∇
    ∇y = filter.output.∇

    # reset the gradients to 0
    fill!(∇x, zero(T))
    fill!(filter.params.∇, zero(T))

    nr_y, nc_y = filter.sizeout
    nr_f, nc_f = filter.sizefilter
    for ry=1:nr_y, cy=1:nc_y, i=1:nr_f, j=1:nc_f
        rx = ry+i-1
        cx = cy+j-1
        ∇x[rx,cx] += w[i,j] * ∇y[ry,cy]
        ∇w[i,j] += x[rx,cx] * ∇y[ry,cy]
    end

    ∇b[1] = sum(∇y)
    return
end

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

"Wraps several filters for a convolution layer"
immutable ConvLayer{T,N,P<:Params} <: Learnable
    sizein::NTuple{N,Int}
    sizeout::NTuple{N,Int}
    input::SumNode{T,N}
    output::OutputNode{T,N}
    filters::Vector{ConvFilter{T,N,P}}
    params::P
end

function ConvLayer{T,N}(::Type{T}, sizein::NTuple{N,Int}, sizefilter::NTuple{N,Int}, numfilters::Int)
    filters = [ConvFilter(T, sizein, sizefilter) for i=1:numfilters]
    sizeout = filters[1].sizeout
    input = InputNode(T,sizein)
    output = OutputNode(T,sizeout..., numfilters)
    params = consolidate_params(T, filters)

    # TODO: need a concatenating layer of some sort... hmmm
    # or maybe we need a "link" function that connects a view to part of the array?
    for filter in filters
        link_nodes!(input, filter.input)
        # link_nodes!(output, filter.output)
    end

    ConvLayer(sizein, sizeout, input, output, filters, params)
end

function transform!(conv::ConvLayer)
    foreach(transform!, conv.filters)
    value(output)
end

function grad!(conv::ConvLayer)
    foreach(grad!, conv.filters)
end


# unpacking and packing for optimization toolboxes.
function packbcW(b, c::Vector{T}, W) where T <: Real
    D = length(b[1])
    M = length(W)
    @assert length(b) == length(c) == M

    v = Vector{T}(undef,M+M*D+M*div(D*(D+1),2))

    st = 1
    fin = M
    v[st:fin] = c

    for m = 1:M
        v[fin+(m-1)*D+1:fin+m*D] = b[m]
    end
    st = fin+1
    fin = fin+M*D

    for m = 1:M
        L = cholesky(W[m]).L
        chol_c, chol_a = packCholeskyType(L)
        st = fin+1
        fin = fin + D
        v[st:fin] = chol_c

        st = fin+1
        fin = fin + div(D*(D+1),2)-D
        v[st:fin] = chol_a
    end

    return v
end

# forces c to be positive. Forces W to be posdef.
function unpackbcW(v::Vector{T}, M::Int, D::Int) where T <: Real

    st = 1
    fin = M
    c = v[st:fin]
    c = abs.(c) # force positive.

    b = collect( v[fin+(m-1)*D+1:fin+m*D] for m = 1:M )
    st = fin+1
    fin = fin+M*D

    W = Vector{Matrix{T}}(undef,M)
    for m = 1:M
        st = fin+1
        fin = fin + D
        chol_c = v[st:fin]
        chol_c = abs.(chol_c) # force positive.

        st = fin+1
        fin = fin + div(D*(D+1),2)-D
        chol_a = v[st:fin]

        L = parseCholeskyType(chol_c, chol_a)
        W[m] = L*L'
    end

    return b, c, W
end

# modifies L such that the lower triangular off-diagonal entries are replaced by a.
# The diagonal entries of L are unmodified.
function filloffdiagonalentriessym!(L::Matrix{T}, a::Vector{T})::Nothing where T <: Real
    N = size(L,1)
    @assert size(L,2) == size(L,1)

    p = 1
    for j = 1:N
        for i = j+1:N
            L[i,j] = a[p]
            p += 1
        end
    end
    @assert p-1 == round(Int, N*(N+1)/2-N) # debug.

    return nothing
end

function parseCholeskyType(c::Vector{T}, a::Vector{T})::Matrix{T} where T <: Real
    L = convert(Matrix{T}, LinearAlgebra.Diagonal(c))

    filloffdiagonalentriessym!(L, a)

    return L
end

# c is the diagonal, should be all positives.
function packCholeskyType(L)
    c = collect( L[i,i] for i = 1:size(L,1) )

    n = length(c)
    a = Vector{Float64}(undef, round(Int, n*(n+1)/2-n))

    p = 1
    for j = 1:n
        for i = j+1:n
            a[p] = L[i,j]
            p += 1
        end
    end
    @assert p-1 == round(Int, n*(n+1)/2-n) # debug.

    return c, a
end


using Compat, FastTransforms, LowRankApprox
using Compat.Test
import FastTransforms: Cnλ, Λ, lambertw, Cnαβ, Anαβ, pochhammer
import FastTransforms: clenshawcurtisnodes, clenshawcurtisweights, fejernodes1, fejerweights1, fejernodes2, fejerweights2
import FastTransforms: chebyshevmoments1, chebyshevmoments2, chebyshevjacobimoments1, chebyshevjacobimoments2, chebyshevlogmoments1, chebyshevlogmoments2
import Compat: range

@testset "Special functions" begin
    n = 0:1000_000
    λ = 0.123
    @time FastTransforms.Cnλ(n,λ)
    @time FastTransforms.Cnλ(n,λ)

    x =range(0,stop=20,length=81)
    @test norm((Λ.(x) .- Λ.(big.(x)))./Λ.(x),Inf) < 2eps()
    @test norm((lambertw.(x) .- lambertw.(big.(x)))./max.(lambertw.(x),1), Inf) < 2eps()

    x = 0:0.5:10_000
    λ₁,λ₂ = 0.125,0.875
    @test norm((Λ.(x,λ₁,λ₂) .- Λ.(big.(x),big(λ₁),big(λ₂)))./Λ.(big.(x),big(λ₁),big(λ₂)),Inf) < 4eps()
    λ₁,λ₂ = 1//3,2//3
    @test norm((Λ.(x,Float64(λ₁),Float64(λ₂)) .- Λ.(big.(x),big(λ₁),big(λ₂))) ./ Λ.(big.(x),big(λ₁),big(λ₂)),Inf) < 4eps()

    n = 0:1000
    α = 0.125
    β = 0.375

    @time FastTransforms.Cnαβ.(n,α,β);
    @test norm(FastTransforms.Cnαβ.(n,α,β) ./ FastTransforms.Cnαβ.(n,big(α),big(β)) .- 1,Inf) < 3eps()

    @time FastTransforms.Anαβ.(n,α,β);
    @test norm(FastTransforms.Anαβ.(n,α,β) ./ FastTransforms.Anαβ.(n,big(α),big(β)) .- 1,Inf) < 4eps()

    @test pochhammer(2,1) == pochhammer(2,1.0) == pochhammer(2.0,1) == 2
    @test pochhammer(1.1,2.2) ≈ gamma(3.3)/gamma(1.1)
    @test pochhammer(-2,1) == pochhammer(-2,1.0) == pochhammer(-2.0,1) == -2
end

@testset "Fejer and Clenshaw--Curtis quadrature" begin
    N = 20
    f(x) = exp(x)

    x = clenshawcurtisnodes(Float64, N)
    μ = chebyshevmoments1(Float64, N)
    w = clenshawcurtisweights(μ)
    @test norm(dot(f.(x),w)-2sinh(1)) ≤ 4eps()

    μ = chebyshevjacobimoments1(Float64, N, 0.25, 0.35)
    w = clenshawcurtisweights(μ)
    @test norm(dot(f.(x),w)-2.0351088204147243) ≤ 4eps()

    μ = chebyshevlogmoments1(Float64, N)
    w = clenshawcurtisweights(μ)
    @test norm(sum(w./(x .- 3)) - π^2/12) ≤ 4eps()

    x = fejernodes1(Float64, N)
    μ = chebyshevmoments1(Float64, N)
    w = fejerweights1(μ)
    @test norm(dot(f.(x),w)-2sinh(1)) ≤ 4eps()

    μ = chebyshevjacobimoments1(Float64, N, 0.25, 0.35)
    w = fejerweights1(μ)
    @test norm(dot(f.(x),w)-2.0351088204147243) ≤ 4eps()

    μ = chebyshevlogmoments1(Float64, N)
    w = fejerweights1(μ)
    @test norm(sum(w./(x .- 3)) - π^2/12) ≤ 4eps()

    x = fejernodes2(Float64, N)
    μ = chebyshevmoments2(Float64, N)
    w = fejerweights2(μ)
    @test norm(dot(f.(x),w)-2sinh(1)) ≤ 4eps()

    μ = chebyshevjacobimoments2(Float64, N, 0.25, 0.35)
    w = fejerweights2(μ)
    @test norm(dot(f.(x),w)-2.0351088204147243) ≤ 4eps()

    μ = chebyshevlogmoments2(Float64, N)
    w = fejerweights2(μ)
    @test norm(sum(w./(x .- 3)) - π^2/12) ≤ 4eps()
end

if VERSION < v"0.7-"
    @testset "Allocation-free ID matrix-vector products" begin
        for T in (Float64, ComplexF64)
            r = rand(T)
            A = idfact([r/(i+j-1) for i in 1:200, j = 1:50])
            P = A[:P]
            k, n = size(A)

            x = rand(T, n)
            y = zeros(T, k)

            @test norm(FastTransforms.mul!(y, A, P, x, 1, 1) - A*x) < 10eps()*norm(A*x)

            x = rand(T, k)
            y = zeros(T, n)

            @test norm(FastTransforms.At_mul_B!(y, A, P, x, 1, 1) - At_mul_B(A,x), Inf) < 10eps()

            fill!(y, zero(T))
            @test norm(FastTransforms.Ac_mul_B!(y, A, P, x, 1, 1) - A'x, Inf) < 10eps()
        end
    end
else
    @testset "Allocation-free ID matrix-vector products" begin
        for T in (Float64, ComplexF64)
            r = rand(T)
            A = idfact([r/(i+j-1) for i in 1:200, j = 1:50])
            P = A[:P]
            k, n = size(A)

            x = rand(T, n)
            y = zeros(T, k)

            @test norm(FastTransforms.mul!(y, A, P, x, 1, 1) - A*x) < 10eps()*norm(A*x)

            x = rand(T, k)
            y = zeros(T, n)

            @test norm(FastTransforms.At_mul_B!(y, A, P, x, 1, 1) - transpose(A)*x, Inf) < 10eps()

            fill!(y, zero(T))
            @test norm(FastTransforms.Ac_mul_B!(y, A, P, x, 1, 1) - A'x, Inf) < 10eps()
        end
    end
end


# Julia implementation of Polarisable Ion Tight Binding
#
# Starting with:
# Universal tight binding model for chemical reactions in solution and at surfaces. II. Water
# A. Y. Lozovoi1, T. J. Sheppard1, D. L. Pashov2, J. J. Kohanoff1, and A. T. Paxton2, a)
# The Journal of Chemical Physics 141, 044504 (2014);
# https://doi.org/10.1063/1.4890343
#
# TODO: Fix all the things.

#module PITB # PolarisableIonTightBinding ; OK, shorter form easier to play with

include("FunctionalForm.jl") # explicit form of pair-potentials
include("Lozovoi.jl") # parameters for 2014 Lozovoi paper; H20, corrected
include("PolynomialApproximation.jl") # usin ApproxFun to speedup evaluation

#end