using Distributions
using PyPlot
using Roots
using JLD

function tauchen86(μ::AbstractFloat,ρ::AbstractFloat,σ::AbstractFloat,n::Int64,λ::AbstractFloat)
    #output is xgrid, xprob
    # x_t+1 = μ + ρ x_t + σ e_{t+1}, e_t+1 ∼ N(0,1)
    xhi = μ/(1-ρ) + λ*sqrt(σ^2/(1-ρ)^2)
    xlo = μ/(1-ρ) - λ*sqrt(σ^2/(1-ρ)^2)
    xgrid = zeros(n);
    xscale =(xhi-xlo)/(n-1)

    for i=1:n
        xgrid[i] = xlo + xscale*(i-1)
    end
    m=zeros(n-1);
    for i=1:n-1
        m[i] = (xgrid[i]+xgrid[i+1])/2
    end
    xprob = zeros(n,n) # xprob[i,j] = Pr(x_t+1=xgrid[j]|x_t=xgrid[i])
    for i=1:n # this is the state today
        normie = Normal(μ+ρ*xgrid[i],σ)
        normpdf(x) = pdf.(normie,x)
        normcdf(x) = cdf.(normie,x)
        for j=2:n-1
            xprob[i,j] = normcdf(m[j]) - normcdf(m[j-1])
        end
        xprob[i,1] = normcdf(m[1])
        xprob[i,n] = 1 - normcdf(m[n-1])
    end
    xprob = xprob ./ sum(xprob,2) # make sure the rows sum to 1
    return ( xgrid,xprob, xscale )
end

function trunc_lognpdf(x, UP, LO, mu, sig)
    # truncated log normal pdf
    logn = LogNormal(mu, sig)
    return (x-LO.>=-eps()) .* (x-UP.<=eps()).*pdf.(logn,x)/(cdf.(logn,UP)-cdf.(logn,LO))
end

# set parameters
# PARAMETERS THAT AFFECT STEADY STATE:
# note, we have log utility, so no CRRA parameter
# and we have a quarterly model
r = 0.01                        # 4 percent steady state annual real interest rate
α = 0.3                         # capital share
H = 1.0                         # aggregate hours worked
δ  = 0.03                       # depreciation
sH_over_sL = 1.2 / 0.8
pLH = 0.1                       # prob of going from low to high persistent skill
pHL = 0.1                      # prob of going fom
γ = 0.#0.004                       # TFP growth
GoverY = 0.01#18                        # steady state govt spending/gdp
g = 1/(1-GoverY)                # steady state value of g_t, where G_t/Y_t = (1-(1/g_t))
η = 0.                         # borrowing constraint (normalized by TFP)
In    = 0.443993816237631       # normalizing constant for the mollifier
na = 300                         # cash on hand ditn grid points - set to smaller numbers for debugging
ns = 2# 5                         # skill distribution grid points
zlo =  1. / 3.                      # second income shock to mollify actual income
zhi = 2. - zlo                       # upper bound on this process
BoverY = 0.26

# AGGREGATE SHOCKS
ρB = 0.5 # persistence of discount factor shock
ρG = 0.5 # persistence of govt spending shock
ρZ = 0.5 # persistence of tfp growth shock
ρμ = 0.5 # persistence of investment shock
ρlamw = 0.5 # persistence of wage mkup shock
ρlamf = 0.5 # persistence of price mkup shock
ρmon = 0.5 # persistence of mon policy shock

# OTHER PARAMETERS THAT AFFECT DYNAMICS
spp  = 4.                        # second derivative of investment adjustment cost
lamw = 1.5                       # wage markup
ϕh   = 2.                        # inverse frisch elasticity
Φw   = 10.                       # rotemberg cost for wages
lamf = 1.5                       # price markup
Φp   = 1.                       # rotemberg cost for prices
ρR   = 0.75                      # persistence in taylor rule
ψπ   = 1.5                      # weight on inflation in taylor rule
ψy   = 0.5                       # weight on output growth in taylor rule

# OPTIONS
TRUNCATE      = true # default: true
mindens       = 1e-8
RESCALE_XWTS  = true # default: true
SIMPLE_TAYLOR = false # default: false

# solve for steady state of aggregate scalar variables
# here I am assuming firms are subsidized in ss so that real mc = 1
Rk = r + δ                               # rental rate on capital
ω = (α^(α/(1-α)))*(1-α)*Rk^(-α/(1-α))    # real wage
kl = (α/(1-α))*(ω/Rk)*ℯ^γ                # capital/labor ratio
k = kl*H                                 # capital
x = (1-(1-δ)*ℯ^(-γ))*k                   # investment
y = (ℯ^(-α*γ))*(k^α)*H^(1-α)             # gdp
bg = BoverY*y                            # govt debt
T = Rk*k*ℯ^(-γ) - x - (1-(1/g))*y - ((1+r)*ℯ^(-γ)-1)*bg        # net transfer to hhs

function persistent_skill_process(sH_over_sL::AbstractFloat, pLH::AbstractFloat, pHL::AbstractFloat, ns::Int)
    f1 = [[1-pLH pLH];[pHL 1-pHL]] # f1[i,j] is prob of going from i to j
    ss_skill_distr = [pHL/(pLH+pHL); pLH/(pLH+pHL)]
    slo = 1. / (ss_skill_distr'*[1;sH_over_sL])
    sgrid = slo*[1;sH_over_sL]
    sscale = sgrid[2] - sgrid[1]
    swts     = (sscale/ns)*ones(ns) #quadrature weights
    f = f1 ./ repmat(swts',ns,1)
    return (f, sgrid, swts)
end
(f, sgrid, swts) = persistent_skill_process(sH_over_sL, pLH, pHL, ns)

function cash_grid(sgrid::AbstractArray, ω::AbstractFloat, H::AbstractFloat, r::AbstractFloat, η::AbstractFloat, γ::AbstractFloat,
                   T::AbstractFloat, zlo::AbstractFloat, na::Int)
    smin = minimum(sgrid)*zlo                           # lowest possible skill
    alo_ss = ω*smin*H - (1+r)*η*ℯ^(-γ) + T + sgrid[1]*ω*H*0.05       # lowest possible cash on hand in ss

    alo = alo_ss                    # lower bound on cash on hand - could be < alo_ss
    ahi = max(alo*2,alo+5.)                      # upper bound on cash on hand
    ascale = (ahi-alo)              # size of w grids

    # make grids
    agrid    = collect(linspace(alo,ahi,na)) #Evenly spaced grid
    awts = (ascale/na)*ones(na)          #quadrature weights
    return (agrid, awts)
end
smin = minimum(sgrid)*zlo                           # lowest possible skill
alo_ss = ω*smin*H - (1+r)*η*ℯ^(-γ) + T + sgrid[1]*ω*H*0.05       # lowest possible cash on hand in ss

alo = alo_ss                    # lower bound on cash on hand - could be < alo_ss
ahi = max(alo*2,alo+5.)                      # upper bound on cash on hand
ascale = (ahi-alo)              # size of w grids

(agrid, awts) = cash_grid(sgrid, ω, H, r, η, γ, T, zlo, na)

function mollifier(z::AbstractFloat,ehi::AbstractFloat,elo::AbstractFloat)
    # mollifier function
    In = 0.443993816237631
    if z<ehi && z>elo
        temp = -1.0 + 2.0*(z-elo)/(ehi-elo)
        out = (2.0/(ehi-elo))*exp(-1.0/(1.0-temp*temp))/In
    else
        out = 0.0
    end

    return out
end

zgrid = linspace(zlo,zhi,na)
zwts = (zhi-zlo)/na
sumz=0.
for i=1:na
    sumz += mollifier(zgrid[i],zhi,zlo)*zwts
end

# experiment with a different q
ne = 100
σe = 0.01
(legrid, fe, sscale) = tauchen86(0.0,0.0,σe,ne,2.0)
egrid = exp.(legrid)
eprob = fe[1,:]

function convoluted_q(x::AbstractFloat, zhi::AbstractFloat, zlo::AbstractFloat, ne::Int, egrid::Vector{Float64}, eprob::Vector{Float64})
    sumne = 0.
    for i=1:ne
        sumne += mollifier(x - egrid[i], zhi, zlo)*eprob[i]
    end
    return sumne
end

qfunction(x) = mollifier(x, zhi, zlo)#/sumz # this ensures the mollifer sums to 1, at least on this particular grid
#qfunction(x) = convoluted_q(x, zhi, zlo, ne, egrid, eprob)

function dmollifier(x::AbstractFloat, ehi::AbstractFloat, elo::AbstractFloat)
    In = 0.443993816237631
    if x<ehi && x>elo
        temp = (-1.0 + 2.0*(x-elo)/(ehi-elo))
        out  = -(2*temp ./ ((1 - temp.^2).^2)) .* (2/(ehi-elo)).*mollifier(x, ehi, elo)
    else
        out = 0.0
    end
    return out
end

qp(z) = dmollifier(z,zhi,zlo)

Win = 1 ./ (5. + 0.02*(repeat(agrid,ns)-5))
Win = 2*ones(na*ns)/(ahi+alo)
aswts = kron(swts,awts)

function sspolicy(na::Int, ns::Int, β::AbstractFloat, R::AbstractFloat,
                     ω::AbstractFloat,
                     H::AbstractFloat,
                     η::AbstractFloat,
                     T::AbstractFloat,
                     γ::AbstractFloat,
                     qfunction::Function,
                     agrid::Vector{Float64},
                     sgrid::Vector{Float64},
                     aswts::Vector{Float64},
                     Win::Vector{Float64},
                     f::Array{Float64,2},damp::Float64 = 0.5, dist::Float64 = 1.,
                         tol::Float64 = 1e-4, maxit::Int64 = 500)
    n = na*ns
    c  = zeros(n)      # consumption
    bp = zeros(n)      # savings
    counter = 1
    Wout = copy(Win)
    while dist>tol && counter<maxit # for debugging
        # compute c(w) given guess for Win = β*R*E[u'(c_{t+1})]
        for iss in 1:ns
            for ia in 1:na
                c[na*(iss-1)+ia] =  min(1/Win[na*(iss-1)+ia],agrid[ia]+η)
            end
        end
        bp = repeat(agrid,ns) - c  # compute bp(w) given guess for Win
        Wout = parameterized_expectations(na,ns,β,R,ω,H,η,T,γ,qfunction,agrid,sgrid,aswts,c,bp,f)
        dist = maximum(abs.(Wout-Win))
        #println(dist)
        if mod(counter,25)==1
            println(dist)
            #plot(Wout)
        end
        Win = damp*Wout + (1.0-damp)*Win
        counter += 1
    end
    if counter==maxit
        warn("Euler iteration did not converge")
    end
    tr = kolmogorov_fwd(na,ns,ω,H,η,T,γ,qfunction,agrid,sgrid,bp,f)
    return (c,bp,Wout,tr)
end

function parameterized_expectations(na::Int,ns::Int, β::AbstractFloat, R::AbstractFloat, ω::AbstractFloat,
                                    H::AbstractFloat, η::AbstractFloat, T::AbstractFloat, γ::AbstractFloat,
                                    qfunction::Function,
                                    agrid::Vector{Float64}, sgrid::Vector{Float64},
                                    aswts::Vector{Float64}, c::Vector{Float64},
                                    bp::Vector{Float64}, f::Array{Float64,2})
    l_out = zeros(na*ns)
    for iss=1:ns
        for ia=1:na
            sumn = 0.0
            for isp=1:ns
                for iap=1:na
                    sumn += (aswts[na*(isp-1)+iap]/c[na*(isp-1)+iap])*qfunction((agrid[iap] - R*(ℯ^(-γ))*bp[na*(iss-1)+ia] - T)/(ω*H*sgrid[isp]))*f[iss,isp] ./ sgrid[isp]
                end
            end
            l_out[na*(iss-1)+ia] = (β*R*(ℯ^(-γ))/ω*H)*sumn
        end
    end
    return l_out
end

function kolmogorov_fwd(na::Int, ns::Int, ω::AbstractFloat,
                        H::AbstractFloat, η::AbstractFloat, T::AbstractFloat, γ::AbstractFloat,
                        qfunction::Function,agrid::Vector{Float64},
                        sgrid::Vector{Float64}, bp::Vector{Float64},f::Array{Float64,2})
    tr = zeros(na*ns,na*ns)
    for iss=1:ns
        for ia=1:na
            for isp=1:ns
                for iap=1:na
                    tr[na*(isp-1)+iap,na*(iss-1)+ia] = qfunction((agrid[iap] - R*(ℯ^(-γ))*bp[na*(iss-1)+ia] - T)/(ω*H*sgrid[isp]))*f[iss,isp] ./ (ω*H*sgrid[isp])
                end
            end
        end
    end
    return tr
end

# just to test if code returns
R = 1+r
βlo = 0.5*(ℯ^γ)/R # excess should be -ve
βhi = (ℯ^γ)/R # excess should be +ve

β = (βlo+βhi)/2.0

function findss(na::Int, ns::Int, βlo::AbstractFloat,
                βhi::AbstractFloat, R::AbstractFloat,
                     ω::AbstractFloat,
                     H::AbstractFloat,
                     η::AbstractFloat,
                     T::AbstractFloat,
                     γ::AbstractFloat,
                     bg::AbstractFloat,
                     qfunction::Function,
                     agrid::Vector{Float64},
                     sgrid::Vector{Float64},
                     aswts::Vector{Float64},
                     Win::Vector{Float64},
                     f::Array{Float64,2},
                     excess::AbstractFloat = 5000., tol::AbstractFloat = 1e-4,
                     maxit::Int64 = 20)
    counter=1
    n       = ns*na
    c = zeros(n)
    bp = zeros(n)
    KF = zeros(n,n)
    m = zeros(n)
    report = zeros(maxit,2)
    #Win0=copy(Win)
    while abs(excess)>tol && counter<maxit # clearing markets
        β = (βlo+βhi)/2.0
        Win_guess = ones(n) # or Win
        (c, bp, Win, KF) = sspolicy(na, ns, β, R, ω, H, η, T, γ, qfunction, agrid, sgrid,
                                       aswts, Win_guess, f)

        LPMKF=aswts[1]*KF
        # find eigenvalue closest to 1
        (D,V) = eig(LPMKF)
        order_D = sortperm(abs.(D), rev = true)
        V = V[:,order_D]
        D = D[order_D]
        if abs(D[1]-1)>2e-1 # that's the tolerance we are allowing
            warn("your eigenvalue is too far from 1, something is wrong")
        end
        m = real(V[:,1]) #Pick the eigen vecor associated with the largest eigenvalue and moving it back to values
        m = m/(aswts'*m) #Scale of eigenvectors not determinate: rescale to integrate to exactly 1
        excess = (aswts'*(m .* bp))[1] - bg  #compute excess supply of savings, which is a fn of w
                # bisection
        println([βlo β βhi])
        println(excess)
        report[counter,1] = β # save the history of guesses and excess supply so we can check it's upward-sloping and monotonic
        report[counter,2] = excess
        if excess>0
            βhi=β
        elseif excess<0
            βlo = β
        end
        counter += 1
    end
    return (Win, c, m, β, report)
end

tic()
(ell, c, m, β, report) = findss(na, ns, βlo, βhi, R, ω, H, η, T, γ, bg, qfunction, agrid, sgrid,aswts, Win, f)
toc()


@testset "Limited Length Cycles" begin
    completedg = CompleteDiGraph(4)
    pathdg = PathDiGraph(5)
    cycledg = CycleDiGraph(5)

    @test length(simplecycles_limited_length(completedg, 0)) == 0
    @test length(simplecycles_limited_length(completedg, 1)) == 0
    @test length(simplecycles_limited_length(completedg, 2)) == 6
    @test length(simplecycles_limited_length(completedg, 3)) == 14
    @test length(simplecycles_limited_length(completedg, 4)) == 20
    @test length(simplecycles_limited_length(completedg, 4, 10)) == 10
    @test length(simplecycles_limited_length(completedg, 4, typemax(Int))) == 20

    @test length(simplecycles_limited_length(pathdg, 1)) == 0
    @test length(simplecycles_limited_length(pathdg, 2)) == 0
    @test length(simplecycles_limited_length(pathdg, 3)) == 0
    @test length(simplecycles_limited_length(pathdg, 4)) == 0
    @test length(simplecycles_limited_length(pathdg, 5)) == 0

    @test length(simplecycles_limited_length(cycledg, 1)) == 0
    @test length(simplecycles_limited_length(cycledg, 2)) == 0
    @test length(simplecycles_limited_length(cycledg, 3)) == 0
    @test length(simplecycles_limited_length(cycledg, 4)) == 0
    @test length(simplecycles_limited_length(cycledg, 5)) == 1

    selfloopg = DiGraph([
        0 1 0 0;
        0 0 1 0;
        1 0 1 0;
        0 0 0 1;
    ])
    cycles = simplecycles_limited_length(selfloopg, nv(selfloopg))
    @test [3] in cycles
    @test [4] in cycles
    @test [1, 2, 3] in cycles || [2, 3, 1] in cycles || [3, 1, 2] in cycles
    @test length(cycles) == 3

    octag = smallgraph(:octahedral)
    octadg = DiGraph(octag)
    octalengths, _ = simplecycleslength(octadg)
    for k = 1:6
        @test sum(octalengths[1:k]) == length(simplecycles_limited_length(octag, k))
        @test sum(octalengths[1:k]) == length(simplecycles_limited_length(octadg, k))
    end
end


@testset "Kabsch algorithm" begin

    @testset "Test I" begin
        a = [1  1 0
             2  1 0
             1+cos(pi/4) 1-sin(pi/4) 0
             1  0 0 ]
        b = [1 1 0
             1 2 0
             1+cos(pi/4) 1+sin(pi/4)  0 ] # mean(b,1) 1.2357  1.56904  0.0
        sa = a[1:3, :]
        ma = mean(sa, dims=1) # mean x, y, z for a[1:3,:]

        # Center b, sa, a
        center!(b)
        sa[:,:] = sa .- ma
        a[:,:]  = a  .- ma # center != 0

        @test isapprox(vec(mean(b, dims=1)), [0.0, 0.0, 0.0], atol=1e-13)
        @test isapprox(vec(mean(sa, dims=1)), [0.0, 0.0, 0.0], atol=1e-13)

        F = kabsch(b, sa) # Reference: b
        R = sa * F
        RR = a * F

        @test R ≈ b
        @test RR[1:3,:] ≈ b

        @test RR[4,:] .- RR[1,:] ≈ [1.0, 0.0, 0.0]
        @test sqrt((RR[1,1] - RR[4,1])^2 + (RR[1,2] - RR[4,2])^2) ≈ 1.0

        @test isapprox(PDB.rmsd(R, b), 0.0, atol=1e-13)
        @test isapprox(PDB.rmsd(RR[1:3,:], b), 0.0, atol=1e-13)
    end

    @testset "BiomolecularStructures' test" begin

        P = [51.65 -1.90 50.07
             50.40 -1.23 50.65
             50.68 -0.04 51.54
             50.22 -0.02 52.85]
        Q = [51.30 -2.99 46.54
             51.09 -1.88 47.58
             52.36 -1.20 48.03
             52.71 -1.18 49.38] # P and Q are from BiomolecularStructures.jl' kabsch tests
        Qdistances = Float64[ sqrt(sum(abs2, Q[j,:] .- Q[i,:])) for i in 1:4, j in 1:4 ]

        center!(P)
        center!(Q)

        @test isapprox(vec(mean(P, dims=1)), zeros(3), atol=1e-13)
        @test isapprox(vec(mean(Q, dims=1)), zeros(3), atol=1e-13)

        rotationmatrix = kabsch(P, Q)
        rotated = Q * rotationmatrix

        @test PDB.rmsd(P, rotated) ≈ 0.0030426652601371583

        # Internal distances mustn't change
        @test Float64[sqrt(sum(abs2, Q[j,:] .- Q[i,:])) for i in 1:4, j in 1:4] ≈ Qdistances # Translated
        @test Float64[sqrt(sum(abs2, rotated[j,:] .- rotated[i,:])) for i in 1:4, j in 1:4] ≈ Qdistances # Translated  and rotated
    end

    @testset "Test II" begin
        P = [-1. 0. 0.
             0. 2. 0.
             0. 1. 0.
             0. 1. 1]
        Q = [0. -1. -1.
             0. -1. 0
             0. 0. 0.
             -1. 0. 0.]

        center!(P)
        center!(Q)
        rotated = Q * kabsch(P, Q)
        @test isapprox(PDB.rmsd(P, rotated), 0.695, atol=0.001)
    end

    @testset "RMSF" begin
        A = [ -1. -1. -1.
              -1.  0.  1. ]
        B = [  1.  1.  1.
              -1.  0.  1. ]
        w = [ .25 , .75 ]

        @test_throws ArgumentError mean_coordinates(Matrix{Float64}[A[1:1,:], B])
        @test_throws ArgumentError mean_coordinates(Matrix{Float64}[A[:,1:2], B])
        @test_throws ArgumentError mean_coordinates(Matrix{Float64}[A])
        @test_throws ArgumentError mean_coordinates(Matrix{Float64}[A,  B'])
        @test_throws ArgumentError mean_coordinates(Matrix{Float64}[A', B'])

        @test mean_coordinates(Matrix{Float64}[A, B]) == [ 0. 0. 0.
                                                          -1. 0. 1. ]

        @test mean_coordinates(Matrix{Float64}[A, B], w) == [ 0.5 0.5 0.5
                                                             -1.  0.  1. ]

        @test mean_coordinates(Matrix{Float64}[A, B], reverse(w)) == [ -0.5 -0.5 -0.5
                                                                       -1.   0.   1. ]

        @test rmsf(Matrix{Float64}[A, B]) == [sqrt(3), 0.]
        @test rmsf(Matrix{Float64}[A, B], w) == [sqrt(0.25*6.75 + 0.75*0.75), 0.]
    end

    @testset "Superimpose PDBs" begin

        hemoglobin = read(joinpath(pwd(),"data","2hhb.pdb.gz"),PDBFile,group="ATOM",model="1")

        α1 = @residues hemoglobin model "1" chain "A" group "ATOM" residue All
        α2 = @residues hemoglobin model "1" chain "C" group "ATOM" residue All
        β1 = @residues hemoglobin model "1" chain "B" group "ATOM" residue All
        β2 = @residues hemoglobin model "1" chain "D" group "ATOM" residue All

        a1, a2, rα = superimpose(α1, α2)

        @test isapprox(rα, 0.230, atol=0.001) # Bio3D RMSD

        _, _, rα2 = superimpose(α1, α2, zip(1:length(α1), 1:length(α2)))
        @test rα2 == rα

        _, _, rα2 = superimpose(α1, α2, zip(1:2, 1:2))
        @test rα2 < rα/10  # aligning only 2 points is accurate

        b1, b2, rβ = superimpose(β1, β2)

        @test isapprox(rβ, 0.251, atol=0.001) # Bio3D & Chimera's MatchMaker RMSD

        @test length(a1) == length(α1)
        @test length(a2) == length(α2)
        @test length(b1) == length(β1)
        @test length(b2) == length(β2)

        @test a1 != α1
        @test a2 != α2

        @test b1 != β1
        @test b2 != β2

        @testset "Vector{PDBResidue}" begin

            @test isapprox(PDB.rmsd(α1, α2), 0.230, atol=0.001)
            @test isapprox(PDB.rmsd(β1, β2), 0.251, atol=0.001)

            @test isapprox(PDB.rmsd(a1, a2, superimposed=true), 0.230, atol=0.001)
            @test isapprox(PDB.rmsd(b1, b2, superimposed=true), 0.251, atol=0.001)

            ca_a1 = CAmatrix(a1)
            ca_a2 = CAmatrix(a2)
            ca_b1 = CAmatrix(b1)
            ca_b2 = CAmatrix(b2)

            @testset "Centered" begin
                @test isapprox(mean(ca_a1, dims=1), zeros(1,3), atol=1e-13)
                @test isapprox(mean(ca_a2, dims=1), zeros(1,3), atol=1e-13)
                @test isapprox(mean(ca_b1, dims=1), zeros(1,3), atol=1e-13)
                @test isapprox(mean(ca_b2, dims=1), zeros(1,3), atol=1e-13)

                @test PDB._iscentered(ca_a1)
                @test PDB._iscentered(ca_a2)
                @test PDB._iscentered(ca_b1)
                @test PDB._iscentered(ca_b2)
            end

            @testset "RMSD" begin

                @test rα == PDB.rmsd(ca_a1, ca_a2)
                @test rβ == PDB.rmsd(ca_b1, ca_b2)
            end
        end

        @testset "coordinatesmatrix and centered..." begin

            @test coordinatesmatrix(centeredresidues(α1)) ≈ coordinatesmatrix(a1)
            @test coordinatesmatrix(centeredresidues(β1)) ≈ coordinatesmatrix(b1)

            @test centeredcoordinates(α1) ≈ coordinatesmatrix(a1)
            @test centeredcoordinates(β1) ≈ coordinatesmatrix(b1)
        end

        @testset "Superimpose with centered PDBs" begin

            b12, b22, rβ2 = superimpose(b1, b2)
            @test rβ2 ≈ rβ
        end

        @testset "RMSF" begin

            @test rmsf(Vector{PDBResidue}[α1,α2]) == rmsf(Vector{PDBResidue}[α1,α2],[.5,.5])
            @test rmsf(Vector{PDBResidue}[β1,β2]) == rmsf(Vector{PDBResidue}[β1,β2],[.5,.5])
        end
    end

    @testset "PDBResidue without alpha-carbon" begin
        small_2WEL = read(joinpath(pwd(), "data", "2WEL_D_region.pdb"), PDBFile)
        small_6BAB = read(joinpath(pwd(), "data", "6BAB_D_region.pdb"), PDBFile)

        aln_2WEL, aln_6BAB, RMSD = superimpose(small_2WEL, small_6BAB)
        @test length(aln_2WEL) == 3
        @test length(aln_6BAB) == 3
        @test small_2WEL[2].atoms[1].coordinates ≉ aln_2WEL[2].atoms[1].coordinates
        @test small_6BAB[2].atoms[1].coordinates ≉ aln_6BAB[2].atoms[1].coordinates
        @test RMSD < 1.0e-14 # e.g. 6.9e-15, 3.7e-15 & 1.6e-15

        @testset "PDB without CA" begin

            filter!(atom -> atom.atom != "CA", small_2WEL[1].atoms)
            filter!(atom -> atom.atom != "CA", small_6BAB[3].atoms)
            @test_throws ArgumentError superimpose(small_2WEL, small_6BAB)
        end
    end
end


# p18.jl - Chebyshev differentiation via FFT (compare p11.jl)

xx = -1:.01:1; ff = @. exp(xx)*sin(5*xx); clf();
for N = [10 20]
    x = cheb(N)[2]; f = @. exp(x)*sin(5*x);
    axes([.15, .66-.4*(N==20), .31, .28]);
    plot(x,f,"k.",markersize=6); grid(true);
    plot(xx,ff);
    title("f(x), N=$N");
    error = chebfft(f) - @. exp(x)*(sin(5*x)+5*cos(5*x));
    axes([.55, .66-.4*(N==20), .31, .28]);
    plot(x,error,".-",markersize=10); grid(true);
    title("error in f'(x),  N=$N");
end


"""
    assigndist_beta(α, β; trunc_lower = -Inf, trunc_upper = Inf)

Assign parameters to a Beta distribution with parameters `α` and `β`, optionally
truncating the distribution.
"""
function assigndist_beta(α, β; trunc_lower = -Inf, trunc_upper = Inf)
    Truncated(Beta(α, β), trunc_lower, trunc_upper)
end

export assigndist_beta


include("spline_kernel.jl")

# Removing t = 0, such that Σ is invertible
t = Vector(0.1:0.1:1); p = 2;

# Creating generators U,V that result in a positive-definite matrix Σ
U, V = spline_kernel(t, p);
Σ    = spline_kernel_matrix(U, V);
chol = cholesky(Σ)

# Creating a symmetric extended generator representable semiseperable matrix
K = SymSemiseparable(U,V)
# Calculating its Cholesky factorization
L = SymSemiseparableChol(K)
# Creating a test vector
xt = ones(K.n);

# Testing multiplication
@test isapprox(L*xt, chol.L*xt, atol=1e-6)
@test isapprox(L'*xt, chol.U*xt, atol=1e-6)

# Testing inverses (Using Cholesky factorizations)
@test isapprox(L'\(L\xt), chol.U\(chol.L\xt), atol=1e-6)
@test isapprox(inv(L), chol.U\(chol.L\Diagonal(ones(K.n))), atol=1e-6)
@test isapprox(inv(L,xt),  chol.U\(chol.L\xt), atol=1e-6)

# Testing logdet
@test isapprox(logdet(L), logdet(chol.L), atol=1e-10)
@test isapprox(logdet(L'), logdet(chol.U), atol=1e-10)

# Testing the Cholesky as a lower triangular matrix
C = SymSemiseparableChol(U,V);
@test isapprox(C*xt, tril(U*V')*xt)

# Testing going from Cholesky back to the matrix K
Kl = SymSemiseparable(L)
@test isapprox(K*x, Kl*x)


using Roots, EngEconomics

# Given
MARR = 0.06
A_Init = -1000
A_Year = 1500
B_Init = -2000
B_Year = 2800

# Find the better option using Incremental ROR
ΔInit = B_Init - A_Init
ΔYear = B_Year - A_Year
ΔIRR(x) = ΔInit + presentWorthFactor(x, 1) * ΔYear
ΔIRR_ans = find_zero(ΔIRR, 0.05)

# Because ΔIRR_ans > MARR, the second alternative, B, is better
println("Choose Alt. 2")


#== INCLUDE files ==#
include("aggregate.jl")
include("twoassets.jl")

include("solveHJB.jl")
include("solveKFE.jl")

#== Instance of prices ==#
pr = Prices(0.0, 0.04, 0.03, 0.09, 8.0)

#== Instance of TwoAssetProblem ==#
twoap = TwoAssetsProb2(pr; γ = 2.0, ρ = 0.06, ξ = 0.10, χ₀ = 0.08, χ₁= 3.0, τ = 0.0, T = 0.0)

#== Create and FiniteDifference Structure ==#
fde = FDExp(twoap; z = [.8, 1.3], λ = [-1/3 1/3; 1/3 -1/3],
fixedΔa = true, fixedΔb = false, bn = 100, bmin = -2.0, bmax = 40.0, an =70, amax = 70.0, invΔᴷ = 0.05)

#== Solve the HJB equation ==#
solve_hjb!(twoap, fde, fde.sol)

#== Solve the KF equation ==#
solve_fp!(fde, fde.sol, maxit = 4000, tolFK = 1e-9)

include("main_fig.jl");

#== Policies ==#
# fig_pol(fde)
#== Distributions ==#
fig_dens(fde)


# -----------------------------------------------------------
#
# Evaluating the 1D Galerkin Basis functions
#
# -----------------------------------------------------------

const K_max = 10

# Efficiency criticality: HIGH
# Central to the efficiency of the package

# Performing fastmath on an array of coefficients
# of x^k and |x|^k, giving the value at a specified point
# supported on [-1, 1]
@fastmath function array2poly(v::AbstractArray{T, 1}, x::Real) where T <: Real
    if abs(x)>1
        return zero(T)
    end
    n = length(v)
    k = div(n,2)
    s = zero(T)
    @inbounds for i in k:-1:1
        # Using Horner's method
        s *= x
        s += v[i] + flipsign(v[i+k], x)
    end
    return s
end

function array2poly(v::AbstractArray{T, 1}) where T <: Real
    return (x-> array2poly(v,x))
end

#precompute Legendre
leg_coeffs = legendre(K_max)

# Legendre polynomial supported on [-1, 1]
function LegendreP(k, x)
    k<=K_max || throw(DomainError())
    return array2poly(leg_coeffs[k+1], x)
end

function LegendreP(k)
    k<=K_max || throw(DomainError())
    return array2poly(leg_coeffs[k+1])
end

# precomputing the DG functions
# (Thanks to Alex Arslan for this.)
# Meaning: [baisis size k][coefficient, mode]
# Lengths: [K_max][2*k, k]
const dg_coeffs = Vector{Matrix{Float64}}(undef, K_max)

for k in 1:K_max
    Q = dg_basis(k)
    @assert length(Q) == k
    @assert all(length(Q[mode]) == 2*k for mode in 1:k)
    dg_coeffs[k] = Matrix{Float64}(undef, 2*k, k)
    for mode in 1:k
        dg_coeffs[k][:,mode] = Q[mode][:]
    end
end

# This is the dg basis function corresponding a given mode
# supported on [-1, 1]
function h(k, mode, x)
    mode<=k || throw(DomainError())
    return array2poly((@view dg_coeffs[k][:,mode]), x)
end

function h(k, mode)
    mode<=k || throw(DomainError())
    return array2poly((@view dg_coeffs[k][:,mode]))
end

# ----------------------------------------------
#
# Methods relating to the 1D Position Basis:
#
# ----------------------------------------------

# Legendre polynomial on [0, 1]
function leg(mode::Int, x::T) where T <: Real
    return sqrt(2.0)*LegendreP(mode-1, 2*x-1)
end

# level >= 1
# cell in 1:1<<level
# mode in 1:k
# Legendre polynomial on [(cell-1)*h, cell*h] with h = 1/(1<<level)
function basis(level::Int, cell::Int, mode::Int, x::T) where T <: Real
    return leg(mode, (1<<level)*x - (cell-1)) * (2.0)^(level/2)
end

function basis(level::Int, cell::Int, mode::Int)
    return x->basis(level, cell, mode, x)
end

# Perform numerical integration to get position basis coeffs
#
# Never used except if user demands to evolve wave equation
# using only the position basis, never going through heir
function pos_vcoeffs_DG(k::Int, level::Int, f::Function;
                        rtol = REL_TOL, atol = ABS_TOL, maxevals = MAX_EVALS)
    vcoeffs = Array{Float64}(undef, (1<<level)*(k))
    i = 1
    for cell in 1:(1<<level)
        for mode in 1:k
            fcn            = x->(basis(level, cell, mode,x)*f(x))
            left_bound  = (cell-1)/(1<<level)
            right_bound = (cell)/(1<<level)
            vcoeffs[i]  = hquadrature(fcn, left_bound, right_bound; atol=atol)[1]
            i += 1
        end
    end
    return vcoeffs
end

# ----------------------------------------------
# Building the hier2pos matrix:
# ----------------------------------------------

# Given an array of the type above, of coefficients
# for both both x^k and |x|^k, then given a choice of
# side (right vs. left) this becomes just a pure
# polynomial array of half the length
function convert_polyarray(v::AbstractArray{T, 1}, side = "left") where T <: Real
    n = Int(length(v)//2)
    if side == "left"
        return [v[i]-v[n+i] for i in 1:n]
    elseif side == "right"
        [v[i]+v[n+i] for i in 1:n]
    else throw(ArgumentError())
    end
end

# Gives the polynomial array corresponding to p(x/c)
function scale_polyarray(v::AbstractArray{T, 1}, c::Real) where T <: Real
    n = length(v)
    return [v[i] * (1//c^(i-1)) for i in 1:n]
end

# Gives the polynomial array corresponding to p(x-a)
function shift_polyarray(v::AbstractArray{T, 1}, a::Real) where T <: Real
    n = length(v)
    v_new = zeros(v)
    for i::Int in 1:n
        for j::Int in 1:i
            v_new[j] += v[i] * (-a)^(i-j) * binomial(i-1, j-1)
        end
    end
    return v_new
end

# Performs symbolic integration of the polynomials
# represented by v and w on the interval [a, b]
function integrate_polyarray(v::AbstractArray{T, 1}, w::AbstractArray{T, 1}; a::Real = 0, b::Real = 1) where T <: Real
    ans = zero(T)
    for i::Int in 1:length(v)
        for j::Int in 1:length(w)
            ans += v[i] * w[j] * ((b^(i+j-1)-a^(i+j-1))//(i+j-1))
        end
    end
    return ans
end

# With a bit of case work, we can do an explicit
# symbolic integration of the elements in the
# hierarchical basis against the legendre polynomials
# defining the position basis:
function hier2pos(k::Int, max_level::Int, level::Int, cell::Int, mode::Int)

    ans = Real[]

    # Compute the support and discontinuity
    # of the hierarchical basis element
    left_point  = (cell-1)//(1<<max(0, level-1))
    mid_point   = (cell- 1//2)//(1<<max(0, level-1))
    right_point = cell//(1<<max(0, level-1))

    # Level 0 of the hierarchical basis is a continous
    # Legendre polynomial
    if level == 0
        vl = vr = sqrt(2)*shift_polyarray(
                                scale_polyarray(leg_coeffs[mode][1:k],
                                                1//2),
                                            1//2)

    # The higher DG levels are given by two polynomials
    # defined on neighboring (right & left) intervals of size h/2,
    # which would need to be integrated against individually
    else
        h = 1//(1<<max(0, level-1))
        vl = shift_polyarray(
                scale_polyarray(
                    convert_polyarray((@view dg_coeffs[k][:,mode]), "left"),
                                1//2),
                            1//2)
        vl = shift_polyarray(scale_polyarray(vl, h), (cell-1)*h)*sqrt(1<<(level))

        vr = shift_polyarray(
                scale_polyarray(
                    convert_polyarray((@view dg_coeffs[k][:,mode]), "right"),
                                 1//2),
                            1//2)
        vr = shift_polyarray(scale_polyarray(vr, h), (cell-1)*h)*sqrt(1<<(level))
    end

    h = 1//(1<<max_level)
    for i in 1:1<<max_level
        for p in 1:k
            # Check if our hier function is supported on this interval
            if (i-1)*h < left_point || i*h > right_point
                push!(ans, 0)
                continue
            end

            # Obtain the polynomial array for the position basis function
            unit_leg = shift_polyarray(
                            scale_polyarray(leg_coeffs[p][1:k], 1//2), 1//2)
            pos_element = shift_polyarray(
                                scale_polyarray(unit_leg, h), (i-1)*h)
            pos_element *= sqrt(1<<(max_level+1)) # (2.0)^((max_level+1)/2)

            # Perform symbolic integration depending on which
            # interval the position basis function overlaps with the
            # DG basis element
            if (i-1)*h < mid_point
                val = integrate_polyarray(vl, pos_element; a=(i-1)*h, b=i*h)
            else
                val = integrate_polyarray(vr, pos_element; a=(i-1)*h, b=i*h)
            end
            push!(ans, val)
        end
    end
    return ans
end

# Construct the change of basis matrix from hierarchical
# to position basis
function hier2pos(k::Int, max_level::Int; atol=ABS_TOL)
    j = 1
    I = Int[]
    J = Int[]
    V = Float64[]
    for level in 0:max_level
        for cell in 1:(1<<max(0, level-1))
            for mode in 1:k
                ans = pos_vcoeffs_DG(k, max_level, v(k, level, cell, mode))
                for i in 1:length(ans)
                    if abs(ans[i]) > atol
                        push!(I, i)
                        push!(J, j)
                        push!(V, ans[i])
                    end
                end
                j += 1
            end
        end
    end
    return sparse(I, J, V, k * (1<<max_level), k * (1<<max_level), +)
end

function pos2hier(k::Int, max_level::Int; atol=ABS_TOL)
    return hier2pos(k, max_level; atol=atol)'
end


# # Matrix operators

#md # ```@meta
#md # CurrentModule = ImmersedLayers
#md # ```

#=
Many solutions of partial differential equations with immersed layers, particularly Poisson problems,
lead to saddle-point problems, in which the Schur complement operator is a matrix
composed from some of the surface-grid operators discussed in [Surface-grid operations](@ref).
The package provides some convenience tools for constructing these matrices. By
their nature, the construction of these matrices is slow, since each column
involves the application of the same set of operations. However, the point
of this construction is to do it once and store it for repeated application.
=#

#=
One common saddle point system is

$$A = \begin{bmatrix} L & R \\ R^T & 0 \end{bmatrix}$$

where $L$ is the discrete Laplacian and $R$ and $R^T$ are the regularization
and interpolation operators ([`regularize!`](@ref) and [`interpolate!`](@ref)),
respectively. This system arises in the solution of the Poisson equation
with Dirichlet boundary conditions on the immersed surface. The Schur complement of this is
$S = - R^T L^{-1} R$. This matrix can be obtained using the function [`create_RTLinvR`](@ref).
=#

#=
Another common saddle point system is

$$A = \begin{bmatrix} L & D_s \\ G_s & R_n^T R_n \end{bmatrix}$$

where $D_s$ and $G_s$ are the surface divergence and gradient operators
([`surface_divergence!`](@ref) and [`surface_grad!`](@ref)),
respectively, and $R_n$ and $R_n^T$ are [`regularize_normal!`](@ref) and [`normal_interpolate!`](@ref).
This system arises in the solution of the Poisson equation
with Neumann boundary conditions on the immersed surface. The Schur complement of this is
$S = R_n^T R_n - G_s L^{-1} D_s$. Each of the matrices in this are individually
provided by the package, by the functions [`create_nRTRn`](@ref) and [`create_GLinvD`](@ref),
respectively. However, it is useful to know that the sum of these two
matrices is exactly the matrix $-C_s L^{-1}C_s^T$, where $C_s$ and $C_s^T$ are
surface curl operators [`surface_curl!`](@ref). This complete matrix is provided by
[`create_CLinvCT`](@ref).
=#

#=
Another helpful matrix operator is the surface filter, given by

$$\tilde{R}^T R$$

where $\tilde{R}^T$ is a modified form of the interpolation operator,
designed to return the regularized field to the surface points while
maintaining the integral value of the original field [^1]. We can
obtain this matrix with [`create_surface_filter`](@ref).
=#

#md # ## Matrix construction functions

#md # ```@docs
#md # create_RTLinvR
#md # create_CLinvCT
#md # create_CL2invCT
#md # create_GLinvD
#md # create_GLinvD_cross
#md # create_nRTRn
#md # create_surface_filter
#md # ```

#md # [^1]: Goza, A., et al., (2016) "Accurate computation of surface stresses and forces with immersed boundary methods," J. Comput. Phys., 321, 860--873.


""" Computing with permutations: the computer algebra of the symmetric group.
"""
module Permutations
export decompose_permutation_by_bubble_sort!,
  decompose_permutation_by_insertion_sort!, permutation_to_expr

using Compat
using ...Syntax
using ...Theories: dom, codom, compose, id, otimes, munit, braid

# Decomposition
###############

""" Decompose permutation into adjacent transpositions using bubble sort.

An *adjacent transposition*, also known as a *simple transposition*, is a
transposition of form (i i+1), represented here as simply the number i.

This algorithm appears as Algorithm 2.7 in the PhD thesis of Jonathan Huang,
"Probabilistic reasonsing and learning on permutations: Exploiting structural
decompositions of the symmetric group". As Huang notes, the algorithm is
very similar to the well-known bubble sort. It has quadratic complexity.

See also: `decompose_permutation_by_insertion_sort!`
"""
function decompose_permutation_by_bubble_sort!(σ::Vector{Int})::Vector{Int}
  n = length(σ)
  result = Int[]
  for i in 1:n-1
    for j = n-1:-1:i
      if σ[j+1] < σ[j]
        σ[j], σ[j+1] = σ[j+1], σ[j]
        push!(result, j)
      end
    end
  end
  result
end

""" Decompose permutation into adjacent transpositions using insertion sort.

An *adjacent transposition*, also known as a *simple transposition*, is a
transposition of form (i i+1), represented here as simply the number i.

Bubble sort and insertion sort are, in a sense, dual algorithms (Knuth, TAOCP,
Vol 3: Searching and Sort, Sec 5.3.4: Networks for sorting, Figures 45 & 46). A
minimal example on which they give different decompositions is the permutation:

  [1,2,3] ↦ [3,2,1]

See also: `decompose_permutation_by_bubble_sort!`
"""
function decompose_permutation_by_insertion_sort!(σ::Vector{Int})::Vector{Int}
  n = length(σ)
  result = Int[]
  for i in 2:n
    for j in i:-1:2
      if σ[j-1] > σ[j]
        σ[j-1], σ[j] = σ[j], σ[j-1]
        push!(result, j-1)
      end
    end
  end
  result
end

# Conversion to expression
##########################

""" Convert a typed permutation into a morphism expression.

Warning: The morphism expression is not simplified.
"""
function permutation_to_expr(σ::Vector{Int}, xs::Vector; sort::Symbol=:insertion)
  permutation_to_expr!(copy(σ), copy(xs); sort=sort)
end
function permutation_to_expr!(σ::Vector{Int}, xs::Vector; sort::Symbol=:insertion)
  n = length(σ)
  @assert length(xs) == n

  transpositions = if sort == :bubble
    decompose_permutation_by_bubble_sort!(σ)
  elseif sort == :insertion
    decompose_permutation_by_insertion_sort!(σ)
  else
    error("Sorting algorithm not supported: $sort")
  end
  if isempty(transpositions)
    return id(otimes(xs))
  end

  layers = map(transpositions) do τ
    layer = [
      τ > 1 ? id(otimes(xs[1:τ-1])) : nothing,
      braid(xs[τ], xs[τ+1]),
      τ+1 < n ? id(otimes(xs[τ+2:n])) : nothing,
    ]
    xs[τ], xs[τ+1] = xs[τ+1], xs[τ]
    foldl(otimes, filter(!isnothing, layer))
  end
  foldl(compose, layers)
end

end


# I took this from the ConjugatePriors package

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

struct NormalInverseGamma{T<:Real} <: ContinuousUnivariateDistribution
    mu::T
    v0::T     # scales variance of Normal
    shape::T
    scale::T

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

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

mu(d::NormalInverseGamma) = d.mu
v0(d::NormalInverseGamma) = d.v0
shape(d::NormalInverseGamma) = d.shape
scale(d::NormalInverseGamma) = d.scale
rate(d::NormalInverseGamma) = 1. / d.scale

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

# Probably should guard agains dividing by and taking the log of 0.

function pdf(d::NormalInverseGamma, x::T, sig2::T) where T<:Real
    Zinv = d.scale.^d.shape / gamma(d.shape) / sqrt(d.v0 * 2.0*pi)
    return Zinv * 1.0/(sqrt(sig2)*sig2.^(d.shape+1.0)) * exp(-d.scale/sig2 - 0.5/(sig2*d.v0)*(x-d.mu).^2)
end

function logpdf(d::NormalInverseGamma, x::T, sig2::T) where T<:Real
    lZinv = d.shape*log(d.scale) - lgamma(d.shape) - 0.5*(log(d.v0) + log(2pi))
    return lZinv - 0.5*log(sig2) - (d.shape+1.)*log(sig2) - d.scale/sig2 - 0.5/(sig2*d.v0)*(x-d.mu).^2
end

function rand(d::NormalInverseGamma)
    # Guard against invalid precisions
    sig2 = rand(InverseGamma(d.shape, d.scale))
    if sig2 <= zero(Float64)
        sig2 = eps(Float64)
    end
    mu = rand(Normal(d.mu, sqrt(sig2*d.v0)))
    return mu, sig2
end


@testset "Calculation Tools" begin
    @testset "Test radial_mean" begin
        a = rr((10,10)) .< 5.0
        (rm, ax) = MicroscopyTools.radial_mean(a)
        @test rm[1] ≈ 1.0
        @test rm[5] ≈ 1.0
        @test rm[6] ≈ 0.0
        @test ax[1] == 0.5
        @test ax[end] == 7.5
    end

    @testset "Δ_phase" begin
        @test MicroscopyTools.Δ_phase([1,1im,-1,-1im,1], 1) ≈ -pi/2 .* ones(4)
    end

    @testset "moment projection" begin
        @test MicroscopyTools.moment_proj([1 3; 2 4], 2, pdims = (1,)) == [0.25 0.25]
        @test MicroscopyTools.moment_proj_normed([1 3; 2 4], 2, pdims = (1,)) == [0.5 0.5]
    end
end


#=
P26 (**) Generate the combinations of K distinct objects chosen from the N elements of a list
In how many ways can a committee of 3 be chosen from a group of 12 people? We all know that there are C(12,3) = 220 possibilities (C(N,K) denotes the well-known binomial coefficients). For pure mathematicians, this result may be great. But we want to really generate all the possibilities in a list.

Example:
* (combination 3 '(a b c d e f))
((A B C) (A B D) (A B E) ... )
=#

function filter_colls(colls, len)
    filter(coll -> length(coll) == len, colls)
end

function append_indices(indices, last_index)
    next_index = indices[end] + 1
    map(i -> vcat(indices, i), next_index:last_index)
end

function combination(n, coll)
    function _combination(index_list, count)
        if count == n
            index_list
        else
            new_index_list = map(indices -> append_indices(indices, length(coll)), index_list)
            flattened = reduce(vcat, new_index_list)
            _combination(filter_colls(flattened, count + 1), count + 1)
        end
    end

    index_list = _combination(map(idx -> [idx], 1:length(coll)), 1)
    map(indices -> map(i -> coll[i], indices), index_list)
end

coll = [:a, :b, :c, :d, :e, :f]
combos = combination(3, coll)
@assert length(combos) == 20
@assert combos[1] == [:a, :b, :c]
@assert combos[2] == [:a, :b, :d]
@assert combos[3] == [:a, :b, :e]
@assert combos[4] == [:a, :b, :f]
@assert combos[5] == [:a, :c, :d]
@assert combos[6] == [:a, :c, :e]
@assert combos[7] == [:a, :c, :f]
@assert combos[8] == [:a, :d, :e]
@assert combos[9] == [:a, :d, :f]
@assert combos[10] == [:a, :e, :f]
@assert combos[11] == [:b, :c, :d]
@assert combos[12] == [:b, :c, :e]
@assert combos[13] == [:b, :c, :f]
@assert combos[14] == [:b, :d, :e]
@assert combos[15] == [:b, :d, :f]
@assert combos[16] == [:b, :e, :f]
@assert combos[17] == [:c, :d, :e]
@assert combos[18] == [:c, :d, :f]
@assert combos[19] == [:c, :e, :f]
@assert combos[20] == [:d, :e, :f]

println("Tests passed: JL-26.jl")


import Rates
using Test

@testset "Continuous Compounding" begin
    r = 0.1
    t = 1.5

    @test Rates.erf(Rates.Continuous, r, t) ≈ exp(r*t)
    @test Rates.discountfactor(Rates.Continuous, r, t) ≈ 1.0/exp(r*t)
    @test Rates.erf_to_rate(Rates.Continuous, exp(r*t), t) ≈ r
    @test Rates.discountfactor_to_rate(Rates.Continuous, 1.0/exp(r*t), t) ≈ r
    @test_throws AssertionError Rates.discountfactor(Rates.Continuous, r, -0.1)
    @test_throws AssertionError Rates.discountfactor_to_rate(Rates.Continuous, 1.0, -0.1)
end

@testset "Simple Compounding" begin
    r = 0.1
    t = 1.5

    @test Rates.erf(Rates.Simple, r, t) ≈ 1+r*t
    @test Rates.discountfactor(Rates.Simple, r, t) ≈ 1.0/(1+r*t)
    @test Rates.erf_to_rate(Rates.Simple, 1+r*t, t) ≈ r
    @test Rates.discountfactor_to_rate(Rates.Simple, 1.0/(1+r*t), t) ≈ r
    @test_throws AssertionError Rates.discountfactor(Rates.Simple, r, -0.1)
    @test_throws AssertionError Rates.discountfactor_to_rate(Rates.Simple, 1.0, -0.1)
end

@testset "Exponential Compounding" begin
    r = 0.1
    t = 1.5

    @test Rates.erf(Rates.Exponential, r, t) ≈ (1+r)^t
    @test Rates.discountfactor(Rates.Exponential, r, t) ≈ 1.0/((1+r)^t)
    @test Rates.erf_to_rate(Rates.Exponential, (1+r)^t, t) ≈ r
    @test Rates.discountfactor_to_rate(Rates.Exponential, 1.0/((1+r)^t), t) ≈ r
    @test_throws AssertionError Rates.discountfactor(Rates.Exponential, r, -0.1)
    @test_throws AssertionError Rates.discountfactor_to_rate(Rates.Exponential, 1.0, -0.1)
end


using FastTransforms
using Compat.Test
import FastTransforms.pochhammer

const sqrtpi = 1.772453850905516027298

@testset "Pochhammer" begin
    @test pochhammer(2,3) == 24
    @test pochhammer(0.5,3) == 0.5*1.5*2.5
    @test pochhammer(0.5,0.5) == 1/sqrtpi
    @test pochhammer(0,1) == 0
    @test pochhammer(-1,2) == 0
    @test pochhammer(-5,3) == -60
    @test pochhammer(-1,-0.5) == 0
    @test 1.0/pochhammer(-0.5,-0.5) == 0
    @test pochhammer(-1+0im,-1) == -0.5
end


function fresnelS0(x::T) where {T}
    x2 = x*x
    x4 = x2*x2
    n = @evalpoly(x4, T(3.18016297876567817986E11),
                      -T(4.42979518059697779103E10),
                      T(2.54890880573376359104E9),
                      -T(6.29741486205862506537E7),
                      T(7.08840045257738576863E5),
                      -T(2.99181919401019853726E3))
    d = @evalpoly(x4, T(6.07366389490084639049E11),
                      T(2.24411795645340920940E10),
                      T(4.19320245898111231129E8),
                      T(5.17343888770096400730E6),
                      T(4.55847810806532581675E4),
                      T(2.81376268889994315696E2),
                      T(1))
    x*x2*n/d
end
function fresnelC0(x::T) where {T}
    x2 = x*x
    x4 = x2*x2
    n = @evalpoly(x4, T(9.99999999999999998822E-1),
                      -T(2.05525900955013891793E-1),
                      T(1.88843319396703850064E-2),
                      -T(6.45191435683965050962E-4),
                      T(9.50428062829859605134E-6),
                      -T(4.98843114573573548651E-8))
    d = @evalpoly(x4, T(1.00000000000000000118E0),
                      T(4.12142090722199792936E-2),
                      T(8.68029542941784300606E-4),
                      T(1.22262789024179030997E-5),
                      T(1.25001862479598821474E-7),
                      T(9.15439215774657478799E-10),
                      T(3.99982968972495980367E-12))
    x*n/d
end
fresnelS(x::T) where {T} = x < T(1.6) ? fresnelS0(x) : imag((1 + im)*erf(x*sqrt(T(pi))*(1 - im)/2)/2)
fresnelC(x::T) where {T} = x < T(1.6) ? fresnelC0(x) : real((1 + im)*erf(x*sqrt(T(pi))*(1 - im)/2)/2)


using QuDirac

# This file is an example of a discrete-time quantum random walk
# implemented using QuDirac.

# First, we define the Hadamard operator `H`, defined as
#
# H | 0 ⟩ = 1/√2 * ( | 0 ⟩ + | 1 ⟩ )
# H | 1 ⟩ = 1/√2 * ( | 0 ⟩ - | 1 ⟩ )
#
@rep_op " H | n > = 1/√2 * ( | 0 > + (-1)^n * | 1 > )" 0:1

# If an operation on a state only alters basis labels, not coefficients,
# it can be simpler and more efficient to use `maplabels` than to construct
# a whole new operator.
#
# In this case, we make a function `shift_ket`
# that maps over the basis labels of the input Ket such that:
#
# shift_ket(| 0, j ⟩) -> | 0, j - 1 ⟩
# shift_ket(| 1, j ⟩) -> | 1, j + 1 ⟩
#
function shift_map(label::StateLabel)
    if label[1] == 0
        return StateLabel(0, label[2] - 1)
    else
        return StateLabel(1, label[2] + 1)
    end
end

shift_ket(kt::Ket) = maplabels(shift_map, kt)

function walk_nsteps(steps)
    steps += 1

    # Allocate space for the results
    results = Array(Ket{KroneckerDelta, 2, Float64}, steps);

    # Initial state
    results[1] = d" 1.0 * | 0,0 > "

    # Calculate the each step by referring to the previous step
    for i=2:steps
        results[i] = shift_ket(act_on(H, results[i-1], 1))
    end

    return results
end

println("""
This example provides the `walk_nsteps` function, which takes in the
number of steps to walk and returns an array containing the result of
each step. The initial state is | 0,0 ⟩.""")


#' ---
#' title: Sensitivity analysis on Hill Model
#' author: Shao-Ting Chiu (r07945001@ntu.edu.tw)
#' date: 2020/08/27
#' ---

#' # Introduction
#' We are going to explore the

using PyCall
using Plots
using Parameters

include("../src/Sens.jl")
include("../src/Model.jl")
sp = pyimport("sympy")

#' Model: Hill Formula

s = sp.symbols("s", nonnegative=true) # Output / input
k, n = sp.symbols("k n", nonnegative=true) # dissociation coefficient constant and Hill coefficient

model = Model.hill(k=k, n=n)
X = model(s)

# Local Sensitivity
Sens.localSens(X, s)

rs = Sens.responseCoef(X, s)

frac = Sens.actFrac(X, X.subs(s,0), X.subs(s,1) )

rs
sp.solve(rs-n,frac)


lines = readlines(ARGS[1])
vals = [parse(Int, x) for x in split(lines[1], ",")]

function solution1(vals)
    min_val = min(vals...)
    max_val = max(vals...)

    target = nothing
    for candidate = min_val:max_val
        total_fuel = sum([abs(x - candidate) for x in vals])
        if isnothing(target)
            target = total_fuel
        elseif total_fuel < target
            target = total_fuel
        end
    end
    return target
end

function fuel_burnt(val1, val2)
    return sum([x for x = 1:abs(val1 - val2)])
end

function solution2(vals)
    min_val = min(vals...)
    max_val = max(vals...)

    target = nothing
    for candidate = min_val:max_val
        total_fuel = sum([fuel_burnt(x, candidate) for x in vals])
        if isnothing(target)
            target = total_fuel
        elseif total_fuel < target
            target = total_fuel
        end
    end
    return target
end

println(solution1(vals))
println(solution2(vals))


"""
    modularity(g, c, distmx=weights(g), γ=1.0)

Return a value representing Newman's modularity `Q` for the undirected and
directed graph `g` given the partitioning vector `c`. This method also supports
weighted graphs if the distance matrix is provided.

Modularity ``Q`` for undirected graph:
```math
Q = \\frac{1}{2m} \\sum_{c} \\left( e_{c} - \\gamma \\frac{K_c^2}{2m} \\right)
```

Modularity ``Q`` for directed graph:
```math
Q = \\frac{1}{m} \\sum_{c} \\left( e_{c} - \\gamma \\frac{K_c^{in} K_c^{out}}{m} \\right)
```

where:
- ``m``: total number of edges in the network
- ``e_c``: number of edges in community ``c``
- ``K_c``: sum of the degrees of the nodes in community ``c`` or the
  sum of the weighted degree of the nodes in community ``c`` when the graph is
  weighted. ``K_c^{in}`` sum of the in-degrees of the nodes in community ``c``.

### Optional Arguments
- `distmx=weights(g)`: distance matrix for weighted graphs
- `γ=1.0`: where `γ > 0` is a resolution parameter. When the modularity is used
  to find communities structure in networks (i.e with [Louvain's method for
  community detection](https://en.wikipedia.org/wiki/Louvain_Modularity)),
  higher resolutions lead to more communities, while lower resolutions lead to
  fewer communities. Where `γ=1.0` it lead to the traditional definition of
  the modularity.

### References
- M. E. J. Newman and M. Girvan. "Finding and evaluating community structure in networks".
  Phys. Rev. E 69, 026113 (2004). [(arXiv)](https://arxiv.org/abs/cond-mat/0308217)
- J. Reichardt and S. Bornholdt. "Statistical mechanics of community detection".
  Phys. Rev. E 74, 016110 (2006). [(arXiv)](https://arxiv.org/abs/cond-mat/0603718)
- E. A. Leicht and M. E. J. Newman. "Community structure in directed networks".
  Physical Review Letter, 100:118703, (2008). [(arXiv)](https://arxiv.org/pdf/0709.4500.pdf)

# Examples
```jldoctest
julia> using LightGraphs

julia> barbell = blockdiag(complete_graph(3), complete_graph(3));

julia> add_edge!(barbell, 1, 4);

julia> modularity(barbell, [1, 1, 1, 2, 2, 2])
0.35714285714285715

julia> modularity(barbell, [1, 1, 1, 2, 2, 2], γ=0.5)
0.6071428571428571

julia> using SimpleWeightedGraphs

julia> triangle = SimpleWeightedGraph(3);

julia> add_edge!(triangle, 1, 2, 1);

julia> add_edge!(triangle, 2, 3, 1);

julia> add_edge!(triangle, 3, 1, 1);

julia> barbell = blockdiag(triangle, triangle);

julia> add_edge!(barbell, 1, 4, 5); # this edge has a weight of 5

julia> modularity(barbell, [1, 1, 1, 2, 2, 2])
0.045454545454545456
```
"""
function modularity(
    g::AbstractGraph,
    c::AbstractVector{<:Integer};
    distmx::AbstractArray{<:Number}=weights(g),
    γ=1.0
    )

    m = sum([distmx[src(e), dst(e)] for e in edges(g)])
    m = is_directed(g) ? m : 2 * m

    m == 0 && return 0.
    nc = maximum(c)
    kin = zeros(Float32, nc)
    kout = zeros(Float32, nc)
    Q = 0.0
    for u in vertices(g)
        for v in neighbors(g, u)
            c1 = c[u]
            c2 = c[v]
            if c1 == c2
                Q += distmx[u,v]
            end
            kout[c1] += distmx[u,v]
            kin[c2] += distmx[u,v]
        end
    end
    Q = Q * m
    @inbounds for i = 1:nc
        Q -= γ * kin[i] * kout[i]
    end
    return Q / m^2
end


using TravelingSalesmanExact, GLPK
using TravelingSalesmanHeuristics
using TravelingSalesmanExact: ATT, euclidean_distance
using TravelingSalesmanBenchmarks
using Plots
using Random
gr(fmt=:svg)

Random.seed!(758)
N = 50
cost = 100*rand(N, N)
cost = (cost + transpose(cost) )/2

t_exact, c_exact = get_optimal_tour(cost, with_optimizer(GLPK.Optimizer))

c(q) = solve_tsp(cost; quality_factor = q)[2]

qs = range(10, stop = 100, step = 10)

plot(qs, c, xlabel="quality", ylabel="Cost", label="solve_tsp 1", title="random symmetric cost matrix")
for j = 2:5
    plot!(qs, c, label="solve_tsp $j")
end
hline!([c_exact], label="Exact cost")

file = @isdefined(WEAVE_ARGS) ? WEAVE_ARGS[:file] : nothing
TravelingSalesmanBenchmarks.bench_footer(file)


"""
複数の変数に対して，任意の q 値に対するパーセンタイル値を求めるには，以下のような関数を定義すればよい。

missing を除去して計算される。
"""

function quantile2(df, qs=[0, 0.25, 0.5, 0.75, 1])
    Names =["q$(Int(100q))" for q in qs]
    Names = replace(Names, "q0" => "min", "q50" => "median", "q100" => "max")
    qtile = map(x -> quantile(skipmissing(x), qs), eachcol(df))
    df2 = DataFrame(Matrix(reshape(vcat(qtile...), length(qs), :)'), Names)
    n = map(x -> length(filter(!ismissing, x)), eachcol(df))
    insertcols!(df2, 1, :variable => names(df), :n => n)
    df2
end

"""
使用例

julia> using RDatasets, Statistics

julia> iris = dataset("datasets", "iris");

julia> quantile2(iris[:, 1:4])
4×7 DataFrame
 Row │ variable     n      min      q25      median   q75      max
     │ String       Int64  Float64  Float64  Float64  Float64  Float64
─────┼─────────────────────────────────────────────────────────────────
   1 │ SepalLength    150      4.3      5.1     5.8       6.4      7.9
   2 │ SepalWidth     150      2.0      2.8     3.0       3.3      4.4
   3 │ PetalLength    150      1.0      1.6     4.35      5.1      6.9
   4 │ PetalWidth     150      0.1      0.3     1.3       1.8      2.5
"""


using JuMP, VariationalInequality

# https://cdr.lib.unc.edu/indexablecontent/uuid:778ca632-74ca-4858-8c3c-6dcfc7e6e703
# Example 4.2. This example is adapted from the example in (Fukushima, 1986). Let
m = VIPModel()
@variable(m, x[1:3])

@constraint(m, x[1]^2 + 0.4x[2]^2 + 0.6x[3]^2 <= 1)
@constraint(m, 0.6x[1]^2 + 0.4x[2]^2 + x[3]^2 <= 1)
@constraint(m, x[1] + x[2] + x[3] >= sqrt(3))

@mapping(m, F1, 2x[1] + 0.2x[1]^3 - 0.5x[2] + 0.1x[3] - 4)
@mapping(m, F2, -0.5x[1] + x[2] + 0.1x[2]^3 + 0.5)
@mapping(m, F3, 0.5x[1] - 0.2x[2] + 2x[3] - 0.5)

@innerproduct(m, F1, x[1])
@innerproduct(m, F2, x[2])
@innerproduct(m, F3, x[3])
# or
# @innerproduct(m, [F1, F2, F3], x)

solveVIP(m, algorithm=:fixed_point, max_iter=1000, step_size=0.1)
sol1, Fval1, gap1 = save_solution(m)
@assert 0<= gap1 < 1e-6

# The above `solveVIP` sets the value of variables at the solution
clear_values(m)

solveVIP(m, algorithm=:extra_gradient, max_iter=1000, step_size=0.1)
sol2, Fval2, gap2 = save_solution(m)
@assert 0<= gap2 < 1e-6

# sol2, Fval2, gap2 = solveVIP(m, algorithm=:hyperplane, max_iter=1000, step_size=0.1)

@show sol1
@show sol2
@show gap1
@show gap2

# OKAY.
# x∗ = (0.9168, 0.4850, 0.3303)


using Test
import GeneralizedSVD:GenSVD, gsvd
using LinearAlgebra
using DataFrames
using CSV

"""
    test(m, p, n)

This function tests the time performance
and backward stability of Generalized Singular Value Decomposition (GSVD)

First, we generate a m by n random matrix A and p by n random matrix B,
Create a copy of A and B, respectively (original A and B will be overwritten)

Then, we call gsvd function, return U, V, Q, C, S, R, k, l.
Calculate and print time elapsed for computing GSVD.

Compute:

      res_a = ||U' * A_ * Q - C * R||/(max(m,n)* ||A_|| * eps)

      res_b = ||V' * B_ * Q - S * R||/(max(p,n)* ||B_|| * eps)

      orthog_u = ||U' * U - I||/(m * eps)

      orthog_v = ||V' * V - I||/(p * eps)

      orthog_q = ||Q' * Q - I||/(n * eps)

# Arguments
- m: num of rows in A
- p: num of rows in B
- n: num of cols in A, B

no return
"""
function computemetric(F::GenSVD, A, B)
    e = eps(Float64)
    m, n = size(A)
    p = size(B)[1]
    res_a = opnorm(F.U'*A*F.Q - F.C*F.R, 1)/(max(m,n)*opnorm(A, 1)*e)
    res_b = opnorm(F.V'*B*F.Q - F.S*F.R, 1)/(max(p,n)*opnorm(B, 1)*e)
    orthog_cs = opnorm(I - F.C'*F.C - F.S'*F.S, 1)/((F.k+F.l)*e)
    orthog_u = opnorm(I - F.U'*F.U, 1)/(m*e)
    orthog_v = opnorm(I - F.V'*F.V, 1)/(p*e)
    orthog_q = opnorm(I - F.Q'*F.Q, 1)/(n*e)
    return res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q
end

function computemetric(F::GeneralizedSVD, A, B)
    e = eps(Float64)
    m, n = size(A)
    p = size(B)[1]
    res_a = opnorm(F.U'*A*F.Q - F.D1*F.R0, 1)/(max(m,n)*opnorm(A, 1)*e)
    res_b = opnorm(F.V'*B*F.Q - F.D2*F.R0, 1)/(max(p,n)*opnorm(B, 1)*e)
    orthog_cs = opnorm(I - F.D1'*F.D1 - F.D2'*F.D2, 1)/((F.k+F.l)*e)
    orthog_u = opnorm(I - F.U'*F.U, 1)/(m*e)
    orthog_v = opnorm(I - F.V'*F.V, 1)/(p*e)
    orthog_q = opnorm(I - F.Q'*F.Q, 1)/(n*e)
    return res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q
end

function test(m, p, n)
    A = randn(m, n)
    B = randn(p, n)
    F = gsvd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F, A, B)
    return F.k, F.l, res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q
end

function test()
    df = DataFrame(m = Int64[], p = Int64[], n = Int64[], k = Int64[], l = Int64[], res_a = Float64[],
    res_b = Float64[], orthog_cs = Float64[], orthog_u = Float64[],
    orthog_v = Float64[], orthog_q = Float64[])

    # case 1: m >= n & p >= n
    println("=======================Case 1============================")
    for i = 1:20
        a, b, c, d, e, f, g, h = test(60, 50, 40)
        push!(df, [60 50 40 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(300, 250, 200)
        push!(df, [300 250 200 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(900, 750, 600)
        push!(df, [900 750 600 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(1500, 1250, 1000)
        push!(df, [1500 1250 1000 a b c d e f g h])
        println("---------------------------------------------------------------")
    end

    # case 2: m >= n > p
    println("=======================Case 2================================")
    for i = 1:20
        a, b, c, d, e, f, g, h = test(60, 40, 50)
        push!(df, [60 40 50 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(300, 200, 250)
        push!(df, [300 200 250 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(900, 600, 750)
        push!(df, [900 600 750 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(1500, 1000, 1250)
        push!(df, [1500 1000 1250 a b c d e f g h])
        println("---------------------------------------------------------------")
    end

    # case 3: p >= n > m
    println("=======================Case 3==============================")
    for i = 1:20
        a, b, c, d, e, f, g, h = test(40, 60, 50)
        push!(df, [40 60 50 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(200, 300, 250)
        push!(df, [200 300 250 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(600, 900, 750)
        push!(df, [600 900 750 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(1000, 1500, 1250)
        push!(df, [1000 1500 1250 a b c d e f g h])
        println("---------------------------------------------------------------")
    end

    # case 4: n > m & n > p
    println("=====================Case 4============================")
    for i = 1:20
        a, b, c, d, e, f, g, h = test(20, 30, 60)
        push!(df, [20 30 60 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(200, 300, 600)
        push!(df, [200 300 600 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(400, 600, 1200)
        push!(df, [400 600 1200 a b c d e f g h])
        println("---------------------------------------------------------------")
    end
    for i = 1:20
        a, b, c, d, e, f, g, h = test(1000, 1500, 3000)
        push!(df, [1000 1500 3000 a b c d e f g h])
        println("---------------------------------------------------------------")
    end

    CSV.write("gsvd_rand_matrix_test.csv", df, append=true)
    return df
end

# Example 1 from Ch.4 of Jenny's Thesis
function test1()
    A  = Float64[1 2 1 0; 2 3 1 1; 3 4 1 2;4 5 1 3;5 6 1 4]
    B = Float64[6 7 1 5;7 1 -6 13;-4 8 9 -2]
    @time F1 = gsvd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F1, A, B)
    println("--------Residual of gsvd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    println([A; B] ≈ [F1.U*F1.C; F1.V*F1.S]*F1.R*F1.Q')

    F2 = svd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F2, A, B)
    println("--------Residual of svd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    return F1, F2
end

# Example 2 from Ch.4 of Jenny's Thesis
# It turns out this example is wrong

# This thus is used as Example 2 in working notes
function test2()
    A = [1. 2 1 0;2 3 1 1;3 4 1 2]
    B = [4. 5 1 3;5 6 1 4;6 7 1 5;7 1 -6 13]
    F1 = gsvd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F1, A, B)
    println("--------Residual of gsvd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    println([A; B] ≈ [F1.U*F1.C; F1.V*F1.S]*F1.R*F1.Q')

    F2 = svd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F2, A, B)
    println("--------Residual of svd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    return F1, F2
end

function test2_wrapper()
    A = [1. 2 1 0;2 3 1 1;3 4 1 2];
    B = [4. 5 1 3;5 6 1 4;6 7 1 5;7 1 -6 13];
    return U, V, Q, D1, D2, k, l, R = gsvdwrapper(A, B)
end

# Example 1 in working notes
function test3()
    A = [1. 2 3 0; 5 4 2 1; 0 3 5 2; 2 1 3 3; 2 0 5 3];
    B = [1. 0 3 -1; -2 5 0 1; 4 2 -1 2];
    F1 = gsvd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F1, A, B)
    println("--------Residual of gsvd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    println([A; B] ≈ [F1.U*F1.C; F1.V*F1.S]*F1.R*F1.Q')

    F2 = svd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F2, A, B)
    println("--------Residual of svd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    return F1, F2
end

function test3_wrapper()
    A = [1. 2 3 0; 5 4 2 1; 0 3 5 2; 2 1 3 3; 2 0 5 3];
    B = [1. 0 3 -1; -2 5 0 1; 4 2 -1 2];
    return U, V, Q, D1, D2, k, l, R = gsvdwrapper(A, B)
end

# Example 4 in working notes
function test4()
    A = [1. 4 2 3 0;3 4 0 -2 1;4 7 5 6 3];
    B = [1. 4 2 3 0;2 5 3 4 1;3 6 4 5 2;0 1 -1 3 1];
    F1 = gsvd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F1, A, B)
    println("--------Residual of gsvd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    println([A; B] ≈ [F1.U*F1.C; F1.V*F1.S]*F1.R*F1.Q')

    F2 = svd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F2, A, B)
    println("--------Residual of svd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    return F1, F2
end

# Early exit after preprocessing
function test5()
    # rank(A) = 3, rank(B) = 1, rank([A;B]) = 4
    A = [1. 2 3 4;3 5 2 1;4 6 2 1];
    B = [0 0 3. 0;0 0 0 0;0 0 2. 0; 0 0 0 0];
    F1 = gsvd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F1, A, B)
    println("--------Residual of gsvd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    println([A; B] ≈ [F1.U*F1.C; F1.V*F1.S]*F1.R*F1.Q')

    F2 = svd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F2, A, B)
    println("--------Residual of svd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    return F1, F2
end

# Early exit in CSD
function test6()
    # rank(A) = 4, rank(B) = 1, rank([A;B]) = 4
    A = [1.0 2 3 4;2 5 2 1;4 2 3 1;2 4 5 1];
    B = [1. 1 0 0;0 0 0 0; 0 0 0 0; 0 0 0 0];
    F1 = gsvd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F1, A, B)
    println("--------Residual of gsvd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    println([A; B] ≈ [F1.U*F1.C; F1.V*F1.S]*F1.R*F1.Q')

    F2 = svd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F2, A, B)
    println("--------Residual of svd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    return F1, F2
end

# Example in the document GSVD.jl VS. gsvd.m
function test7()
    U = [1/2 0 sqrt(3)/2 0;
        0 -sqrt(3)/2 0 1/2;
        sqrt(3)/2 0 -1/2 0;
        0 1/2 0 sqrt(3)/2]
    V = [1/2 0 sqrt(3)/2 0;
        0 -sqrt(3)/2 0 1/2;
        sqrt(3)/2 0 -1/2 0;
        0 1/2 0 sqrt(3)/2]
    Q = [1/2 0 sqrt(3)/2 0;
        0 -sqrt(3)/2 0 1/2;
        sqrt(3)/2 0 -1/2 0;
        0 1/2 0 sqrt(3)/2]
    R = [0 1 0 0;0 0 1 0;0 0 0 1]
    C = [sqrt(3)/2 0 0;0 1/2 0;0 0 0;0 0 0]
    S = [1/2 0 0;0 sqrt(3)/2 0;0 0 1;0 0 0]
    A = U*C*R*Q'
    B = V*S*R*Q'
    F1 = gsvd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F1, A, B)
    println("--------Residual of gsvd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    println([A; B] ≈ [F1.U*F1.C; F1.V*F1.S]*F1.R*F1.Q')

    F2 = svd(A, B)
    res_a, res_b, orthog_cs, orthog_u, orthog_v, orthog_q = computemetric(F2, A, B)
    println("--------Residual of svd---------")
    println("res_a: ", res_a)
    println("res_b: ", res_b)
    println("orthog_cs: ", orthog_cs)
    println("orthog_u: ", orthog_u)
    println("orthog_v: ", orthog_v)
    println("orthog_q: ", orthog_q)
    return F1, F2
end

# function profiling_function(m, p, n)
#     maxI = 50
#     t_pre = 0.0
#     t_qr = 0.0
#     t_csd = 0.0
#     t_all = 0.0
#     for i = 1:maxI
#         println("---$i-th run---")
#         A = randn(m, n)
#         B = randn(p, n)
#         ans = @timed gsvd(A, B)
#         t_pre = t_pre + ans[1][1]
#         t_qr = t_qr + ans[1][2]
#         t_csd = t_csd + ans[1][3]
#         t_all = t_all + ans[2]
#     end
#     t_pre = t_pre/maxI
#     t_qr = t_qr/maxI
#     t_csd = t_csd/maxI
#     t_all = t_all/maxI
#     p_pre = 100*t_pre/t_all
#     p_qr = 100*t_qr/t_all
#     p_csd = 100*t_csd/t_all
#     return m, p, n, t_pre, p_pre, t_qr, p_qr, t_csd, p_csd, t_all
# end
#
# function test_profiling()
#     df = DataFrame(m = Int64[], p = Int64[], n = Int64[],
#     t_pre = Float64[], p_pre = Float64[],
#     t_qr = Float64[], p_qr = Float64[],
#     t_csd = Float64[], p_csd = Float64[],
#     t_all = Float64[])
#
#     # case 1: m >= n & p >= n
#     println("=======================Case 1============================")
#     t1a = profiling_function(1500, 1200, 1000)
#     push!(df, [t1a[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t1b = profiling_function(500, 500, 500)
#     push!(df, [t1b[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t1c = profiling_function(650, 310, 230)
#     push!(df, [t1c[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t1d = profiling_function(430, 610, 210)
#     push!(df, [t1d[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#
#     # case 2: m >= n > p
#     println("=======================Case 2================================")
#     t2a = profiling_function(1500, 1000, 1200)
#     push!(df, [t2a[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t2b = profiling_function(720, 220, 540)
#     push!(df, [t2b[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t2c = profiling_function(440, 180, 440)
#     push!(df, [t2c[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t2d = profiling_function(370, 290, 350)
#     push!(df, [t2d[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#
#     # case 3: p >= n > m
#     println("=======================Case 3==============================")
#     # Catch LAPACK Exception: DBDSDC did not converge
#     t3a = profiling_function(1000, 1500, 1200)
#     push!(df, [t3a[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t3b = profiling_function(250, 300, 300)
#     push!(df, [t3b[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t3c = profiling_function(360, 660, 600)
#     push!(df, [t3c[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t3d = profiling_function(130, 520, 480)
#     push!(df, [t3d[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#
#     # case 4: n > m & n > p
#     println("=====================Case 4============================")
#     # Catch LAPACK Exception: DBDSDC did not converge
#     # t4a = profiling_function(1000, 1200, 1500)
#     t4a = profiling_function(1300, 1200, 1500)
#     push!(df, [t4a[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     t4b = profiling_function(260, 600, 770)
#     push!(df, [t4b[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     # Might early exit
#     # t4c = profiling_function(370, 250, 700)
#     t4c = profiling_function(370, 500, 700)
#     push!(df, [t4c[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#     # Might early exit
#     # t4d = profiling_function(120, 120, 400)
#     t4d = profiling_function(220, 220, 400)
#     push!(df, [t4d[i] for i in 1:10])
#     println("---------------------------------------------------------------")
#
#     CSV.write("LAPACK-JuliaX.X-profiling.csv", df, append=true)
#     return df
# end
#
# function wrapper_profiling_function(m, p, n)
#     maxI = 20
#     t_dggsvp3 = 0.0
#     t_dtgsja = 0.0
#     t_all = 0.0
#     for i = 1:maxI
#         println("---$i-th run---")
#         A = randn(m, n)
#         B = randn(p, n)
#         ans = @timed gsvdwrapper(A, B)
#         t_dggsvp3 = t_dggsvp3 + ans[1][1]
#         t_dtgsja = t_dtgsja + ans[1][2]
#         t_all = t_all + ans[2]
#     end
#     t_dggsvp3 = t_dggsvp3/maxI
#     t_dtgsja = t_dtgsja/maxI
#     t_all = t_all/maxI
#     p_dggsvp3 = 100*t_dggsvp3/t_all
#     p_dtgsja = 100*t_dtgsja/t_all
#     return m, p, n, t_dggsvp3, p_dggsvp3, t_dtgsja, p_dtgsja, t_all
# end
#
# function test_wrapper_profiling()
#     df = DataFrame(m = Int64[], p = Int64[], n = Int64[],
#     t_dggsvp3 = Float64[], p_dggsvp3 = Float64[],
#     t_dtgsja = Float64[], p_dtgsja = Float64[],
#     t_all = Float64[])
#
#     # case 1: m >= n & p >= n
#     println("=======================Case 1============================")
#     t1a = wrapper_profiling_function(1500, 1200, 1000)
#     push!(df, [t1a[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t1b = wrapper_profiling_function(500, 500, 500)
#     push!(df, [t1b[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t1c = wrapper_profiling_function(650, 310, 230)
#     push!(df, [t1c[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t1d = wrapper_profiling_function(430, 610, 210)
#     push!(df, [t1d[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#
#     # case 2: m >= n > p
#     println("=======================Case 2================================")
#     t2a = wrapper_profiling_function(1500, 1000, 1200)
#     push!(df, [t2a[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t2b = wrapper_profiling_function(720, 220, 540)
#     push!(df, [t2b[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t2c = wrapper_profiling_function(440, 180, 440)
#     push!(df, [t2c[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t2d = wrapper_profiling_function(370, 290, 350)
#     push!(df, [t2d[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#
#     # case 3: p >= n > m
#     println("=======================Case 3==============================")
#     t3a = wrapper_profiling_function(1000, 1500, 1200)
#     push!(df, [t3a[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t3b = wrapper_profiling_function(250, 300, 300)
#     push!(df, [t3b[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t3c = wrapper_profiling_function(360, 660, 600)
#     push!(df, [t3c[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t3d = wrapper_profiling_function(130, 520, 480)
#     push!(df, [t3d[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#
#     # case 4: n > m & n > p
#     println("=====================Case 4============================")
#     t4a = wrapper_profiling_function(1300, 1200, 1500)
#     push!(df, [t4a[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t4b = wrapper_profiling_function(260, 600, 770)
#     push!(df, [t4b[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t4c = wrapper_profiling_function(370, 500, 700)
#     push!(df, [t4c[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#     t4d = wrapper_profiling_function(220, 220, 400)
#     push!(df, [t4d[i] for i in 1:8])
#     println("---------------------------------------------------------------")
#
#     CSV.write("Julia1.3-profiling.csv", df, append=true)
#     return df
# end


function nlp_cvx_205_010(
    optimizer,
    objective_tol,
    primal_tol,
    dual_tol,
    termination_target = TERMINATION_TARGET_LOCAL,
    primal_target = PRIMAL_TARGET_LOCAL,
)
    # Test Goals:
    # - linear objective
    # - intersection convex quadratic constraints
    # - exponential cones
    # Variants
    #   010 - intersection set

    model = Model(optimizer)

    @variable(model, x)
    @variable(model, y >= 0, start = 0.1)
    @variable(model, z)

    @objective(model, Max, y)
    @NLconstraint(model, y * exp(x / y) <= z)
    @NLconstraint(model, y * exp(-x / y) <= z)
    @NLconstraint(model, x^2 + y^2 <= -z + 5)

    optimize!(model)

    check_status(model, FEASIBLE_PROBLEM, termination_target, primal_target)
    check_objective(model, 1.7912878443121907, tol = objective_tol)
    return check_solution(
        [x, y, z],
        [0.0, 1.7912878443121907, 1.7912878443121907],
        tol = primal_tol,
    )
end


""" Julia program to Check if a number is a Palindrome Number or not.
A palindrome number is a number such that the reverse of the number is equal to the number itself
"""

function check_palindrome(num)
    temp = num
    rev = 0
    while (temp > 0)
        rem = temp % 10
        rev = (rev * 10) + rem
        temp = temp ÷ 10;
    end
    if (num == rev)
        return true
    else
        return false
    end
end

print("Enter the number: ")
num = readline()
num = parse(Int, num)
res = check_palindrome(abs(num))
if res
    println("The given number $num is a Palindrome Number.")
else
    println("The given number $num is not a Palindrome Number.")
end

"""
Time Complexity: O(log(num)), where 'num' is the given number
Space Complexity: O(1)

SAMPLE INPUT AND OUTPUT

SAMPLE 1
Enter the number: 121
The given number 121 is a Palindrome Number.

SAMPLE 2
Enter the number: 1234
The given number 1234 is not a Palindrome Number.

"""


using BenchmarkTools

A = rand(1000,1000)
B = rand(1000,1000)
C = rand(1000,1000)

function fused(A,B,C)
    A .+ B .+ C
end
@btime fused(A,B,C)
δ = rand(10,10)
D = zeros(1000,1000)
function fused_1!(D,A,B,C)
    D = A .+ B .+ C # equiv to tmp = A .+ B .+ C; D = tmp
end
δ = fused_1!(D,A,B,C)
@btime fused_1!(D,A,B,C)

D = zeros(1000,1000)
function fused_2!(D,A,B,C)
    for j in 1:2000, i in 1:2000
        D[i,j] = A[i,j] + B[i,j] + C[i,j]
    end
    return D
end
δ = fused_2!(D,A,B,C)

D = zeros(1000,1000)
function fused_3!(D,A,B,C)
D .= A .+ B .+ C
end
fused_3!(D,A,B,C)
δ = fused_3!(D,A,B,C)
@btime fused_2!(D,A,B,C)
@btime fused_3!(D,A,B,C)

a = [1,2,3]
function f!(a)
a[1] = 10
end
f!(a)
a # a = [10,2,3]
function g!(a)
a = [1,2]
end
g!(a)
a # a = [10,2,3]

@btime f!(a) # a[1] = 10
@btime g!(a) # a = [1,2]

function g!(a)
    deleteat!(a, 3)
    a[:] = [1,2]
end
g!(a)
a # a = [10,2,3]

function f_alloc(A)
    B = A[1:5,1:5]
end

function f_point(A)
    B = @view A[1:5,1:5]
end

@btime f_alloc(A) # allocate array, 80 ns
@btime f_point(A)

A = rand(1000,1000) # Bind A to 1000x1000 rand matrix
B = f_alloc(A) # Copy 5x5 slice of A to new allocation, B
A .= zeros(1000,1000) # change contents of A
B # B is unchanged

A = rand(1000,1000) # Bind A to new 1000x1000 rand matrix
B = f_point(A) # Bind B to a pointer, which points at 5x5 slice of A
A .= zeros(1000,1000) # change contents of A

function f!(B)
    B[1,2]=10
end

f!(B)

B[1,2]

A[1,2]

N = 1000000
function f(x,y,N)
    z=0
    for i=1:N
        z += x*y
    end
 end

 @btime f(0.0,3.0)

 function f(x,y)
     z=0
     for i=1:10
         z += x*y
     end
  end

  @btime f(2,3)

@code_llvm f(2,5)

@code_llvm f(2.0,5.0)

@code_typed f(2,5.0)
@code_llvm f(2,5.0)

ff(x::Int,y::Int) = 2x + y
ff(x::Float64,y::Float64) = x/y
@show ff(2,5)
@show ff(2.0,5.0)

ff(x::Number,y::Number) = 2x
ff(x::Union{Int,Float64},y::Union{Int,Float64}) = 10 + x

ff(2.0,5)
typeof(2.0) <: Int

ff(x::Float64,y::Number) = 5x + 2y
ff(x::Number,y::Int) = x - y
ff(2.0,5)

function h()
    a = [1,2,3]
end
@btime h()

function hh()
    a = [0.0,0.3183098861837,3.0]
end
@btime hh()

function no_prealloc(A,B)
    C = rand(1000,1000) # preallocate array inside of function
    for j in 1:1000, i in 1:1000
        val = A[i,j] + B[i,j]
        C[i,j] = val
    end
end
@btime no_prealloc(A,B) # 3 ms, 2 allocations

C = zeros(1000,1000) # preallocate array outside of function
function w_prealloc!(C,A,B)
    for j in 1:1000, i in 1:1000
        val = A[i,j] + B[i,j]
        C[i,j] = val
    end
end
@btime w_prealloc!(C,A,B) # 2 ms, 0 allocations

w_prealloc!(C,A,B)

C

a = [0 0 0]
v = [1 2 3]
function f!(a,v)
    for i=1:100
        a .+= v
    end
end

@btime f!(a,v)

v = @SVector [1, 2, 3]
function g!(a,v)
    for i=1:100
        a' .+= v
    end
end

@btime g!(a,v)

A.+B.+C./10

function unfused(A,B,C)
    A + B + C
end
@btime unfused(A,B,C) # same as no_broadcast()
D
function fused(A,B,C,D)
    tmp = A .+ B
     tmp .+= C
end
@btime fused(A,B,C)

b = rand(100)
function dothis(b)
    a = sin.(b)
end

dothis(b)


export Mesh

"""
    `Mesh(args)`

Constructs a mesh of collocation points and associated Fourier modes.

# Arguments
Can be either
- `xmin , xmax , N `; or
- `L , N ` (same as above with `xmin=-L` and `xmax=L`); or
- `param :: NamedTuple`, param contains `N` and `L`, then same as above; or
- `x` a vector of regularly spaced collocation points`.

The mesh as `N` collocation points regularly spaced between `xmin` (included) and `xmax` (excluded)

# Return values
`m=Mesh(args)` is of parametric type and offers
with
- `m.N `: number of collocation points and Fourier modes;
- `m.xmin`: minimum of the mesh;
- `m.xmax`: maximum of the mesh;
- `m.dx`: distance between two collocation points;
- `m.x`: the vector of collocation points;
- `m.kmin`: minimum of Fourier modes;
- `m.kmax`: maximum of Fourier modes;
- `m.dk`: distance between two Fourier modes;
- `m.k`: the vector of Fourier modes.

"""
struct Mesh

    N    :: Int64
    xmin :: Float64
    xmax :: Float64
    dx   :: Float64
    x    :: Vector{Float64}
    kmin :: Float64
    kmax :: Float64
    dk   :: Float64
    k    :: Vector{Float64}

    function Mesh( xmin , xmax , N :: Int64)

        dx   = (xmax-xmin)/N
        x    = zeros(Float64, N)
        x   .= range(xmin, stop=xmax, length=N+1)[1:end-1]
        dk   = 2π/(N*dx)
        kmin = -N/2*dk
        kmax = (N/2-1)*dk
        k    = zeros(Float64, N)
        k   .= dk .* vcat(0:(N-1)÷2, -N÷2:-1)

        new( N, xmin, xmax, dx, x, kmin, kmax, dk, k)

    end

    function Mesh(L , N :: Int64)

        xmin = - L
        xmax =   L
        N    =   N

        Mesh( xmin, xmax, N)

    end

    function Mesh( x )

        N    =   length(x)
        dx   =   (x[end]-x[1])/(N-1)
        xmin = x[1]
        xmax = x[end]+dx

        Mesh( xmin, xmax, N)

    end

    function Mesh(param :: NamedTuple)

        if :xmin in keys(param) && :xmax in keys(param)
            xmin = param.xmin
            xmax = param.xmax
        else
            xmin = - param.L
            xmax =   param.L
        end
        N    =   param.N

        Mesh( xmin, xmax, N)

    end
end


# using Revise, Test
# using Plots
using BifurcationKit, OrdinaryDiffEq, ForwardDiff
	# using Zygote, DiffEqSensitivity
	using LinearAlgebra, Parameters

norminf = x -> norm(x, Inf)

function Fsl!(f, u, p, t)
	@unpack r, μ, ω, c3 = p
	u1 = u[1]
	u2 = u[2]

	ua = u1^2 + u2^2

	f[1] = r * u1 - ω * u2 - ua * (c3 * u1 - μ * u2)
	f[2] = r * u2 + ω * u1 - ua * (c3 * u2 + μ * u1)

	return f
end

Fsl(x, p) = Fsl!(similar(x), x, p, 0.)
dFsl(x, dx, p) = ForwardDiff.derivative(t -> Fsl(x .+ t .* dx, p), 0.)

# function to compute differentials
function diffAD(f, x, dx)
	# Zygote.pullback(t->f(x .+ t.* dx), 0.)[1]
	ForwardDiff.derivative(t->f(x .+ t.* dx), 0.)
end

####################################################################################################
par_sl = (r = 0.1, μ = 0., ω = 1.0, c3 = 1.0,)
u0 = [.001, .001]
δ = 1e-8
prob = ODEProblem(Fsl!, u0, (0., 100.), par_sl)
algsl = KenCarp4()#Rodas4P()
####################################################################################################
sol = solve(prob, algsl, abstol =1e-9, reltol=1e-6)
# plot(sol[1,:], sol[2,:])

function flowTS(x, t, pb = prob; alg = algsl, kwargs...)
	_pb = remake(pb; u0 = x, tspan = (zero(eltype(t)), t) )
	sol = DiffEqBase.solve(_pb, alg; abstol =1e-10, reltol=1e-9, save_everystep = false, kwargs...)
	return sol.t, sol
end
flowDE = (x, t, pb = prob; alg = algsl, kwargs...) -> flowTS(x, t, pb = pb; alg = alg, kwargs...)[2][end]

dflowDE = (x, dx, ts; kwargs...) -> diffAD(z -> flowDE(z, ts; kwargs...), x, dx)
dflowDEfd = (x, dx, ts) -> (flowDE(x .+ δ .* dx, ts) - flowDE(x, ts)) ./ δ

@test dflowDE(u0,u0,1.) .- dflowDEfd(u0,u0,1.) |> norminf < 10δ
####################################################################################################
# defining the Poincare Map
normals = [[-1., 0.]]
centers = [zeros(2)]

sectionH(x, c, n) = dot( x .- c[1], n[1])
pSection(u, t, integrator) = sectionH(u, centers, normals) * (integrator.iter > 1)
affect!(integrator) = terminate!(integrator)
cb = ContinuousCallback(pSection, affect!; affect_neg! = nothing)

Π(x) = flowDE(x, Inf; callback = cb, save_everystep = false) # Poincaré return map
T(x) = flowTS(x, Inf64; callback = cb)[1][end] # time to section
dΠ(x, dx) = diffAD(Π, x, dx)
dΠFD(x, dx) = (Π(x .+ δ .* dx) .- Π(x)) ./ δ

function DPoincare(x, dx, p, normal, center, _cb; verbose = false)
	verbose && printstyled(color=:blue, "\nEntree dans DPoincare\n")
	abs(dot(normal, dx)) > 1e-12 && @warn "Vector does not belong to hyperplane!  dot(normal, dx) = $(abs(dot(normal, dx))) and $(dot(dx, dx))"
	# compute the Poincare map from x
	_tΣ, _solΣ = flowTS(x, Inf; callback = _cb, save_everystep = false)
	tΣ, solΣ = _tΣ[end], _solΣ[end]

	z = Fsl(solΣ, p)
	verbose && @show z tΣ solΣ
	# solution of the variational equation at time tΣ
	# We need the callback to be INACTIVE here!!!
	y = dflowDE(x, dx, tΣ; callback = nothing)
	verbose && @show y
	out = y .- (dot(normal, y) / dot(normal, z)) .* z
end

# vecteurs for the flow and the variational flow
u0 = [0, 1.]
du0 = [0, -1.]

# check that we cross the sections the way we want
ts, ss = flowTS([0., 1], Inf; callback = cb, save_everystep = true, save_at = LinRange(0,1,20))
# plot(ts,ss[:,:]')
# plot(ss[1,:], ss[2,:], label="flow");scatter!(ss[1,[1]], ss[2,[1]]);plot!(sol[1,:], sol[2,:], label="sol")

# these vectors should be the same
# println("--> dΠ using Forward Diff")
# res = dΠ(u0, du0); show(res)

# println("--> dΠ using Zygote")
# gives completely wrong answer
# Zygote.pullback(t->Π(u0 .+ t.* du0), 0.)[1] |> Base.show

println("--> dΠ using Analytical formula")
resAna = DPoincare(u0, du0, par_sl, normals[1], centers[1], cb);show(resAna)

println("--> dΠ using Finite differences")
resFD = dΠFD(u0, du0);show(resFD)

println("--> Norm of the difference = ", resAna - resFD |> norminf)
@test resAna - resFD |> norminf < 1000δ
####################################################################################################
# matrix of the Poincare map, analytical formula
const FD = ForwardDiff
const BK = BifurcationKit

tΣ, solΣ = flowTS(u0, Inf64; callback = cb)
	tΣ = tΣ[end]; solΣ = solΣ[end]
dϕ = FD.jacobian( x -> flowTS(x, tΣ)[2][end], (u0))
F = Fsl(Π(u0), par_sl)
normal = normals[1]

dTfd = BK.finiteDifferences(T, u0; δ = 1e-8)
dT = -normal' * dϕ ./ dot(normal, F)
@test norm(dTfd - dT, Inf) < 1e-5

Jtmp = dϕ .- F * normal' * dϕ #./ dot(F, normal)
dΠfd = BK.finiteDifferences(Π, u0; δ = 1e-8)
Jtmp = dϕ .- F * normal' * dϕ ./ dot(F, normal)
@test norm(dΠfd - Jtmp, Inf) < 2e-5
####################################################################################################
# comparison with BK
using BifurcationKit
const BK = BifurcationKit

function FslMono!(f, x, p, t)
	u = x[1:2]
	du = x[3:4]
	Fsl!(view(f,1:2), u, p, t)
	f[3:4] .= dFsl(u, du, p)
end
probMono = ODEProblem(FslMono!, vcat(u0, u0), (0., 100.), par_sl)

probHPsh = BK.PoincareShootingProblem(
		prob, algsl,
		# probMono, Rodas4P(autodiff=false),
		normals, centers; abstol =1e-10, reltol=1e-10)

@show BK.diffPoincareMap(probHPsh, u0, par_sl, du0, 1)

resDP = DPoincare(u0, du0, par_sl, normals[1], centers[1], cb; verbose = true)
resDPBK = BK.diffPoincareMap(probHPsh, u0, par_sl, du0, 1)
@test norminf(resDP - resDPBK) < 1e-6


##############################################################################
#
# Symmetric triangular distribution from Distributions Handbook
#
##############################################################################

immutable Triangular <: ContinuousUnivariateDistribution
    location::Float64
    scale::Float64
    function Triangular(l::Real, s::Real)
        s > zero(s) || error("scale must be positive")
        new(float64(l), float64(s))
    end
end

Triangular(location::Real) = Triangular(location, 1.0)
Triangular() = Triangular(0.0, 1.0)

function cdf(d::Triangular, x::Real)
    a, b, c = d.location - d.scale, d.location + d.scale, d.location
    if x <= a
        return 0.0
    elseif a <= x <= c
        return (x - a)^2 / ((b - a) * (c - a))
    elseif c < x <= b
        return 1.0 - (b - x)^2 / ((b - a) * (b - c))
    else
        return 1.0
    end
end

entropy(d::Triangular) = 0.5 + log(d.scale)

function insupport(d::Triangular, x::Number)
    return isreal(x) && isfinite(x) &&
           d.location - d.scale <= x <= d.location + d.scale
end

kurtosis(d::Triangular) = -0.6

mean(d::Triangular) = d.location

median(d::Triangular) = d.location

mode(d::Triangular) = d.location
modes(d::Triangular) = [d.location]

function pdf(d::Triangular, x::Real)
    if insupport(d, x)
        return -abs(x - d.location) / (d.scale^2) + 1.0 / d.scale
    else
        return 0.0
    end
end

function quantile(d::Triangular, p::Real)
    a, b, c = d.location - d.scale, d.location + d.scale, d.location
    if p <= 0.0
        return a
    elseif p < 0.5
        return a + sqrt(p * 2.0 * d.scale^2)
    elseif p >= 0.5
        return b -  sqrt((1.0 - p) * 2.0 * d.scale^2)
    else
        return b
    end
end

function rand(d::Triangular)
    ξ1, ξ2 = rand(), rand()
    return d.location + (ξ1 - ξ2) * d.scale
end

function skewness(d::Triangular)
    a = d.location - d.scale
    b = d.location + d.scale
    c = (b - a) / 2 + a
    den = sqrt(2.0) * (a + b - 2.0 * c) *
                      (2.0 * a - b - c) *
                      (a - 2.0 * b + c)
    num = 5.0 * (a^2 + b^2 + c^2 - a * b - a * c - b * c)^1.5
    return den / num
end

var(d::Triangular) = d.scale^2 / 6.0