#
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

################################## INSTALLATION ####################################################
# (1) Enter Package Manager via     ]
# (2) Install FMI via               add FMI       or   add "https://github.com/ThummeTo/FMI.jl"
# (3) Install FMIFlux via           add FMIFlux   or   add "https://github.com/ThummeTo/FMIFlux.jl"
################################ END INSTALLATION ##################################################

# this example covers creation and training of a ME-NeuralFMU
# here, solver step size is adaptive controlled for better training performance

using FMI
using FMIFlux
using Flux
using DifferentialEquations: Tsit5
import Plots
using Zygote

modelFMUPath = joinpath(dirname(@__FILE__), "../model/SpringPendulum1D.fmu")
realFMUPath = joinpath(dirname(@__FILE__), "../model/SpringFrictionPendulum1D.fmu")

t_start = 0.0
t_step = 0.1
t_stop = 5.0
tData = collect(t_start:t_step:t_stop)

myFMU = fmiLoad(realFMUPath)
fmiInstantiate!(myFMU; loggingOn=false)
fmiSetupExperiment(myFMU, t_start, t_stop)

fmiEnterInitializationMode(myFMU)
fmiExitInitializationMode(myFMU)

x0 = fmi2GetContinuousStates(myFMU)

realSimData = fmiSimulate(myFMU, t_start, t_stop; saveat=tData, recordValues=["mass.s", "mass.v", "mass.f", "mass.a"], setup=false)
fmiUnload(myFMU)

fmiPlot(realSimData)

myFMU = fmiLoad(modelFMUPath)

fmiInstantiate!(myFMU; loggingOn=false)
fmuSimData = fmiSimulate(myFMU, t_start, t_stop; saveat=tData, recordValues=["mass.s", "mass.v", "mass.a"])

posData = fmi2SimulationResultGetValues(realSimData, "mass.s")
velData = fmi2SimulationResultGetValues(realSimData, "mass.v")

# loss function for training
global integratorSteps
function losssum()
    global integratorSteps, problem

    solution = problem(x0)

    tNet = collect(data[1] for data in solution.u)
    posNet = collect(data[2] for data in solution.u)
    #velNet = collect(data[3] for data in solution.u)

    integratorSteps = length(tNet)

    #mse_interpolate(tData, posData, tNet, posNet, tData) # mse_interpolate(tData, velData, tNet, velNet, tData)
    Flux.mse(posData, posNet)
end

# callback function for training
global iterCB = 0
function callb()
    global iterCB += 1
    global integratorSteps

    if iterCB % 10 == 1
        avg_ls = losssum()
        @info "Loss: $(round(avg_ls, digits=5))   Avg displacement in data: $(round(sqrt(avg_ls), digits=5))   Integ.Steps: $integratorSteps"
    end

    if iterCB % 100 == 1
        fig = plotResults()
        println("Fig. update.")
        display(fig)
    end
end

function plotResults()
    solutionAfter = problem(x0, t_start)
    fig = Plots.plot(xlabel="t [s]", ylabel="mass position [m]", linewidth=2,
        xtickfontsize=12, ytickfontsize=12,
        xguidefontsize=12, yguidefontsize=12,
        legendfontsize=12, legend=:bottomright)
    Plots.plot!(fig, tData, fmi2SimulationResultGetValues(fmuSimData, "mass.s"), label="FMU", linewidth=2)
    Plots.plot!(fig, tData, posData, label="reference", linewidth=2)
    Plots.plot!(fig, collect(data[1] for data in solutionAfter.u), collect(data[2] for data in solutionAfter.u), label="NeuralFMU", linewidth=2)
    fig
end

# NeuralFMU setup
numStates = fmiGetNumberOfStates(myFMU)
additionalVRs = [fmi2String2ValueReference(myFMU, "mass.m")]
numAdditionalVRs = length(additionalVRs)

net = Chain(inputs -> fmiDoStepME(myFMU, inputs, -1.0, [], [], additionalVRs),
            Dense(numStates+numAdditionalVRs, 16, tanh),
            Dense(16, 16, tanh),
            Dense(16, numStates))

problem = ME_NeuralFMU(myFMU, net, (t_start, t_stop), Tsit5(); saveat=tData)
solutionBefore = problem(x0, t_start)
fmiPlot(problem)

# train it ...
p_net = Flux.params(problem)

optim = ADAM()
# Feel free to increase training steps or epochs for better results
Flux.train!(losssum, p_net, Iterators.repeated((), 1000), optim; cb=callb)

fmiUnload(myFMU)


using Test
using QBase

@testset "./src/measure.jl" begin

@testset "measure()" begin
    @testset "State" begin
        ρ = State([0.5 0.5im;-0.5im 0.5])
        Π = POVM([[1 0;0 0],[0 0;0 1]])

        marginals = measure(Π, ρ)

        @test marginals isa Probabilities
        @test marginals == [0.5,0.5]
    end

    @testset "Ket" begin
        ψ = Ket(1/sqrt(2)*[1,im])
        Π = POVM([[0.5 0.5im;-0.5im 0.5],[0.5 -0.5im;0.5im 0.5]])

        marginals = measure(Π, ψ)

        @test marginals isa Probabilities
        @test marginals ≈ [0,1]
    end

    @testset "Vector{State}" begin
        @testset "trine_measurements" begin
           conditionals = measure(
               mirror_symmetric_qubit_3povm(π/3), trine_qubit_states()
           )
           @test conditionals ≈ [2/3 1/6 1/6;1/6 2/3 1/6;1/6 1/6 2/3]
           @test conditionals isa Conditionals
       end

       @testset "classical_measurements" begin
           Π = POVM([[1 0;0 0],[0 0;0 1],[0 0;0 0]])
           ρ_set = State.([[0.5 0.5;0.5 0.5],[1 0;0 0],[0 0;0 1]])

           @test measure(Π, ρ_set) == [0.5 1 0;0.5 0 1;0 0 0]

           Π = POVM([[1 0 0;0 1 0;0 0 0],[0 0 0;0 0 0;0 0 1]])
           ρ_set = State.([[1 0 0;0 0 0;0 0 0],[0 0 0;0 1 0;0 0 0],[0 0 0;0 0 0;0 0 1]])

           @test measure(Π, ρ_set) == [1 1 0;0 0 1]
       end
    end

    @testset "Vector{Ket}" begin
        ψ_kets = Ket.([[1,0],[0,1]])
        Π = POVM([[0 0;0 1],[1 0;0 0],[0 0;0 0]])

        conditionals = measure(Π, ψ_kets)

        @test conditionals isa Conditionals
        @test conditionals == [0 1;1 0;0 0]
    end
end

end


function build_globalmin{T <: Union(ASCIIString, UTF8String)}(list::Array{T})

  returns  = returns(list)
  params  = modelparams(returns)
  weights = rowsums(Array(inv(params[5]))) / sum(inv(params[5]))

  port = Portfolio(params[1],
                   params[2],
                   params[3],
                   params[4],
                   params[5],
                   weights)
  port
end

function build_efficient(x)
  #code here
end

function build_tangency(x)
  #code here
end


using TerraDG

configfile = "./advection.yaml"
TerraDG.main(configfile)


type PMLCoef
     vz::SparseMatrixCSC{Float64, Int64}
     vx::SparseMatrixCSC{Float64, Int64}
     pz::SparseMatrixCSC{Float64, Int64}
     px::SparseMatrixCSC{Float64, Int64}
end

function InitCpml(nz::Int64, nx::Int64, ext::Int64, dz::Float64, dx::Float64, vmax::Float64, iflag::Int64)
      if ext == 10
         R = 0.01
      elseif ext == 20
         R = 0.001
      elseif ext == 30
         R = 0.0001
      else
         error("unsupported damping layers")
      end
      ll = 2;
      if iflag == 1
         Nz = nz + 2*ext
         Nx = nx + 2*ext
         vz = spzeros(Nz, Nx)
         vx = spzeros(Nz, Nx)
         pz = spzeros(Nz, Nx)
         px = spzeros(Nz, Nx)
         #  upper and left
         for i = 1 : ext
             vz[i,:] = -1.5 * vmax/(ext*dz) * log(R) *  ((ext+1-i)/ext)^ll
             vx[:,i] = -1.5 * vmax/(ext*dx) * log(R) *  ((ext+1-i)/ext)^ll
             pz[i,:] = -1.5 * vmax/(ext*dz) * log(R) *  ((ext+1-i)/ext)^ll
             px[:,i] = -1.5 * vmax/(ext*dx) * log(R) *  ((ext+1-i)/ext)^ll
         end
         #  lower
         for iz = nz+ext+1 : Nz
             vz[iz,:] = -1.5 * vmax/(ext*dz) * log(R) * ((iz-nz-ext)/ext)^ll
             pz[iz,:] = -1.5 * vmax/(ext*dz) * log(R) * ((iz-nz-ext)/ext)^ll
         end
         #  right
         for ix = nx+ext+1 : Nx
             vx[:,ix] = -1.5 * vmax/(ext*dx) * log(R) * ((ix-nx-ext)/ext)^ll
             px[:,ix] = -1.5 * vmax/(ext*dx) * log(R) * ((ix-nx-ext)/ext)^ll
         end
      elseif iflag == 2
         Nz = nz +   ext
         Nx = nx + 2*ext
         vz = spzeros(Nz, Nx)
         vx = spzeros(Nz, Nx)
         pz = spzeros(Nz, Nx)
         px = spzeros(Nz, Nx)
         #  lower
         for iz = nz+1 : Nz
             vz[iz,:] = -1.5 * vmax/(ext*dz) * log(R) * ((iz-nz)/ext)^ll
             pz[iz,:] = -1.5 * vmax/(ext*dz) * log(R) * ((iz-nz)/ext)^ll
         end
         #  left
         for ix = 1 : ext
             vx[:,ix] = -1.5 * vmax/(ext*dx) * log(R) * ((ext+1-ix)/ext)^ll
             px[:,ix] = -1.5 * vmax/(ext*dx) * log(R) * ((ext+1-ix)/ext)^ll
         end
         #  right
         for ix = nx+ext+1 : Nx
             vx[:,ix] = -1.5 * vmax/(ext*dx) * log(R) * ((ix-nx-ext)/ext)^ll
             px[:,ix] = -1.5 * vmax/(ext*dx) * log(R) * ((ix-nx-ext)/ext)^ll
         end
      end
      Cpml = PMLCoef(vz, vx, pz, px)
      return Cpml
end

# =============================partial derivative matrix========================
type FidMtx
     nz :: Int64
     nx :: Int64
     ext:: Int64
     iflag :: Int64
     dz :: Float64
     dx :: Float64
     dt :: Float64
     MvzBvz :: SparseMatrixCSC{Float64,Int64}
     MvzBp  :: SparseMatrixCSC{Float64,Int64}
     MvxBvx :: SparseMatrixCSC{Float64,Int64}
     MvxBp  :: SparseMatrixCSC{Float64,Int64}
     MpzBpz :: SparseMatrixCSC{Float64,Int64}
     MpzBvz :: SparseMatrixCSC{Float64,Int64}
     MpxBpx :: SparseMatrixCSC{Float64,Int64}
     MpxBvx :: SparseMatrixCSC{Float64,Int64}
end

function modExpand(par::Array{Float64,2}, ext::Int64, iflag::Int64)
    temp1 = repmat(par[:,  1], 1, ext)
    temp2 = repmat(par[:,end], 1, ext)
    par   = hcat(temp1, par, temp2)
    if iflag == 1
       temp1 = repmat(par[1,  :]', ext, 1)
       temp2 = repmat(par[end,:]', ext, 1)
       par   = vcat(temp1, par, temp2)
    elseif iflag == 2
       temp2 = repmat(par[end,:]', ext, 1)
       par   = vcat(par, temp2)
    end
    return par
end

function Mvx(vx_pml::SparseMatrixCSC{Float64,Int64}, dx::Float64, dt::Float64, ext::Int64)
    (m,n) = size(vx_pml)
    a1 = 9/8;   a2 = -1/24;
    c1 = a1/dx; c2 = a2/dx;
    C3 = zeros(m*n)
    denum = zeros(m*n)
    for ix = 1 : n
        for iz = 1 : m
            a = 1/dt + vx_pml[iz,ix]/2
            b = 1/dt - vx_pml[iz,ix]/2
            denum[(ix-1)*m+iz] = 1 / a
            C3[(ix-1)*m+iz]= b / a
        end
    end
    tmp = spzeros(n, n)
    tmp[1,1] = -c1; tmp[1,2] = c1; tmp[1,3] = c2;
    for ix = 2: n-2
        tmp[ix,ix  ] = -c1; tmp[ix, ix+1] = c1;
        tmp[ix,ix-1] = -c2; tmp[ix, ix+2] = c2;
    end
    tmp[n-1,n-1] = -c1; tmp[n-1, n  ] = c1; tmp[n-1,n-2] = -c2;
    tmp[n  ,n  ] = -c1; tmp[n  , n-1] =-c2;
    MvxBvx = spdiagm(C3)
    MvxBp  = spdiagm(denum) * kron(tmp, speye(m))
    return MvxBvx, MvxBp
end

function Mvz(vz_pml::SparseMatrixCSC{Float64,Int64}, dz::Float64, dt::Float64, ext::Int64)
    (m,n) = size(vz_pml)
    a1 = 9/8;   a2 = -1/24;
    c1 = a1/dz; c2 = a2/dz;
    C3 = zeros(m*n)
    denum = zeros(m*n)
    for ix = 1 : n
        for iz = 1 : m
            a = 1/dt + vz_pml[iz,ix]/2
            b = 1/dt - vz_pml[iz,ix]/2
            denum[(ix-1)*m+iz] = 1 / a
            C3[(ix-1)*m+iz]= b / a
        end
    end
    tmp = spzeros(m, m)
    tmp[1,1] = -c1; tmp[1,2] = c1; tmp[1,3] = c2;
    for iz = 2: m-2
        tmp[iz,iz  ] = -c1; tmp[iz, iz+1] = c1;
        tmp[iz,iz-1] = -c2; tmp[iz, iz+2] = c2;
    end
    tmp[m-1,m-1] = -c1; tmp[m-1, m  ] = c1; tmp[m-1, m-2] = -c2;
    tmp[m  ,m  ] = -c1; tmp[m  , m-1] =-c2;
    MvzBvz = spdiagm(C3)
    MvzBp  = spdiagm(denum) * kron(speye(n), tmp)
    return MvzBvz, MvzBp
end

function Mpx(px_pml::SparseMatrixCSC{Float64,Int64}, v::Array{Float64,2}, dx::Float64, dt::Float64, ext::Int64, iflag::Int64)
    v = modExpand(v, ext, iflag)
    (m, n) = size(px_pml)
    a1 = 9/8  ; a2 = -1/24;
    c1 = a1/dx; c2 = a2/dx;
    C3 = zeros(m*n)
    denum = zeros(m*n)
    for ix = 1 : n
        for iz = 1 : m
            a = 1/dt + px_pml[iz,ix]/2
            b = 1/dt - px_pml[iz,ix]/2
            denum[(ix-1)*m+iz] = (v[iz,ix])^2 / a
            C3[(ix-1)*m+iz]= b / a
        end
    end
    tmp = spzeros(n, n)
    tmp[1,1] =  c1; tmp[1,2] = c2;
    tmp[2,1] = -c1; tmp[2,2] = c1; tmp[2,3] = c2;
    for ix = 3: n-1
        tmp[ix,ix-1] = -c1; tmp[ix, ix  ] = c1;
        tmp[ix,ix-2] = -c2; tmp[ix, ix+1] = c2;
    end
    tmp[n,n-1] = -c1; tmp[n,n] = c1; tmp[n,n-2] = -c2;
    MpxBpx = spdiagm(C3)
    MpxBvx = spdiagm(denum) * kron(tmp, speye(m))
    return MpxBpx, MpxBvx
end

function Mpz(pz_pml::SparseMatrixCSC{Float64,Int64}, v::Array{Float64,2}, dz::Float64, dt::Float64, ext::Int64, iflag::Int64)
    v = modExpand(v, ext, iflag)
    (m, n) = size(pz_pml)
    a1 = 9/8;   a2 = -1/24;
    c1 = a1/dz; c2 = a2/dz;
    C3 = zeros(m*n)
    denum = zeros(m*n)
    for ix = 1 : n
        for iz = 1 : m
            a = 1/dt + pz_pml[iz,ix]/2
            b = 1/dt - pz_pml[iz,ix]/2
            denum[(ix-1)*m+iz] = (v[iz,ix])^2 / a
            C3[(ix-1)*m+iz]= b / a
        end
    end
    tmp = spzeros(m, m)
    tmp[1,1] =  c1; tmp[1,2] = c2;
    tmp[2,1] = -c1; tmp[2,2] = c1; tmp[2,3] = c2;
    for iz = 3: m-1
        tmp[iz,iz-1] = -c1; tmp[iz, iz  ] = c1;
        tmp[iz,iz-2] = -c2; tmp[iz, iz+1] = c2;
    end
    tmp[m,m-1] = -c1; tmp[m,m] = c1; tmp[m, m-2] = -c2
    MpzBpz = spdiagm(C3)
    MpzBvz = spdiagm(denum) * kron(speye(n), tmp)
    return MpzBpz, MpzBvz
end

function DispStable!(vmax::Float64, vmin::Float64, f0::Float64, dz::Float64, dx::Float64, dt::Float64)
    h = vmin / 5 / f0
    if dz >= h || dx >=h
       warn("large grid size may produce frequency dispersion, h should less than $h")
    end
    h = dx
    if dz < dx
       h = dz
    end
    tt = 6*h / (7*sqrt(3)*vmax)
    if dt >= tt
       warn("large time step may cause unstable, dt should less than $tt")
    end
    return nothing
end

function InitFidMtx(nz::Int64, nx::Int64, ext::Int64, iflag::Int64, dz::Float64, dx::Float64, dt::Float64, vmax::Float64, vmin::Float64, f0::Float64, v::Array{Float64,2})
    DispStable!(vmax, vmin, f0, dz, dx, dt)
    Cpml = InitCpml(nz, nx, ext, dz, dx, vmax, iflag)
    (MvzBvz, MvzBp)  = Mvz(Cpml.vz,    dz, dt, ext       )
    (MvxBvx, MvxBp)  = Mvx(Cpml.vx,    dx, dt, ext       )
    (MpzBpz, MpzBvz) = Mpz(Cpml.pz, v, dz, dt, ext, iflag)
    (MpxBpx, MpxBvx) = Mpx(Cpml.px, v, dx, dt, ext, iflag)
    fidMtx = FidMtx(nz, nx, ext, iflag, dz, dx, dt, MvzBvz, MvzBp, MvxBvx, MvxBp, MpzBpz, MpzBvz, MpxBpx, MpxBvx)
    return fidMtx
end

# =========================forward modeling ====================================
type SnapShot
     isz:: Int64
     isx:: Int64
     nz :: Int64
     nx :: Int64
     ext:: Int64
     iflag :: Int64
     dt :: Float64
     it :: Int64
     vz :: Array{Float64, 1}
     vx :: Array{Float64, 1}
     pz :: Array{Float64, 1}
     px :: Array{Float64, 1}
end

function InitSnapShot(isz::Int64, isx::Int64, nz::Int64, nx::Int64, ext::Int64, iflag::Int64, dt::Float64, it::Int64)
    if iflag == 1
       Nz = nz + 2*ext
       Nx = nx + 2*ext
    elseif iflag == 2
       Nz = nz +   ext
       Nx = nx + 2*ext
    end
    spt = SnapShot(isz, isx, nz, nx, ext, iflag, dt, it, zeros(Nz*Nx), zeros(Nz*Nx), zeros(Nz*Nx), zeros(Nz*Nx))
    return spt
end

function Base.copy(snapShot1::SnapShot, snapShot2::SnapShot)
    if snapShot2.nz != snapShot1.nz || snapShot2.nx != snapShot1.nx || snapShot2.ext != snapShot1.ext || snapShot2.dt != snapShot1.dt || snapShot2.iflag != snapShot1.iflag
       error("the two snapShot are different")
    end
    snapShot1.isz = snapShot2.isz
    snapShot1.isx = snapShot2.isx
    snapShot1.it  = snapShot2.it
    snapShot1.vz[:] = snapShot2.vz[:]
    snapShot1.vx[:] = snapShot2.vx[:]
    snapShot1.pz[:] = snapShot2.pz[:]
    snapShot1.px[:] = snapShot2.px[:]
    return nothing
end

# =========================forward modeling ====================================
type Source
     iz :: Int64
     ix :: Int64
     dt :: Float64
     nt :: Int64
     p  :: Array{Float64,1}
end

function Ricker(f0::Float64, dt::Float64)
	  nw = 2.2/f0/dt
	  nw = 2*floor(Int,nw/2)+1
	  nc = floor(Int,nw/2)
	  w  = zeros(nw)
	  k  = collect(1:nw)
    k  = vec(k)
	  alpha = (nc-k+1)*f0*dt*pi
	  beta = alpha.^2
	  w = (1.-beta.*2).*exp(-beta)
	  return w
end

function InitSource(isz::Int64, isx::Int64, f0::Float64, dt::Float64)
    p = Ricker(f0, dt)
    nt= length(p)
    src = Source(isz, isx, dt, nt, p)
    return src
end

function AddSource!(spt::SnapShot, src::Source)
    dt = spt.dt
    if spt.dt != src.dt
       error("time sampling dismatch")
    end
    nz = spt.nz
    nx = spt.nx
    ext= spt.ext
    iflag = spt.iflag
    if iflag == 1
       zupper = ext
       Nz  = nz + 2*ext
    elseif iflag == 2
       zupper = 0
       Nz  = nz +   ext
    end
    Nx  = nx + 2*ext
    it  = spt.it
    if 0.0 <= (it-1)*dt <= (src.nt-1)*dt
       indt = it
       indz = src.iz + zupper
       indx = src.ix + ext
       pos = (indx-1) * Nz + indz
      #  spt.vz[pos] = spt.vz[pos] + src.p[indt]
       spt.pz[pos] = spt.pz[pos] + (src.p[indt])/2
       spt.px[pos] = spt.px[pos] + (src.p[indt])/2
    end
    return nothing
end

type ShotGather
     isz :: Int64
     isx :: Int64
     irz :: Array{Int64, 1}
     irx :: Array{Int64, 1}
     ot  :: Float64
     nt  :: Int64
     dt  :: Float64
     d   :: Array{Float64, 2}
end

function InitShotGather(isz::Int64, isx::Int64, pos::Array{Int64,2}, ot::Float64, nt::Int64, dt::Float64)
    nrec = size(pos, 1)
    irecz= pos[:, 1]
    irecx= pos[:, 2]
    d    = zeros(nt, nrec)
    shot = ShotGather(isz, isx, irecz, irecx, ot, nt, dt, d)
    return shot
end

function spt2shot!(shot::ShotGather, spt::SnapShot, Nz::Int64, Nx::Int64, ext::Int64, zupper::Int64)
    nrec = length(shot.irx)
    it = spt.it
    for irec = 1 : nrec
        iz = shot.irz[irec] + zupper
        ix = shot.irx[irec] + ext
        ind= (ix-1)*Nz + iz
        shot.d[it, irec] = spt.pz[ind] + spt.px[ind]
    end
    return nothing
end

function OneStepForward!(spt2::SnapShot, spt1::SnapShot, fidMtx::FidMtx)
    spt2.it  = spt1.it + 1
    spt2.vz = fidMtx.MvzBvz * spt1.vz + fidMtx.MvzBp  * (spt1.pz+spt1.px)
    spt2.vx = fidMtx.MvxBvx * spt1.vx + fidMtx.MvxBp  * (spt1.pz+spt1.px)
    spt2.pz = fidMtx.MpzBpz * spt1.pz + fidMtx.MpzBvz *  spt2.vz
    spt2.px = fidMtx.MpxBpx * spt1.px + fidMtx.MpxBvx *  spt2.vx
    return nothing
end

function MultiStepForward(pos::Array{Int64,2}, src::Source, fidMtx::FidMtx; tmax=2.0, interval=500, print_flag=false)
    nz = fidMtx.nz ; nx = fidMtx.nx;
    ext= fidMtx.ext; iflag = fidMtx.iflag;
    if iflag == 1
       zupper = ext
       Nz = nz + 2*ext
    elseif iflag == 2
       zupper = 0
       Nz = nz +   ext
    end
    Nx = nx + 2*ext
    dt = fidMtx.dt
    nt = round(Int64, tmax/dt)+1
    isz= src.iz; isx= src.ix;
    shot = InitShotGather(isz, isx, pos, 0.0, nt, dt)
    tl = 0.0; tu = tl + (src.nt-1)*dt;
    spt1 = InitSnapShot(src.iz, src.ix, nz, nx, ext, iflag, dt, 1)
    spt2 = InitSnapShot(src.iz, src.ix, nz, nx, ext, iflag, dt, 2)
    AddSource!(spt1, src)
    spt2shot!(shot, spt1, Nz, Nx, ext, zupper)
    for it = 2: nt
        OneStepForward!(spt2, spt1, fidMtx)
        if tl <= (it-1)*dt <= tu   #Add sources
           AddSource!(spt2, src)
        end
        copy(spt1, spt2)
        spt2shot!(shot, spt1, Nz, Nx, ext, zupper)
        if print_flag
           if mod(it, interval) == 0
              println("the current step: $it")
           end
        end
    end
    return shot
end

"""
    fidMtx = AcousticSetup(v, dz, dx, dt, f0, ext=20, iflag=1)

Setup sparse matrix for computing spatial derivation, return a composite type FidMtx

# Arguments
* `v   :: Array{Float64,2}`: P-wave velocity model
* `dz  :: Float64`: vertical grid size
* `dx  :: Float64`: horizontal grid size
* `dt  :: Float64`: size of time step
* `f0  :: Float64`: dominant frequency of Ricker wavelet

# keywords Arguments
* `ext=20` : number of PML absorbing boundry layers
* `iflag=1`: four sides(iflag=1) or three sides(iflag=2) absorbing boundries

# Output
* `fidMtx :: FidMtx`: composite type of sparse matrix
"""
function AcousticSetup(v::Array{Float64,2}, dz::Float64, dx::Float64, dt::Float64, f0::Float64; ext=30, iflag=1)
    (nz, nx) = size(v)
    vmax = maximum(v); vmin = minimum(v)
    fidMtx = InitFidMtx(nz, nx, ext, iflag, dz, dx, dt, vmax, vmin, f0, v)
    return fidMtx
end

"""
    shot = SeisAcousticWave(fidMtx, pos, isz, isx, f0, dt, tmax=2.0)

finite difference modeling of acoustic wave field, generate a common shot gather

# Arguments
* `fidMtx :: FidMtx`        : composite type of sparse matrix
* `pos    :: Array{Int64,2}`: index of receivers, first column is the vertical index, second column contains horizontal index.
* `isz    :: Int64`         : vertical index of source
* `isz    :: Int64`         : horizontal index of source
* `f0     :: Float64`       : dominant frequency of Ricker wavelet
* `dt     :: Float64`       : size of time step

# keywords Arguments
* `tmax=1.0` : length of simulation

# Output
* `shot :: ShotGather`: composite type for common shot gather
"""
function SeisAcousticWave(fidMtx::FidMtx, pos::Array{Int64,2}, isz::Int64, isx::Int64, f0::Float64, dt::Float64; tmax=1.0, print_flag=false, interval=100)
    src  = InitSource(isz, isx, f0, dt)
    shot = MultiStepForward(pos, src, fidMtx, tmax=tmax, print_flag=print_flag, interval=interval)
    return shot
end


module ABCDistances

using StatsBase: Weights, cov, sample, mean
using Distributions
using ProgressMeter
import Base.show, Base.copy

export
  ABCDistance, Lp, Euclidean, Logdist, WeightedEuclidean, MahalanobisEmp, ##ABCDistance types
  init, evaldist, ##ABCDistance methods
  ABCInput, RefTable, ABCOutput, ABCRejOutput, ABCPMCOutput, ##General ABC types
  sortABCOutput!, show, copy, parameter_means, parameter_vars, parameter_covs, ##General ABC methods
  abcRejection, abcPMC2, abcPMC3, abcPMC3V, abcPMC4, abcPMC5, ##ABC algorithms
  rgk, rgk_os, ##g&k methods
  Stoichiometry, gillespie_partial_sim, gillespie_sim ##Gillespie algorithm

include("distances.jl")
include("abc.jl")
include("abc_rejection.jl")
include("abc_pmc.jl")
include("abc_pmc_interface.jl")
include("gk.jl")
include("gillespie.jl")
end


function utotal(p, data :: Data)

	@unpack N, cutoff, side, l = data

	nt = Threads.nthreads()

	U = zeros(nt)

	npt = div_thread(N, nt)
	first_atom, next_atom = initial_linklist(p, data)

	Threads.@threads for id in 1:nt

		@inbounds @simd for i in npt[id, 1]:npt[id, 2]

			icell = trunc(Int64, p[i][1]/cutoff) + 1
			jcell = trunc(Int64, p[i][2]/cutoff) + 1

			for ic in icell-1:icell+1, jc in jcell-1:jcell+1

				iw, jw = wrap_cell(ic, jc, l)

				j = first_atom[iw, jw]

				while j > 0

					if j > i

						r = rpbc(p[i], p[j], side)

						if r < cutoff
							U[id] += upair(r, data)
						end
					end

					j = next_atom[j]
				end
			end
		end
	end

	return sum(U)
end


module ODEPrecompileTest
    using ModelingToolkit

    function system(; kwargs...)
        # Define some variables
        @parameters t σ ρ β
        @variables x(t) y(t) z(t)
        D = Differential(t)

        # Define a differential equation
        eqs = [D(x) ~ σ*(y-x),
            D(y) ~ x*(ρ-z)-y,
            D(z) ~ x*y - β*z]

        de = ODESystem(eqs)
        return ODEFunction(de, [x,y,z], [σ,ρ,β]; kwargs...)
    end

    # Build an ODEFunction as part of the module's precompilation. These cases
    # will not work, because the generated RGFs are put into the ModelingToolkit cache.
    const f_bad = system()
    const f_noeval_bad = system(; eval_expression=false)

    # Setting eval_expression=false and eval_module=[this module] will ensure
    # the RGFs are put into our own cache, initialised below.
    using RuntimeGeneratedFunctions
    RuntimeGeneratedFunctions.init(@__MODULE__)
    const f_noeval_good = system(; eval_expression=false, eval_module=@__MODULE__)
end


## Definition of a phase-type distribution, and related functions.
using Distributions
using Statistics: cor, cov, median, std, quantile

import Base: minimum, maximum
import Distributions: cdf, insupport, logpdf, pdf
import Statistics: mean

macro check_args(D, cond)
    quote
        if !($(esc(cond)))
            throw(ArgumentError(string(
                $(string(D)), ": the condition ", $(string(cond)),
                " is not satisfied.")))
        end
    end
end

struct PhaseType <: ContinuousUnivariateDistribution
    # Defining properties
    π # entry probabilities
    T # transition probabilities

    # Derived properties
    t # exit probabilities
    p # number of phases

    function PhaseType(π, T, t, p)
        @check_args(PhaseType, all(π .>= zero(π[1])))
        @check_args(PhaseType, isapprox(sum(π), 1.0, atol=1e-4))
        @check_args(PhaseType, p == length(π) && p == length(t) &&
            all(p .== size(T)))
        @check_args(PhaseType, all(t .>= zero(t[1])))
        @check_args(PhaseType, all(isapprox(t, -T*ones(p))))
        new(π, T, t, p)
    end
end

PhaseType(π, T) = PhaseType(π, T, -T*ones(length(π)), length(π))
PhaseType(π, T, t) = PhaseType(π, T, t, length(π))

minimum(d::PhaseType) = 0
maximum(d::PhaseType) = Inf
insupport(d::PhaseType, x::Real) = x > 0 && x < Inf

pdf(d::PhaseType, x::Real) = transpose(d.π) * exp(d.T * x) * d.t
logpdf(d::PhaseType, x::Real) = log(pdf(d, x))
cdf(d::PhaseType, x::Real) = 1 - transpose(d.π) * exp(d.T * x) * ones(d.p)

mean(d::PhaseType) = -transpose(d.π) * inv(d.T) * ones(d.p)

iscoxian(d::PhaseType) = all(isapprox.(d.T[diagm(0 => ones(d.p),
    1 => ones(d.p-1)) .< 1], 0))

function reverse_coxian(π::AbstractArray{Float64}, T::AbstractArray{Float64},
        t::AbstractArray{Float64})
    # Get the time-reversed distribution
    Δ_v = Diagonal((-π' * inv(T))')
    πStar = (t' * Δ_v)'
    TStar = inv(Δ_v) * T' * Δ_v

    # Relabel the states from 1,..,p as p,...,1
    πCanon = reverse(πStar)
    λ = -reverse(diag(TStar))
    TCanon = diagm(0 => -λ, 1 => λ[1:end-1])
    (πCanon, TCanon, -TCanon*ones(length(π)))
end

function reverse_coxian(dist::PhaseType)
    PhaseType(reverse_coxian(dist.π, dist.T, dist.t) ...)
end

function sort_into_canonical_form(π::AbstractArray{Float64},
        T::AbstractArray{Float64}, t::AbstractArray{Float64})
    p = length(π)
    λ = -diag(T)
    π = π[:]

    while true
        swapped = false
        for i = 1:(p-1)
            if λ[i] > λ[i+1]
                w = λ[i+1]/λ[i]
                π[i] += (1-w)*π[i+1]
                π[i+1] *= w
                λ[i:i+1] = λ[i+1:-1:i]
                swapped = true
            end
        end

        if swapped == false
            break
        end
    end

    TSort = diagm(0 => -λ, 1 => λ[1:end-1])
    (π, TSort, -TSort*ones(p))
end

function sort_into_canonical_form(dist::PhaseType)
    PhaseType(sort_into_canonical_form(dist.π, dist.T, dist.t) ...)
end


#------------------------------------------------------------------------------#
#                                    Module                                    #
#------------------------------------------------------------------------------#

"""
# Description

    module aaHTC

AbstractAlgebra backend (aa) module to the Hermite Polynomial Tensor package.

# Usage

This module is `include`d by the `HermitePolyTensor` package and is not  usually
used in isolation, or directly; rather through the  interfaces  defined  on  the
`HPT` submodule, which is loaded automatically by the package:

```julia-repl
julia> using HermitePolyTensor

julia> typeof(HPT.aaHTC)
Module

```
"""
module aaHTC

#------------------------------------------------------------------------------#
#                                   Imports                                    #
#------------------------------------------------------------------------------#

import AbstractAlgebra

#------------------------------------------------------------------------------#
#                                   Exports                                    #
#------------------------------------------------------------------------------#

# Auxiliary Functions
export SVHP

# Tensor Component Functions
export HTC

#------------------------------------------------------------------------------#
#                             Auxiliary Functions                              #
#------------------------------------------------------------------------------#

"""
# Description

    SVHP(
        n::Int; ax::Int = 1, st::Bool = true
    )::AbstractAlgebra.Generic.MPoly{Rational{BigInt}}

Returns a symbolic `n`-th order single-variable Hermite polynomial in  the  axis
`ax`,  axis  1,  2,  and  3  corresponding  to  the  x,  y,  and  z  directions,
respectively. A "probabilist's" polynomial is returned if `st`  is  `true`  (the
default), otherwise, a "physicist's".

This function is not used directly; rather through the interfaces defined on the
`HPT` submodule, which is loaded automatically by the package, by specifying the
AbstractAlgebra ("aa") backend:

# Usage

```julia-repl
julia> using HermitePolyTensor

julia> HPT.SVHP(3, be = "aa")
x^3-3//1*x

julia> HPT.SVHP(4, ax = 2, be = "aa")
y^4-6//1*y^2+3//1

julia> HPT.aaHTC.SVHP(5, ax = 3, st = false) # Direct access
32//1*z^5-160//1*z^3+120//1*z

```
"""
function SVHP(
    n::Int; ax::Int = 1, st::Bool = true
)::AbstractAlgebra.Generic.MPoly{Rational{BigInt}}
    # Validations
    if n < 0
        throw(DomainError("n = $n outside the valid domain [0, ∞)"))
    elseif ax <= 0 || ax >= 4
        throw(DomainError("ax = $ax outside the valid domain [1, 3]"))
    end
    # Execution
    R, (x, y, z) = AbstractAlgebra.PolynomialRing(
        AbstractAlgebra.QQ, ["x", "y", "z"]
    )
    v = ax > 1 ? ax > 2 ? z : y : x
    po = 0 * v
    if st
        for m in 0:Int(floor(n/2))
            ex = n - 2m
            nm = factorial(big(n)) * (-1)^m
            dn = factorial(big(m)) * factorial(big(ex)) * 2^m
            po += nm//dn * v^ex
        end
    else
        for m in 0:Int(floor(n/2))
            ex = n - 2m
            nm = factorial(big(n)) * (-1)^m
            dn = factorial(big(m)) * factorial(big(ex))
            po += nm//dn * (2v)^ex
        end
    end
    po
end

#------------------------------------------------------------------------------#
#                          Tensor Component Functions                          #
#------------------------------------------------------------------------------#

"""
# Description

    HTC(
        id::String; D::Int = 2, st::Bool = true
    )::AbstractAlgebra.Generic.MPoly{Rational{BigInt}}

Returns a single symbolic *component* of a Hermite Polynomial Tensor.

The indices are specified by `id`, a `String` containing x's, y's,  and/or  z's;
invalid characters on `id` are ignored, while `id`  without  x's,  y's,  or  z's
represent a zero-rank  tensor  (whose  only  component  is  `1`  for  all  space
dimensions).

The Euclidean space dimensionality is specified  by  `D`.  The  tensor  rank  is
implied by the provided `id`, and is always the total  count  of  *valid*  index
characters, considering `D`.

A "probabilist's" polynomial is  returned  if  `st`  is  `true`  (the  default),
otherwise, a "physicist's".

# Usage

This function is not used directly; rather through the interfaces defined on the
`HPT` submodule, which is loaded automatically by the package, by specifying the
AbstractAlgebra ("aa") backend:

```julia-repl
julia> using HermitePolyTensor

julia> HPT.aaHTC.HTC("xxy")
x^2*y-y

```
"""
function HTC(
    id::String; D::Int = 2, st::Bool = true
)::AbstractAlgebra.Generic.MPoly{Rational{BigInt}}
    # Validations
    if D <= 0 || D >= 4
        throw(DomainError("dimension D = $D is outside the valid domain [1, 3]"))
    end
    # Execution
    xc = count(i -> (i == 'x'), id)
    yc = D > 1 ? count(i -> (i == 'y'), id) : 0
    zc = D > 2 ? count(i -> (i == 'z'), id) : 0
    ret = SVHP(xc, ax = 1, st = st)
    if yc != 0
        ret *= SVHP(yc, ax = 2, st = st)
    end
    if zc != 0
        ret *= SVHP(zc, ax = 3, st = st)
    end
    return ret
end

end # module


using LsqFit
using Optim
using DataFrames
import StatsBase.fitted

include("dynamics.jl")

struct SIRX <: Dynamics
    α::Float64
    β::Float64
    κ::Float64
    κ0::Float64
end

# default constructor
SIRX() = SIRX(1.0,1.0,0.0,0.0)

# another constructor
function getParams(κ::Float64,κ0::Float64,
    R0Free::Float64,TInfected::Float64,d::Dynamics)
    β = 1.0/TInfected
    α = R0Free*β
    return SIRX(α,β,κ,κ0)
end

# number of states
function nstates(d::SIRX)
    return 4
end

# names of states
function stateNames(d::SIRX)
    return ["S" "I" "R" "X"]
end

function R0Eff(d::SIRX)
    return d.α/(d.β+d.κ+d.κ0)
end

# change in a day
"""
change(s::Vector{Float64},d::SIRX)

Return the change in the state of the population in a day

s = state vector (S,I,R,X)
d = SIRX dynamics parameters
"""
function change(s::Vector{Float64},d::SIRX)
    # this is done purely for readability of the formula
    st = NamedTuple{(:S,:I,:R,:X)}(s)
    S = -( d.α * st.I + d.κ0) * st.S
    I = d.α*st.I*st.S - d.β*st.I - d.κ0*st.I - d.κ*st.I
    R = d.β*st.I + d.κ0*st.S
    X = (d.κ+d.κ0) * st.I
    return [S,I,R,X]
end

# initializer for states
function initialize(N::Float64,C0::Float64,IXRatio::Float64,d::SIRX)
    state = zeros(nstates(d))
    state[4] = C0/N # X0, page 1, Supplement
    state[2] = IXRatio*(C0/N) # I0, page 1, supplement
    state[1] = 1.0-state[2]-state[4] # S0
    return state
end

"""
caseModel(nt::Int64,N::Float64,C0::Float64,IXRatio::Float64,d::SIRX)

Returns vector of cases given model dynamics and population parameters.
"""
function caseModel(nt::Int64,N::Float64,C0::Float64,IXRatio::Float64,
    d::SIRX)
    s0 = initialize(N,C0,IXRatio,d)
    (d,ds) =evolve(N,s0,d,nt)
    return N.*d[4,:]
end

function estimatedStates(nt::Int64,N::Float64,C0::Float64,
    IXRatio::Float64,d::SIRX)
    s0 = initialize(N,C0,IXRatio,d)
    (d,ds) =evolve(N,s0,d,nt)
    s1 = DataFrame(N.*d',[:S,:I,:R,:X])
    return s1
end

function logCaseModelUnknown(nt::Int64,p::Vector{Float64},
    N::Float64,C0::Float64,R0Free::Float64,TInfected::Float64)
    d = getParams(p[1],p[2],R0Free,TInfected,SIRX())
    return log.(caseModel(nt,N,C0,p[3],d))
end

function sqrtCaseModelUnknown(nt::Int64,p::Vector{Float64},
    N::Float64,C0::Float64,R0Free::Float64,TInfected::Float64)
    d = getParams(p[1],p[2],R0Free,TInfected,SIRX())
    return sqrt.(caseModel(nt,N,C0,p[3],d))
end


# For the system of ODEs du/dx = df(x,u), take one step with eighth order
# (ninth order locally) Runge Kutta.  y contains initial values at the
# start point x=t.  The final values at x=t+h are given in ys.  work
# is scratch space of size 7n, where n is the dimension of y and ys.
# Method of G. J. Cooper and J. H. Verner, SIAM Journal of Numerical
# Analysis 9, pp. 389-405 (1972).
function rk8!(df, t, h, y, ys, work)
    n = length(y)
    rk8aux(df, t, h, y, ys, view(work,1:n),
           view(work,  n+1:2*n), view(work,2*n+1:3*n), view(work,3*n+1:4*n),
           view(work,4*n+1:5*n), view(work,5*n+1:6*n), view(work,6*n+1:7*n))
end
function rk8aux(df, t, h, y, ys, yp, f1,f2,f3,f4,f5,f6)
    n = length(y)
    df(t, y, yp)
    for k = 1:n
        ys[k] = y[k] + h * (1/2) * yp[k]
    end
    df(t+h*(1/2), ys, f1)
    for k = 1:n
        ys[k] = y[k] + h * (1/4) * (yp[k] + f1[k])
    end
    df(t+h*(1/2), ys, f2)
    for k = 1:n
        ys[k] = y[k] + h * ((1/7) * yp[k] +
                            (-7+3*sqrt(21))/98 * f1[k] +
                            (21-5*sqrt(21))/49 * f2[k] )
    end
    df(t+h*(7-sqrt(21))/14, ys, f1)
    for k = 1:n
        ys[k] = y[k] + h * ((11-sqrt(21))/84 * yp[k] +
                            (18-4*sqrt(21))/63 * f2[k] +
                            (21+sqrt(21))/252 * f1[k])
    end
    df(t+h*(7-sqrt(21))/14, ys, f3)
    for k = 1:n
        ys[k] = y[k] + h * ((5-sqrt(21))/48 * yp[k] +
                            (9-sqrt(21))/36 * f2[k] +
                            (-231-14*sqrt(21))/360 * f1[k] +
                            (63+7*sqrt(21))/80 * f3[k])
    end
    df(t+h*(1/2), ys, f4)
    for k = 1:n
        ys[k] = y[k] + h * ((10+sqrt(21))/42 * yp[k] +
                            (-432-92*sqrt(21))/315 * f2[k] +
                            (633+145*sqrt(21))/90 * f1[k] +
                            (-504-115*sqrt(21))/70 * f3[k] +
                            (63+13*sqrt(21))/35 * f4[k])
    end
    df(t+h*(7+sqrt(21))/14, ys, f2)
    for k = 1:n
        ys[k] = y[k] + h * ((1/14) * yp[k] +
                            (14+3*sqrt(21))/126 * f3[k] +
                            (13+3*sqrt(21))/63 * f4[k] +
                            (1/9) * f2[k])
    end
    df(t+h*(7+sqrt(21))/14, ys, f1)
    for k = 1:n
        ys[k] = y[k] + h * ((1/32) * yp[k] +
                            (91+21*sqrt(21))/576 * f3[k] +
                            (11/72) * f4[k] +
                            (-385+75*sqrt(21))/1152 * f2[k] +
                            (63-13*sqrt(21))/128 * f1[k])
    end
    df(t+h*(1/2), ys, f5)
    for k = 1:n
        ys[k] = y[k] + h * ((1/14) * yp[k] +
                            (1/9) * f3[k] +
                            (-733+147*sqrt(21))/2205 * f4[k] +
                            (515-111*sqrt(21))/504 * f2[k] +
                            (-51+11*sqrt(21))/56 * f1[k] +
                            (132-28*sqrt(21))/245 * f5[k])
    end
    df(t+h*(7-sqrt(21))/14, ys, f6)
    for k = 1:n
        ys[k] = y[k] + h * ((-42-7*sqrt(21))/18 * f3[k] +
                            (-18-28*sqrt(21))/45 * f4[k] +
                            (-273+53*sqrt(21))/72 * f2[k] +
                            (301-53*sqrt(21))/72 * f1[k] +
                            (28+28*sqrt(21))/45 * f5[k] +
                            (49+7*sqrt(21))/18 * f6[k])
    end
    df(t+h, ys, f2)
    # approximate solution at t+h
    for k = 1:n
        ys[k] = y[k] + h * ((1/20) * yp[k] +
                            (49/180) * f1[k] +
                            (16/45) * f5[k] +
                            (49/180) * f6[k] +
                            (1/20) * f2[k])
    end
end


function ga_v2(;
        model::Model,
        nth_param_set::Int64,
        max_generation::Int64,
        n_population::Int64,
        n_gene::Int64,
        initial_threshold::Float64,
        allowable_error::Float64,
        local_search_method::String,
        n_children::Int64,
        maxiter::Int64)::Nothing
    """
        1. Initialization
            As an initial population, create np individuals randomly.
            ga_v2 also represents individuals as n-dimensional real number
            vectors, where n is the dimension of the search space. Set
            Generation to 0, and set the iteration number of converging
            operations Niter to 1.
        2. Selection for reproduction
            As parents for the recombination operator, ENDX, select m
            individuals, p1, p2, . . . ,pm, without replacement from the
            population.
        3. Generation of offsprings
            Generate Nc children by applying ENDX to the selected parents.
            This algorithm assigns the worst objective value to the children.
        4. Local Search
            Apply the local search method to the best individual in a family
            consisting of the two parents, i.e., p1 and p2, and their children.
            Note here that the children are assumed to have the worst objective
            value. Thus, whenever the objective values of the two parents have
            been actually computed in previous generations, the algorithm
            applies the local search to either of the parents. When all of the
            individuals in the family have the same objective value, on the
            other hand, the local search is applied to a randomly selected
            individual from the family.
        5. Selection for survival
            Select two individuals from the family. The first selected
            individual should be the individual with the best objective value,
            and the second should be selected randomly. Then, replace the two
            parents (p1 and p2) with the selected individuals. Note that the
            individual to which the local search has been applied in the
            previous step is always selected as the best.
        6. Application of ENDX/MGG
            To achieve a good search performance, ga_v2 optimizes a function,
            gradually narrowing the search space. For this purpose, the
            converging phase slightly converges the population by repeating the
            following procedure Niter times.
            (i) Select m individuals without replacement from the population.
                The selected individuals, expressed here as p1, p2, . . . , pm,
                are used as the parents for an extended normal distribution
                crossover (ENDX) applied in the next step.
            (ii) Generate Nc children by applying ENDX to the parents selected
                in the previous step. To reduce the computational cost, ga_v2
                forgoes any computation of the objective values of the Nc
                individuals generated here. Instead, the algorithm assigns the
                newly generated children a single objective value, one which is
                inferior to the objective values of any of the possible
                candidate solutions.
            (iii) Select two individuals from a family containing the two
                parents, i.e., p1 and p2, and their children. The first
                selected individual should be the one with the best objective
                value, and the second should be selected randomly. Then,
                replace the two parents with the selected individuals.
        7. Adaptation of Niter
            If the best individual has not improved during the last np
            generations, Niter <- 2 * Niter. Otherwise, set Niter to 1.
        8. Termination
            Stop if the halting criteria are satisfied.
            Otherwise, Generation <- Generation + 1, and return to the step 2.
    """
    if n_population < n_gene + 2
        error("n_population must be larger than $(n_gene + 2)")
    end

    n_iter::Int64 = 1
    N0::Vector{Float64} = zeros(3 * n_population)

    population::Matrix{Float64} = get_initial_population(
        model, nth_param_set, n_population, n_gene, initial_threshold
    )
    N0[1] = population[1, end]
    open(
        joinpath(
            model.path,
            "logs",
            "$nth_param_set.log"
        ), "a"
    ) do f
        write(f,
            @sprintf(
                "Generation%d: Best Fitness = %.6e\n",
                1, population[1, end]
            )
        )
    end
    best_indiv::Vector{Float64} = model.gene2val(population[1, 1:n_gene])
    best_fitness::Float64 = population[1, end]

    open(
        joinpath(
            model.path,
            "fitparam",
            "$nth_param_set",
            "fit_param1.dat"
        ), "w"
    ) do f
        for val in best_indiv
            write(f, @sprintf("%.6e\n", val))
        end
    end
    open(
        joinpath(
            model.path,
            "fitparam",
            "$nth_param_set",
            "generation.dat"
        ), "w"
    ) do f
        write(f, @sprintf("%d", 1))
    end
    open(
        joinpath(
            model.path,
            "fitparam",
            "$nth_param_set",
            "best_fitness.dat"
        ), "w"
    ) do f
        write(f, @sprintf("%.6e", best_fitness))
    end

    if population[1, end] <= allowable_error
        return
    end

    generation::Int64 = 2
    while generation <= max_generation
        ip::Vector{Int} = sample(
            collect(1:n_population), n_gene + 2, replace=false
        )
        population .= converging!(
            model.obj_func, ip, population, n_population, n_gene
        )
        population .= local_search!(
            model.obj_func, ip, population, n_population, n_gene,
            method=local_search_method, n_children=n_children, maxiter=maxiter
        )
        if n_iter > 1
            for _ in 1:n_iter
                ip .= sample(
                    collect(1:n_population), n_gene + 2, replace=false
                )
                population .= converging!(
                    model.obj_func, ip, population, n_population, n_gene
                )
            end
        end

        if generation % length(N0) == 0
            N0[end] = population[1, end]
            if N0[1] == N0[end]
                n_iter *= 2
            else
                n_iter = 1
            end
        else
            N0[generation % length(N0)] = population[1, end]
        end
        open(
            joinpath(
                model.path,
                "logs",
                "$nth_param_set.log"
            ), "a"
        ) do f
            write(f,
                @sprintf(
                    "Generation%d: Best Fitness = %.6e\n",
                    generation, population[1, end]
                )
            )
        end
        best_indiv .= model.gene2val(population[1, 1:n_gene])
        if population[1, end] < best_fitness
            open(
                joinpath(
                    model.path,
                    "fitparam",
                    "$nth_param_set",
                    "fit_param$generation.dat"
                ), "w"
            ) do f
                for val in best_indiv
                    write(f, @sprintf("%.6e\n", val))
                end
            end
            open(
                joinpath(
                    model.path,
                    "fitparam",
                    "$nth_param_set",
                    "generation.dat"
                ), "w"
            ) do f
                write(f, @sprintf("%d", generation))
            end
        end
        best_fitness = population[1, end]

        open(
            joinpath(
                model.path,
                "fitparam",
                "$nth_param_set",
                "best_fitness.dat"
            ), "w"
        ) do f
            write(f, @sprintf("%.6e", best_fitness))
        end

        open(
            joinpath(
                model.path,
                "fitparam",
                "$nth_param_set",
                "count_num.dat"
            ), "w"
        ) do f
            write(f, @sprintf("%d", generation))
        end

        if population[1, end] <= allowable_error
            break
        end

        generation += 1
    end

    return
end

function ga_v2_continue(;
        model::Model,
        nth_param_set::Int64,
        max_generation::Int64,
        n_population::Int64,
        n_gene::Int64,
        initial_threshold::Float64=1e12,
        allowable_error::Float64,
        local_search_method::String,
        n_children::Int64,
        maxiter::Int64,
        p0_bounds::Vector{Float64})::Nothing
    if n_population < n_gene + 2
        error("n_population must be larger than $(n_gene + 2)")
    end

    n_iter::Int64 = 1
    N0::Vector{Float64} = zeros(3 * n_population)

    count::Int64 = readdlm(
        joinpath(
            model.path,
            "fitparam",
            "$nth_param_set",
            "count_num.dat"
        )
    )[1, 1]
    best_generation::Int64 = readdlm(
        joinpath(
            model.path,
            "fitparam",
            "$nth_param_set",
            "generation.dat"
        )
    )[1, 1]
    best_indiv::Vector{Float64} = readdlm(
        joinpath(
            model.path,
            "fitparam",
            "$nth_param_set",
            "fit_param$best_generation.dat"
        )
    )[:, 1]
    best_indiv_gene::Vector{Float64} = model.val2gene(best_indiv)
    best_fitness::Float64 = model.obj_func(best_indiv_gene)

    population::Matrix{Float64} = get_initial_population_continue(
        model, nth_param_set, n_population, n_gene, initial_threshold, p0_bounds
    )
    if best_fitness < population[1, end]
        for i in 1:n_gene
            @inbounds population[1, i] = best_indiv_gene[i]
        end
        population[1, end] = best_fitness
    else
        best_indiv = model.gene2val(population[1, 1:n_gene])
        best_fitness = population[1, end]
        open(
            joinpath(
                model.path,
                "fitparam",
                "$nth_param_set",
                "fit_param$count.dat"
            ), "w"
        ) do f
            for i = 1:n_gene
                write(f, @sprintf("%.6e", best_indiv[i]))
            end
        end
    end

    N0[1] = population[1, end]
    open(
        joinpath(
            model.path,
            "logs",
            "$nth_param_set.log"
        ), "a"
    ) do f
        write(f,
            @sprintf(
                "Generation%d: Best Fitness = %.6e\n",
                count + 1, population[1, end]
            )
        )
    end

    if population[1, end] <= allowable_error
        return
    end

    generation::Int64 = 2 + count
    while generation <= max_generation
        ip::Vector{Int} = sample(
            collect(1:n_population), n_gene + 2, replace=false
        )
        population .= converging!(
            model.obj_func, ip, population, n_population, n_gene
        )
        population .= local_search!(
            model.obj_func, ip, population, n_population, n_gene,
            method=local_search_method, n_children=n_children, maxiter=maxiter
        )
        if n_iter > 1
            for _ in 1:n_iter
                ip .= sample(
                    collect(1:n_population), n_gene + 2, replace=false
                )
                population .= converging!(
                    model.obj_func, ip, population, n_population, n_gene
                )
            end
        end

        if generation % length(N0) == 0
            N0[end] = population[1, end]
            if N0[1] == N0[end]
                n_iter *= 2
            else
                n_iter = 1
            end
        else
            N0[generation % length(N0)] = population[1, end]
        end
        open(
            joinpath(
                model.path,
                "logs",
                "$nth_param_set.log"
            ), "a"
        ) do f
            write(f,
                @sprintf(
                    "Generation%d: Best Fitness = %.6e\n",
                    generation, population[1, end]
                )
            )
        end
        best_indiv .= model.gene2val(population[1, 1:n_gene])
        if population[1, end] < best_fitness
            open(
                joinpath(
                    model.path,
                    "fitparam",
                    "$nth_param_set",
                    "fit_param$generation.dat"
                ), "w"
            ) do f
                for val in best_indiv
                    write(f, @sprintf("%.6e\n", val))
                end
            end
            open(
                joinpath(
                    model.path,
                    "fitparam",
                    "$nth_param_set",
                    "generation.dat"
                ), "w"
            ) do f
                write(f, @sprintf("%d", generation))
            end
        end
        best_fitness = population[1, end]

        open(
            joinpath(
                model.path,
                "fitparam",
                "$nth_param_set",
                "best_fitness.dat"
            ), "w"
        ) do f
            write(f, @sprintf("%.6e", best_fitness))
        end

        open(
            joinpath(
                model.path,
                "fitparam",
                "$nth_param_set",
                "count_num.dat"
            ), "w"
        ) do f
            write(f, @sprintf("%d", generation))
        end

        if population[1, end] <= allowable_error
            break
        end

        generation += 1
    end

    return
end


using LinearAlgebra
using LinearMaps
using MAT
using SparseArrays
using Arpack

using PyCall, SparseArrays

function scipyCSC_to_julia(A)
    m, n = A.shape
    colPtr = Int[i+1 for i in PyArray(A."indptr")]
    rowVal = Int[i+1 for i in PyArray(A."indices")]
    nzVal = Vector{Float64}(PyArray(A."data"))
    B = SparseMatrixCSC{Float64,Int}(m, n, colPtr, rowVal, nzVal)
    return PyCall.pyjlwrap_new(B)
end

function read_arxiv(file::String)
    I = Int64[]
    J = Int64[]
    open(file) do f
        for line in eachline(f)
            if line[1] == '#'; continue; end
            data = split(line, ",")
            push!(I, parse(Int64, data[1]))
            push!(J, parse(Int64, data[2]))
        end
    end
    I .+= 1
    J .+= 1
    n = max(maximum(I), maximum(J))
    A = sparse(I, J, 1, n, n)
    A = max.(A, A')
    A = min.(A, 1)
    return A
end

function main(PyA, k::Int64)
    m, n = PyA.shape
    colPtr = Int[i+1 for i in PyArray(PyA."indptr")]
    rowVal = Int[i+1 for i in PyArray(PyA."indices")]
    nzVal = Vector{Float64}(PyArray(PyA."data"))
    A = SparseMatrixCSC{Float64,Int}(m, n, colPtr, rowVal, nzVal)
    d = vec(sum(A, dims=2))
    τ = sum(d) / length(d)
    N = size(A)[1]

    # normalized regularized laplacian
    D = Diagonal(1.0 ./ sqrt.(d .+ τ))
    Aop = LinearMap{Float64}(X -> A * X .+ (τ / N) * sum(X), N, N, isposdef=true, issymmetric=true)
    NRL = I + D * Aop * D

    (Λ, V) = eigs(NRL, nev=k, tol=1e-6, ncv=2*k+1, which=:LM)

    # axis rotation (not necessary, but could be helpful)
    piv = qr(V', Val(true)).jpvt[1:k]
    piv_svd = svd(V[piv,:]', full=false)
    SCDM_V = V * (piv_svd.U * piv_svd.Vt)

    # save

    return SCDM_V
end

#A = read_arxiv(ARGS[1])
#embed = main(A, 128)
#matwrite("$(ARGS[2])_spectral_embedding.mat", Dict("V" => embed), compress=true)


mutable struct BD_CovMatrix{T<:AbstractFloat, K <: MatTypes} <: CovMatrix
    C::K
    B::K
    D::Vector{T}
    c_cov::T
end

function BD_CovMatrix(::Type{K}, n, μ_w) where K <: MatTypes

    c_cov = 1/μ_w * 2/((n+1.3)^2) + (1-1/μ_w)*min(1,2*(μ_w-1)/((n+2)^2 +μ_w))
#    c_cov *= 2
    C,B = init_matrix(K,n), init_matrix(K,n)
    D = ones(n)

    BD_CovMatrix(C,B,D,c_cov)
end

cov_matrix(cov_mat::BD_CovMatrix) = cov_mat.C
eigvals(cov_mat::BD_CovMatrix) = cov_mat.D

function update_p_σ(::Type{BD_CovMatrix{T,K}},c_σ, p_σ, μ_w, B, ẑ) where {T,K}
    (1-c_σ)*p_σ  + √(c_σ*(2-c_σ)*μ_w)*B*ẑ
end

function update_p_c(::Type{BD_CovMatrix{T,K}},c_c, p_c, H_σ, c_σ, μ_w, BD, ẑ) where {T,K}
    (1-c_c)*p_c + H_σ*√(c_σ*(2 - c_σ)*μ_w)*BD*ẑ
end

function update_C(::Type{BD_CovMatrix{T,K}}, C::K, c_cov, μ_w, p_c, w, BD, z, μ) where {T,K}
    C = (1-c_cov) * C +
        c_cov * 1/μ_w * ⊗(K,p_c) +
        c_cov * (1-1/μ_w) * ∑( w[i]* ⊗(K,BD*z[i]) for i=1:μ)
    C
end

function update_state!(d, state::CMAState{T,CM}, method::CMA) where {T,CM <: BD_CovMatrix}

    @unpack w,n,μ_w,c_σ,d_σ,c_c,p_σ,p_c,cov_mat,m,chi_D,σ,t,xs = state
    @unpack C,B,D,c_cov = cov_mat

    state.f_x_previous = state.f_x
    copy!(state.x_previous, state.x)

    λ = method.λ
    μ = floor(Int,λ/2)

    BD = B*Diagonal(D)

    z = [randn(n) for i=1:λ]
    x = [m + σ*BD*z[i] for i=1:λ]

    fx = eval_objfun(T, λ, d, x)

    idx = sortperm(fx)
    x,z,fx = x[idx], z[idx], fx[idx]

    m = ∑( w[i] * x[i] for i=1:μ )
    ẑ = ∑( w[i] * z[i] for i=1:μ )

    p_σ = update_p_σ(CM, c_σ, p_σ, μ_w, B, ẑ)
    H_σ = update_H_σ(CM, p_σ, c_σ, t, n)
    p_c = update_p_c(CM, c_c, p_c, H_σ, c_σ, μ_w, BD, ẑ)
    C = update_C(CM, C, c_cov, μ_w, p_c, w, BD, z, μ)

    σ = update_σ(CM, σ, c_σ, d_σ, p_σ, chi_D)

    D,B = eigen(C)
    B = typeof(C) <: Diagonal ? Diagonal(B) : B

    D = sqrt.(abs.(D)) #eigenvalues can become negative because of numerical errors

    t += 1
    state.x = x[1]
    state.f_x = fx[1]
    xs = x

    @pack! cov_mat = C,B,D,c_cov
    @pack! state = w,n,μ_w,c_σ,d_σ,c_c,p_σ,p_c,cov_mat,m,chi_D,σ,t,xs

    false # should the procedure force quit?
end


#以一个部件在所有时层的状况为一个子图，约束主要是子图间的空间关系
#也就是部件与部件的关系
using Plasmo, Ipopt
using PlasmoTest

G = OptiGraph()         #建立主图
O = Ipopt.Optimizer     #求解器为Ipopt

sub1 = OptiGraph()      #建立子图
sub2 = OptiGraph()
add_subgraph!(G,sub1)   #把子图加到主图中
add_subgraph!(G,sub2)

@optinode(sub1,ng1[1:3])    #子图有1-3三个结点
@optinode(sub2,ng2[1:3])

for node in getnodes(sub1)      #批量添加结点，这些结点内部数学表达是相同的
    @variable(node,0 <= x <= 2)
    @variable(node,2 <= y <= 4)
    @constraint(node,x + y >= 5)
end

for node in getnodes(sub2)
    @variable(node,x)
    @NLnodeconstraint(node,exp(x) >= 2)
    @variable(node,y >= 0)
    @constraint(node,x + y >= 3)
end

#添加约束，“ng?[x]”表示 “第?个子图的第x个结点”
@linkconstraint(G,ng1[1][:x]==ng2[1][:y])
@linkconstraint(G,ng2[1][:x]==ng1[2][:y]+1)
@linkconstraint(G,ng2[1][:y]==ng2[2][:y])
@linkconstraint(G,ng2[2][:x]==ng1[3][:y])
@linkconstraint(G,ng2[2][:y]==ng2[3][:y])

#添加目标函数
@objective(G, Min, ng2[1][:x] + ng2[2][:x] + ng2[3][:x])
optimize!(G,O)          #G和O都在最开始有定义
println("Objective = ", digit5(objective_value(G)))

#把图的关系保存成图片
#plt_G = plt_G = Plots.plot(G,node_labels = true, markersize=60, labelsize=30, linewidth=4, layout_options = Dict(:tol => 0.01, :iterations => 2))
#savefig("006.png")


function monomial1d(x::Array{Float64,2},p::Int)::Tuple{Array{Float64,2},Array{Float64,2}}

n = length(x);
v0 = zeros(Float64,n,1);
v1 = ones(Float64,n,1);

if (p==0)
    f = v1;
    fx = v0;
elseif (p==1)
    f = [v1 x];
    fx = [v0 v1];
elseif (p==2)
    f = [v1 x x.^2];
    fx = [v0 v1 2*x];
elseif (p==3)
    f = [v1 x x.^2 x.^3];
    fx = [v0 v1 2*x 3*x.^2];
elseif (p==4)
    f = [v1 x x.^2 x.^3 x.^4];
    fx = [v0 v1 2*x 3*x.^2 4*x.^3];
else
    error("polynomial degree must be less than 5");
end

return f, fx;

end

function monomial2d(xy::Array{Float64,2},p::Int)::Tuple{Array{Float64,2},Array{Float64,2},Array{Float64,2}}

n = size(xy,1);
v0 = zeros(Float64,n,1);
v1 = ones(Float64,n,1);

x = xy[:,1];
y = xy[:,2];

if (p==0)
    f = v1;
    fx = v0;
    fy = v0;
elseif (p==1)
    f = [v1 x y];
    fx = [v0 v1 v0];
    fy = [v0 v0 v1];
elseif (p==2)
    f = [v1 x y x.*y x.^2 y.^2];
    fx = [v0 v1 v0 y 2*x v0];
    fy = [v0 v0 v1 x v0 2*y];
elseif (p==3)
    f = [v1 x y x.*y x.^2 y.^2 x.*y.^2 (x.^2).*y x.^3 y.^3];
    fx = [v0 v1 v0 y 2*x v0 y.^2 2*x.*y 3*x.^2 v0];
    fy = [v0 v0 v1 x v0 2*y 2*x.*y x.^2 v0 3*y.^2];
elseif (p==4)
    f = [v1 x y x.*y x.^2 y.^2 x.*y.^2 (x.^2).*y x.^3 y.^3 x.*y.^3 (x.^2).*y.^2 (x.^3).*y x.^4 y.^4];
    fx = [v0 v1 v0 y 2*x v0 y.^2 2*x.*y 3*x.^2 v0 y.^3 2*x.*y.^2 3*(x.^2).*y 4*x.^3 v0];
    fy = [v0 v0 v1 x v0 2*y 2*x.*y x.^2 v0 3*y.^2 3*x.*y.^2 2*(x.^2).*y x.^3 v0 4*y.^3];
else
    error("polynomial degree must be less than 5");
end

return f, fx, fy;

end

function monomial3d(xyz::Array{Float64,2},p::Int)::Tuple{Array{Float64,2},Array{Float64,2},Array{Float64,2},Array{Float64,2}}

n = size(xyz,1);
v0 = zeros(Float64,n,1);
v1 = ones(Float64,n,1);

x = xyz[:,1];
y = xyz[:,2];
z = xyz[:,3];

if (p==0)
    f = v1;
    fx = v0;
    fy = v0;
    fz = v0;
elseif (p==1)
    f = [v1 x y z];
    fx = [v0 v1 v0 v0];
    fy = [v0 v0 v1 v0];
    fz = [v0 v0 v0 v1];
elseif (p==2)
    f = [v1 x y z x.*y x.*z y.*z x.^2 y.^2 z.^2];
    fx = [v0 v1 v0 v0 y z v0 2*x v0 v0];
    fy = [v0 v0 v1 v0 x v0 z v0 2*y v0];
    fz = [v0 v0 v0 v1 v0 x y v0 v0 2*z];
elseif (p==3)
    f = [v1 x y z x.*y x.*z y.*z x.^2 y.^2 z.^2 x.*y.*z x.*y.^2 x.*z.^2 (x.^2).*y (x.^2).*z y.*z.^2 (y.^2).*z x.^3 y.^3 z.^3];
    fx = [v0 v1 v0 v0 y z v0 2*x v0 v0 y.*z y.^2 z.^2 2*x.*y 2*x.*z v0 v0 3*x.^2 v0 v0];
    fy = [v0 v0 v1 v0 x v0 z v0 2*y v0 x.*z 2*x.*y v0 x.^2 v0 z.^2 2*y.*z v0 3*y.^2 v0];
    fz = [v0 v0 v0 v1 v0 x y v0 v0 2*z x.*y v0 2*x.*z v0 x.^2 2*y.*z y.^2 v0 v0 3*z.^2];
elseif (p==4)
    f = [v1 x y z x.*y x.*z y.*z x.^2 y.^2 z.^2 ...
         x.*y.*z x.*y.^2 x.*z.^2 (x.^2).*y (x.^2).*z y.*z.^2 (y.^2).*z x.^3 y.^3 z.^3 ...
         x.*y.*z.^2 (x.*y.^2).*z (x.^2).*y.*z (x.^2).*y.^2 (x.^2).*z.^2 (y.^2).*z.^2 ...
         (x.^3).*y (x.^3).*z x.*y.^3 (y.^3).*z x.*z.^3 y.*z.^3 x.^4 y.^4 z.^4];
    fx = [v0 v1 v0 v0 y z v0 2*x v0 v0 y.*z y.^2 z.^2 2*x.*y 2*x.*z v0 v0 3*x.^2 v0 v0...
          y.*z.^2 (y.^2).*z 2*x.*y.*z 2*x.*y.^2 2*x.*z.^2 v0 ...
          3*(x.^2).*y 3*(x.^2).*z y.^3 v0 z.^3 v0 4*x.^3 v0 v0];
    fy = [v0 v0 v1 v0 x v0 z v0 2*y v0 x.*z 2*x.*y v0 x.^2 v0 z.^2 2*y.*z v0 3*y.^2 v0...
          x.*z.^2 2*x.*y.*z (x.^2).*z 2*(x.^2).*y v0 2*y.*z.^2 ...
          x.^3 v0 3*x.*y.^2 3*(y.^2).*z v0 z.^3 v0 4*y.^3 v0];
    fz = [v0 v0 v0 v1 v0 x y v0 v0 2*z x.*y v0 2*x.*z v0 x.^2 2*y.*z y.^2 v0 v0 3*z.^2 ...
          2*x.*y.*z x.*y.^2 (x.^2).*y v0 2*(x.^2).*z 2*(y.^2).*z ...
          v0 x.^3 v0 y.^3 3*x.*z.^2 3*y.*z.^2 v0 v0 4*z.^3];
else
    error("polynomial degree must be less than 5");
end

return f, fx, fy, fz;

end

function simplexmonomial(x::Array{Float64,2},porder::Int)::Tuple{Array{Float64,2},Array{Float64,2},Array{Float64,2},Array{Float64,2}}

dim=size(x,2);

if dim==1
     f,fx=monomial1d(x,porder);      # 1D
     fy=[0.0 0.0];
     fz=[0.0 0.0];
elseif dim==2
     f,fx,fy=monomial2d(x,porder);   # 2D
     fz=[0.0 0.0];
elseif dim==3
     f,fx,fy,fz=monomial3d(x,porder);# 3D
else
     error("Only can handle dim=1, dim=2 or dim=3");
end

return f, fx, fy, fz;

end


const p1 = Proj{1}()
const p2 = Proj{2}()
const p3 = Proj{3}()
const p4 = Proj{4}()

# add = PrimRec(p1, Comb(succ, p1))
const add = PrimRec(p1, succ(p1))

# mult = PrimRec(zro, Comb(add, p1, p3))
const mult = PrimRec(zro, add(p1, p3))

const pred = PrimRec(zro, p2)

# cosubrev = PrimRec(p1, Comb(pred, p1))
const cosubrev = PrimRec(p1, pred(p1))

# cosub = Comb(cosubrev, p2, p1)
const cosub = cosubrev(p2, p1)

# dff = Comb(add, cosub, cosubrev)
const dff = add(cosub, cosubrev)

# const1 = Comb(succ, zro)
const const1 = succ(zro)

# sgn = PrimRec(zro, Comb(const1, p1))
const sgn = PrimRec(zro, const1(p1))

# nsgn = PrimRec(const1, Comb(zro, p1))
const nsgn = PrimRec(const1, zro(p1))

# eq = Comb(nsgn, dff)
const eq = nsgn(dff)

# neq = Comb(sgn, dff)
const neq = sgn(dff)

# remainder = PrimRec(
#     zro,
#     Comb(
#         mult,
#         Comb(succ, p1),
#         Comb(neq, p1, Comb(pred, p3)),
#     ),
# )
const remainder = PrimRec(zro, mult(succ(p1), neq(p1, pred(p3))))

# ge = Comb(nsgn, cosubrev)
const ge = nsgn(cosubrev)

ifel = PrimRec(p2, p3)

# square = Comb(mult, p1, p1)
const square = mult(p1, p1)

# floor_sqrt = PrimRec(
#     zro,
#     Comb(ifel, Comb(eq, Comb(square, Comb(succ, p1)), Comb(succ, p2)), Comb(succ, p1), p1),
# )
const floor_sqrt = PrimRec(zro, ifel(eq(square(succ(p1)), succ(p2)), succ(p1), p1))

# traingle = PrimRec(zro, Comb(add, p1, p2))
const traingle = PrimRec(zro, add(p1, p2))

# pair = Comb(add, p1, Comb(traingle, Comb(add, p1, Comb(succ, p2))))
const pair = add(p1, traingle(add(p1, succ(p2))))

# fst = PrimRec(zro, Comb(mult, Comb(neq, Comb(pair, p1, zro), p2), Comb(succ, p1)))
const fst = PrimRec(zro, mult(neq(pair(p1, zro), p2), succ(p1)))

# scd = PrimRec(
#     zro,
#     Comb(
#         ifel,
#         Comb(eq, p2, Comb(pair, Comb(fst, p2), zro)),
#         Comb(succ, Comb(fst, p2)),
#         Comb(pred, p1),
#     ),
# )
const scd = PrimRec(zro, ifel(eq(p2, pair(fst(p2), zro)), succ(fst(p2)), pred(p1)))

# fib_pair = PrimRec(
#     Comb(pair, zro, const1),
#     Comb(pair, Comb(scd, p1), Comb(add, Comb(fst, p1), Comb(scd, p1))),
# )
const fib_pair = PrimRec(pair(zro, const1), pair(scd(p1), add(fst(p1), scd(p1))))

# fib = Comb(fst, fib_pair)
const fib = fst(fib_pair)


"""
Test script for dirBin0Rec0 model: one parameter for total number of links and one for reciprocity
"""

include("../../../add_load_paths.jl")

using StaticNets

# test sampling

nSample = 500
N=30
    θ_0 = -3.1
    η_0 = 2
    model = ErgmDirBin0Rec0()
    diadProb = diadProbFromPars(model, [θ_0, η_0])
    A_vec = [samplSingMatCan(model, diadProb, N) for i=1:nSample]

using PyPlot
hist([sum(A) for A in A_vec])
hist([sum(A'.*A) for A in A_vec])

# Test Estimates
par_est = reduce(hcat,[estimate(model, A) for A in A_vec])
hist(par_est[2,:], density=true )
 vlines(η_0, 0, 1, "r")
hist(par_est[1,:], density=true )
 vlines(θ_0, 0, 1, "r")

# Are likelihood and loglikelihoods equal??
using ErgmRcall

#


include(joinpath(Pkg.dir("MathProgBase"),"test","nlp.jl"))
convexnlptest(KnitroSolver())
rosenbrocktest(KnitroSolver())
nlptest(KnitroSolver())
nlptest_nohessian(KnitroSolver())


srand(17)
N = 1000
z = 3
g =  random_regular_graph(N, z, seed=1)
Js = random_couplings(g)
σ, E = ground_state_τeo(g, Js, maxiters=10_000)
@test typeof(E) == Int
@test E == -1266


using ReactionNetworkImporters, OrdinaryDiffEq, DiffEqBiological,
      Sundials, Plots, DiffEqDevTools, ODEInterface, ODEInterfaceDiffEq,
      LSODA, TimerOutputs

gr()
prnbng = loadrxnetwork(BNGNetwork(), "BNGRepressilator",
                       joinpath(dirname(pathof(ReactionNetworkImporters)),"..","data","bcr","bcr.net"))

rn = deepcopy(prnbng.rn)
addodes!(rn; build_jac=false, build_symfuncs=false, build_paramjac=false)
tf = 100000.0
oprob = ODEProblem(rn, prnbng.u₀, (0.,tf), prnbng.p);

densejac_rn = deepcopy(prnbng.rn)
# zeroout_jac=true is needed to keep the generated expressions from being too big for the compiler
addodes!(densejac_rn; build_jac=true, zeroout_jac = true, sparse_jac = false, build_symfuncs=false, build_paramjac=false)
densejacprob = ODEProblem(densejac_rn, prnbng.u₀, (0.,tf), prnbng.p);

sparsejac_rn = deepcopy(prnbng.rn)
addodes!(sparsejac_rn; build_jac=true, sparse_jac = true, build_symfuncs=false, build_paramjac=false)
sparsejacprob = ODEProblem(sparsejac_rn, prnbng.u₀, (0.,tf), prnbng.p);

@show numspecies(rn) # Number of ODEs
@show numreactions(rn) # Apprx. number of terms in the ODE
@show numparams(rn) # Number of Parameters

const to = TimerOutput()
u₀ = prnbng.u₀
u = copy(u₀);
du = similar(u);
p = prnbng.p
@timeit to "ODERHS Eval1" rn.f(du,u,p,0.)
@timeit to "ODERHS Eval2" rn.f(du,u,p,0.)
sparsejac_rn.f(du,u,p,0.)

J = zeros(length(u),length(u))
@timeit to "DenseJac Eval1" densejac_rn.jac(J,u,p,0.)
@timeit to "DenseJac Eval2" densejac_rn.jac(J,u,p,0.)

Js = similar(sparsejac_rn.odefun.jac_prototype)
@timeit to "SparseJac Eval1" sparsejac_rn.jac(Js,u,p,0.)
@timeit to "SparseJac Eval2" sparsejac_rn.jac(Js,u,p,0.)
show(to)

sol = solve(oprob, CVODE_BDF(), saveat=tf/1000., reltol=1e-5, abstol=1e-5)
plot(sol,legend=false, fmt=:png)

@time sol = solve(oprob,CVODE_BDF(),abstol=1/10^12,reltol=1/10^12)
test_sol = TestSolution(sol)

abstols = 1.0 ./ 10.0 .^ (5:8)
reltols = 1.0 ./ 10.0 .^ (5:8);
setups = [
          #Dict(:alg=>Rosenbrock23(autodiff=false)),
          Dict(:alg=>TRBDF2(autodiff=false)),
          Dict(:alg=>CVODE_BDF()),
          #Dict(:alg=>rodas()),
          #Dict(:alg=>radau()),
          #Dict(:alg=>Rodas4(autodiff=false)),
          #Dict(:alg=>Rodas5(autodiff=false)),
          Dict(:alg=>KenCarp4(autodiff=false)),
          #Dict(:alg=>RadauIIA5(autodiff=false)),
          #Dict(:alg=>lsoda()),
          ]

wp = WorkPrecisionSet(oprob,abstols,reltols,setups;error_estimate=:l2,
                      saveat=tf/10000.,appxsol=test_sol,maxiters=Int(1e5),numruns=1)
plot(wp)

wp = WorkPrecisionSet(densejacprob,abstols,reltols,setups;error_estimate=:l2,
                      saveat=tf/10000.,appxsol=test_sol,maxiters=Int(1e5),numruns=1)
plot(wp)

setups = [
          #Dict(:alg=>Rosenbrock23(autodiff=false)),
          Dict(:alg=>TRBDF2(autodiff=false)),
          #Dict(:alg=>CVODE_BDF()),
          #Dict(:alg=>rodas()),
          #Dict(:alg=>radau()),
          #Dict(:alg=>Rodas4(autodiff=false)),
          #Dict(:alg=>Rodas5(autodiff=false)),
          Dict(:alg=>KenCarp4(autodiff=false)),
          #Dict(:alg=>RadauIIA5(autodiff=false)),
          #Dict(:alg=>lsoda()),
          ]
wp = WorkPrecisionSet(sparsejacprob,abstols,reltols,setups;error_estimate=:l2,
                      saveat=tf/10000.,appxsol=test_sol,maxiters=Int(1e5),numruns=1)
plot(wp)


"""
    recoverableProblem(C, c, X, α, dg)

Solves recoverable problem ``REC(c) = min_{x \\in X} min_{y \\in X^{\\alpha}_x} (Cx + cy)``.

Check section 4 _Solving the problems by MIP formulations_ of [publication](https://arxiv.org/abs/1811.06719)
for more information about this algorithm.

# Arguments
- `C`: is a vector of nonnegative first stage costs.
- `c`: is a vector of a nonnegative nominal second stage costs.
- `X`: is a set of feasible solutions represented as a list functions, each of which
    accepts a list of JuMP variables as an argument and returns a JuMP linear constraint.
- `α`: fixed number belonging to ``[0, 1]``
- `pd`: an instance of [`ProblemDescriptor`](@ref)
"""
function recoverableProblem(C, c, X, α, dg)
    @assert size(C) == size(c)

    n = size(C, 1)

    model = Model(solver=CplexSolver(CPXPARAM_ScreenOutput = getProperty("recoverableProblem.cplexLogging")))
    if ndims(C) == 1
        @variable(model, x[1:n], Bin)
        @variable(model, y[1:n], Bin)
        if hasEqualCardinalityProperty(dg)
            @variable(model, z[1:n] >= 0)
        else
            @variable(model, z[1:n], Bin)
        end
    elseif ndims(C) == 2
        m = size(C, 2)
        @variable(model, x[1:n, 1:m], Bin)
        @variable(model, y[1:n, 1:m], Bin)
        if hasEqualCardinalityProperty(dg)
            @variable(model, z[1:n, 1:m] >= 0)
        else
            @variable(model, z[1:n, 1:m], Bin)
        end
    else
        throw(ArgumentError("Unsupported number of dimensions ($(ndims(C)))"))
    end

    @objective(model, Min, vecdot(C, x) + vecdot(c, y))

    if hasEqualCardinalityProperty(dg)
        l = ceil(n * (1 - α))
        @constraint(model, sum(z) >= l)
    else
        @constraint(model, sum(x-z) <= α * sum(x))
        @constraint(model, z .>= x + y - 1)
    end

    @constraint(model, z .<= x)
    @constraint(model, z .<= y)

    # Constraints defining set of feasible solutions
    for constraint in X
        JuMP.addconstraint(model, constraint(x))
    end

    for constraint in X
        JuMP.addconstraint(model, constraint(y))
    end

    status = solve(model)
    (getvalue(x), getvalue(y), getobjectivevalue(model))
end


using PtFEM, Test

data = Dict(
# Plane(ndim, nst, nxe, nye, nip, direction=(:x|:y), finite_element(nod, nodof), axisymmetric=false)
# Plane(ndim, nst, nxe, nye, nip, direction=:z, finite_element(nod, nodof), axisymmetric=true)
# Plane(ndim, nst, nre, nze, nip, direction=:r, finite_element(nod, nodof), axisymmetric=false)
  :struc_el => Plane(2, 6, 1, 5, 4, :r, Quadrilateral(8, 3), false),
  :properties => [1.0e5 0.3;],
  :r_coords => [0.0, 0.5],
  :z_coords => [10.0, 8.0, 6.0, 4.0, 2.0, 0.0],
  :support => [
    ( 1, [1 0 1]),
    ( 4, [1 0 1]),
    ( 6, [1 0 1]),
    ( 9, [1 0 1]),
    (11, [1 0 1]),
    (14, [1 0 1]),
    (16, [1 0 1]),
    (19, [1 0 1]),
    (21, [1 0 1]),
    (24, [1 0 1]),
    (26, [0 0 0]),
    (27, [0 0 0]),
    (28, [0 0 0])
    ],
  :loaded_nodes => [
    (3, [1/pi 0.0 0.0])
    ],
  :lth => 1,
  :iflag => 1,
  :chi => 0.0
)

@time m, dis_df, sigma_df = p52(data)

@test m.loads[0:65] ≈ [0.0,0.0675569552650584,-0.06755339718587051,
  0.0675550611183897,-0.0025282635314929514,-0.06754930096906765,
  0.06755376595399615,-0.005063646782360032,-0.06754378259701828,
  0.05743046037889881,-0.057432490517240836,0.057441944750754405,
  -0.005005739743328588,-0.05742749087610757,0.04753054505535036,
  -0.04752501175531152,0.04752761039720702,-0.0024265026287448132,
  -0.04751826130337188,0.04753200512262878,-0.00485859624896776,
  -0.04750446544560081,0.0380108152341777,-0.03801430582540627,
  0.038043595025430185,-0.004598096664133727,-0.0379939151061887,
  0.029138408322084745,-0.029128707630258533,0.029131687887729216,
  -0.002121143506426845,-0.02911866562356955,0.029141015554487087,
  -0.0042482014668140024,-0.029092503165066133,0.021034656820675384,
  -0.021040857210335804,0.021090965865308207,-0.0037823104185696304,
  -0.021005867287750803,0.014004054458491054,-0.013993277627426427,
  0.013994731459463667,-0.0016135944093911055,-0.013978288565264622,
  0.014005550176560485,-0.00323312039147469,-0.01394313064790418,
  0.008132647707849692,-0.008137157612832228,0.008212440365727661,
  -0.0025592520670215577,-0.008085266130752576,0.003764582745644812,
  -0.0037356644208552323,0.003738861252011861,-0.0009009830493624234,
  -0.003731710717854891,0.0037540460087339984,-0.0018087025205919894,
  -0.0036869554668832335,0.000937846981324757,-0.0009620426947358215,
  0.0010519661074415124,-0.0009218802871890891,-0.0008805978030738389]


using FeedbackParticleFilters, Random, Statistics, LinearAlgebra, Distributions

println("Testing FPF.jl:")

@testset "FPF.jl" begin

    print("  inner constructor for FPFState")
    ens   = UnweightedParticleEnsemble(randn(3,10))
    h(x)  = [x[1] * x[2], x[2] * x[3]]
    eq    = PoissonEquation(h, ens)
    state = FPFState(ens, eq)
    print(".")
    @test state.ensemble == ens
    print(".")
    @test state.eq == eq
    println("DONE.")

    print("  inner constructor for FPF")
    f(x)   = -x
    g(x)   = hcat([1.; -2.; 1.])
    init   = [0., 0., 0.]
    st_mod = DiffusionStateModel(f, g, init)
    ob_mod = DiffusionObservationModel{Float64, Float64, typeof(h)}(3, 2, h)
    struct DummyMethod <: GainEstimationMethod end
    method = DummyMethod()
    fpf    = FPF(st_mod, ob_mod, method, 10)
    print(".")
    @test fpf.state_model == st_mod
    print(".")
    @test fpf.obs_model == ob_mod
    print(".")
    @test fpf.gain_method == method
    print(".")
    @test fpf.N == 10

    struct DummyModel <: ObservationModel{Vector{Float64}, Vector{Float64}, ContinuousTime} end
    ob_mod2 = DummyModel()
    print(".")
    @test_throws ErrorException FPF(st_mod, ob_mod2, method, 10) # wrong type of observation model
    println("DONE.")

    print("  method initial_condition")
    print(".")
    @test initial_condition(fpf) ≈ [0., 0., 0.]
    println("DONE.")

    print("  method no_of_particles")
    print(".")
    @test no_of_particles(fpf) == 10
    print(".")
    @test no_of_particles(state) == 10
    println("DONE.")

    print("  method state_model")
    print(".")
    @test state_model(fpf) == st_mod
    println("DONE.")

    print("  method obs_model")
    print(".")
    @test obs_model(fpf) == ob_mod
    println("DONE.")

    print("  method gain_estimation_method")
    print(".")
    @test gain_estimation_method(fpf) == method
    println("DONE.")

    print("  method for Statistics.mean")
    print(".")
    @test Statistics.mean(state) ≈ mean(ens)
    println("DONE.")

    print("  method for Statistics.cov")
    print(".")
    @test Statistics.cov(state) ≈ cov(ens)
    println("DONE.")

    print("  method for Statistics.var")
    print(".")
    @test Statistics.var(state) ≈ var(ens)
    println("DONE.")

    print("  outer constructors for FPFState")
    state2 = FPFState(fpf)
    print(".")
    @test no_of_particles(state2) == 10
    print(".")
    @test state2.ensemble.positions == zeros(3,10)
    filt_prob = FilteringProblem(st_mod, ob_mod)
    state3 = FPFState(filt_prob, 20)
    print(".")
    @test no_of_particles(state3) == 20
    println("DONE.")

    print("  outer constructors for FPF")
    fpf2 = FPF(filt_prob, method, 100)
    print(".")
    @test no_of_particles(fpf2) == 100
    println("DONE.")

    print("  method initialize")
    state4 = initialize(fpf)
    print(".")
    @test state4 isa FPFState
    print(".")
    @test state4.ensemble.positions ≈ zeros(3,10)
    println("DONE.")

    print("  method assimilate!")
    function FeedbackParticleFilters.solve!(eq::PoissonEquation, method::DummyMethod)
        eq.gain .= Statistics.mean(eq.positions) * ones(size(eq.gain))
    end

    init   = MvNormal(3, 1.)
    st_mod = DiffusionStateModel(f, g, init)
    ob_mod = DiffusionObservationModel{Float64, Float64, typeof(h)}(3, 2, h)
    method = DummyMethod()
    fpf    = FPF(st_mod, ob_mod, method, 10)
    state  = initialize(fpf)

    oldstate = deepcopy(state)
    solve!(oldstate.eq, method)
    print(".")
    @test oldstate.eq.gain == Statistics.mean(oldstate.eq.positions) * ones(3,10,2) # test DummyMethod

    assimilate!([0.01, -0.02], state, fpf, 0.01)
    error = [0.01, -0.02] .- 0.01*oldstate.eq.H/2 .- 0.01*oldstate.eq.mean_H/2
    heun!(oldstate.eq, oldstate.ensemble, error, method)
    print(".")
    @test state.eq.gain ≈ oldstate.eq.gain

    print(".")
    @test state.eq.positions ≈ state.ensemble.positions
    println("DONE.")

    print("  method propagate!")
    init   = [0., 0., 0.]
    st_mod = DiffusionStateModel(f, g, init)
    fpf   = FPF(st_mod, ob_mod, method, 2)
    state = initialize(fpf)
    Random.seed!(0)
    propagate!(state, fpf, 0.01)
    print(".")
    @test state.ensemble.positions ≈ [0.06791074260357777 -0.013485387193052173; -0.1656826965800072 -0.11732341492662196; -0.03530074003005963 0.029733585084941616]
    println("DONE.")

    print("  method update!")
    init   = [0., 0., 0.]
    st_mod = DiffusionStateModel(f, g, init)
    fpf    = FPF(st_mod, ob_mod, method, 2)
    state  = initialize(fpf)
    state2 = initialize(fpf)
    Random.seed!(0)
    update!(state, fpf, [0.01, -0.02], 0.01)
    Random.seed!(0)
    propagate!(state2, fpf, 0.01)
    assimilate!([0.01, -0.02], state2, fpf, 0.01)
    print(".")
    @test state.ensemble.positions ≈ state2.ensemble.positions
    print(".")
    @test state.eq.gain ≈ state2.eq.gain
    println("DONE.")

end; #FPF.jl


module Day3
export day3

function parseInput(inputfile="inputs/input03.txt")
    lines = collect(eachline(inputfile))
    data = BitArray{2}(undef, length(lines), length(lines[1]))
    for (i, line) in enumerate(lines)
        data[i,:] .= collect(line) .== '#'
    end
    data
end

function day3(inputfile="inputs/input03.txt")
    parsedInput = parseInput(inputfile)
    slopes = ((1,1), (3,1), (5,1), (7,1), (1,2))
    (countTrees(parsedInput, 3, 1), reduce(*, map(s->countTrees(parsedInput, s...), slopes)))
    #(count(isValidPWD, parsedInput), count(isValidPWDPart2, parsedInput))
end

function countTrees(data, right, down)
    trees = 0
    line = 1
    position = 0
    while line <= size(data, 1)
        if data[line, position+1]
            trees += 1
        end
        line += down
        position = mod(position+right, size(data, 2))
    end
    trees
end

end #module


module ConstrainedRootSolvers

using DocStringExtensions: METHODLIST, TYPEDEF, TYPEDFIELDS
using UnPack: @unpack

# export public Types
export BisectionMethod, NelderMeadMethod, NewtonBisectionMethod,
       NewtonRaphsonMethod, ReduceStepMethod, ReduceStepMethodND,
       ResidualTolerance, SolutionTolerance, SolutionToleranceND

# export public functions
export find_peak, find_zero

include("method.jl"   )
include("tolerance.jl")
include("find_zero.jl")

include("findpeak/bisection.jl"   )
include("findpeak/neldermead.jl"  )
include("findpeak/reducestep.jl"  )
include("findpeak/reducestepND.jl")

end # module


using PyPlot
using SpecialFunctions: erfcx

"""
    ψ1(α, ϵ, N=100)

Approximates `(1+ϵ)^α - 1` by its Taylor expansion about `ϵ=0` using up to
`N` terms.
"""
function ψ1(α::T, ϵ::Complex{T}, N=100) where T <: AbstractFloat
    s = Complex(α, zero(T))
    term = s
    for n = 1:N
        term *= -( (n-α)/(n+1) ) * ϵ
        s += term
        if abs(term) < eps(T)
            break
        end
    end
    if abs(term) > 10*eps(T)
        error("ψ1 expansion did not converge after $N terms")
    end
    return s
end

"""
    ψ2(α, ϵ, N=100)

Approximates `(1+ϵ)^α - (1+αϵ)` by its Taylor expansion about `ϵ=0` using up
to `N` terms.
"""
function ψ2(α::T, ϵ::Complex{T}, N=100) where T <: AbstractFloat
    s = Complex(-α * (1-α) / 2, zero(T))
    term = s
    for n = 1:N
        term *= -( (n+1-α)/(n+2) ) * ϵ
        s += term
        if abs(term) < eps(T)
            break
        end
    end
    if abs(term) > 10*eps(T)
        error("ψ1 expansion did not converge after $N terms")
    end
    return s
end

function H(α::T, β::T, w::Complex{T}, x::T, sep::T) where T <: AbstractFloat
    xa = x^(1/α)
    ϵ = ( w - xa ) / xa
    if abs(ϵ) > sep
        return w^(α-β) / (w^α-x) - 1 / (α*ϵ*x^(β/α))
    else
        return ( ψ1(α-β,ϵ) - (1+ϵ)^(α-β) * ψ2(α,ϵ)/ψ1(α,ϵ) ) / (α*x^(β/α))
    end
end

a(ϕ) = acosh(2ϕ/((4ϕ-π)*sin(ϕ)))
b(ϕ) = ( (4π*ϕ-π^2) / a(ϕ) )

function Qpos(α::T, β::T, x::T, N::Integer, sep::T) where T <: AbstractFloat
    ϕ = parse(T, "1.17210")
    h = a(ϕ) / N
    μ = b(ϕ) * N
    w0r = μ * ( 1 - sin(ϕ) )
    w0 = Complex(w0r, zero(T))
    s = ( exp(w0r) * cos(ϕ) / 2 ) * real(H(α, β, w0, x, sep))
    for n = 1:N
        un = n * h
        iunmϕ = Complex(-ϕ, un)
        wn = μ * ( 1 + sin(iunmϕ) )
        s += real( exp(wn) * H(α, β, wn, x, sep) * cos(iunmϕ) )
    end
    return ( x^((1-β)/α) * exp(x^(1/α)) / α ) + ( a(ϕ) * b(ϕ) / π ) * s
end

function Hplus(α::T, β::T, w::Complex{T}, x::T, sep::T) where T <: AbstractFloat
    γp = x^(1/α) * exp(complex(zero(T), π/α))
    ϵp = ( w - γp ) / γp
    if abs(ϵp) > sep
        return w^(α-β) / ( w^α + x ) - γp^(1-β) / ( α * ϵp * γp^β )
    else
        return ( ψ1(α-β,ϵp) - (1+ϵp)^(α-β)*ψ2(α,ϵp) / ψ1(α,ϵp) ) / (α*γp^β)
    end
end

function Hminus(α::T, β::T, w::Complex{T}, x::T, sep::T
               ) where T <: AbstractFloat
    γm= x^(1/α) * exp(complex(zero(T), -π/α))
    ϵm = ( w - γm) / γm
    if abs(ϵm) > sep
        return w^(α-β) / ( w^α + x ) - γm^(1-β) / ( α * ϵm * γm^β )
    else
        return ( ψ1(α-β,ϵm) - (1+ϵm)^(α-β)*ψ2(α,ϵm) / ψ1(α,ϵm) ) / (α*γm^β)
    end
end

function Qneg(α::T, β::T, x::T, N::Integer, sep::T) where T <: AbstractFloat
    @assert 1 < α < 2
    ϕ = parse(T, "1.17210")
    h = a(ϕ) / N
    μ = b(ϕ) * N
    w0r = μ * ( 1 - sin(ϕ) )
    w0 = Complex(w0r, zero(T))
    s = ( exp(w0r) * cos(ϕ) / 2 ) * real(Hplus(α, β, w0, x, sep))
    for n = 1:N
        un = n * h
        iunmϕ = Complex(-ϕ, un)
        wn = μ * ( 1 + sin(iunmϕ) )
        s += real( exp(wn) * ( Hplus(α, β, wn, x, sep)
                             + Hminus(α, β, wn, x, sep) ) * cos(iunmϕ) ) / 2
    end
    sum_residues = ( ( x^((1-β)/α) / α ) * exp(x^(1/α)*cos(π/α))
                     *cos( π*(1-β)/α + x^(1/α)*sin(π/α) ) )
    return sum_residues + ( a(ϕ)*b(ϕ)/π ) * s
end

α = 0.5
β = 1.0
sep = 0.2
N = 10

figure(1)
x = 2.0
t = range(-2.0, 2.0, length=201)
θ = π/4
w = x^(1/α) .+ t * exp(Complex(0.0, θ))
Hvals = H.(α, β, w, x, sep)
plot(t, real.(Hvals), t, imag.(Hvals))
grid(true)

figure(2)
x = range(0.0, 1.0, length=201)
plot(x, Qpos.(α, β, x, N, sep) - erfcx.(-x))
grid(true)

figure(3)
α = 3/2
x = 2.0
γplus = x^(1/α) * exp(Complex(0.0,π/α))
t = range(-2.0, 2.0, length=201)
θ = π/4
w = x^(1/α) .+ t * exp(Complex(0.0, θ))
Hvals = Hplus.(α, β, w, x, sep)
plot(t, real.(Hvals), t, imag.(Hvals))
grid(true)

figure(4)
x = range(0.0, 5.0, length=201)
plot(x, Qneg.(α, β, x, N, sep))
grid(true)


# Sioux Falls network data
# http://www.bgu.ac.il/~bargera/tntp/

#Link travel time = free flow time * ( 1 + B * (flow/capacity)^Power ).
#Link generalized cost = Link travel time + toll_factor * toll + distance_factor * distance

# Traffic Assignment Data structure
mutable struct TA_Data
    network_name::String

    number_of_zones::Int
    number_of_nodes::Int
    first_thru_node::Int
    number_of_links::Int

    start_node::Array{Int,1}
    end_node::Array{Int,1}
    capacity::Array{Float64,1}
    link_length::Array{Float64,1}
    free_flow_time::Array{Float64,1}
    B::Array{Float64,1}
    power::Array{Float64,1}
    speed_limit::Array{Float64,1}
    toll::Array{Float64,1}
    link_type::Array{Int64,1}

    total_od_flow::Float64

    travel_demand::Array{Float64,2}
    od_pairs::Array{Tuple{Int64,Int64},1}

    toll_factor::Float64
    distance_factor::Float64

    best_objective::Float64
end

search_sc(s,c) = something(findfirst(isequal(c), s), 0)

function download_tntp(force_download=false)
  ta_root_dir = joinpath(dirname(dirname(@__FILE__)))
  dest = ta_root_dir
  data_dir = joinpath(dest, "TransportationNetworks-$(TNTP_SHA)")

  # Download
  if !isdir(data_dir) || force_download
    if isdir(data_dir)
      rm(data_dir)
    end
    file = joinpath(ta_root_dir, "tntp.zip")
    dl = download("https://github.com/bstabler/TransportationNetworks/archive/$(TNTP_SHA).zip", file)
    run(unpack_cmd(file, dest, ".zip", ""))
    rm(file)
  end

  return data_dir
end

function read_ta_network(network_name)
  tntp_dir = download_tntp()
  network_dir = joinpath(tntp_dir, network_name)

  @assert ispath(network_dir)

  network_data_file = ""
  trip_table_file = ""

  for f in readdir(network_dir)
    if occursin(".zip", lowercase(f))
      zipfile = joinpath(network_dir, f)
      run(unpack_cmd(zipfile, network_dir, ".zip", ""))
      rm(zipfile)
    end
  end

  for f in readdir(network_dir)
    if occursin("_net", lowercase(f)) && occursin(".tntp", lowercase(f))
      network_data_file = joinpath(network_dir, f)
  elseif occursin("_trips", lowercase(f)) && occursin(".tntp", lowercase(f))
      trip_table_file = joinpath(network_dir, f)
    end
  end

  @assert network_data_file != ""
  @assert trip_table_file != ""

  return network_data_file, trip_table_file
end

function load_ta_network(network_name; best_objective=-1.0, toll_factor=0.0, distance_factor=0.0)
  network_data_file, trip_table_file = read_ta_network(network_name)

  load_ta_network(network_name, network_data_file, trip_table_file, best_objective=best_objective, toll_factor=toll_factor, distance_factor=distance_factor)
end

function load_ta_network(network_name, network_data_file, trip_table_file; best_objective=-1.0, toll_factor=0.0, distance_factor=0.0)

    @assert ispath(network_data_file)
    @assert ispath(trip_table_file)

    ##################################################
    # Network Data
    ##################################################

    number_of_zones = 0
    number_of_links = 0
    number_of_nodes = 0
    first_thru_node = 0

    n = open(network_data_file, "r")

    while (line=readline(n)) != ""
        if occursin("<NUMBER OF ZONES>", line)
            number_of_zones = parse(Int, line[ search_sc(line, '>')+1 : end ] )
        elseif occursin("<NUMBER OF NODES>", line)
            number_of_nodes = parse(Int, line[ search_sc(line, '>')+1 : end ] )
        elseif occursin("<FIRST THRU NODE>", line)
            first_thru_node = parse(Int, line[ search_sc(line, '>')+1 : end ] )
        elseif occursin("<NUMBER OF LINKS>", line)
            number_of_links = parse(Int, line[ search_sc(line, '>')+1 : end ] )
        elseif occursin("<END OF METADATA>", line)
            break
        end
    end

    @assert number_of_links > 0

    start_node = Array{Int64}(undef, number_of_links)
    end_node = Array{Int64}(undef, number_of_links)
    capacity = zeros(number_of_links)
    link_length = zeros(number_of_links)
    free_flow_time = zeros(number_of_links)
    B = zeros(number_of_links)
    power = zeros(number_of_links)
    speed_limit = zeros(number_of_links)
    toll = zeros(number_of_links)
    link_type = Array{Int64}(undef, number_of_links)

    idx = 1
    while !eof(n)
      line = readline(n)
        if occursin("~", line) || line == ""
            continue
        end

        if occursin(";", line)
            line = strip(line, [' ', '\n', ';'])

            numbers = split(line)
            start_node[idx] = parse(Int64, numbers[1])
            end_node[idx] = parse(Int64, numbers[2])
            capacity[idx] = parse(Float64, numbers[3])
            link_length[idx] = parse(Float64, numbers[4])
            free_flow_time[idx] = parse(Float64, numbers[5])
            B[idx] = parse(Float64, numbers[6])
            power[idx] = parse(Float64, numbers[7])
            speed_limit[idx] = parse(Float64, numbers[8])
            toll[idx] = parse(Float64, numbers[9])
            link_type[idx] = Int(round(parse(Float64, numbers[10])))

            idx = idx + 1
        end
    end

    ##################################################
    # Trip Table
    ##################################################

    number_of_zones_trip = 0
    total_od_flow = 0

    f = open(trip_table_file, "r")

    while (line=readline(f)) != ""
        if occursin("<NUMBER OF ZONES>", line)
            number_of_zones_trip = parse(Int, line[ search_sc(line, '>')+1 : end ] )
        elseif occursin("<TOTAL OD FLOW>", line)
            total_od_flow = parse(Float64, line[ search_sc(line, '>')+1 : end ] )
        elseif occursin("<END OF METADATA>", line)
            break
        end
    end

    @assert number_of_zones_trip == number_of_zones # Check if number_of_zone is same in both txt files
    @assert total_od_flow > 0

    travel_demand = zeros(number_of_zones, number_of_zones)
    od_pairs = Array{Tuple{Int64, Int64}}(undef, 0)

    origin = -1

    while !eof(f)
        line = readline(f)

        if line == ""
            origin = -1
            continue
        elseif occursin("Origin", line)
            origin = parse(Int, split(line)[2] )
        elseif occursin(";", line)
            pairs = split(line, ";")
            for i=1:size(pairs)[1]
                if occursin(":", pairs[i])
                    pair = split(pairs[i], ":")
                    destination = parse(Int64, strip(pair[1]) )
                    od_flow = parse(Float64, strip(pair[2]) )

                    # println("origin=$origin, destination=$destination, flow=$od_flow")

                    travel_demand[origin, destination] = od_flow
                    push!(od_pairs, (origin, destination))
                end
            end
        end
    end

    # Preparing data to return
    ta_data = TA_Data(
        network_name,
        number_of_zones,
        number_of_nodes,
        first_thru_node,
        number_of_links,
        start_node,
        end_node,
        capacity,
        link_length,
        free_flow_time,
        B,
        power,
        speed_limit,
        toll,
        link_type,
        total_od_flow,
        travel_demand,
        od_pairs,
        toll_factor,
        distance_factor,
        best_objective)

    return ta_data

end # end of load_network function


rootdir = Pkg.dir("Dyno")
filename = joinpath(rootdir,"examples","rbc.mod")

import Dyno
@time model = Dyno.import_model(filename, print_code=false)

model.functions.f_static

y = [model.calibration[p] for p in model.symbols[:endogenous]]
e = [model.calibration[p] for p in model.symbols[:exogenous]]
p = [model.calibration[p] for p in model.symbols[:parameters]]
using Dolang
model.functions.f_static(Der{1},[y;e],p)
model.functions.f_dynamic(Der{1},[y;y;y;e],p)

exo = [0.0, 0.1, 0.05, 0.0]''

sim = Dyno.deterministic(model, exo)

dr = Dyno.solve(model)


# ---
# title: 1346. Check If N and Its Double Exist
# id: problem1346
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array
# link: <https://leetcode.com/problems/check-if-n-and-its-double-exist/description/>
# hidden: true
# ---
#
# Given an array `arr` of integers, check if there exists two integers `N` and
# `M` such that `N` is the double of `M` ( i.e. `N = 2 * M`).
#
# More formally check if there exists two indices `i` and `j` such that :
#
#   * `i != j`
#   * `0 <= i, j < arr.length`
#   * `arr[i] == 2 * arr[j]`
#
#
#
# **Example 1:**
#
#
#
#     Input: arr = [10,2,5,3]
#     Output: true
#     Explanation: N = 10 is the double of M = 5,that is, 10 = 2 * 5.
#
#
# **Example 2:**
#
#
#
#     Input: arr = [7,1,14,11]
#     Output: true
#     Explanation: N = 14 is the double of M = 7,that is, 14 = 2 * 7.
#
#
# **Example 3:**
#
#
#
#     Input: arr = [3,1,7,11]
#     Output: false
#     Explanation: In this case does not exist N and M, such that N = 2 * M.
#
#
#
#
# **Constraints:**
#
#   * `2 <= arr.length <= 500`
#   * `-10^3 <= arr[i] <= 10^3`
#
#
## @lc code=start
using LeetCode

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


using LinearAlgebra, Random
using Test
using Quadmath
using IterativeRefinement

const verbose = (get(ENV,"VERBOSITY","0") == "1")

Random.seed!(1101)

include("utils.jl")

function runone(A::Matrix{T},x0::AbstractVector) where {T}
    n = size(A,1)
    DT = widen(T)
    # println("wide type is $DT")
    Ad = DT.(A)
    xd = DT.(x0)
    b = T.(Ad * xd)
    # checkme: Demmel et al. use refined solver here
    xtrue = Ad \ DT.(b)
    xt = T.(xtrue)
    Rv, Cv = equilibrators(A)
    if maximum(abs.(Cv)) > 10
        RA = Diagonal(Rv)*A
    else
        RA = A
    end
    a = opnorm(RA,Inf)
    F = lu(RA,check=false)
    # Some version of OpenBLAS gave exact singularity for one of our "random" cases.
    # Handle gracefully so we can just try again.
    if F.info != 0
        return false
    end
    κnorm = condInfest(RA,F,a)
    RAx = RA*Diagonal(xt)
    a = opnorm(RAx,Inf)
    F = lu(RAx, check=false)
    if F.info != 0
        return false
    end
    κcomp = condInfest(RAx,F,a)
    crit = 1 / (max(sqrt(n),10) * eps(real(T)))
    if verbose
        println("problem difficulty (rel. to convergence criterion):")
        println("normwise: ", κnorm/crit, " componentwise: ", κcomp/crit)
    end
    xhat,Bnorm,Bcomp = @inferred(rfldiv(A,b))
    # xhat,Bnorm,Bcomp = rfldiv(A,b)
    Enorm = norm(xhat-xtrue,Inf)/norm(xtrue,Inf)
    Ecomp = maximum(abs.(xhat-xtrue) ./ abs.(xtrue))
    if verbose
        println("Bounds: $Bnorm $Bcomp")
        println("Errors: $Enorm $Ecomp")
    end
    if Bnorm > 0.1
        @test κcomp > 100 * crit
    else
        γ = max(10,sqrt(n))
        @test Enorm < 1.1*Bnorm
        if κnorm < crit
            @test Bnorm < γ * eps(real(T))
        end
        @test Ecomp < 1.1*Bcomp
        if κcomp < crit
            @test Bcomp < γ * eps(real(T))
        end
    end
    return true
end

# pick log10(condition-number) for various cases
function lkval(class,T)
    if class == :easy
        if real(T) <: Float32
            return 5.0
        elseif real(T) <: Float64
            return 13.0
        elseif real(T) <: Float128
            return 29.0
        end
    elseif class == :moderate
        if real(T) <: Float32
            return 7.5
        elseif real(T) <: Float64
            return 16.0
        end
    elseif class == :painful
        if real(T) <: Float32
            return 9.0
        elseif real(T) <: Float64
            return 18.0
        end
    end
    throw(ArgumentError("undefined lkval"))
end

@testset "matrix rhs $T" for T in (Float32, Float64, ComplexF32, ComplexF64)
    for n in [10]
        A = mkmat(n,lkval(:easy,T),T)
        nrhs = 3
        X = rand(T,n,nrhs)
        B = copy(X)
        X1 = copy(X)
        bn1 = zeros(T,nrhs)
        bc1 = zeros(T,nrhs)
        # check validity w/ view arg (someday maybe more tricky AbstractArrays)
        runone(A,view(X,:,2))
        for j=1:nrhs
            x,bnorm,bcomp = @inferred(rfldiv(A,view(X,:,j)))
            X1[:,j] .= x
            bn1[j] = bnorm
            bc1[j] = bcomp
        end
        X2, bn2, bc2 = @inferred(rfldiv(A,B))
        @test X1 ≈ X2
        @test bn1 ≈ bn2
        @test bc1 ≈ bc2
    end
end

@testset "preprocessed args $T" for T in (Float32, Float128)
    n = 16
    A = mkmat(n,lkval(:easy,T),T)
    # make it badly scaled
    s = 1 / sqrt(floatmax(T))
    A = s * A
    x = rand(T,n)
    b = A * x
    # basic usage for comparison
    x1, bn1, bc1 = rfldiv(A,b)

    # example of use with precomputed factor
    Rv, Cv = equilibrators(A)
    R = Diagonal(Rv)
    As = R * A * Diagonal(Cv)
    bs = R * b
    F = lu(As)
    a = opnorm(As,Inf)
    κnorm = condInfest(As,F,a)
    x2, bn2, bc2 = rfldiv(As,bs; F=F, κ = κnorm, equilibrate = false)
    cx2 = Diagonal(Cv) * x2
    @test cx2 ≈ x1
    @test bn2 ≈ bn1
    @test bc2 ≈ bc1
    # make sure this was not an empty test
    x3, bn3, bc3 = @test_logs (:warn, r"no convergence.*") rfldiv(A,b; F=F, κ = κnorm, equilibrate = false)
    @test ! (x3 ≈ x1)
end

@testset "well-conditioned $T" for T in (Float32, Float64, ComplexF32, ComplexF64)
    for n in [10,30,100]
        A = mkmat(n,lkval(:easy,T),T)
        x = rand(n)
        runone(A,x)
    end
end

@testset "marginally-conditioned $T" for T in (Float32, Float64, ComplexF32, ComplexF64)
    for n in [10,30,100]
        A = mkmat(n,lkval(:moderate,T),T)
        x = rand(n)
        runone(A,x)
    end
end

@info "The next block of tests is expected to produce warnings"

@testset "badly-conditioned $T" for T in (Float32, Float64, ComplexF32, ComplexF64)
    # We don't test for convergence failure here because
    # the method occasionally works in this regime.
    for n in [10,30,100]
        LU_ok = false
        for j in 1:10
            A = mkmat(n,lkval(:painful,T),T)
            x = rand(n)
            LU_ok = runone(A,x)
            if LU_ok
                break
            end
        end
        if !LU_ok
            @info "failed to find nonsingular example for n=$n"
            @test_broken LU_ok
        end
    end
end

include("eigen.jl")


#=

  Euler #38 in Julia.

  """
  Take the number 192 and multiply it by each of 1, 2, and 3:

      192 × 1 = 192
      192 × 2 = 384
      192 × 3 = 576

  By concatenating each product we get the 1 to 9 pandigital,
  192384576. We will call 192384576 the concatenated product of 192
  and (1,2,3)

  The same can be achieved by starting with 9 and multiplying by
  1, 2, 3, 4, and 5, giving the pandigital, 918273645, which is the
  concatenated product of 9 and (1,2,3,4,5).

  What is the largest 1 to 9 pandigital 9-digit number that can be
  formed as the concatenated product of an integer with
  (1,2, ... , n) where n > 1?
  """

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

=#

include("Euler.jl")

# 0.00016527s
function euler38a()
    for n in 9876:-1:9
        println
        s = string(n)
        i = 2
        while length(s) < 9
            s *= string(n*i)
            i += 1
        end
        if length(s) == 9 && is_pandigital(s)
            return parse(Int,s)
        end
    end
    return nothing
end

run_euler(euler38a)


using Manifolds
using Manifolds: +ₚ, *ₚ, offsetframe, dimincrease, dim, codim
using Test

using LinearAlgebra
using Random
using StaticArrays
using Suppressor

# Randomized equality checking. Also tests call and rand.
function ≅(m1::AbstractManifold{D}, m2::AbstractManifold{D}, n=1000) where D
    params = rand(D, n)
    vals1 = hcat(m1(zeros(D)...), mapslices(x -> m1(x...), params, dims = 1))
    vals2 = hcat(m2(zeros(D)...), mapslices(x -> m2(x...), params, dims = 1))
    if vals1 ≈ vals2
        true
    else
        bad = findall([vals1[:, i] ≉ vals2[:, i] for i in axes(vals1, 1)])
        println("Inconsistencies found in $(length(bad)) parameters. Examples:")
        foreach(Iterators.take(bad, 10)) do b
            println("  ", params[:, b], ", ", vals1[:, b], ", ", vals2[:, b])
        end
        false
    end
end
function ≅(m1::AbstractManifold, m2::AbstractManifold, n=1000)
    println("Mismatched dimensions.")
    false
end

⟂(v1::AbstractVector, v2::AbstractVector) = iszero(v1 ⋅ v2)

include("base.jl")
include("modifiers.jl")
#include("products.jl")