function energie_kgr(u, v, epsilon, k, dx, llambda, sigma)

    z = (u + conj(v)) / 2
    dtz = 1im / 2 / epsilon * ifft(sp.sqrt(1 + epsilon * k .^ 2) * fft(u - conj(v)))

    Q = -epsilon * dx * sp.real(1im * sp.sum(dtz * conj(z)))
    E = epsilon * dx * np.linalg.norm(dtz) .^ 2 \
        + dx * np.linalg.norm(ifft(k * fft(z))) .^ 2 \
        + dx * np.linalg.norm(
        z) .^ 2 / epsilon \
        - llambda / (sigma + 1) * dx * sp.sum(abs(z) .^ (2 * sigma + 2))

    return Q, E

end


@inline max_min(x,y) = ifelse(abs(y) < abs(x), (x,y) , (y,x))

function maxtomin(a::T, b::T, c::T, d::T) where {T}
    a, b = max_min(a, b)
    c, d = max_min(c, d)

    a, c = max_min(a, c)
    b, d = max_min(b, d)

    b, c = max_min(b, c)

    return a, b, c, d
end

function maxtomin(a::T, b::T, c::T, d::T) where {T}
    a, b = max_min(a, b)
    c, d = max_min(c, d)
    a, c = max_min(a, c)
    b, d = max_min(b, d)
    b, c = max_min(b, c)
    return a, b, c, d
end

function vec_sum(x0::T, x1::T, x2::T, x3::T) where {T}
    s3 = x3
    s2, e3 = two_sum(x2, s3)
    s1, e2 = two_sum(x1, s2)
    s0, e1 = two_sum(x0, s1)
    return s0,e1,e2,e3
end

function vecsum_errbranch(x::NTuple{4,T}) where {T}
    y = r = e = zeros(T, 4)
    j = 1
    e[1] = x[1]
    for i = 1:2
        r[i], t = two_sum(e[i], x[i+1])
        if t !== zero(T)
            y[j] = r[i]
            e[i+1] = t
            j += 1
        else
            e[i+1] = r[i]
        end
    end
    y[j], y[j+1] = two_sum(e[3], x[4])
    return y
end

function fast_vecsum_errbranch(x::NTuple{4,T}) where {T}
    y = zeros(T, 4)
    j = 1
    # e[1] = x1
    # i = 1
    r, t = two_sum(x[1], x[2])
    if t !== zero(T)
       y[j] = r
       e = t
       j += 1
    else
       e = r
    end
    # i = 2
    r, t = two_sum(e, x[3])
    if t !== zero(T)
       y[j] = r
       e = t
       j += 1
    else
       e = r
    end

    y[j], y[j+1] = two_sum(e, x[4])
    return y
end

function fast_vecsum_errbranch(x1::T,x2::T,x3::T,x4::T) where {T}
    y = zeros(T, 4)
    j = 1
    # e[1] = x1
    # i = 1
    r, t = two_sum(x1, x2)
    if t !== zero(T)
       y[j] = r
       e = t
       j += 1
    else
       e = r
    end
    # i = 2
    r, t = two_sum(e, x3)
    if t !== zero(T)
       y[j] = r
       e = t
       j += 1
    else
       e = r
    end

    y[j], y[j+1] = two_sum(e, x4)
    return y
end

function quadword(x1::T, x2::T, x3::T, x4::T) where {T}
    a1, a2 = two_sum(x1, x2)
    b1, b2 = two_sum(x3, x4)
    c1, c2 = two_sum(a1, b1)
    d1, d2 = two_sum(a2, b2)
    e1to4 = vec_sum(c1,c2,d1,d2)
    y = vecsum_errbranch(e1to4)
    return (y...,)
end

@inline function fast_quadword(x1::T, x2::T, x3::T, x4::T) where {T}
    a,b,c,d = maxtomin(x1,x2,x3,x4)
    return fast_vecsum_errbranch(a,b,c,d)
end


using ITensors

import ITensors: op

function op(::OpName"expτSS", ::SiteType"S=1/2", s1::Index, s2::Index; τ)
  h = 1/2 * op("S+",s1) * op("S-",s2) +
      1/2 * op("S-",s1) * op("S+",s2) +
            op("Sz",s1) * op("Sz",s2)
  return exp(τ * h)
end

function main(; N = 10, cutoff = 1E-8,
                δt = 0.1, ttotal = 5.0)
  # Compute the number of steps to do
  Nsteps = Int(ttotal / δt)

  # Make an array of 'site' indices
  s = siteinds("S=1/2", N; conserve_qns = true)

  # Make gates (1,2),(2,3),(3,4),...
  gates = ops([("expτSS", (n, n+1), (τ = -δt*im/2,)) for n in 1:N-1], s)

  # Include gates in reverse order too
  # (N,N-1),(N-1,N-2),...
  append!(gates, reverse(gates))

  # Function that measures <Sz> on site n
  function measure_Sz(psi::MPS, n)
    psi = orthogonalize(psi, n)
    sn = siteind(psi,n)
    Sz = scalar(dag(prime(psi[n],"Site"))*op("Sz",sn)*psi[n])
    return real(Sz)
  end

  # Initialize psi to be a product state (alternating up and down)
  psi0 = productMPS(s, n -> isodd(n) ? "Up" : "Dn")

  c = div(N,2)

  # Compute and print initial <Sz> value
  t = 0.0
  Sz = measure_Sz(psi0,c)
  println("$t $Sz")

  # Do the time evolution by applying the gates
  # for Nsteps steps
  psi = psi0
  for step in 1:Nsteps
    psi = apply(gates, psi; cutoff=cutoff)
    t += δt
    Sz = measure_Sz(psi,c)
    println("$t $Sz")
  end

  # Now do the same evolution with an MPO
  rho0 = MPO(psi0)
  rho = rho0
  for step in 1:Nsteps
    rho = apply(gates, rho; cutoff=cutoff, apply_dag = true)
    t += δt
  end
  @show inner(psi, rho, psi)
  @show inner(psi, psi)
  @show tr(rho)

  return nothing
end


@doc """
    vardecomp2level(data::Array{Float64, 2}, factor::Array{Float64}, betas::Array{Float64}, factorassign::Array{Float64})

Description:
Compute the portion of the variation of each observable series that may be attributed to their corresponding/assigned latent factors across all levels.

Inputs:
- data = Matrix with each column representing a data series.
- factor = Matrix containing latent factor estimates.
- betas = Matrix containing observation equation coefficient parameter estimates.
- factorassign = Matrix containing the indeces of factors across all levels (columns) assigned to each observable series (rows).

Output:
- vardecomps = Matrix containing the variance contributions of factors across all levels (columns) corresponding to each observable series (rows).
"""
function vardecomp2level(data, factor, betas, factorassign)

    T, N = size(data)
    T, Nf = size(factor)

    # Record data variances
    varvars = zeros(N)
    for i in 1:N
        errors = data[:,i] - betas[i, 2] * factor[:, 1] + betas[i, 3] * factor[:, 1+factorassign[i, 2]]
        varvars[i] = betas[i, 2]^2 * var(factor[:, 1]) + betas[i, 3]^2 * var(factor[:, 1+factorassign[i, 2]]) + var(errors)
    end

    # Record factor estimate variances
    varfacs = zeros(Nf)
    for j in 1:Nf
        varfacs[j] = var(factor[:, j])
    end

    # Compute variance decompositions
    vardecomps = zeros(N, 2)
    for i in 1:N
        vardecomps[i, 1] = (betas[i, 2]^2 * varfacs[1]) / varvars[i]
        vardecomps[i, 2] = (betas[i, 3]^2 * varfacs[1+factorassign[i, 2]]) / varvars[i]
    end

    return vardecomps
end;
######################
######################
######################
######################
######################
######################
######################
######################
######################
######################
######################
######################


"""
    modularity(g, c)

Return a value representing Newman's modularity `Q` for the undirected graph
`g` given the partitioning vector `c`.
"""
function modularity end
@traitfn function modularity(g::::(!IsDirected), c::Vector)
    m = 2 * ne(g)
    m == 0 && return 0.
    nc = maximum(c)
    a = zeros(Int, nc)
    Q = 0
    for u in vertices(g)
        for v in neighbors(g, u)
            if u <= v
                c1 = c[u]
                c2 = c[v]
                if c1 == c2
                    Q += 2
                end
                a[c1] += 1
                a[c2] += 1
            end
        end
    end
    Q = Q * m
    @inbounds for i = 1:nc
        Q -= a[i] * a[i]
    end
    return Q / m / m
end


using Random,Distributions,Polynomials
using Statistics,QuadGK,FFTW,LinearAlgebra
using Optim,Roots
include("./BCC97_option.jl")

Random.seed!(50000)

function random_number_generator(symbol,normlization,M,I,mydim)
    if symbol==1
        rand0=rand(Normal(0,1),mydim,M+1,I)
        myrand=cat(myrand,-myrand,3)
    else
        myrand=rand(Normal(0,1),mydim,M+1,I)
    end # if

    if normlization==true
        myrand=(myrand .-mean(myrand)) ./std(myrand)
    end

    return myrand
end

function CIR_genetate_path(x0,κ,θ,σ,T,M,I,x_disc)
    dt=T/M
    x=zeros(M+1,I)
    x[1,:]=map(x->x=x0,x[1,:])
    xh=zeros(M+1,I)

    ran=rand(Normal(0,1),M+1,I)

    if isequal(x_disc,"exact")
        d=4*κ*θ/σ^2
        c=(σ^2*(1-exp(-κ*dt)))/(4*κ)
        if d>1
            for t in 2:M+1
                l=x[t-1,:]*(exp(-κ*dt))/c
                χ=rand(Chisq(d-1),I)
                x[t,:]=c*((ran[t,:] .+sqrt(l))^2 .+χ)
            end
        else
            for t in 2:M+1
                l=x[t-1,:]*(exp(-κ*dt))/c
                N=map(x->rand(Poisson(x/2)),l)
                χ=map(x->rand(Chisq(d+2*x)),N)
                x[t,:]=c*χ
            end
        end
    else
        for t in 2:M+1
            tmp1=map(x->dt*κ*(θ-max(0,x)),xh[t-1,:])
            tmp2=map(x->√(dt)*σ*sqrt(max(0,x)),xh[t-1,:])
            tmp3=ran[t,:] .*tmp2
            xh[t,:]=(xh[t-1,:] .+tmp1 .+tmp3)
            x[t,:]=map(x->max(0,x),xh[t,:])
        end
    end

    return x

end

function SRD_generate_path(x_disc,x0,κ,θ,σ,T,M,I,myrand,row,cho_matrix)
    dt=T/M
    x=zeros(M+1,I)
    x[1,:]=map(x->x=x0,x[1,:])
    xh=zeros(M+1,I)
    xh[1,:]=map(x->x=x0,xh[1,:])

    sdt=√(dt)

    @simd for t in 2:M+1
        ran=cho_matrix*myrand[:,t,:]
        if isequal(x_disc,"FULL Truncation")
            tmp1=map(x->dt*κ*(θ-max(0,x)),xh[t-1,:])
            tmp2=map(x->sdt*σ*sqrt(max(0,x)),xh[t-1,:])
            tmp3=ran[row,:] .*tmp2
            xh[t,:]=(xh[t-1,:] .+tmp1 .+tmp3)
            x[t,:]=map(x->max(0,x),xh[t,:])
        elseif isequal(x_disc,"Partial Truncation")
            tmp1=map(x->dt*κ*(θ-x),xh[t-1,:])
            tmp2=map(x->sdt*σ*sqrt(max(0,x)),xh[t-1,:])
            tmp3=ran[row,:] .*tmp2
            xh[t,:]=(xh[t-1,:] .+tmp1 .+tmp3)
            x[t,:]=map(x->max(0,x),xh[t,:])
        elseif isequal(x_disc,"Truncation")
            tmp1=map(x->dt*κ*(θ-x),x[t-1,:])
            tmp2=map(x->sdt*σ*sqrt(x),x[t-1,:])
            tmp3=ran[row,:] .*tmp2
            x[t,:]=map(x->max(0,x),(x[t-1,:] .+tmp1 .+tmp3))
        elseif isequal(x_disc,"Reflection")
            tmp1=map(x->dt*κ*(θ-abs(x)),xh[t-1,:])
            tmp2=map(x->sdt*σ*sqrt(abs(x)),xh[t-1,:])
            tmp3=ran[row,:] .*tmp2
            xh[t,:]=(xh[t-1,:] .+tmp1 .+tmp3)
            x[t,:]=map(x->abs(x),xh[t,:])
        elseif isequal(x_disc,"Higham-Mao")
            tmp1=map(x->dt*κ*(θ-x),xh[t-1,:])
            tmp2=map(x->sdt*σ*sqrt(abs(x)),xh[t-1,:])
            tmp3=ran[row,:] .*tmp2
            xh[t,:]=(xh[t-1,:] .+tmp1 .+tmp3)
            x[t,:]=map(x->abs(x),xh[t,:])
        elseif isequal(x_disc,"Simple Reflection")
            tmp1=map(x->dt*κ*(θ-x),x[t-1,:])
            tmp2=map(x->sdt*σ*sqrt(x),x[t-1,:])
            tmp3=ran[row,:] .*tmp2
            x[t,:]=map(x->abs(x),(x[t-1,:] .+tmp1 .+tmp3))
        elseif isequal(x_disc,"Absorption")
            tmp1=map(x->dt*κ*(θ-max(0,x)),xh[t-1,:])
            tmp2=map(x->sdt*σ*sqrt(max(0,x)),xh[t-1,:])
            tmp3=ran[row,:] .*tmp2
            xh[t,:]=map(x->max(0,x),xh[t-1,:] .+tmp1 .+tmp3)
            x[t,:]=map(x->max(0,x),xh[t,:])
        else
            print("wrong method!")
        end # if

    end

    return x

end

function H93_generate_path(S0,r,v,row,cho_matrix,M,I,T,myrand,s_disc)
    S=zeros(M+1,I)
    dt=T/M
    S[1,:]=map(x->x=S0,S[1,:])
    bias=0.0
    sdt=√(dt)
    @simd for t in 2:M+1
        ran=cho_matrix*myrand[:,t,:]
        # if true
        #     bias=mean(v[t,:] .*ran[row,:]*sdt)
        # end
        if isequal(s_disc,"Log")
            tmp0=(r .-0.5*v[t,:])*dt+map(x->sqrt(x),v[t,:]) .*ran[row,:]*sdt .-bias
            tmp1=map(x->exp(x),tmp0)
            S[t,:]=S[t-1,:] .*tmp1
        elseif isequal(s_disc,"Native")
            tmp2=exp(r*dt) .+map(x->sqrt(x),v[t,:]) .*ran[row,:]*sdt-bias
            S[t,:]=S[t-1,:] .*tmp2
        else
            print("wrong method1")
        end # if

    end # for
    return S
end

function ZCB_mcs(r0,κ_r,θ_r,σ_r,T,M,I,x_disc)
    dt=T/M
    r=CIR_genetate_path(r0,κ_r,θ_r,σ_r,T,M,I,x_disc)
    zcb=zeros(M+1,I)
    zcb[end,:]=map(x->x=1.0,zcb[1,:])
    for t=M+1:-1:2
        tmp1=map(x->exp(x/2*dt),-(r[t,:] .+r[t-1,:]))
        zcb[t-1,:]=zcb[t,:] .*tmp1
    end

    return sum(zcb,dims=2)/I
end

function H93_index_paths(S0,r,v,row,cho_matrix,M,I,T,myrand)
    S=zeros(M+1,I)
    dt=T/M
    S[1,:]=map(x->x=log(S0),S[1,:])
    sdt=√(dt)
    for t in 2:M+1
        ran=cho_matrix*myrand[:,t,:]
        S[t,:]+=S[t-1,:]
        S[t,:]+=((r[t,:]+r[t-1,:])/2 .-v[t,:]/2)*dt
        S[t,:]+=map(x->sqrt(x),v[t,:]) .*ran[row,:]*sdt
    end # for
    # if momath==1
    #     S[t,:]-=mean(map(x->sqrt(x),v[t,:]) .*ran[row,:]*sdt)
    # end
    S_index_path=map(x->exp(x),S)
    return S_index_path
end
#

function H93_euroption_mcs(args)
    cor_martix=[1.0 0.1;0.1 1.0]
    cho_matrix=LinearAlgebra.cholesky(cor_martix).L
    r=0.05
    x0=0.01
    κ=1.5
    θ=0.02
    σ=0.15
    T=1/12
    M=2
    dt=T/M
    S0=100.0
    I=25000
    x_disc="FULL Truncation"
    symbol=2
    normlization=false
    s_disc="Log"
    K=90
    mydim=2
    myrand=random_number_generator(symbol,normlization,M,I,mydim)
    v=SRD_generate_path(x_disc,x0,κ,θ,σ,T,M,I,myrand,2,cho_matrix)
    S=H93_generate_path(S0,r,v,1,cho_matrix,M,I,T,myrand,s_disc)
    h=map(x->max(x-K,0),S)
    H93_euroption_valution=sum(h[end,:]*exp(-r*T))/I

    return H93_euroption_valution
end

function get_condtion(A,B)
    len=length(A)
    C=zeros(sum(A))
    i=1
    for t in 1:len
        if A[t]==1
            C[i]=B[t]
            i+=1
        end
    end

    return C
end

function BCC97_SVSI_MCS(args)

    ρ=0.1
    cor_martix=[1.0 ρ 0.0;ρ 1.0 0.0;0.0 0.0 1.0]
    cho_matrix=LinearAlgebra.cholesky(cor_martix).L
    r=0.05
    x0=0.01
    κ=1.5
    θ=0.02
    σ=0.15
    T=1/12
    M=20
    dt=T/M
    S0=100.0
    I=25000
    x_disc="FULL Truncation"
    symbol=2
    normlization=false
    s_disc="Log"
    K=90
    row=1
    mydim=3
    D=10
    myrand=random_number_generator(symbol,normlization,M,I,mydim)
    r1=SRD_generate_path(x_disc,r,κ,θ,σ,T,M,I,myrand,1,cho_matrix)
    v=SRD_generate_path(x_disc,x0,κ,θ,σ,T,M,I,myrand,3,cho_matrix)
    S_path=H93_index_paths(S0,r1,v,row,cho_matrix,M,I,T,myrand)

    h=map(x->max(K-x,0),S_path)
    Va=map(x->max(K-x,0),S_path)

    for tt=M-1:-1:1
        df=map(x->exp(-x),(r1[tt,:].+r1[tt+1,:])/2*dt)
        itm=map(x->x>0 ? 1 : 0,h[tt,:])
        no_itm=sum(filter(!iszero,itm))

        println(tt)
        println(no_itm)
         rel_S=get_condtion(itm,S_path[tt,:])
        # rel_S=filter(!iszero,itm .*S_path[tt,:])
        if no_itm==0
            cv=zeros(I)
        else

            rel_v=get_condtion(itm,v[tt,:])
            rel_r=get_condtion(itm,r1[tt,:])
            rel_V0=get_condtion(itm,Va[tt,:])
            tmp000=get_condtion(itm,df)

            rel_V=rel_V0 .*tmp000
            mymatrix=zeros(D+1,no_itm)
            mymatrix[11,:]=rel_S .*rel_v .*rel_r
            mymatrix[10,:]=rel_S .*rel_v
            mymatrix[9,:]=rel_S .*rel_r
            mymatrix[8,:]=rel_v .*rel_r
            mymatrix[7,:]=rel_S .^2
            mymatrix[6,:]=rel_v .^2
            mymatrix[5,:]=rel_r .^2
            mymatrix[4,:]=rel_S
            mymatrix[3,:]=rel_v
            mymatrix[2,:]=rel_r
            mymatrix[1,:]=map(x->x=1,mymatrix[1,:])

            temp0=inv(mymatrix*mymatrix')*mymatrix
            reg=temp0*rel_V

            cv=reg'*mymatrix
        end
        erg=zeros(I)
        erg0=erg .*itm .*cv
        res=h[tt,:].>erg0
        @simd for i=1:I
            if res[i]==true
                global Va[tt,i]=h[tt,i]
            else
                global Va[tt,i]=Va[tt+1,i]*df[i]
            end
        end
    end

    df1=map(x->exp(-x),(r1[1,:]+r1[2,:])/2*dt)
    C0=H93_call_func(S0,K,T,r,κ,θ,σ,ρ,x0)
    κ_r,θ_r,σ_r,r0=0.3,0.04,0.1,0.04
    B0t=B(κ_r,θ_r,σ_r,r0,T)
    P0=C0+K*B0t-S0
    P0_MCS=sum(h[end,:]*B0t)/I

    x=B0t*h[end,:]
    y=Va[2,:] .*df1
    convar=true

    if convar
        x_mean=x .-mean(x)
        y_mean=y .-mean(y)
        b=sum(x .*y)/sum(x_mean .^2)
        y_cv=y .-1.0*(B0t*h[end,:] .-P0)
    else
        y_cv=y
    end

    SE=std(y)/√(I)

    V0_CV=max(sum(y_cv)/I,h[1,1])

    V0_LSM=max(sum(y)/I,h[1,1])

    return   V0_LSM
end

#
# res2=ZCB_mcs(r0,κ_r,θ_r,σ_r,T,M,I,x_disc)


# Useful for LinearAlgebra tests
function generate_well_conditioned_matrix(rng, N)
    A = randn(rng, N, N)
    return A * A' + I
end

generate_well_conditioned_matrix(N) = generate_well_conditioned_matrix(Random.GLOBAL_RNG, N)


struct MonteCarlo <: AbstractMonteCarlo
    n::Integer
    MonteCarlo(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

struct SobolSampling <: AbstractQuasiMonteCarlo
    n::Integer
    SobolSampling(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

struct HaltonSampling <: AbstractQuasiMonteCarlo
    n::Integer
    HaltonSampling(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

struct LatinHypercubeSampling <: AbstractQuasiMonteCarlo
    n::Integer
    LatinHypercubeSampling(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

function sample(inputs::Array{<:UQInput}, sim::MonteCarlo)
    return sample(inputs, sim.n)
end

function sample(inputs::Array{<:UQInput}, sim::AbstractQuasiMonteCarlo)
    random_inputs = filter(i -> isa(i, RandomUQInput), inputs)
    deterministic_inputs = filter(i -> isa(i, DeterministicUQInput), inputs)

    n_rv = count_rvs(random_inputs)

    u = qmc_samples(sim, n_rv)

    samples = quantile.(Normal(), u)
    samples = DataFrame(names(random_inputs) .=> eachcol(samples))

    if !isempty(deterministic_inputs)
        samples = hcat(samples, sample(deterministic_inputs, sim.n))
    end

    to_physical_space!(inputs, samples)

    return samples
end

function qmc_samples(sim::SobolSampling, rvs::Integer)
    return transpose(QuasiMonteCarlo.sample(sim.n, zeros(rvs), ones(rvs), SobolSample()))
end

function qmc_samples(sim::HaltonSampling, rvs::Integer)
    h = HaltonPoint(rvs; length=sim.n)

    u = zeros(sim.n, rvs)

    for (i, hp) in enumerate(h)
        u[i, :] = hp
    end

    return u
end

function qmc_samples(sim::LatinHypercubeSampling, rvs::Integer)
    return transpose(
        QuasiMonteCarlo.sample(sim.n, zeros(rvs), ones(rvs), LatinHypercubeSample())
    )
end


"""
Based on
https://hyperopt.github.io/hyperopt/
"""
module TestQuantLogNormal

using Test
using TreeParzen

# Simple quantlognormal
# Minimize the objective over the space
space = Dict(:x => HP.QuantLogNormal(:x, 0.0, 1.0, 2.0))
best = fmin(params -> params[:x]^2, space, 100)
@test 0 <= best[:x] <= 1

# What if properties are the same?
space = Dict(:x => HP.QuantLogNormal(:x, 0.0, 1.0, 1.0))
fmin(params -> params[:x]^2, space, 100)

end # module TestQuantLogNormal
true


# Generate Gauss quadrature rules on cubes (0,0,...) - (1,1,...) by doing an outer product
# over all dimensions

import Base.Cartesian: @nloops, @nref, @ntuple, @nexprs

for dim in (1,2,3)
    @eval begin
        function (::Type{QuadratureRule{HyperCube{$dim}}})(quad_type::GaussLegendre, order::Int)
            p,w = FastGaussQuadrature.gausslegendre(order)
            weights = Vector{Float64}(undef, order^($dim))
            points = Vector{Vec{$dim,Float64}}(undef, order^($dim))
            count = 1
            @nloops $dim i j->(1:order) begin
                t = @ntuple $dim q-> 0.5*(p[$(Symbol("i"*"_q"))] .+ 1.0)
                points[count] = Vec{$dim,Float64}(t)
                weight = 1.0
                @nexprs $dim j->(weight *= w[i_{j}])
                weights[count] = weight
                count += 1
            end
            return QuadratureRule{HyperCube{$dim},$dim,Float64}(weights./sum(weights), points)
        end
    end
end


import GreyDecision.GreyNumbers: GreyNumber
import GreyDecision.Utility: makeminmax
import GreyDecision.Moora: moora

@testset "Moora with white numbers" begin
    tol = 0.0001
    w =  [0.110, 0.035, 0.379, 0.384, 0.002, 0.002, 0.010, 0.077]
    Amat = [
        GreyNumber{Float64}(100.0, 100.0) GreyNumber{Float64}(92.0, 92.0) GreyNumber{Float64}(10.0, 10.0) GreyNumber{Float64}(2.0, 2.0) GreyNumber{Float64}(80.0, 80.0) GreyNumber{Float64}(70.0, 70.0) GreyNumber{Float64}(95.0, 95.0) GreyNumber{Float64}(80.0, 80.0)
        GreyNumber{Float64}(80.0, 80.0) GreyNumber{Float64}(70.0, 70.0) GreyNumber{Float64}(8.0, 8.0) GreyNumber{Float64}(4.0, 4.0) GreyNumber{Float64}(100.0, 100.0) GreyNumber{Float64}(80.0, 80.0) GreyNumber{Float64}(80.0, 80.0) GreyNumber{Float64}(90.0, 90.0)
        GreyNumber{Float64}(90.0, 90.0) GreyNumber{Float64}(85.0, 85.0) GreyNumber{Float64}(5.0, 5.0) GreyNumber{Float64}(0.0, 0.0) GreyNumber{Float64}(75.0, 75.0) GreyNumber{Float64}(95.0, 95.0) GreyNumber{Float64}(70.0, 70.0) GreyNumber{Float64}(70.0, 70.0)
        GreyNumber{Float64}(70.0, 70.0) GreyNumber{Float64}(88.0, 88.0) GreyNumber{Float64}(20.0, 20.0) GreyNumber{Float64}(18.0, 18.0) GreyNumber{Float64}(60.0, 60.0) GreyNumber{Float64}(90.0, 90.0) GreyNumber{Float64}(95.0, 95.0) GreyNumber{Float64}(85.0, 85.0)
    ]
    fns = makeminmax([maximum, maximum, maximum, maximum, maximum, maximum, maximum, maximum])

    result = moora(Amat, w, fns)
    scores = result[:scores]

    @test result[:bestindex] == 4

    @test isapprox(scores[1], GreyNumber(0.33159387), atol = tol)
    @test isapprox(scores[2], GreyNumber(0.29014464), atol = tol)
    @test isapprox(scores[3], GreyNumber(0.37304311), atol = tol)
    @test isapprox(scores[4], GreyNumber(0.01926526), atol = tol)
 end


## Test first circular product computations

using DocStringExtensions

include(joinpath(dirname(@__FILE__), "..", "examples", "circularEntropyUtils.jl"))

using KernelDensityEstimate
using Gadfly, Colors
using ApproxManifoldProducts
using Distributions
using Random

using TransformUtils
using Optim

const TU = TransformUtils
const KDE = KernelDensityEstimate

# Define circular manifold

logmap_SO2(Rl::Matrix{Float64}) = sign(Rl[2,1])*acos(Rl[1,1])
difftheta(wth1::Float64, wth2::Float64)::Float64 = logmap_SO2(TU.R(wth1)'*TU.R(wth2))

## test product of just two Gaussian products

# example with correct linear product
mus = [-0.1;0.1]
lams = [100.0; 100.0]

la = KDE.getEuclidLambda(lams)
mu = KDE.getEuclidMu(mus, lams)

# example with incorrect linear product
mus = [-pi+0.1;pi-0.1]
lams = [100.0; 100.0]

la = KDE.getEuclidLambda(lams)
mu = KDE.getEuclidMu(mus, lams)

## Include entropy calculations for bandwidth determination

# some test points to work with
pts = 0.1*randn(30)
pts = [pts; TU.wrapRad.(0.6*randn(70) .- pi)]
shuffle!(pts)

# p = kde!(pts)

# N = length(pts)
CV  = zeros(100)
global i_global = 0
BW= range(0.05, 1.0, length=100)
for bw in BW
    global i_global
    i_global += 1
    CV[i_global] = manifoldLooCrossValidation(pts, bw, own=true, diffop=difftheta)
end

BW[findfirst(CV .== maximum(CV))]

# getBW(kde!(pts))[1,1]

## do the same with

PD = zeros(100)

global i_global = 0
for bw in BW
    global i_global
    i_global += 1
    pd = kde!(pts, [bw;])
    PD[i_global] = KernelDensityEstimate.entropy(pd)
end

## optimize the KDE bandwidth

# initial testing values
lower = 0.001
upper = 10.0

# pts = 10.0.+randn(100)

minEntropyLOOCV = (bw) -> -manifoldLooCrossValidation(pts, bw, own=true, diffop=difftheta)

# TODO Compare Optim.GoldenSection vs KDE.golden
@time res = optimize(minEntropyLOOCV, lower, upper, GoldenSection(), x_tol=0.001)

bw = res.minimizer

pc = kde!(pts, [bw])

gg = (x)->pc([x;])[1]

arr = [gg;]

pl = plotCircBeliefs(arr)

#


using Test
using ImplicitGraphs

G = iGrid()
@test G[(1, 1), (1, 2)]
@test length(G[(0, 0)]) == 4
@test dist(G, (0, 0), (1, 1)) == 2

G = iCycle(10)
@test has(G, 1, 10)

@test length(find_path(G, 1, 5)) == 5

G = iCycle(10, false)
@test !has(G, 1, 10)

G = iKnight()
@test deg(G, (0, 0)) == 8

G = iCube(4)
@test dist(G, "0000", "1111") == 4

G = iShift([1, 2, 3], 5)
@test deg(G, (1, 1, 1, 1, 1)) == 3

G = iKnight()
s = (5, 5)
t = (0, 0)
score(vw) = sum(abs.(vw))
P = guided_path_finder(G, s, t, score = score, depth = 2)
@test length(P) > 0


__precompile__()

module IsoRank

using LinearMaps
using DataStructures

export isorank, greedyalign

"""
    kronlm([T], A, B)

Kronecker product of `A` and `B`, stored as a linear operator (from
[LinearMaps.jl](https://github.com/Jutho/LinearMaps.jl.git)) so that
you don't have to create the actual matrix, i.e. `kronlm(A,B)*x == kron(A,B)*x`.
This is much faster than
directly creating the matrix: `O(|V|^2+|V||E|)` instead of `O(|V|^2 + |E|^2)` for each
step of the power iteration where `|V|` and `|E|` are the number of nodes and edges
in the graphs.

# Arguments
- `A,B` : linear operators with multiply and transpose operations
- `T` : element type of the resulting linear operator
"""
function kronlm(::Type{T},A,B) where {T}
    f = (y,x) -> begin
        y[:] = vec(B * reshape(x, size(B,2), size(A,2)) * A')
        y end
    fc = (y,x) -> begin
        y[:] = vec(B' * reshape(x, size(B,1), size(A,1)) * A)
        y end
    m = size(A,1) * size(B,1)
    n = size(A,2) * size(B,2)
    LinearMap{T}(f, fc, m, n)
end
kronlm(A,B) = kronlm(promote_type(eltype(A),eltype(B)),A,B)

"""
    powermethod!(A, x, Ax=similar(x);
                 <keyword arguments>) -> radius, x, [log/history]

Performs power method in order to find the dominant eigenvector
of the linear operator A. Eigenvector is normalized w.r.t. L_1 norm.
Modifies initial eigenvector estimate x.

# Arguments
- `A` : linear operator
- `x` : initial estimate of the eigenvector, not necessarily normalized
- `Ax` : scratch space for the iteration process

# Keyword arguments
- `maxiter` : maximum # of iterations
- `tol=eps(Float64) * size(A,2)` : error tolerance in L_1 norm
- `log=true,verbose=true` : logging and printing
"""
function powermethod!(A, x, Ax=similar(x);
                      maxiter=15, tol=eps(Float64) * size(A,2),
                      log=true, verbose=true)
    T = eltype(A)
    x ./= norm(x,1)
    verbose && println("Running power method, maxiter = $maxiter, tol = $tol")
    history = Tuple{Int,T,T}[]
    iter = 0
    radius = zero(T)
    err = Inf
    while iter <= maxiter
        A_mul_B!(Ax, A, x)
        radius = norm(Ax,1)
        Ax ./= radius # want |x|_1 = 1
        err = norm(Ax-x,1)
        verbose && @show iter,err,radius
        log && push!(history,(iter,err,radius))
        copy!(x, Ax)
        err < tol && break
        iter += 1
    end
    isconverged = err < tol
    if log radius,x,history,isconverged else radius,x end
end

"""
    isorank(G1::SparseMatrixCSC, G2::SparseMatrixCSC,
            [alpha::Real=0.85, B::AbstractMatrix=ones(Float64,size(G1,1),size(G2,1))];
            <keyword arguments>) -> R [, res, L]

Creates the IsoRank matrix, which contains topological similarities of
all nodes across two networks (See Rohit Singh, Jinbo Xu, and Bonnie
Berger. (2008) Global alignment of multiple protein interaction
networks with application to functional orthology detection,
Proc. Natl. Acad. Sci. USA, 105:12763-12768.). That is, finds the
PageRank values of the modified adjacency matrix of the product graph
of G1 and G2.  `B`, containing prior node similarities, acts as the
personalization vector, allowing you to incorporate external
information. If you don't have node similarities, you can still create
a good IsoRank matrix by damping like PageRank does.

# Arguments
- `G1,G2` : two adjacency matrices of undirected networks
- `B` : matrix of node similarities between `G1` and `G2`, not necessarily normalized
- `alpha`: weight between edge and node conservation

# Keyword arguments
- `details=false` : If true, returns (R,res,L) where R is the IsoRank
    matrix of node similarities, res is the power method detailed
    results structure, L is the linear operator that the power method
    finds the eigenvector of; if false, returns R
- See [`powermethod!`](@ref) for other keyword arguments
"""
function isorank(G1::AbstractMatrix, G2::AbstractMatrix,
                 alpha::Real=0.85, B::AbstractMatrix=ones(Float64,size(G1,1),size(G2,1));
                 details=false, args...)
    A = kronlm(Float64,G2,G1)
    res = pagerank(A, alpha, vec(B); details=details, args...)
    if details
        R = reshape(res[1], size(G1,1), size(G2,1))
        R, res[2], res[3], A
    else
        R = reshape(res, size(G1,1), size(G2,1))
        R
    end
end

"""
     pagerank(A, alpha=0.85, p = fill(1/size(A,2),size(A,2)),
              x=copy(p), Ax=similar(x);
              <keyword args>) -> x [, res, L]

Creates PageRank vector.

# Arguments
- `A` : Adjacency matrix of the graph. A[u,v] = 1 if u -> v
- `alpha` : Damping.
- `p` : Initial probability vector, or personalization vector, not necessarily normalized.

# Keyword arguments
- `details=false` : If true, returns (x,res,L) where x is the PageRank
  vector, res is the power method detailed results structure, L is the linear
  operator that the power method finds the eigenvector of; if false,
  returns x.
- See [`powermethod!`](@ref) for other keyword arguments
"""
function pagerank(A, alpha=0.85, p = fill(1/size(A,2),size(A,2));
                  details=false, args...)
    S = 1.0 ./ (A * ones(Float64,size(A,2)))
    D = find(isinf,S) # S[i] = Inf if node i has no outlinks
    S[D] = 0.0
    p = p./norm(p,1)
    L = LinearMap{Float64}((y,x) -> begin
                           At_mul_B!(y, A, S .* x)
                           y .= alpha .* y .+ (alpha * sum(x[D]) + 1.0 - alpha) .* p
                           y
                           end, size(A,1), size(A,2))
    x = copy(p)
    res = powermethod!(L, x; args...)
    if details x, res, L else x end
end

"""
    greedyalign(R::AbstractMatrix,seeds=Vector{Tuple{Int,Int}}();
                maxiter=size(R,1)-length(seeds))

Given matrix `R`, find a alignment using the greedy method.

# Arguments
- `R` : m x n matrix
- `seeds` : Initial seed node pairs
- `maxiter` : Stop after aligning `maxiter` node pairs
"""
function greedyalign(R::AbstractMatrix,seeds=Vector{Tuple{Int,Int}}();
                     maxiter=size(R,1)-length(seeds))
    f = zeros(Int,size(R,1))
    n1 = size(R,1); n2 = size(R,2)
    kv = (((i,j),-R[i,j]) for i=1:n1, j=1:n2)
    Q = PriorityQueue{Tuple{Int,Int},eltype(R)}(kv)
    L1 = Set{Int}()
    L2 = Set{Int}() # nodes already aligned
    for k = 1:length(seeds)
        i,j = seeds[k]
        f[i] = j
        push!(L1,i)
        push!(L2,j)
        for ip = setdiff(1:n1,L1); dequeue!(Q,(ip,j)); end
        for jp = setdiff(1:n2,L2); dequeue!(Q,(i,jp)); end
    end
    println("Building priority queue")
    iter = 1
    while iter <= maxiter && !isempty(Q)
        i,j = dequeue!(Q)
        f[i] = j
        push!(L1,i)
        push!(L2,j)
        for ip = setdiff(1:n1,L1); dequeue!(Q,(ip,j)); end
        for jp = setdiff(1:n2,L2); dequeue!(Q,(i,jp)); end
        print("\rIteration $iter/$maxiter")
        iter += 1
    end
    println()
    f
end

end


using DifferentialEquations
using DiffEqOperators
using Plots; gr()

function SemiLinearHeat(;non_homo::Function,tspan,u0,B1::Symbol,B2::Symbol, params = [1.0,0.5])

    N = length(u0)       # Deciding the length of the u0 to be able to build Our Derivative
    dx = 1/N # Deciding the step in the x variable

    # Dirichlet Boundary Conditions

    if B1 in  Set([:Dirichlet0,:Dirichlet]) && B2 in Set([:Dirichlet0,:Dirichlet])
        A= DerivativeOperator{Float64}(2,2,dx,N,:None,:None)
        function heat(du,u,L,t)
            mul!(du,L,u)
        end
    end

    # Neumann Boundary Conditions
    if B1 in  Set([:Neumann,:Neumann0]) && B2 in  Set([:Neumann,:Neumann0])
        B = DerivativeOperator{Float64}(1,2,dx,N,:None,:None)
        deriv_start, deriv_end = (B*u0)[1], (B*u0)[end]
        A =  DerivativeOperator{Float64}(2,2,dx,N,B1,B2;BC=(deriv_start , deriv_end))
    end

    # Robin Boundary Condition
    if B1 in  Set([:Robin]) && B2 in  Set([:Robin])
        B = DerivativeOperator{Float64}(1,2,dx,N,:None,:None)
        deriv_start, deriv_end = (B*u0)[1], (B*u0)[end]
        Param = params
        left_RBC = params[1]*u0[1] - params[2]*deriv_start
        right_RBC = params[1]*u0[end] + params[2]*deriv_end
        A= DerivativeOperator{Float64}(2, 2, dx, N, B1, B2;
        BC = ((params[1],params[2],left_RBC),(params[1],params[2],right_RBC)))
    end

    f(u,p,t) = A*u .+ non_homo(u)  # This is the heat equation with a non-linear Part
    prob = ODEProblem(heat , u0 ,tspan,A)

end

#######################################################
# More generalized function

u0= T*cos.(cos.(x.-0.1))

D2 = Derivative(S,2)
L = D2[1:n,1:n]
tspan = (0.0,10.0)

function heat(du,u,L,t)
    mul!(du,L,u)
end

function SemiLinearHeat(domain, discretization, u0, non_linear)
    if discretization == "ApproxFun"

        S = Fourier()
        n=100
        x = points(S,n)
        T = ApproxFun.plan_transform(S,n)
        Ti = ApproxFun.plan_itransform(S,n)
        u0= T*u0
        D2 = Derivative(S,2)
        L = D2[1:n,1:n]

        function heat(du,u,L,t)
            mul!(du,L,u)
        end

        prob = ODEProblem(heat, u0,domain,L)

    end

end

u0 = cos.(cos.(x.-0.1))
non_homo(u) =0;

prob = SemiLinearHeat((0.0,10.0), "ApproxFun", u0, non_homo)
sol = solve(prob)
plot(x,Ti*sol(10.0))
plot!(x,Ti*sol(0.5))
plot!(x,Ti*sol(2.0))
plot!(x,Ti*sol(3.0))

Missing around and learning DifferentialEquations.jl/semilinear_heat.jl


const program = parse.(Int, split(readchomp("input"), ','))

function run!(program::AbstractVector)
    pos = 1
    while pos < length(program) && program[pos] != 99
        opcode = program[pos]
        if opcode == 1
            op = +
        elseif opcode == 2
            op = *
        else
            error("unexpected opcode $opcode")
        end
        p1, p2, out = program[pos+1:pos+3] .+ 1
        program[min(out, end)] = op(program[p1], program[p2])
        pos += 4
    end
    return program
end

function output(input, noun, verb)
    program = copy(input)
    program[2] = noun
    program[3] = verb
    run!(program)
    return first(program)
end

part1(input) = println("Part 1 answer: ", output(input, 12, 2))

function part2(input, target)
    bound = 99
    noun = verb = -1
    for outer noun = 0:bound, outer verb = 0:bound
        output(input, noun, verb) == target && break
    end
    println("Part 2 answer: ", 100 * noun + verb)
end

part1(program)
part2(program, 19690720)


#=
  https://edu.swi-prolog.org/mod/assign/view.php?id=254&forceview=1
  """
  Coin tosses

  http://cplint.eu/p/coin_tosses.swinb

  Coin tosses

  Consider a process where you repeatedly toss coins and record the results.

  After each toss, you continue tossing with probability 0.8.

  Write a predicate tosses(Coin,L) that, given an initial coin id Coin, returns in L the list of
  results of coin tosses obtained using the process above.

  Moreover, write a predicate length_tosses(N) that returns the number of coin tosses.

  Compute the probability of the sequence [h,h,h] using MCINTYRE.

  Compute the probability of the sequence [h,h,h,h,h] given that the subsequence [h,h,h] was observed
  using rejection sampling and Metropolis Hastings.

  Compute the probability of the sequences of 10 coin tosses using MCINTYRE.

  Compute the expected length of the sequences of coin tosses using MCINTYRE.
  """

  Note: The cplint version (corrected by  Fabrizio Riguzzi) at http://cplint.eu/p/coin_tosses_hakank_rzf.swinb
  give another solutions:
   - first problem: probability of the sequence [h,h,h] -> 0.015
   - second problem: probability of the sequence [h,h,h,h,h] given that the subsequence [h,h,h] -> about 0.03
   - third problem: probability of the sequences of 10 coin tosses -> about 0.026
   - fourth problem: expected length of the sequences of coin tosses -> about 4.

  Cf ~/blog/coin_tosses.blog
     ~/webppl/coin_tosses.wppl

  Note: compared to BLOG and WebPPL models, this is a for loop solutions.
        (To check: It seems that using recursion as in those models don't
                   work in Turing.
        )

=#

#=
ulia> @time include("coin_tosses.jl")
Problem 1
Probability of problem 1:

Distributions of variable (num:20)
0.00000 =>   35055  (0.876375)
1.00000 =>    4945  (0.123625)

Problem 2
Probability of problem 2:

Distributions of variable (num:20)
0.00000 =>   29875  (0.746875)
1.00000 =>   10125  (0.253125)

Problem 3
Probability of problem 3:

Distributions of variable (num:20)
0.00000 =>   38991  (0.974775)
1.00000 =>    1009  (0.025225)

Problem 4
chns MCMC chain (10000×14×4 Array{Float64,3}):

Iterations        = 1:10000
Thinning interval = 1
chns            = 1, 2, 3, 4
Samples per chain = 10000
parameters        = i, tosses[1], tosses[2], tosses[3], tosses[4], tosses[5], tosses[6], tosses[7], tosses[8], tosses[9], tosses[10]
internals         = le, lp, weight

Summary Statistics
  parameters      mean       std   naive_se      mcse          ess      ⋯
      Symbol   Float64   Float64    Float64   Float64      Float64   Fl ⋯

           i    5.4932    2.8637     0.0143    0.0150   39858.1687    1 ⋯
   tosses[1]    1.5024    0.5000     0.0025    0.0024   38948.5470    0 ⋯
   tosses[2]    1.5983    0.6639     0.0033    0.0032   38547.3980    1 ⋯
   tosses[3]    1.6799    0.7610     0.0038    0.0039   39447.3220    0 ⋯
   tosses[4]    1.7387    0.8228     0.0041    0.0041   39791.3739    1 ⋯
   tosses[5]    1.8036    0.8677     0.0043    0.0040   39909.2109    1 ⋯
   tosses[6]    1.8379    0.8995     0.0045    0.0044   40658.2973    0 ⋯
   tosses[7]    1.8668    0.9222     0.0046    0.0045   40137.0842    1 ⋯
   tosses[8]    1.8955    0.9403     0.0047    0.0047   39153.5236    1 ⋯
   tosses[9]    1.9117    0.9531     0.0048    0.0049   38279.3118    1 ⋯
  tosses[10]    1.9292    0.9628     0.0048    0.0051   39562.1308    0 ⋯
                                                         1 column omitted

Quantiles
  parameters      2.5%     25.0%     50.0%     75.0%     97.5%
      Symbol   Float64   Float64   Float64   Float64   Float64

           i    1.0000    3.0000    5.0000    8.0000   10.0000
   tosses[1]    1.0000    1.0000    2.0000    2.0000    2.0000
   tosses[2]    1.0000    1.0000    1.0000    2.0000    3.0000
   tosses[3]    1.0000    1.0000    1.0000    2.0000    3.0000
   tosses[4]    1.0000    1.0000    1.0000    2.0000    3.0000
   tosses[5]    1.0000    1.0000    2.0000    3.0000    3.0000
   tosses[6]    1.0000    1.0000    2.0000    3.0000    3.0000
   tosses[7]    1.0000    1.0000    1.0000    3.0000    3.0000
   tosses[8]    1.0000    1.0000    2.0000    3.0000    3.0000
   tosses[9]    1.0000    1.0000    1.0000    3.0000    3.0000
  tosses[10]    1.0000    1.0000    1.0000    3.0000    3.0000

Probability of problem 4:

Distributions of variable (num:20)
1.00000 =>    8045  (0.201125)
2.00000 =>    6381  (0.159525)
3.00000 =>    5062  (0.126550)
11.00000 =>    4328  (0.108200)
4.00000 =>    4067  (0.101675)
5.00000 =>    3231  (0.080775)
6.00000 =>    2676  (0.066900)
7.00000 =>    2057  (0.051425)
8.00000 =>    1718  (0.042950)
9.00000 =>    1334  (0.033350)
10.00000 =>    1101  (0.027525)
Mean:4.580975

 24.121595 seconds (193.14 M allocations: 12.141 GiB, 35.09% gc time)

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function coin_tosses(problem)
    head = 1
    tail = 2

    if problem == 3 || problem == 4
        n = 10
    else
        n = 5
    end
    i ~ DiscreteUniform(1,n)
    # We create a fixed length list which seems to be much easier
    # than dynamically adding elements to tosses, especially
    # since for problem 2 we always access to tosses[1:5]
    tosses = tzeros(n)

    # We always do the first toss
    i = 1
    tosses[i] ~ Categorical([0.5,0.5])
    while rand(flip(0.8))
        i += 1
        if i > n
            break
        end
        tosses[i] ~ Categorical([0.5,0.5])
    end

    # We must add dummy value (here 3) for the unhandled cases in tosses[1:n]
    for t in 1:n
        if tosses[t] == 0
            tosses[t] ~ Categorical([0,0,1]) # dummy 3
        end
    end

    # * first problem: probability of the sequence [h,h,h] -> 0.015
    # * second problem: probability of the sequence [h,h,h,h,h] given that the subsequence [h,h,h] -> about 0.03
    # * third problem: probability of the sequences of 10 coin tosses -> about 0.026
    # * fourth problem: expected length of the sequences of coin tosses -> about 4.
    if problem == 1
        t ~ Dirac(tosses[1] == head && tosses[2] == head && tosses[3] == head)
    elseif problem == 2
        true ~ Dirac(tosses[1] == head)
        true ~ Dirac(tosses[2] == head)
        true ~ Dirac(tosses[3] == head)
        t ~ Dirac(tosses[1] == head && tosses[2] == head && tosses[3] == head &&
                  tosses[4] == head && tosses[5] == head)
    elseif problem == 3
        t ~ Dirac(i == 10)
    else
        return i
    end
end

function run_problem(problem)
    println("Problem $problem")
    model = coin_tosses(problem)
    num_chns = 4

    # HH has problem with this!
    # chns = sample(model, MH(), MCMCThreads(), 100_000, num_chns)
    # chns = sample(model, MH(), 100_000)

    # chns = sample(model, PG(15), MCMCThreads(), 1_000, num_chns)

    # chns = sample(model, SMC(1000), MCMCThreads(), 10_000, num_chns)
    # chns = sample(model, SMC(1000), 10_000)

    chns = sample(model, IS(), MCMCThreads(), 10_000, num_chns)

    display(chns)
    println("Probability of problem $problem:")

    show_var_dist_pct(chns,:i)
    if problem != 4
        show_var_dist_pct(chns,:t)
    end
end

for problem in 1:4
    run_problem(problem)
    println("\n")
end


#=
 Tutorial 2
=#
# Start repl Julia to open a command line in Julia

my_integer = 5
my_float = .5
## To run section of the code in VSCode
my_range = 1:7
my_vector = [1,3,5]
my_string = "hello"
my_character = 'o'
my_boolean = true

typeof(my_integer)
typeof(my_float)

# Alt + enter to run the code line by line

# \alpha
α = 5;

@show α + my_integer

print("α = $α \n", "My string is: $(my_string)")

## Array can contain any type of element
A = [1, 2, 3]
A = [1 2 3]
ones(3)
zeros(3)
C = [1 3; 5 9]

A = 1:9
B = 1:0.5:9

# Julia starts indexing at 1
B[3]

typeof(C)
size(C)
length(C)
ndims(C)

C[end]
C[1,2]

@show C
@show C[1,2]
@show C[2, :]

#=
Create a new environment
] to active the package manager in the command line
active .
activate test_env

add LinearAlgebra to add packages
status to see what packages are installed

using LinearAlgebra to use the package

backspace to go back to the Julia Command Line
=#

using LinearAlgebra
C*I
C

sin.([1,2,3])
inv(C)*C
C\C # to inverse a matrix

using Random
rand(1)
randn(3)

Random.seed!(1234)

# If Else statements
a = 3
if a == 3
    print("a is 3")
end

c = true
if c
    print("True")
end

# Condition ? If true : Else
c ? print("c is true") : nothing

if c
    print("c is true")
else
    print("c is not true")
end

# Logical operators
b = true
if c | b # or, a & b and, && check if the first term is true then goes to the second term
    print("a or b are true")
end

# For loops
vals = ["a", "b", "c"]
for val in vals
    println(val)
end

e = 3
for val in vals
    e = e + 1
end

for i in eachindex(vals)
    println(vals[i])
end

for (index, value) in enumerate(vals)
    print("Element $index of vals is $value\n")
end

# Functions

function cobbdouglas(l, k, α, β = 1-α; A = 1) # To have default value A = 1
    y = A*l^α*k^β
    return y
end

cobbdouglas(1, 3, 0.5)
cobbdouglas(1, 3, 0.5, 0.6)

# To store the returned output of the function
@show output = cobbdouglas(1, 3, 0.5)

# To evaluate the function at the two points
cobbdouglas.([1,2], 3, 0.5)

cobbdouglasshort(l,k,α) = l^α*k^(1-α)
cobbdouglasshort(1,3,0.5)


"""
    R2BPdynamics(rv, μ, t)

Compute time derivative of state vector in the restricted two-body system. `rv` is the state
vector `[r; v]` {km; km/s}, `μ` is the gravitational parameter {km³/s²}, and `t` is time
{s}.
"""
function R2BPdynamics(rv, μ, t)  #make sure rv and μ are in km and km³/s²
    r,v = rv[1:3], rv[4:6]
    rvdot = zeros(6)
    rvdot[1:3] = v
    rvdot[4:6] = -μ/norm(r)^3 * r   #acceleration [km/s]
    return rvdot
end

"""
    R2BPdynamics(rv, prim::Body, t)

Compute time derivative of state vector in the restricted two-body system. `rv` is the state
vector `[r; v]` {km; km/s}, `prim` is the central body, and `t` is time {s}.
"""
function R2BPdynamics(rv, prim::Body, t)  #make sure rv and μ are in km and km³/s²
    return R2BPdynamics(rv, G*prim.m, t)
end

"""
    R2BPdynamics!(rvdot, rv, μ, t)

In-place version of `R2BPdynamics(rvdot, rv, μ, t)`.
"""
function R2BPdynamics!(rvdot, rv, μ, t)  #make sure rv and μ are in km and km³/s²
    rvdot[:] = R2BPdynamics(rv,μ,t)
    return nothing
end

"""
    R2BPdynamics!(rvdot, rv, prim::Body, t)

In-place version of `R2BPdynamics!(rvdot, rv, prim::Body, t)`.
"""
function R2BPdynamics!(rvdot, rv, prim::Body, t)  #make sure rv and μ are in km and km³/s²
    rvdot[:] = R2BPdynamics(rv, prim, t)
    return nothing
end

"""
    CR3BPdynamics(rv, μ, t)

Compute time derivative of state vector `rv = [r; v]` {NON, NON} in the rotating frame of
the normalized CR3BP where `μ` is the CR3BP mass parameter μ₂/(μ₁+μ₂) {NON} and `t` is time
{NON}.
"""
function CR3BPdynamics(rv,μ,t) #Three body dynamics in Earth/Moon System
    x,y,z,vx,vy,vz = rv
    r₁³= ((x + μ)^2     + y^2 + z^2)^1.5; # distance to m1, LARGER MASS
    r₂³= ((x - 1 + μ)^2 + y^2 + z^2)^1.5; # distance to m2, smaller mass

    rvdot = zeros(6)
    rvdot[1:3] = [vx;vy;vz]
    rvdot[4] = -((1 - μ)*(x + μ)/r₁³) - (μ*(x - 1 + μ)/r₂³) + 2*vy + x;
    rvdot[5] = -((1 - μ)*y      /r₁³) - (μ*y          /r₂³) - 2*vx + y;
    rvdot[6] = -((1 - μ)*z      /r₁³) - (μ*z          /r₂³);
    return rvdot
end

"""
    CR3BPdynamics(rv, sys::System, t)

Compute time derivative of state vector `rv = [r; v]` {NON, NON} in the rotating frame of
the normalized CR3BP where `sys` is the CR3BP system and `t` is time {NON}.
"""
function CR3BPdynamics(rv,sys::System,t) #Three body dynamics in Earth/Moon System
    return CR3BPdynamics(rv,sys.μ,t)
end

"""
    CR3BPdynamics(rv, p::Array, t)

Compute time derivative of state vector `rv = [r; v]` {km, km/s} in the rotating frame of
the non-normalized CR3BP where `p = [μ₁;μ₂;d]` {km³/s²; km³/s²; km} contains the
gravitational parameters of the first and second primary bodies as well as the distance
between them. `t` is time {s}.
"""
function CR3BPdynamics(rv,p::Array,t) #Three body dynamics in Earth/Moon System
    x,y,z,vx,vy,vz = rv
    μ₁,μ₂,d = p # parameters
    R₁ = d*μ₂/(μ₁+μ₂)
    R₂ = d*μ₁/(μ₁+μ₂)
    ωₛ = sqrt((μ₁ + μ₂)/d^3) #rotation rate of system
    r₁³= ((x+R₁)^2 + y^2 + z^2)^1.5; # distance to m1, LARGER MASS
    r₂³= ((x-R₂)^2 + y^2 + z^2)^1.5; # distance to m2, smaller mass

    rvdot = zeros(6)
    rvdot[1:3] = [vx;vy;vz]
    rvdot[4]   = -(μ₁*(x+R₁)/r₁³) - (μ₂*(x-R₂)/r₂³) + 2*ωₛ*vy + ωₛ^2*x;
    rvdot[5]   = -(μ₁*y     /r₁³) - (μ₂*y     /r₂³) - 2*ωₛ*vx + ωₛ^2*y;
    rvdot[6]   = -(μ₁*z     /r₁³) - (μ₂*z     /r₂³);
    return rvdot
end

"""
    CR3BPdynamics!(rvdot, rv, μ, t)

In-place version of `CR3BPdynamics(rv, μ, t)`.
"""
function CR3BPdynamics!(rvdot,rv,μ,t) #Three body dynamics in Earth/Moon System
    rvdot[:] = CR3BPdynamics(rv,μ,t)
    return nothing
end

"""
    CR3BPdynamics!(rvdot, rv, sys::System, t)

In-place version of `CR3BPdynamics(rv, sys::System, t)`.
"""
function CR3BPdynamics!(rvdot,rv,sys::System,t) #Three body dynamics in Earth/Moon System
    rvdot[:] = CR3BPdynamics(rv,sys,t)
    return nothing
end

"""
    CR3BPdynamics!(rvdot, rv, p::Array, t)

In-place version of `CR3BPdynamics(rv, p::Array, t)`.
"""
function CR3BPdynamics!(rvdot,rv,p::Array,t) #Three body dynamics in Earth/Moon System
    rvdot[:] = CR3BPdynamics(rv,p,t)
    return nothing
end

"""
    CR3BPstm(w, μ, t)

Compute time derivative of state vector `w = [r; v; vec(Φ)]` {NON; NON; NON} in the rotating
frame of the normalized CR3BP. `vec(Φ)` is the vectorized state transition matrix while `μ`
is the CR3BP mass parameter μ₂/(μ₁+μ₂) {NON} and `t` is time {NON}.
"""
function CR3BPstm(w,μ,t) #Three body dynamics in Earth/Moon System
    rv = w[1:6]
    Φ = reshape(w[7:42],6,6)
    x,y,z,vx,vy,vz = rv

    r₁³= ((x + μ)^2     + y^2 + z^2)^1.5; # distance to m1, LARGER MASS
    r₂³= ((x - 1 + μ)^2 + y^2 + z^2)^1.5; # distance to m2, smaller mass
    r₁⁵= ((x + μ)^2     + y^2 + z^2)^2.5; # distance to m1, LARGER MASS
    r₂⁵= ((x - 1 + μ)^2 + y^2 + z^2)^2.5; # distance to m2, smaller mass

    omgxx = 1 - (1-μ)*(1/r₁³ - 3*(x + μ)^2/r₁⁵) - μ*(1/r₂³ - 3*(x - 1 + μ)^2/r₂⁵);
    omgxy = 3*(1-μ)*(x + μ)*y/r₁⁵ + 3*μ*(x - 1 + μ)*y/r₂⁵;
    omgxz = 3*(1-μ)*(x + μ)*z/r₁⁵ + 3*μ*(x - 1 + μ)*z/r₂⁵;
    omgyy = 1 - (1-μ)*(1/r₁³ - 3*y^2/r₁⁵) - μ*(1/r₂³ - 3*y^2/r₂⁵);
    omgyz = 3*(1-μ)*y*z/r₁⁵ + 3*μ*y*z/r₂⁵;
    omgzz = - (1-μ)*(1/r₁³ - 3*z^2/r₁⁵) - μ*(1/r₂³ - 3*z^2/r₂⁵);

    F = [   0     0     0     1     0	 0 ;
            0     0     0     0 	1 	 0 ;
            0	  0     0     0     0    1 ;
        omgxx omgxy omgxz     0     2 	 0 ;
        omgxy omgyy omgyz    -2     0 	 0 ;
        omgxz omgyz omgzz     0	    0	 0 ];

    Φdot = F*Φ;
    wdot = zeros(42)
    wdot[1:6] = CR3BPdynamics(rv,μ,t)
    wdot[7:42] = reshape(Φdot, 36, 1);
    return wdot
end

"""
    CR3BPstm(w, sys, t)

Compute time derivative of state vector `w = [r; v; vec(Φ)]` {NON; NON; NON} in the rotating
frame of the normalized CR3BP. `vec(Φ)` is the vectorized state transition matrix while
`sys` is the CR3BP system and `t` is time {NON}.
"""
function CR3BPstm(w,sys::System,t)
    return CR3BPstm(w,sys.μ,t)
end

"""
    CR3BPstm!(wdot, w, μ, t)

In-place version of `CR3BPstm(w, μ, t)`.
"""
function CR3BPstm!(wdot,w,μ,t)
    wdot[:] = CR3BPstm(w,μ,t)
    return nothing
end

"""
    CR3BPstm!(wdot, w, sys::System, t)

In-place version of `CR3BPstm(w, sys::System, t)`.
"""
function CR3BPstm!(wdot,w,sys::System,t) #Three body dynamics in Earth/Moon System
    wdot[:] = CR3BPstm(w,sys,t)
    return nothing
end

"""
    CR3BPinert(rv,μ,t)

Compute time derivative of state vector `rv = [r; v]` {NON, NON} in the inertial frame of
the normalized CR3BP where `μ` is the CR3BP mass parameter μ₂/(μ₁+μ₂) {NON} and `t` is time
{NON}.
"""
function CR3BPinert(rv,μ,t)
    x,y,z,vx,vy,vz = rv
    r₁ = [x +       μ*cos(t); y +       μ*sin(t); 0]
    r₂ = [x - (1 - μ)*cos(t); y - (1 - μ)*cos(t); 0]
    r₁³ = norm(r₁)^3
    r₂³ = norm(r₂)^3
    rvdot = zeros(6)
    rvdot[1:3] = [vx;vy;vz]
    rvdot[4:6] = -(1 - μ)*r₁/r₁³ - μ*r₂/r₂³
    return rvdot
end

"""
    CR3BPinert(rv, sys::System, t)

Compute time derivative of state vector `rv = [r; v]` {NON, NON} in the inertial frame of
the normalized CR3BP where `sys` is the CR3BP system and `t` is time {NON}.
"""
function CR3BPinert(rv,sys::System,t)
    return CR3BPinert(rv,sys.μ,t)
end

"""
    CR3BPinert(rvdot, rv, p::Array, t)

Compute time derivative of state vector `rv = [r; v]` {NON, NON} in the inertial frame of
the non-normalized CR3BP where `p = [μ₁;μ₂;d]` {km³/s²; km³/s²; km} contains the
gravitational parameters of the first and second primary bodies as well as the distance
between them. `t` is time {s}.
"""
function CR3BPinert(rv,p::Array,t)
    x,y,z,vx,vy,vz = rv
    μ₁,μ₂,d = p # parameters
    R₁,R₂ = computeR1R2(p)
    ωₛ = sqrt((μ₁ + μ₂)/d^3);
    r₁ = [x + R₁*cos(ωₛ*t); y + R₁*sin(ωₛ*t); 0] # distance to m1, LARGER MASS
    r₂ = [x - R₂*cos(ωₛ*t); y - R₂*sin(ωₛ*t); 0] # distance to m2, smaller mass
    r₁³= norm(r₁)^3;
    r₂³= norm(r₂)^3;
    rvdot = zeros(6)
    rvdot[1:3] = rv[4:6];
    rvdot[4:6] = -μ₁*r₁/r₁³ - μ₂*r₂/r₂³;
    return rvdot
end

"""
    CR3BPinert!(rvdot, rv, μ, t)

In-place version of `CR3BPinert(rv, μ, t)`.
"""
function CR3BPinert!(rvdot,rv,μ,t)
    rvdot[:] = CR3BPinert(rv,μ,t)
    return nothing
end

"""
    CR3BPinert!(rvdot, rv, sys::System, t)

In-place version of `CR3BPinert(rv, sys::System, t)`.
"""
function CR3BPinert!(rvdot,rv,sys::System,t)
    rvdot[:] = CR3BPinert(rv,sys,t)
    return nothing
end

"""
    CR3BPinert!(rvdot, rv, p::Array, t)

In-place version of `CR3BPinert(rv, p::Array, t)`.
"""
function CR3BPinert!(rvdot,rv,p::Array,t)
    rvdot[:] = CR3BPinert(rv,p,t)
    return nothing
end

"""
    CWdynamics(rv, n, t)

Clohessy-Wiltshire equations. Compute time derivative of state vector `rv = [δr; δv]`
{km; km/s} where `n` {rad/s} is the mean motion of the chief and `t` is time {s}.
"""
function CWdynamics(rv,n,t)
    x,y,z,vx,vy,vz = rv
    rvdot = zeros(6)
    rvdot[1:3] = rv[4:6]
    rvdot[4] = 2*n*vy + 3*n^2*x
    rvdot[5] = -2*n*vx
    rvdot[6] = -n^2*z
    return rvdot
end

"""
    CWdynamics!(rvdot,rv,n,t)

Clohessy-Wiltshire equations

Inputs: n (scalar) mean motion
"""
function CWdynamics!(rvdot,rv,n,t)
    rvdot[:] = CWdynamics(rv,n,t)
    return nothing
end

"""
See G. Gómez, C. Simó, J. Llibre, and R. Martínez, Dynamics and mission design near
libration points. Vol. II, vol. 3. 2001.

    BCPdynamics(rv, μ, m₃, n₃, t)

Compute time derivative of state vector `rv = [r; v]` {km; km/s} in the normalized
Bicircular Four-Body Problem (BCP). `μ` {NON} is the BCP mass parameter and `m₃` {NON} and
`n₃` {NON} are the normalized mass and mean motion of the tertiary body. `t` is time {NON}.
"""
function BCPdynamics(rv, μ, m₃, n₃, t)
    x,y,z,vx,vy,vz = rv

    # Xₑ = μ*cos(t);      Yₑ = μ*sin(t);      Zₑ = 0
    # Xₘ = (μ-1)*cos(t);  Yₘ = (μ-1)*sin(t);  Zₘ = 0
    # Xₛ = a₃*cos(n₃*t);  Yₛ = a₃*sin(n₃*t);  Zₛ = 0

    a₃ = (1+m₃)^(1/3)/n₃^(2/3)
    θ = (1-n₃)*t
    x₃ =  a₃*cos(θ)
    y₃ = -a₃*sin(θ)

    r₁³ = (  (x+μ)^2 +      y^2 + z^2)^1.5 # distance to m1, LARGER MASS
    r₂³ = ((x-1+μ)^2 +      y^2 + z^2)^1.5 # distance to m2, smaller mass
    r₃³ = ( (x-x₃)^2 + (y-y₃)^2 + z^2)^1.5

    rvdot = zeros(6)
    rvdot[1:3] = [vx;vy;vz]
    rvdot[4] = -(1-μ)*(x+μ)/r₁³ - μ*(x-1+μ)/r₂³ - m₃*(x-x₃)/r₃³ - m₃*cos(θ)/a₃^2 + 2*vy + x
    rvdot[5] = -(1-μ)  *  y/r₁³ - μ   *   y/r₂³ - m₃*(y-y₃)/r₃³ + m₃*sin(θ)/a₃^2 - 2*vx + y
    rvdot[6] = -(1-μ)  *  z/r₁³ - μ   *   z/r₂³ - m₃  *   z/r₃³
    return rvdot
end

"""
    BCPdynamics(rv, sys::BicircularSystem, t)

Compute time derivative of state vector `rv = [r; v]` {km; km/s} in the normalized
Bicircular Four-Body Problem (BCP). `sys` is the BCP system and `t` is time {NON}.
"""
function BCPdynamics(rv, sys::BicircularSystem, t)
    return BCPdynamics(rv, sys.μ, sys.m₃, sys.n₃, t)
end

"""
    BCPdynamics!(rvdot, rv, μ, m₃, n₃, t)

In-place version of `BCPdynamics(rv, μ, m₃, n₃, t)`.
"""
function BCPdynamics!(rvdot, rv, μ, m₃, n₃, t)
    rvdot[:] = BCPdynamics(rv, μ, m₃, n₃, t)
    return nothing
end

"""
    BCPdynamics!(rvdot, rv, sys::BicircularSystem, t)

In-place version of `BCPdynamics(rv, sys::BicircularSystem, t)`.
"""
function BCPdynamics!(rvdot, rv, sys::BicircularSystem, t)
    rvdot[:] = BCPdynamics(rv, sys, t)
    return nothing
end

"""
    BCPstm(wdot, w, μ, m₃, n₃, t)

Compute time derivative of state vector `w = [r; v; vec(Φ)]` {NON; NON; NON} in the
normalized Bicircular Four-Body Problem (BCP). `vec(Φ)` is the vectorized state transition
matrix. `μ` {NON} is the BCP mass parameter and `m₃` {NON} and `n₃` {NON} are the normalized
mass and mean motion of the tertiary body. `t` is time {NON}.
"""
function BCPstm(w, μ, m₃, n₃, t) #Three body dynamics in Earth/Moon System
    rv = w[1:6]
    Φ = reshape(w[7:42],6,6)
    x,y,z,vx,vy,vz = rv

    a₃ = (1+m₃)^(1/3)/n₃^(2/3)
    θ = (1-n₃)*t
    x₃ =  a₃*cos(θ)
    y₃ = -a₃*sin(θ)

    r₁ = sqrt(  (x+μ)^2 +      y^2 + z^2) # distance to m1, Larger Mass
    r₂ = sqrt((x-1+μ)^2 +      y^2 + z^2) # distance to m2, smaller mass
    r₃ = sqrt( (x-x₃)^2 + (y-y₃)^2 + z^2) # distance to m3, LARGEST MASS
    r₁³ = r₁^3; r₂³ = r₂^3; r₃³ = r₃^3
    r₁⁵ = r₁^5; r₂⁵ = r₂^5; r₃⁵ = r₃^5

    g11 = 1 - (1-μ)*(1/r₁³ - 3*(x + μ)^2/r₁⁵) - μ*(1/r₂³ - 3*(x - 1 + μ)^2/r₂⁵) - m₃*(1/r₃³ - 3*(x-x₃)^2/r₃⁵)
    g12 = 3*(1-μ)*(x + μ)*y/r₁⁵ + 3*μ*(x - 1 + μ)*y/r₂⁵ + 3*m₃*(x-x₃)*(y-y₃)/r₃⁵
    # g13 = 0 # planar assumption
    g13 = 3*(1-μ)*(x + μ)*z/r₁⁵ + 3*μ*(x - 1 + μ)*z/r₂⁵ + 3*m₃*(x-x₃)*z/r₃⁵
    g22 = 1 - (1-μ)*(1/r₁³ - 3*y^2/r₁⁵) - μ*(1/r₂³ - 3*y^2/r₂⁵) - m₃*(1/r₃³ - 3*(y-y₃)^2/r₃⁵)
    # g23 = 0 # planar assumption
    g23 = 3*(1-μ)*y*z/r₁⁵ + 3*μ*y*z/r₂⁵ + 3*m₃*(y-y₃)*z/r₃⁵
    # g33 = -(1-μ)/r₁³ - μ/r₂³ - m₃/r₃³ # planar assumption
    g33 = -(1-μ)*(1/r₁³ - 3*z^2/r₁⁵) - μ*(1/r₂³ - 3*z^2/r₂⁵) - m₃*(1/r₃³ - 3*z^2/r₃⁵)

    F = [  0   0   0  1   0   0 ;
           0   0   0  0   1   0 ;
           0   0   0  0   0   1 ;
         g11 g12 g13  0   2   0 ;
         g12 g22 g23 -2   0   0 ;
         g13 g23 g33  0	  0   0 ]

    Φdot = F*Φ
    wdot[1:6] = BCPdynamics(rv,μ,m₃,n₃,t)
    wdot[7:42] = reshape(Φdot, 36, 1);
    return wdot
end

"""
    BCPstm(wdot, w, μ, m₃, n₃, t)

Compute time derivative of state vector `w = [r; v; vec(Φ)]` {NON; NON; NON} in the
normalized Bicircular Four-Body Problem (BCP). `vec(Φ)` is the vectorized state transition
matrix, `sys` is the BCP system and `t` is time {NON}.
"""
function BCPstm(w,sys::BicircularSystem,t) #Three body dynamics in Earth/Moon System
    return BCPstm(w, sys.mu, sys.m3, sys.n3 ,t)
end

"""
    BCPstm!(wdot, w, μ, m₃, n₃, t)

In-place version of `BCPstm(w, μ, m₃, n₃, t)`.
"""
function BCPstm!(wdot,w,μ,m₃,n₃,t) #Three body dynamics in Earth/Moon System
    wdot[:] = BCPstm(w,μ,m₃,n₃,t)
    return nothing
end

"""
    BCPstm!(wdot, w, sys::BicircularSystem, t)

In-place version of `BCPstm(w, sys::BicircularSystem, t)`.
"""
function BCPstm!(wdot,w,sys::BicircularSystem,t) #Three body dynamics in Earth/Moon System
    wdot[:] = BCPstm(w,sys,t)
    return nothing
end


"""
    DualPerplex{T <: Real} = Nilplexification{Prplex{T}}

A dual-perplex number is a nilplexification of the perplex numbers.
"""
const DualPerplex{T <: Real} = Nilplexification{Prplex{T}}

function DualPerplex(z::Prplex{T}) where T <: Real
    DualPerplex{T}(z, zero(Prplex{T}))
end

function DualPerplex(a::T, b::T, c::T, d::T) where T <: Real
    DualPerplex{T}(Prplex{T}(a, b), Prplex{T}(c, d))
end

function DualPerplex(a::T, b::T, c::T) where T <: Real
    DualPerplex{T}(Prplex{T}(a, b), Prplex{T}(c, zero(T)))
end

function DualPerplex(a::T, b::T) where T <: Real
    DualPerplex{T}(Prplex{T}(a, b), zero(Prplex{T}))
end

function DualPerplex(a::T) where T <: Real
    DualPerplex{T}(Prplex{T}(a, zero(T)), zero(Prplex{T}))
end

function DualPerplex(a::Real, b::Real)
    DualPerplex(promote(a, b)...)
end

function DualPerplex(a::Real, b::Real, c::Real)
    DualPerplex(promote(a, b, c)...)
end

function DualPerplex(a::Real, b::Real, c::Real, d::Real)
    DualPerplex(promote(a, b, c, d)...)
end

"""
    unreal(z::DualPerplex)

The unreal part of a dual-perplex number is a 3-vector.
"""
function unreal(z::DualPerplex)
    vcat(unreal(z.l), array(z.r))
end

function show(io::IO, z::DualPerplex)
    print(io, "[1: ")
    print(io, z.l.l)
    print(io, ", R: ")
    print(io, z.l.r)
    print(io, ", A: ")
    print(io, z.r.l)
    print(io, ", RA: ")
    print(io, z.r.r)
    print(io, "]")
end

function random(::Type{DualPerplex{T}}) where T <: Real
    DualPerplex{T}(random(Prplex{T}), random(Prplex{T}))
end

function barR(z::DualPerplex{T}) where T <: Real
    DualPerplex{T}(barR(z.l), barR(z.r))
end

function barA(z::DualPerplex{T}) where T <: Real
    DualPerplex{T}(z.l, -(z.r))
end

function quadrance(z::DualPerplex)
    (z * barA(z)).l
end

function norm(z::DualPerplex)
    abs2(quadrance(z))
end

function iszerodivisor(z::DualPerplex)
    iszerodivisor(quadrance(z))
end

function inv(z::DualPerplex)
    if iszerodivisor(z)
        error(ZeroDivisorInverse)
    end

    q = quadrance(z)
    barA(z) * barR(q) / abs2(q)
end


module juliaset

function createjuliaset{T,V,U}(x::Array{V,1}, y::Array{U,1}, n_iter::Int, escape_tol::Real, R::T)

if T==Function
  G = R;
elseif T<:Number
  function G(z)
    z^2 + R;
  end
else
  error("invalid function R")
end

if V<:Real
else
  error("invalid type in array x")
end

if U<:Real
else
  error("invalid type in array Y")
end

A = zeros(length(x), length(y))

for i in 1:length(x)
  for j in 1:length(y)
    z = x[i] + im * y[j]
    for k in 1:n_iter
      z = G(z)
      if abs(z) > escape_tol # diverged
        A[i,j] = k

        break
      end
    end
    if A[i,j] == 0 # didn't diverge
      A[i,j] = n_iter + 1

    end
  end
end

return A;
matshow(log(A))

end

export createjuliaset
end


"Kuramoto model parameter structure."
struct kp
    G::Float64
    σ::Float64
    w::Array{Float64,2}
    l::Array{Float64,2}
end

"Kuramoto order parameter"
kop(sol) = [abs(mean(exp.(1.0im .* u_t))) for u_t in sol.u];

"Kuramoto dfun."
function kf(du,u,h,p,t)
    G = p.G/length(u)
    du .= 2*2*π/1000
    for j=1:length(u)
        for i=1:length(u)
            if i!=j
                uⱼ = h(p, t - p.l[i,j]; idxs=j)[1]
                du[i] += G * p.w[i,j] * sin(uⱼ - u[i])
            end
        end
    end
end

"Kuramoto noise function."
function kg(du,u,h,p,t)
    du .= sqrt(2*p.σ)
end

"Kuramoto history function."
function kh(p,t;idxs=nothing)
    nn = size(p.w,1)
    x = ((1:nn)/nn*2*pi .+ t) .* 2 .* pi
    collect(idxs==nothing ? x : x[idxs])
end


function main()
    N = parse(Int, readline())
    ps = parse.(Int, split(readline()))

    dp = falses(sum(ps) + 1)
    dp[begin] = true # Score zero
    for p in ps
        dp = dp .| vcat(falses(p), dp[begin:end-p])
    end

    println(sum(dp))
end

main()


@testset "Scalar operators" begin
    B = FEDVR(1.0:7, 4)
    B̃ = B[:,2:end-1]
    r = axes(B,1)

    V = B'*QuasiDiagonal(identity.(r))*B
    @test V == Diagonal(B.x)

    Ṽ = apply(*, B̃', QuasiDiagonal(identity.(r)), B̃)
    @test Ṽ == Diagonal(B.x[2:end-1])
end

@testset "Mass matrices and inverses" begin
    t = range(0,stop=20,length=5)
    @testset "Order $k" for k ∈ [2,5]
        R = FEDVR(t, k)
        R̃ = R[:,2:end-1]

        m = size(R,2)
        n = size(R̃,2)

        @testset "Mass matrices" begin
            d = R'R
            @test d == Diagonal(Ones(m))

            d̃ = R̃'R̃
            @test d̃ == Diagonal(Ones(n))

            @test R'R̃ == (R̃'R)' == BandedMatrix((-1 => ones(Int,n),), (m,n), (1,-1))
            @test R̃'R[:,1:end-1] == BandedMatrix((1 => ones(Int,n),), (n,m-1), (-1,1))
        end

        @testset "Inverses" begin
            R⁻¹ = pinv(R)
            # R̃⁻¹ = pinv(R̃)

            @test R⁻¹*R === I
            @test R*R⁻¹ === I

            cu = rand(size(R,2))
            cv = rand(size(R,2))
            cuv = [cu cv]

            u = R*cu
            v = R*cv
            uv = R*cuv

            @test R⁻¹*u === cu
            @test R⁻¹*v === cv
            @test R⁻¹*uv === cuv

            ut = u'
            # Fails with: ERROR: MethodError: no method matching axes(::UniformScaling{Float64}, ::Int64)
            # @test ut*R⁻¹' === ut.args[1]

            @warn "Need to implement/test basis inverses for restricted bases"
        end
    end
end


using FEASTSolver
using LinearAlgebra
using Profile
using NonlinearEigenproblems: PEP, compute_Mder, contour_block_SS, polyeig, compute_Mlincomb, compute_MM

function info(Λ, X, residuals, c, r)
    inside(x) = in_contour(x, c, r)
    in_eig = Λ[inside.(Λ)]
    in_res = residuals[inside.(Λ)]
    print("number inside : ")
    println(size(Λ[inside.(Λ)])[1])
    if sum(inside.(Λ)) > 0
        in_res_conv = in_res[in_res .<= 1e-5]
        in_eig_conv = in_eig[in_res .<= 1e-5]
        print("number inside converged : ")
        println(size(in_eig_conv)[1])
        print("max res inside: ")
        println(maximum(residuals[inside.(Λ)]))
        if size(in_res_conv, 1) > 0
            print("max res inside non spurious: ")
            println(maximum(in_res_conv))
        end
    end
    # display(in_eig_conv)
end

function bf_arrays()
    N = diagm(-1 => [1,1,1,1,1,1,1])
    Mh0 = 1/6*(4*I + N + N')
    Mh1 = N - N'
    Mh2 = -1*(2*I - N - N')
    Mh3 = Mh1
    Mh4 = -Mh2
    c = [0.6 1.3; 1.3 0.1; 0.1 1.2; 1.0 1.0; 1.2 1.0]
    I8 = Matrix(I, 8, 8)
    M0 = c[1,1] * kron(I8, Mh0) + c[1,2] * kron(Mh0, I8)
    M1 = c[2,1] * kron(I8, Mh1) + c[2,2] * kron(Mh1, I8)
    M2 = c[3,1] * kron(I8, Mh2) + c[3,2] * kron(Mh2, I8)
    M3 = c[4,1] * kron(I8, Mh3) + c[4,2] * kron(Mh3, I8)
    M4 = c[5,1] * kron(I8, Mh4) + c[5,2] * kron(Mh4, I8)
    return [M0, M1, M2, M3, M4]
end

function bf()
    N = diagm(-1 => [1,1,1,1,1,1,1])
    Mh0 = 1/6*(4*I + N + N')
    Mh1 = N - N'
    Mh2 = -1*(2*I - N - N')
    Mh3 = Mh1
    Mh4 = -Mh2
    c = [0.6 1.3; 1.3 0.1; 0.1 1.2; 1.0 1.0; 1.2 1.0]
    I8 = Matrix(I, 8, 8)
    M0 = c[1,1] * kron(I8, Mh0) + c[1,2] * kron(Mh0, I8)
    M1 = c[2,1] * kron(I8, Mh1) + c[2,2] * kron(Mh1, I8)
    M2 = c[3,1] * kron(I8, Mh2) + c[3,2] * kron(Mh2, I8)
    M3 = c[4,1] * kron(I8, Mh3) + c[4,2] * kron(Mh3, I8)
    M4 = c[5,1] * kron(I8, Mh4) + c[5,2] * kron(Mh4, I8)
    function butterfly(A0, A1, A2, A3, A4)
        f(z) =  z^4*A4 + z^3*A3 + z^2*A2 + z*A1 + A0
    end

    return butterfly(M0, M1, M2, M3, M4)
end

C = complex(1.0,1.0)
R = 0.5

T = bf()
e, v, res = nlfeast!(T, rand(ComplexF64,64,30), 2^2, 0, c=complex(1.0,1.0), r=0.5)
e, v, res = @timev nlfeast!(T, rand(ComplexF64,64,20), 2^9, 0, c=complex(1.0,1.0), r=0.5, debug=false, ϵ=10e-16, spurious=5e-3)
# e, v, res = nlfeast_moments!(T, rand(ComplexF64,64,15), 2^4, 40, moments=2, c=complex(1.0,1.0), r=0.5, debug=true, ϵ=10e-16, spurious=5e-3)
# # e, v, res = companion(bf_arrays())
# # e, v, res = @profile nlfeast!(T, rand(ComplexF64,64,30), 2^4, 20, c=complex(1.0,1.0), r=0.5, debug=true, ϵ=10e-16)
# # Profile.print()
# # e, v, res = nlfeast_it!(T, rand(ComplexF64,64,30), 2^3, 0, c=complex(1.0,1.0), r=0.5)
# # e, v, res = @timev nlfeast_it!(T, rand(ComplexF64,64,30), 2^3, 20, c=complex(1.0,1.0), r=0.5, debug=true, ϵ=10e-14)
# # display(e)
# inside(x) = in_contour(x, complex(1.0, 1.0), 0.5)
# in_eig = e[inside.(e)]
# in_res = res[inside.(e)]
# in_res_conv = in_res[in_res .<= 5e-3]
# in_eig_conv = in_eig[in_res .<= 5e-3]
# print("\nmax res inside: ")
# println(maximum(res[inside.(e)]))
# # print("max res inside non spurious: ")
# # println(maximum(in_res_conv))
# print("number inside : ")
# println(size(e[inside.(e)])[1])
# # print("number inside converged : ")
# # println(size(in_eig_conv)[1])
# display(in_eig)
# println()
# display(in_res)

# inside(x) = in_contour(x, complex(1.0, 1.0), 0.5)
#
# nep = PEP(bf_arrays())
# # e,v = polyeig(nep)
# e, v = contour_block_SS(nep, radius=R, σ=C, N=2^8, k=30,K=8)
# res = zeros(size(e,1))
# for i=1:size(e,1)
#     res[i] = norm(compute_Mlincomb(nep, e[i], v[:,i]))
# end
#
# info(e, v, res, C, R)
# in_eig = e[inside.(e)]
# display(in_eig)
#
# println()
#
# e, v, res = block_SS!(T, rand(ComplexF64,64,30), 2^6, 4, c=C, r=R)
# # e, v, res = nlfeast_moments_SS!(T, rand(ComplexF64,64,16), 2^6, 1, c=C, r=R, moments=2, debug=true, ϵ=10e-16, spurious=1e-3)
# info(e, v, res, C, R)
# inside(x) = in_contour(x, complex(1.0, 1.0), 0.5)
# in_eig = e[inside.(e)]
# in_res = res[inside.(e)]
# display(in_eig)
# display(in_res)


module Lorentz

using Vector2d
using Docile

#using ImmutableArrays
#import Base.convert

export run_Lorentz_gas
export run_many_particles

export Particle, Disc, collision_time, Plane

type Particle
    x::Vector2D
    v::Vector2D
end

immutable Disc
    centre::Vector2D
    radius::Float64
end

@doc """Compute *time of collision* of a particle and a disc,
        assuming the particle starts outside the disc and the
        particle speed is one (using the quadratic formula).

        Returns -1 if no collision.
        """ ->
function collision_time(p::Particle, disc::Disc)

    disp = p.x-disc.centre

    B = dot(p.v, disp)
    C = dot(disp, disp) - disc.radius*disc.radius

    discriminant = B*B - C

    if discriminant < 0
        return -1.
    end

    return -B - sqrt(discriminant)  # NB: this supposes that the particle is *outside* the disc

end

@doc """Compute normal vector to disc boundary, at a point x that must lie *on the boundary*""" ->
function normal(disc::Disc, x)
    return (x - disc.centre) / disc.radius
end

immutable Plane
    c::Vector2D  # point on plane
    normal::Vector2D
end

#Plane(c::Vector{Float64}, normal::Vector{Float64}) =
#    Plane(Vector2D(c), Vector2D(normal))

collision_time(p::Particle, plane::Plane) =
    dot(plane.c - p.x, plane.normal) / dot(p.v, plane.normal)

normal(plane::Plane, x) = plane.normal

function collision(p::Particle, current_cell, boundaries, jump_directions, previous_hit)

    min_collision_time = 100.0
    which_hit = -1

    for i in 1:5
        if i == previous_hit
            continue
        end

        @inbounds collision_t = collision_time(p, boundaries[i])

        if collision_t > 0.0 && collision_t < min_collision_time
            which_hit, min_collision_time = i, collision_t
        end
    end

    x_new = p.x + p.v*min_collision_time
    new_cell = current_cell

    @assert which_hit > 0

    if which_hit == 1  # reflect off disc
        @inbounds n = normal(boundaries[1], x_new)

        v_new = p.v - 2.0*n*dot(n, p.v)  # reflejar solo si es disco; si es plano, pasar a traves

        speed = sqrt(dot(v_new, v_new))
        v_new /= speed

    else
        # hit plane, so periodise
        @inbounds jump = jump_directions[which_hit]
        # print "jump = ", jump

        new_cell += jump
        x_new -= jump  # this is supposing that we are in exactly unit cell

        v_new = p.v

        which_hit += 2
        if which_hit > 5
            which_hit -= 4
        end
    end

    return x_new, v_new, new_cell, min_collision_time, which_hit
end

function initial_condition(radius)

    x, y = 0.0, 0.0

    while true
        x, y = rand(2) .- 0.5

        if (x*x + y*y) >= radius^2
            break
        end
    end

    # velocity in 2D:
    theta = rand() * 2*pi

    xx = Vector2D(x, y)
    vv = Vector2D(cos(theta), sin(theta))

    return xx, vv
end

# fast version:
function collision_time_with_disc(x, v, r)
    B = dot(v, x);
    C = dot(x, x) - r*r;

    discriminant = B*B - C;

    if (discriminant < 0)
        return -1.0;

    else
        return -B - sqrt(discriminant);
    end
end

intersection_horiz_boundary(xx, vv, y) =
    @inbounds return (y - xx[2]) / vv[2]

intersection_vert_boundary(xx, vv, x) =
    @inbounds return (x - xx[1]) / vv[1]

function new_collision(xx, vv, r, cell, previous_hit)

    min_collision_time = 1000.0

    collision_times =
        ( collision_time_with_disc(xx, vv, r),
            intersection_vert_boundary(xx, vv, -0.5),
            intersection_horiz_boundary(xx, vv, 0.5),
            intersection_vert_boundary(xx, vv, 0.5),
            intersection_horiz_boundary(xx, vv, -0.5)
        )

    object_hit = -1

    for i in 1:5  # num of collision times

        if i==previous_hit
            continue
        end

        @inbounds coll_time = collision_times[i]

        if coll_time > 0.0 && coll_time < min_collision_time

            min_collision_time = coll_time
            object_hit = i
        end
    end

    #@assert object_hit > 0

    #println(min_collision_time)

    xx += vv*min_collision_time

    @inbounds begin
        x, y = xx[1], xx[2]
        x_cell, y_cell = cell[1], cell[2]
    end

    if object_hit == 1
        n = xx / r  # vector normal

        vv -= 2. * dot(vv, n) * n
        speed = sqrt(dot(vv, vv))
        vv /= speed

    elseif object_hit == 2
        x = 0.5
        x_cell -= 1
        object_hit = 4

    elseif object_hit == 3
        y = -0.5
        y_cell += 1
        object_hit = 5

    elseif object_hit == 4
        x = -0.5
        x_cell += 1
        object_hit = 2

    elseif object_hit == 5
        y = 0.5
        y_cell -= 1
        object_hit = 3
    end

    cell = Vector2D(x_cell, y_cell)
    xx = Vector2D(x, y)

    return xx, vv, cell, min_collision_time, object_hit

end

### Main program

function make_boundaries(radius)

    boundaries = {}

    push!(boundaries, Disc([0., 0.], radius) )

    push!(boundaries, Plane([-0.5, -0.5], [0., -1.]) )
    push!(boundaries, Plane([0.5, -0.5], [1., 0.]) )
    push!(boundaries, Plane([0.5, 0.5], [0., 1.]) )
    push!(boundaries, Plane([-0.5, 0.5], [-1., 0.]) )

    jump_directions = [Vector2D(0.,0.)]  # dummy

    for boundary in boundaries[2:end]
        push!(jump_directions, normal(boundary, 0))
    end

    println("# Boundaries: ", boundaries)
    println("# Jump directions: ", jump_directions)

    return boundaries, jump_directions
end

function Lorentz_gas(radius, boundaries, jump_directions, t_final)

    current_cell = Vector2D(0., 0.)

    #collision_times = -ones(5)

    x0, v0 = initial_condition(radius)
    p = Particle(x0, v0)

    total_time = 0.0

    xs = [x0[1]]
    ys = [x0[2]]

    which_hit = -1
    time_since_disc_collision = 0.0
    disc_collision_times = Float64[]

    output_time = 10.
    next_output_time = output_time
    num_data = int(round(t_final / output_time))

    println("num_data = ", num_data)

    step = 0

    positions = [x0]
    times = [0.0]

    #while total_time <= t_final
    while step <= num_data+1
        x_new, v_new, new_cell, collision_t, which_hit =
            #collision(p, current_cell, boundaries,
            #        jump_directions, which_hit)
        new_collision(p.x, p.v, radius, current_cell, which_hit)

        next_time = total_time + collision_t

        #println("next_time = ", next_time, "; next_output_time = ", next_output_time)

        #while (next_time > next_output_time)
        if (next_time >= next_output_time)
            # not necessary to use a while

            new_position = p.x + (next_output_time - total_time) * p.v
            new_position += new_cell

            #println(next_output_time, "\t", new_position[1], "\t", new_position[2])

            push!(positions, new_position)
            push!(times, next_output_time)

            next_output_time += output_time
            step += 1

            if step == num_data
                break
            end

        end

        total_time = next_time

        if total_time + collision_t < t_final
            total_time += collision_t
            current_cell = new_cell

            p.x, p.v = x_new, v_new

            #println(x_new[1], "\t", x_new[2], "\t", v_new[1], "\t", v_new[2])

            time_since_disc_collision += collision_t

            if which_hit == 1  # disc

                x_new += new_cell

                push!(xs, x_new[1])
                push!(ys, x_new[2])

                push!(disc_collision_times, time_since_disc_collision)
                time_since_disc_collision = 0.0
            end

        else
            collision_t = t_final - total_time

            x_new = p.x + p.v*collision_t
            x_new += current_cell

            println("# Position at time ", t_final, " = ", x_new)
            println("# Cell: ", current_cell)
            println("# Distance from origin = ", (dot(x_new, x_new))^0.5)
#            println("# Distance squared from origin = ", dot(x_new, x_new))

            break
        end

    end

    return xs, ys, disc_collision_times, times, positions
end

function many_particles(N, radius, boundaries, jump_directions, t_final)

    xs, ys, disc_collision_times, times, positions = Lorentz_gas(radius, boundaries, jump_directions, t_final)

 println("times = ", times)
    println(length(positions))

    all_positions = { { positions[i] } for i in 1:length(positions) }

    for n in 1:N-1
        xs, ys, disc_collision_times, times, positions = Lorentz_gas(radius, boundaries, jump_directions, t_final)

         println("times = ", times)
    println("all_positions = ", all_positions)
    println(length(positions))
    println(length(all_positions))

        for i in 1:length(positions)
            push!(all_positions[i], positions[i])
        end
    end

    all_positions
end

# function continuous_time_trajectory()

#     p = Particle(initial_condition()...)

#     t = 0.0;
#     next_output_time = output_time;

#     step = 1;  /// location in data array

#     while (t < max_time) {

#         next_t = t + min_collision_time;

#         while (next_t > next_output_time) {
#             /// use a while in case have very long path

#             new_position = p.position + (next_output_time - t)*p.velocity;

#             new_position += Vec(x_cell, y_cell);

#             displacement = new_position - p.initial_position;
#             disp_norm = dot(displacement, displacement);

#             /// |Delta x|
#             basic_moment = sqrt(sqrt(disp_norm)); /// |Delta x|^{1/2}
#             /// the basic unit is |Delta x|^{1/2}

#             moment = basic_moment;
#             /// moment is multiplied over and over by basic_moment to give the powers

#             for (int m=1; m<=num_moments; m++) {
#                 moments_data[m][step] += moment;
#                 moment *= basic_moment;
#             }

#             vel_vel_correlation_data[step] += dot(p.velocity, p.initial_velocity);
#             vel_pos_correlation_data[step] += dot(displacement, p.initial_velocity);
#             current_vel_pos_correlation_data[step] += dot(displacement, p.velocity);

#             step++;
#             next_output_time += output_time;

#             if (step > num_data) return;

#         }

#         implementCollision();

#         t = next_t;

#     //cout << "After collision:\n";
#     //cout << "Pos: ";

#         findCollision();

#     }

# }

function run_Lorentz_gas(radius, max_time)

    println("# Using radius: ", radius)

    boundaries, jump_directions = make_boundaries(radius)
    @time xs, ys = Lorentz_gas(radius, boundaries, jump_directions, max_time)
end

function run_many_particles(N, radius, max_time)

    println("# Using radius: ", radius)
    println("# Using $N particles")

    boundaries, jump_directions = make_boundaries(radius)
    @time all_positions = many_particles(N, radius, boundaries, jump_directions, max_time)
end

if length(ARGS) > 1
    radius = float(ARGS[1])
    run_Lorentz_gas(radius)
end

# using PyPlot
# plot(xs, ys)

end

function elastic_collision(obstacle, particle)
end

function periodic_boundary(obstacle, particle)  # needs *more* information and returns more information
end

# There's a fundaental difference betweeen "nromal"elastic objects and periodic boundaries
# There could also be other types of boundary, e.g. ones where crossing it randomizes the position of the disc in the new cell or just updates which cell the particle
# lieves in.

# E.g. think of a sequence of cells with pre-randomised (quenched) disc positions.


"""
`binary_boundary_search(fun, x_range, err)`

Find a boundary value `X` at which `fun(X)` changes its value from `0` to `1`.
That is, `fun(x) = 0` for all `x < X` and `fun(x) = 1` for all `x > X`.

**Arguments**
- `fun`: Function that returns `0` if `x < X` and `1` if `x > X`.
- `x_range`: Search range as `[x_min, x_max]`.
- `err`: Maximal deviation of the result from the real value.

**Returns**
- `X`: Boundary value, at which `fun(x)` changes from `0` to `1`.
"""
function binary_boundary_search(fun, x_range, err)

    x1 = x_range[1]
    x2 = x_range[2]

    y1 = fun(x1)
    if y1 == 1
        return x1
    end

    y2 = fun(x2)
    if y2 == 0
        return x2
    end

    while abs(x1 - x2) > 2err

        t0 = (x1 + x2) / 2
        x0 = fun(t0)

        if x0 == y1
            y1 = x0
            x1 = t0
        else
            y2 = x0
            x2 = t0
        end

    end

    X = (x1 + x2) / 2
    return X

end


includet("aidpolicy.jl")
using Plots
#6 (or maybe 7) is the limit on my laptop
N=3
G=6
pi_t = 10
pi_b = 1
nsims = pi_t - pi_b+1
obj_opt = zeros(nsims)
obj_tic = zeros(nsims)

for (pi, i) in zip(pi_b:pi_t, 1:nsims)
    tree = initializeGame(N, G)
    calculate_TIC(tree, pi)
    obj_tic[i] = get_objective(tree)
    obj_opt[i] = optimalRule(tree, pi)
end

obj_unif = zeros(nsims) .+ (1- 1/(N-1))^2 .+ (N-2)*(0-1/(N-1))^2
obj_ante = zeros(nsims) .+ (1- 1/(N))^2 .+ (N-1)*(0-1/(N))^2
plot(pi_b:pi_t,[ obj_ante obj_unif obj_opt obj_tic], ylims=[0,1.1], xlabel="B", ylabel="Objective",label=["Ex-Ante Uniform" "Ex-Post Uniform" "Optimal" "T-IC"])

#weights plot
function weights_plot()
    N=3
    G=6
    pi=10
    tic_weights = zeros(G)
    tree = initializeGame(N, G)
    calculate_TIC(tree, pi)
    obj, opt_weights = optimalRule(tree, pi, true, true)
    obj, no_inc_weights = optimalRule(tree, pi, false, true)
    getWeights!(tree, tic_weights, 0)
    plot([ no_inc_weights opt_weights tic_weights], ylim = [0.08, 0.15], xlabel="Period", ylabel="Weight",labels=["Optimal, No IC Constraints" "Optimal Constrained" "T-IC "])
    savefig("figures/weights10.pdf")
end

weights_plot()


using LightGraphs,SimpleWeightedGraphs,Random,Word2Vec,DelimitedFiles

function node2vec_walk(g, node, len,p,q)
    no,ed=preprocess_transition_probs(g,p,q)
    walk=[node]
    while length(walk) < len
        current=last(walk)
        neigh=neighbors(g, current)
        if length(neigh) > 0
            if length(walk)==1
                append!(walk,neigh[alias_draw(no[current][1],
                                              no[current][2])])
            else
                prev=walk[length(walk)-1]
                next=neigh[alias_draw(ed[(prev,current)][1],
                                      ed[(prev,current)][2])]
                append!(walk,next)
            end
        else
            break
        end
    end
    return walk
end

function simulate_walks(g,num_walks,len,p,q)
    walks=Array{Array}(undef,0)
    nodes=vertices(g) |> collect
    for i in 1:num_walks
        nodes=shuffle(nodes)
        for node in nodes
            push!(walks,node2vec_walk(g,node,len,p,q))
        end
    end
    walks
end

function get_alias_edge(g, src, dst,p,q)
    unnormalized_probs=Array{Float64}(undef,0)
    for dst_nbr in neighbors(g,dst)
        if dst_nbr == src
            append!(unnormalized_probs, g.weights[dst,dst_nbr]/p)
        elseif has_edge(g,dst_nbr,src)
            append!(unnormalized_probs, g.weights[dst,dst_nbr])
        else
            append!(unnormalized_probs, g.weights[dst,dst_nbr]/q)
        end
    end
    norm_const=sum(unnormalized_probs)
    normalized_probs=unnormalized_probs/norm_const
    alias_setup(normalized_probs)
end

function preprocess_transition_probs(g,p,q)
    alias_nodes=Dict()
    for node in vertices(g)
        unnormalized_probs=[g.weights[node,nbr] for nbr in neighbors(g,node)]
        norm_const=sum(unnormalized_probs)
        normalized_probs=unnormalized_probs/norm_const
        alias_nodes[node]=alias_setup(normalized_probs)
    end
    alias_edges=Dict()
    ## Undirected networks
    for e in edges(g)
        alias_edges[(e.src,e.dst)]=get_alias_edge(g,e.src,e.dst,p,q)
        alias_edges[(e.dst,e.src)]=get_alias_edge(g,e.dst,e.src,p,q)
    end
    alias_nodes, alias_edges
end

function alias_setup(probs)
    K=length(probs)
    q=zeros(K)
    J=zeros(Int,K)

    smaller=Array{Float64}(undef,0)
    larger=Array{Float64}(undef,0)

    for (kk, prob) in enumerate(probs)
        q[kk]=K*prob
        if q[kk] < 1
            push!(smaller,kk)
        else
            push!(larger,kk)
        end
    end

    while length(smaller) > 0 && length(larger) > 0
        small=Int(pop!(smaller))
        large=Int(pop!(larger))

        J[small]=large
        q[large]=q[large]+q[small]-1.0
        if q[large] < 1.0
            append!(smaller,large)
        else
            append!(larger,large)
        end
    end
    J, q
end

function alias_draw(J,q)
    K=length(J)
    kk=Int(ceil(rand()*K))
    if rand() < q[kk]
        return kk
    else
        return J[kk]
    end
end

function learn_embeddings(walks)
    str_walks=map(x -> string.(x),walks)
    writedlm("/tmp/str_walk.txt",str_walks)
    word2vec("/tmp/str_walk.txt","/tmp/str_walk-vec.txt",verbose=true)
    model=wordvectors("/tmp/str_walk-vec.txt")
    model
end


# winner from benchmarks/which_is_the_fastest_UInt_histogram.jl
"""
    uint_hist(bits, [RADIX_SIZE = 16, RADIX_MASK = 0xffff])

Computes a histogram (counts) for the vector RADIX_SIZE bits at a time. E.g. if eltype(bits) is UInt64 and RADIX_SIZE is 16
then 4 histograms are created for each of the 16 bit chunks.
"""
function uint_hist(bits::AbstractVector{T}, RADIX_SIZE = 16, RADIX_MASK = 0xffff) where T
    iter = ceil(Integer, sizeof(T)*8/RADIX_SIZE)
    hist = zeros(UInt32, 2^RADIX_SIZE, iter)

    for j = 1:length(bits)
        for i = 0:iter-1
            @inbounds hist[1+Int((uint_mapping(bits[j]) >> (i * RADIX_SIZE)) & RADIX_MASK), i+1] += 1
        end
    end
    hist
end


# Tests for Revenue Russell DEA Models
@testset "RevenueRussellDEAModel" begin

    # Test using Book data
    X = [1; 1; 1; 1; 1; 1; 1; 1];
    Y = [7 7; 4 8; 8 4; 3 5; 3 3; 8 2; 6 4; 1.5 5];
    P = [1 1; 1 1; 1 1; 1 1; 1 1; 1 1; 1 1; 1 1];

    # Revenue Russell
    dearevenuerus = dearevenuerussell(X, Y, P)

    @test typeof(dearevenuerus) == RevenueRussellDEAModel

    @test nobs(dearevenuerus) == 8
    @test ninputs(dearevenuerus) == 1
    @test noutputs(dearevenuerus) == 2
    @test ismonetary(dearevenuerus) == false

    @test efficiency(dearevenuerus) == efficiency(dearevenuerus, :Economic)
    @test efficiency(dearevenuerus, :Economic)   ≈ [0.0; 0.25; 0.25; 1.0; 4/3; 1.0; 0.5; 2.5]
    @test efficiency(dearevenuerus, :Technical)  ≈ [0.0; 0.0; 0.0; 13/15; 4/3; 0.5; 11/24; 37/18]
    @test efficiency(dearevenuerus, :Allocative) ≈ [0.0; 0.25; 0.25; 2/15; 0.0; 0.5; 1/24; 4/9]
    @test normfactor(dearevenuerus) ≈ [14.0; 8.0; 8.0; 6.0; 6.0; 4.0; 8.0; 3.0]

    # With monetary = true
    dearevenuerusmon = dearevenuerussell(X, Y, P, monetary = true)

    @test ismonetary(dearevenuerusmon) == true

    @test efficiency(dearevenuerusmon) ≈ efficiency(dearevenuerus) .* normfactor(dearevenuerus)
    @test efficiency(dearevenuerusmon, :Technical) ≈ efficiency(dearevenuerus, :Technical) .* normfactor(dearevenuerus)
    @test efficiency(dearevenuerusmon, :Allocative) ≈ efficiency(dearevenuerus, :Allocative) .* normfactor(dearevenuerus)

    # With CRS (Same result in this example)
    dearevenueruscrs = dearevenuerussell(X, Y, P, rts = :CRS)

    @test efficiency(dearevenueruscrs, :Economic)   ≈ [0.0; 0.25; 0.25; 1.0; 4/3; 1.0; 0.5; 2.5]
    @test efficiency(dearevenueruscrs, :Technical)  ≈ [0.0; 0.0; 0.0; 13/15; 4/3; 0.5; 11/24; 37/18]
    @test efficiency(dearevenueruscrs, :Allocative) ≈ [0.0; 0.25; 0.25; 2/15; 0.0; 0.5; 1/24; 4/9]

    # Print
    show(IOBuffer(), dearevenuerus)

    # Test errors
    @test_throws DimensionMismatch dearevenuerussell([1; 2 ; 3], [4 ; 5], [1; 1; 1]) #  Different number of observations
    @test_throws DimensionMismatch dearevenuerussell([1; 2; 3], [4; 5; 6], [1; 2; 3; 4]) # Different number of observation in prices
    @test_throws DimensionMismatch dearevenuerussell([1 1; 2 2; 3 3 ], [4; 5; 6], [1 1 1; 2 2 2; 3 3 3]) # Different number of output prices and outputs
    @test_throws ArgumentError dearevenuerussell([1; 2; 3], [4; 5; 6], [1; 2; 3], rts = :Error) # Invalid returns to scale

end


@doc doc"""
Ising model with energy $E = -\sum_{ij} J_{ij} \sigma_i \sigma_j$,
where $\sigma_i$ takes value of 1 (up spin) or -1 (down spin).
"""
mutable struct Ising <: Model
    lat :: Lattice
    spins :: Matrix{Int}
    rng :: Random.MersenneTwister

    function Ising(lat::Lattice, rng::Random.AbstractRNG)
        model = new()
        model.lat = lat
        model.rng = rng
        model.spins = rand(model.rng, [1,-1], 1, numsites(lat))
        return model
    end
end

Ising(lat::Lattice) = Ising(lat, Random.seed!(Random.MersenneTwister(0)))
Ising(lat::Lattice, seed) = Ising(lat, Random.seed!(Random.MersenneTwister(0), seed...))

@doc doc"""
    Ising(param)

Generates `Ising` using `param["Lattice"]` and `param["Seed"]` (if defined).
Each spin will be initialized randomly and independently.
"""
function Ising(param::Parameter)
    lat = generatelattice(param)
    if "Seed" in keys(param)
        return Ising(lat, param["Seed"])
    else
        return Ising(lat)
    end
end

include("update.jl")
include("estimator.jl")
include("postproc.jl")