#Covariance functions
export SECov

abstract type CovFunc end

#---------------------------------------
#Squared-exponential covariance function
#    separate length-scales per param
#---------------------------------------

type SECov{N} <: CovFunc
    sig_f::Float64
    lambdas::Vector{Float64}
    function SECov{N}(sf, l::Vector) where N
        @assert isa(N, Integer)
        @assert length(l) == N
        new(sf, l)
    end
end
SECov(p::Vector) = SECov{length(p)-1}(p[1], p[2:end])

function Base.show{N}(io::IO, K::SECov{N})
    print(io,"[sig_f: $(@sprintf("%3.3f",K.sig_f)), lambdas: ")
    _printvec(io, K.lambdas)
    print(io, "]")
end

#Calculate covariance for a pair of points
function (cf::SECov{N}){N}(x1::Vector, x2::Vector)
    @assert length(x1) == N
    @assert length(x2) == N
    cf.sig_f^2*exp(-0.5*dot(x1-x2, diagm(1./cf.lambdas)*(x1-x2)))
end

#Generate covariance matrix from sets of points
function (cf::SECov{N}){N}(X1, X2)
    @assert size(X1,1) == N
    @assert size(X2,1) == N
    D = diagm(1./cf.lambdas)
    K = zeros(size(X1,2),size(X2,2))
    for i=1:size(X1,2)
        for j=1:size(X2,2)
            dx = X1[:,i] - X2[:,j]
            K[i,j] = cf.sig_f^2*exp(-0.5*dot(dx, D*dx))
        end
    end
    K
end
(cf::SECov{N}){N}(X::Matrix) = cf(X,X)

#Return the derivative of a covariance matrix for a data set with respect to a
#single hyperparameter.
#Parameter i == 0: sig_f
function grad{N}(cf::SECov{N}, ::Type{Val{0}}, X)
    D = diagm(1./cf.lambdas)
    K = zeros(size(X,2),size(X,2))
    for i=1:size(X,2)
        for j=1:size(X,2)
            dx = X[:,i] - X[:,j]
            K[i,j] = 2*cf.sig_f*exp(-0.5*dot(dx, D*dx))
        end
    end
    K
end

#Parameter 1 <= i <= n: lambda_i
function grad{Nd,N}(cf::SECov{Nd}, ::Type{Val{N}}, X)
    @assert 1 <= N <= Nd
    D = diagm(1./cf.lambdas)
    K = zeros(size(X,2),size(X,2))
    for i=1:size(X,2)
        for j=1:size(X,2)
            dx = X[:,i] - X[:,j]
            K[i,j] = cf.sig_f^2*exp(-0.5*dot(dx, D*dx))*(dx[N]^2/(2*cf.lambdas[N]^2))
        end
    end
    K
end

"Compute the derivatives of the test point covariance vector w.r.t. the
prediction variables"
function covgrad{Nd}(x::Vector, cf::SECov{Nd}, X)
    @assert length(x) == Nd
    @assert size(X,1) == Nd
    Kd = zeros(size(X,2), Nd)
    D = diagm(1./cf.lambdas)
    for j=1:Nd
        ej = zeros(Nd)
        ej[j] = 1.0
        for i=1:size(X,2)
            dx = X[:,i] - x
            Kd[i,j] = cf.sig_f^2*exp(-0.5*dot(dx,D*dx))*dot(dx,D*ej)
        end
    end
    Kd
end

#The squared-exponential symmetric covariance function
type SECovSym{N} <: CovFunc
    sig_f::Float64
    lambda::Float64
    function SECovSym{N}(sf, l) where N
        @assert isa(N, Integer)
        new(sf, l)
    end
end
SECovSym(N, p::Vector) = SECovSym{N}(p[1], p[2])

function Base.show{N}(io::IO, K::SECovSym{N})
    print(io, @sprintf("[sig_f: %3.3f, lambda: %3.3f]", K.sig_f, K.lambda))
end

#Calculate covariance for a pair of points
function (cf::SECovSym{N}){N}(x1::Vector, x2::Vector)
    @assert length(x1) == N
    @assert length(x2) == N
    cf.sig_f^2*exp(dot(-0.5*(x1-x2),diagm(1./(cf.lambda*ones(N)))*(x1-x2)))
end

#Generate covariance matrix from sets of points
function (cf::SECovSym{N}){N}(X1, X2)
    @assert size(X1,1) == N
    @assert size(X2,1) == N
    D = diagm(1./(cf.lambda*ones(N)))
    K = zeros(size(X1,2),size(X2,2))
    for i=1:size(X1,2)
        for j=1:size(X2,2)
            dx = X1[:,i] - X2[:,j]
            K[i,j] = cf.sig_f^2*exp(-0.5*dot(dx, D*dx))
        end
    end
    K
end
(cf::SECovSym{N}){N}(X::Matrix) = cf(X,X)

#Return the derivative of a covariance matrix for a data set with respect to a
#single hyperparameter.
#Parameter i == 0: sig_f
function grad{N}(cf::SECovSym{N}, ::Type{Val{0}}, X)
    D = diagm(1./(cf.lambda*ones(N)))
    K = zeros(size(X,2),size(X,2))
    for i=1:size(X,2)
        for j=1:size(X,2)
            dx = X[:,i] - X[:,j]
            K[i,j] = 2*cf.sig_f*exp(-0.5*dot(dx, D*dx))
        end
    end
    K
end

#Parameter 1 : lambda
#TODO: recalculate
function grad{N}(cf::SECovSym{N}, ::Type{Val{1}}, X)
    D = diagm(1./(cf.lambda*ones(N)))
    K = zeros(size(X,2),size(X,2))
    for i=1:size(X,2)
        for j=1:size(X,2)
            dx = X[:,i] - X[:,j]
            K[i,j] = cf.sig_f^2*exp(-0.5*dot(dx, D*dx))*(dx[N]^2/(2*cf.lambda^2))
        end
    end
    K
end


using FEniCS
using Base.Test

include("helpers.jl")

@testset "Mesh Tests" begin
  mesh = @inferred_isa UnitSquareMesh(2, 2)
  coords = [(j==0?mod(i,3):div(i,3))*0.5 for i=0:8, j=0:1]
  #@inferred coordinates(mesh)
  @test coordinates(mesh) == coords
  @test hmin(mesh) ≈ sqrt(0.5)
  @test hmax(mesh) ≈ sqrt(0.5)
  @test rmin(mesh) ≈ 0.5*(1-sqrt(0.5))
  @test rmax(mesh) ≈ 0.5*(1-sqrt(0.5))
  @test size(mesh, 0) == 9 # 9 nodes
  @test size(mesh, 1) == 2*2*3+2*2 # edges
  @test size(mesh, 2) == 8 # triangles (cells)
  @test size(mesh, 3) == 0 # no 3d elements here
  @test size(mesh) == [9, 16, 8, 0]
  @test num_cells(mesh) == 8
  @test num_edges(mesh) == 2*2*3+2*2 # edges
  @test num_entities(mesh, 0) == 9
  @test num_faces(mesh) == 8
  @test num_facets(mesh) == 2*2*3+2*2 # edges
  @test num_vertices(mesh) == 9

  mesh = UnitSquareMesh(1, 1)
  init(mesh, 1)
  #@inferred size(mesh)
  @test size(mesh) == [4, 5, 2, 0]
  #@inferred cells(mesh)
  @test cells(mesh) == [0 1 3; 0 2 3]

  mesh = UnitTriangleMesh()
  init(mesh)
  @test size(mesh) == [3, 3, 1, 0]
  @test cells(mesh) == [0 1 2]
end

@testset "MPI Tests" begin
  @inferred mpi_comm_world()
  @test isa(mpi_comm_world(), MPI_Comm)
  @inferred mpi_comm_self()
  @test isa(mpi_comm_self(), MPI_Comm)
  @test isa(UnitSquareMesh(mpi_comm_world(), 4, 4), Mesh) # just test that we have the right type as first argument
  @test isa(UnitSquareMesh(mpi_comm_self(), 4, 4), Mesh) # just test that we have the right type as first argument
end

@testset "FEM Tests" begin
  @testset "FunctionSpace Tests" begin
    mesh = UnitSquareMesh(2, 2)
    V = FunctionSpace(mesh, FEniCS.FAMILY_LAGRANGE, 3)

    @test (@inferred size(FEniCS.mesh(V),0)) == size(mesh, 0)
    @test isa( ufl_element(V), FiniteElement)
    @inferred dim(V)
    @test dim(V) == (3*2+1)^2
  end

  @testset "Expression Tests" begin
    mesh = UnitSquareMesh(1, 1)
    V = FunctionSpace(mesh, FEniCS.FAMILY_LAGRANGE, 1)
    result = [3.0,1.0,4.0,2.0] # ordering may be arbitrary
    u0 = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]")
    @test array(interpolate(u0, V)) == result
    u0 = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]", 1)
    @test array(interpolate(u0, V)) == result
    u0 = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]", V)
    @test array(interpolate(u0, V)) == result
  end

  @testset "Solve Laplace" begin
    mesh = UnitSquareMesh(6, 4)
    V = FunctionSpace(mesh, "Lagrange", 1)
    u = TrialFunction(V)
    v = TestFunction(V)
    f = Constant(6.0)
    a = inner(nabla_grad(u), nabla_grad(v)) * dx
    L = f * v * dx
    unum = FEniCS.Function(V)
    problem = LinearVariationalProblem(a, L, unum)
    solve(problem)
    @test 0*array(unum) ≈ zeros(dim(V))
  end
end

# using FEniCS
# reload("FEniCS")
# mesh = UnitSquareMesh(1, 1)
# V = FunctionSpace(mesh, FEniCS.FAMILY_LAGRANGE, 1)
# v=FEniCS.Vector(dim(V))
# u0 = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]")
# #dolfin.interpolate()
#


module QFT

using QXZoo.GateOps
using QXZoo.Circuit
using QXZoo.DefaultGates
using QXZoo.CompositeGates

export apply_qft!, apply_iqft!

"""
    apply_qft!(cct::Circuit.Circ, qubit_indices::Vector)

Apply the quantum Fourier transform over the given qubit index range.
"""
function apply_qft!(cct::Circuit.Circ, qubit_indices::Vector)
    for idx in 1:length(qubit_indices)
        cct << DefaultGates.h(qubit_indices[idx])
        for jdx in idx+1:length(qubit_indices)
            cct << DefaultGates.c_r_phase(qubit_indices[idx], qubit_indices[jdx], 2π/(2^(qubit_indices[jdx]-qubit_indices[idx]+1)))
        end
    end
    lhs, rhs = swap_idx(qubit_indices)
    for i in 1:length(lhs)
        cct << CompositeGates.swap(lhs[i], rhs[i])
    end
    ;
end
"""
    apply_qft!(cct::Circuit.Circ)

Apply the quantum Fourier transform over the entire qubit register
"""
apply_qft!(cct::Circuit.Circ) = apply_qft!(cct, collect(1:cct.num_qubits))

"""
    apply_qft!(cct::Circuit.Circ, qubit_indices::Vector)

Apply the inverse quantum Fourier transform over the given qubit index range.
"""
function apply_iqft!(cct::Circuit.Circ, qubit_indices::Vector)
    lhs, rhs = swap_idx(qubit_indices)

    for i in 1:length(lhs)
        cct << CompositeGates.swap(lhs[i], rhs[i])
    end

    for idx in length(qubit_indices):-1:1
        for jdx in length(qubit_indices):-1:idx+1
            cct << DefaultGates.c_r_phase(qubit_indices[idx], qubit_indices[jdx], -2π/(2^(qubit_indices[jdx]-qubit_indices[idx]+1)))
        end
        cct << DefaultGates.h(qubit_indices[idx])
    end

    ;
end
"""
    apply_iqft!(cct::Circuit.Circ)

Apply the inverse quantum Fourier transform over the entire qubit register
"""
apply_iqft!(cct::Circuit.Circ) = apply_iqft!(cct, collect(1:cct.num_qubits))

"""
    swap_idx(qubit_indices::Vector)

Return the indices of the qubits required to apply the swap operations as part of the transform.
"""
function swap_idx(qubit_indices::Vector)
    is_even = (length(qubit_indices) & 1 === 0)
    mp = convert(Int, floor(length(qubit_indices)//2))
    lhs = @view qubit_indices[1:mp]
    if is_even
        rhs = @view qubit_indices[end:-1:mp+1]
    else
        rhs = @view qubit_indices[end:-1:mp+2]
    end
    return lhs, rhs
end

end


#Demo 6: Fourier Transforms
#-------------------------------------------------------------------------------

using CMDimCircuits
CMDimCircuits.@using_CData()

#Get a demo display:
include(CMDimCircuits.demoplotcfgscript)
#Normally use something like:
#CMDimData.@includepkg EasyPlotInspect

#==Constants
===============================================================================#
vvst = cons(:a, labels = set(yaxis="Amplitude (V)", xaxis="Time (s)"))
dbvsf = cons(:a, labels = set(yaxis="Amplitude (dB)", xaxis="Frequency (Hz)"))

#==Input data
===============================================================================#
tfund = 1e-9 #Fundamental
osr = 20 #(fundamental) oversampling ratio

#==Computations
===============================================================================#
tvec = timespace(:ts, tfund/osr, :tfund, tfund)
Δt = step(tvec)
sigvec = rand(DataFloat, length(tvec))

#Generate parameter sweeps:
sweeplist = PSweep[
	PSweep("padding", collect(0:10:100))
]

#Generate data:
sigSpec = fill(DataHR, sweeplist) do padding
	#t-vector (+padding):
	tpad = 0:Δt:(Δt*((length(tvec)-1)+padding))
	#x(t)-vector:
	xtpad = zeros(length(tpad))
	xtpad[1:length(sigvec)] = sigvec
	#Padded time-domain signal:
	pad = DataTime(tpad, xtpad, tperiodic=false)

	Fpad = freqdomain(pad) #Frequency-domain signal (DataFreq)
	return abs2(fspectrum(Fpad)) #Spectrum (DataF1)
end

#Informative:
fmax = maximum(xval(sigSpec))

fstep = DataHR{DataFloat}(sweeplist) #Create empty data
for inds in subscripts(fstep)
	fstep.elem[inds...] = sigSpec.elem[inds...].x[2]
end

@show fmax
@show fstep

#==Generate plot
===============================================================================#
plot_t = push!(cons(:plot, vvst, title="Time Domain"),
	cons(:wfrm, DataF1(collect(tvec), sigvec)),
)
plot_f = push!(cons(:plot, dbvsf, title="Sampled Frequency Spectrum"),
	cons(:wfrm, dB20(sigSpec)),
)

pcoll = push!(cons(:plotcoll, title="Fourier Transform vs Padding"), plot_t, plot_f)
	pcoll.ncolumns = 1

#==Display results in pcoll
===============================================================================#
EasyPlot.displaygui(pcoll)

#==Return pcoll to user (call evalfile(...))
===============================================================================#
pcoll


using ModelingToolkit, MethodOfLines, DomainSets, Test, Symbolics, SymbolicUtils

@testset "Test 01: discretization of variables, center aligned grid" begin
    # Test centered order

    @parameters x, y, t
    @variables u(..) v(..)

    t_min= 0.
    t_max = 2.0
    x_min = 0.
    x_max = 2.
    y_min = 0.
    y_max = 2.
    dx = 0.1; dy = 0.2
    order = 2

    domains = [t ∈ Interval(t_min, t_max), x ∈ Interval(x_min, x_max), y ∈ Interval(y_min,y_max)]

    pde = u(t, x, y) ~ v(t, x, y) + Differential(x)(u(t, x, y)) + Differential(y)(v(t, x, y))

    bcs = [u(t_min, x, y) ~ 0, u(t_max, x, y) ~ 0, v(t_min, x, y) ~ 0, v(t_max, x, y) ~ 0]

    @named pdesys = PDESystem(pde,bcs,domains,[t,x,y],[u(t,x,y),v(t,x,y)])

    depvar_ops = map(x->operation(x.val),pdesys.depvars)

    depvars_lhs = MethodOfLines.get_depvars(pde.lhs, depvar_ops)
    depvars_rhs = MethodOfLines.get_depvars(pde.rhs, depvar_ops)
    depvars = collect(depvars_lhs ∪ depvars_rhs)
    # Read the independent variables,
    # ignore if the only argument is [t]
    indvars = first(Set(filter(xs->!isequal(xs, [t]), map(arguments, depvars))))
    x̄ = first(Set(filter(!isempty, map(u->filter(x-> t === nothing || !isequal(x, t.val), arguments(u)), depvars))))

    disc = MOLFiniteDifference([x=>dx, y=>dy], t; approx_order=order)

    s = MethodOfLines.DiscreteSpace(domains, depvars, x̄, disc)

    discx = x_min:dx:x_max
    discy = y_min:dy:y_max

    grid = Dict([x=>discx, y=>discy])

    @test s.grid[x] == discx
    @test s.grid[y] == discy

    @test s.axies[x] == s.grid[x]
    @test s.axies[y] == s.grid[y]

    #@test all([all(I[i] .∈ (collect(s.Igrid),)) for I in values(s.Iedges), i in [1,2]])

    @test all(s.Iaxies[u] == s.Igrid[u] for u in depvars)

end

@testset "Test 02: discretization of variables, edge aligned grid" begin
    # Test centered order
    @parameters x, y, t
    @variables u(..) v(..)

    t_min= 0.
    t_max = 2.0
    x_min = 0.
    x_max = 2.
    y_min = 0.
    y_max = 2.
    dx = 0.1; dy = 0.2
    order = 2

    domains = [t ∈ Interval(t_min, t_max), x ∈ Interval(x_min, x_max), y ∈ Interval(y_min,y_max)]

    pde = u(t, x, y) ~ v(t, x, y) + Differential(x)(u(t, x, y)) + Differential(y)(v(t, x, y))
    bcs = [u(t_min, x, y) ~ 0, u(t_max, x, y) ~ 0, v(t_min, x, y) ~ 0, v(t_max, x, y) ~ 0]

    @named pdesys = PDESystem(pde,bcs,domains,[t,x,y],[u(t,x,y),v(t,x,y)])

    depvar_ops = map(x->operation(x.val),pdesys.depvars)

    depvars_lhs = MethodOfLines.get_depvars(pde.lhs, depvar_ops)
    depvars_rhs = MethodOfLines.get_depvars(pde.rhs, depvar_ops)
    depvars = collect(depvars_lhs ∪ depvars_rhs)
    # Read the independent variables,
    # ignore if the only argument is [t]
    indvars = first(Set(filter(xs->!isequal(xs, [t]), map(arguments, depvars))))
    x̄ = first(Set(filter(!isempty, map(u->filter(x-> t === nothing || !isequal(x, t.val), arguments(u)), depvars))))

    disc = MOLFiniteDifference([x=>dx, y=>dy], t; approx_order=order, grid_align=edge_align)

    s = MethodOfLines.DiscreteSpace(domains, depvars, x̄, disc)

    discx = (x_min-dx/2): dx : (x_max+dx/2)
    discy = (y_min-dy/2): dy : (y_max+dy/2)

    grid = Dict([x=>discx, y=>discy])

    @test s.grid[x] == discx
    @test s.grid[y] == discy

    @test s.axies[x] != s.grid[x]
    @test s.axies[y] != s.grid[y]

    #@test all([all(I[i] .∈ (collect(s.Igrid),)) for I in values(s.Iedges), i in [1,2]])

    @test all(s.Iaxies[u] != s.Igrid[u] for u in depvars)

end


using DSP, DelimitedFiles, LsqFit

function DT_model(t, β::Array{Float64,1}, ρ::Float64, ndet::Float64, nmed::Float64)
	#optimzed semi-infinite model

    n::Float64 = nmed/ndet
    μa::Float64 = β[1]
    μsp::Float64 = β[2]
    D::Float64 = 1/3μsp
	ν::Float64 = 29.9792345/nmed

    Rt = Array{Float64}(undef, length(t))

	if n > 1.0
		A = 504.332889 - 2641.00214n + 5923.699064n^2 - 7376.355814n^3 +
		 5507.53041n^4 - 2463.357945n^5 + 610.956547n^6 - 64.8047n^7
	elseif n < 1.0
		A = 3.084635 - 6.531194n + 8.357854n^2 - 5.082751n^3
	else
		A = 1.0
	end

	zs::Float64 = 1/μsp
	ze::Float64 = 2A*D

	z3m::Float64 = - zs
    z4m::Float64 = 2ze +zs

	Threads.@threads for n in eachindex(t)

   		Rt[n] = -exp(-(ρ^2/(4D*ν*t[n])) - μa*ν*t[n])
   		Rt[n] = Rt[n]/(2*(4π*D*ν)^(3/2)*t[n]^(5/2))

   		Rt[n] = Rt[n]*(z3m*exp(-(z3m^2/(4D*ν*t[n]))) - z4m*exp(-(z4m^2/(4D*ν*t[n]))))

	end

   replace!(Rt, NaN => 0)
   return Rt./maximum(Rt)

end

function get_fit_window(counts, risefactor, tailfactor)

    maxval = maximum(counts)
    ind1::Int = findfirst(x -> x > maxval*risefactor, counts)
    ind2::Int =  findlast(x -> x >maxval*tailfactor, counts)

    return ind1, ind2
end

function pads(u::Array{Float64,1})
    outsize = 2 .* length(u) .- 1
    out = zeros(eltype(u), nextfastfft(outsize))
    copyto!(out, u)

end

function conv_DT(t, β::Array{Float64,1}, data::input_data, out, fftIRF, pl, pli;
    RtDT = Array{Float64}(undef, length(data.IRF))
    )

        RtDT = DT_model(data.t, β, data.ρ, data.nmed, data.ndet)
        copyto!(out, RtDT)

        fftRtDT = pl * out
        fftRtDT .*= fftIRF
        out = pli * fftRtDT

        out = out./maximum(out)

	    tidx = findfirst(x -> x == t[1], data.t)

	    out = out[tidx:tidx+length(t)-1]
	return log.(out)
end

function getfit(input_data, model_params)
    ## get FFT padding and plans
    out = pads(input_data.IRF)

    pl = plan_rfft(out)
    fftIRF = pl * out
    pli = plan_irfft(fftIRF, length(out))

    #get fit windows
    ind1, ind2 = get_fit_window(input_data.DTOF, model_params.risefactor, model_params.tailfactor)

    curve_fit((t, β) -> conv_DT(t, β, input_data, out, fftIRF, pl, pli), input_data.t[ind1:ind2], log.(input_data.DTOF[ind1:ind2]),
    model_params.initparams, lower=model_params.lb, upper=model_params.ub)

end

#=
@benchmark getfit(data,modelp)
BenchmarkTools.Trial:
  memory estimate:  21.43 MiB
  allocs estimate:  5717
  --------------
  minimum time:     8.924 ms (0.00% GC)
  median time:      9.845 ms (0.00% GC)
  mean time:        12.204 ms (4.42% GC)
  maximum time:     54.299 ms (3.45% GC)
  --------------
  samples:          410
  evals/sample:     1

this is if you use only 1024 points
    data1 = input_data(tDTOF[1:1024], RtDTOF[500:1524], RtIRF[500:1524], rand(1024), 2.56, 1.5, 1.51, 700, false)

julia> @benchmark getfit(data1,modelp)
BenchmarkTools.Trial:
  memory estimate:  6.13 MiB
  allocs estimate:  5501
  --------------
  minimum time:     3.154 ms (0.00% GC)
  median time:      3.261 ms (0.00% GC)
  mean time:        5.035 ms (3.86% GC)
  maximum time:     88.410 ms (0.00% GC)
  --------------
  samples:          993
  evals/sample:     1

using DelimitedFiles, LsqFit, DSP, FFTW
filename1 = "/home/heltonmc/Desktop/DTOF.asc"
filename = "/home/heltonmc/Desktop/IRF.asc"

  data_array = readdlm(filename,skipstart=10)
data_array1 = readdlm(filename1,skipstart=10)

RtIRF = data_array[1:end-1,2]
RtIRF = RtIRF./maximum(RtIRF)
tIRF = data_array[1:end-1,1]

RtDTOF = data_array1[1:end-1,2]
RtDTOF = RtDTOF./maximum(RtDTOF)
tDTOF = data_array1[1:end-1,1]

struct input_data
    t::Array{Float64,1}
	DTOF::Array{Float64,1}
	IRF::Array{Float64,1}
	DTOFerrors::Array{Float64,1}
	ρ::Float64
	nmed::Float64
	ndet::Float64
	lambda::Float64
	normpeaks::Bool
end

struct model_params
	#yerrors::Array{Float64,1}          #uncertainties in dependent variable
    model::Function                    #model to be fitted against
	initparams::Array{Float64,1}
	lb::Array{Float64,1}
	ub::Array{Float64,1}
	risefactor::Float64  			#percent of peak to fit on rising edge
	tailfactor::Float64				#perfecnt of peak to fit on failing tail
	dof::Int64
end

struct fitresult
    #Fit inputs
    xdata::Array{Float64,1}     #independent variable
    ydata::Array{Float64,1}     #dependent variable
    yerrors::Array{Float64,1}   #uncertainties in dependent variable
	model::Function             #model to be fitted against

    #Fit results
    residuals::Array{Float64,1}  	 #weighted residuals
    params::Array{Float64,1}    		#best fit parameters for model
	marginerror::Array{Float64,1}        #product of standard error and the critical value of each parameter at a certain significance level (default is 5%) from t-distribution.
    perrors::Array{Float64,1}            #uncertainties in best fit parameters
    dof::Int64                  		#number of degrees of freedom in fit
    chisq::Float64             		 #chi^2 value of fit
    probability::Float64      		  #probability of chi^2 value
end

data = input_data(tDTOF, RtDTOF, RtIRF, rand(length(RtDTOF)), 2.56, 1.5, 1.51, 700, false)

modelp = model_params(DT_model, [0.2,10], [0.001,1], [1,20], 0.9, 0.1, 2)


@with_kw struct eFAST <: GSAMethod
    num_harmonics::Int=4
end

struct eFASTResult{T1}
    first_order::T1
    total_order::T1
end

function gsa(f,method::eFAST,p_range::AbstractVector,n::Int=1000;batch=false)
    @unpack num_harmonics = method
    num_params = length(p_range)
    omega = [floor((n-1)/(2*num_harmonics))]
    m = floor(omega[1]/(2*num_harmonics))
    desol = false

    if m>= num_params-1
        append!(omega,floor.(collect(range(1,stop=m,length=num_params-1))))
    else
        append!(omega,collect(range(0,stop=num_params-2)).%m .+1)
    end

    omega_temp = similar(omega)
    first_order = []
    total_order = []
    s = collect((2*pi/n) * (0:n-1))
    ps = zeros(num_params,n*num_params)

    for i in 1:num_params
        omega_temp[i] = omega[1]
        omega_temp[[k for k in 1:num_params if k != i]] = omega[2:end]
        l = collect((i-1)*n+1:i*n)
        phi = 2*pi*rand()
        for j in 1:num_params
            x =  0.5 .+ (1/pi) .*(asin.(sin.(omega_temp[j]*s .+ phi)))
            ps[j,l] .= quantile.(Uniform(p_range[j][1], p_range[j][2]),x)
        end
    end

    if batch
        all_y = f(ps)
        multioutput = all_y isa AbstractMatrix
    else
        _y = [f(ps[:,j]) for j in 1:size(ps,2)]
        multioutput = !(eltype(_y) <: Number)
        if eltype(_y) <: RecursiveArrayTools.AbstractVectorOfArray
            y_size = size(_y[1])
            _y = vec.(_y)
            desol = true
        end
        all_y = multioutput ? reduce(hcat,_y) : _y
    end

    for i in 1:num_params
        if !multioutput
            ft = (fft(all_y[(i-1)*n+1:i*n]))[2:Int(floor((n/2)))]
            ys = ((abs.(ft))./n).^2
            varnce = 2*sum(ys)
            push!(first_order,2*sum(ys[(1:num_harmonics)*Int(omega[1])])/varnce)
            push!(total_order,1 .- 2*sum(ys[1:Int(floor(omega[1]/2))])/varnce)
        else
            ft = [(fft(all_y[j,(i-1)*n+1:i*n]))[2:Int(floor((n/2)))] for j in 1:size(all_y,1)]
            ys = [((abs.(ff))./n).^2 for ff in ft]
            varnce = 2*sum.(ys)
            push!(first_order, map((y,var) -> 2*sum(y[(1:num_harmonics)*Int(omega[1])])./var, ys, varnce))
            push!(total_order, map((y,var) -> 1 .- 2*sum(y[1:Int(floor(omega[1]/2))])./var, ys, varnce))
        end
    end
    if desol
        f_shape = x -> [reshape(x[:,i],y_size) for i in 1:size(x,2)]
        first_order = map(f_shape,first_order)
        total_order = map(f_shape,total_order)
    end
    return eFASTResult(reduce(hcat,first_order), reduce(hcat,total_order))
end


using VortexDistributions
using Test

@testset "Point vortex " begin

    # unitary
    v0 = [.2 .4 1]
    w0 = PointVortex(v0)
    @test vortex_array(w0) == v0

    v1 = [.2 .4 1;0.7 1.5 -1;-.3 1.2 1]
    w1 = PointVortex(v1)
    @test vortex_array(w1) == v1

    w3 = rand_pointvortex(1000)
    v3 = vortex_array(w3)
    @test v3 == vortex_array(w3)

end

@testset "Single vortex accuracy" begin

    # simple test field
    Nx = 400; Ny = 400
    Lx = 200; Ly = Lx
    x = LinRange(-Lx/2,Lx/2, Nx+1)[1:end-1]; y = LinRange(-Ly/2,Ly/2, Ny+1)[1:end-1];
    psi0 = one.(x*y') |> complex
    psi = Torus(copy(psi0),x,y)
    xp = 1.133
    yp = 2.787
    vp = PointVortex(xp,yp,1)
    sp = ScalarVortex(vp)
    vortex!(psi,sp)
    vfound = findvortices(psi)
    @test isapprox(vfound[1].xv,xp,atol=0.01)
    @test isapprox(vfound[1].yv,yp,atol=0.01)
end

@testset "Multivortex creation and detection" begin
    @test found_near(30)
end

@testset "Periodic dipole" begin
    vp = PointVortex(.1,.3,1)
    vn = PointVortex(-.2,.7,-1)
    dip = Dipole(vp,vn)
    @test typeof(dip) <: VortexGroup
end


using ExoplanetsSysSim
using ApproximateBayesianComputing
const ABC = ApproximateBayesianComputing
using SpecialFunctions
using Statistics
import ApproximateBayesianComputing.CompositeDistributions.CompositeDist
import ApproximateBayesianComputing.TransformedBetaDistributions.LinearTransformedBeta
#import EvalSysSimModel

# https://en.wikipedia.org/wiki/Trigamma_function
function trigamma_x_gr_4(x::T) where T<: Real
   1/x + 0.5/x^2 + 1/(6*x^3) - 1/(30*x^5) + 1/(42*x^7) - 1/(30*x^9) + 5/(66*x^11) - 691/(2730*x^13) + 7/(6*x^15)
end

function trigamma_x_lt_4(x::T) where T<: Real
  n = floor(Int64,5-x)
  z = x+n
  val = trigamma_x_gr_4(z)
  for i in 1:n
    z -= 1
    val += 1/z^2
  end
  val
end

function trigamma(x::T) where T<: Real
   x >= 4 ? trigamma_x_gr_4(x) : trigamma_x_lt_4(x)
end

function var_weighted(x::AbstractArray{Float64,1}, w::AbstractArray{Float64,1} )
  #println("# size(x) = ",size(x), " size(w) = ", size(w)); flush(stdout)
  @assert(length(x)==length(w) )
  sumw = sum(w)
  @assert( sumw > 0. )
  if(sumw!= 1.0)
     w /= sum(w)
     sumw = 1.0
  end
  sumw2 = sum(w.*w)
  xbar =  sum(x.*w)
  covar = sum((x.-xbar).*(x.-xbar) .* w) * sumw/(sumw*sumw-sumw2)
end

function mom_alpha(x_bar::T, v_bar::T) where T<: Real
    x_bar * (((x_bar * (1 - x_bar)) / v_bar) - 1)
end
function mom_beta(x_bar::T, v_bar::T) where T<: Real
    (1 - x_bar) * (((x_bar * (1 - x_bar)) / v_bar) - 1)
end

# For algorithm, see https://scholarsarchive.byu.edu/cgi/viewcontent.cgi?article=2613&context=etd
function fit_beta_mle(x::AbstractArray{T,1}; tol::T = 1e-6, max_it::Int64 = 10, init_guess::AbstractArray{T,1} = Array{T}(undef,0), w::AbstractArray{T,1} = Array{T}(undef,0), verbose::Bool = false ) where T<: Real
    lnxbar =   length(w)>1 ? Statistics.mean(log.(x),AnalyticWeights(w)) : Compat.Statistics.mean(log.(x))
    ln1mxbar = length(w)>1 ? Statistics.mean(log.(1.0.-x),AnalyticWeights(w)) : Compat.Statistics.mean(log.(1.0.-x))

    function itterate( mle_guess::Vector{T} ) where T<:Real
        (alpha, beta) = (mle_guess[1], mle_guess[2])
        dgab = digamma(alpha+beta)
        g1 = dgab - digamma(alpha) + lnxbar
        g2 = dgab - digamma(beta) + ln1mxbar
        tgab = trigamma(alpha+beta)
        G = [dgab-trigamma(alpha) tgab; tgab tgab-trigamma(beta)]
        mle_guess -= G \ [g1, g2]
    end

    local mle_new
    if length(init_guess) != 2
        xbar = length(w)>1 ? Compat.Statistics.mean(x,AnalyticWeights(w)) : Compat.Statistics.mean(x)
        vbar = length(w)>1 ? Compat.Statistics.varm(x,xbar,AnalyticWeights(w)) : Compat.Statistics.varm(x,xbar)
        mle_new = (vbar < xbar*(1.0-xbar)) ? [mom_alpha(xbar, vbar), mom_beta(xbar,vbar)] : ones(T,2)
    else
        mle_new = init_guess
    end
    if verbose
        println("it = 0: ", mle_new)
    end
    if any(mle_new.<=zero(T))
        println("# Warning: mean= ", xbar, " var= ",vbar," (alpha,beta)_init= ",mle_new," invalid, reinitializing to (1,1)")
        verbose = true
        mle_new = ones(T,2)
    end
    for i in 1:max_it
        mle_old = mle_new
        mle_new = itterate( mle_old )
        epsilon = max(abs.(mle_old.-mle_new))
        if verbose
            println("# it = ", i, ": ", mle_new, " max(Delta alpha, Delta beta)= ", epsilon)
        end
        if epsilon < tol
            break
        end
    end
    return mle_new
end

function make_beta(x::AbstractArray{T,1}, w::AbstractArray{T,1};
                   xmean::T = Compat.Statistics.mean(x,AnalyticWeights(w)),
                   xvar::T = Compat.Statistics.varm(x,xmean,AnalyticWeights(w)), tau_factor::T=one(T) ) where T<:Real
    alpha_beta = (xvar < xmean*(1.0-xmean)) ? [mom_alpha(xmean, xvar), mom_beta(xmean,xvar)] : ones(T,2)
    if any(alpha_beta.<=zero(T))
        alpha_beta = fit_beta_mle(x, w=w, init_guess=alpha_beta, verbose=true)
    end
    if any(alpha_beta.<=zero(T))
        alpha_beta = ones(T,2)
    else
        if minimum(alpha_beta)>1.5*tau_factor && sum(alpha_beta)>=20.0*tau_factor
            alpha_beta ./= tau_factor
        end
    end
    #println("Radius relative: a= ",alpha_beta[1], "  b= ",alpha_beta[2])
    Beta(alpha_beta[1], alpha_beta[2])
end

function make_beta_transformed(x::AbstractArray{T,1}, w::AbstractArray{T,1}; xmin::T=zero(T), xmax::T=one(T), xmean::T = Compat.Statistics.mean(x,AnalyticWeights(w)), xvar::T = Compat.Statistics.varm(x,xmean,AnalyticWeights(w)), tau_factor::T=one(T) ) where T<:Real
    alpha_beta = (xvar < xmean*(1.0-xmean)) ? [mom_alpha(xmean, xvar), mom_beta(xmean,xvar)] : ones(T,2)
    if any(alpha_beta.<=zero(T))
        alpha_beta = fit_beta_mle(x, w=w, init_guess=alpha_beta, verbose=true)
    end
    if any(alpha_beta.<=zero(T))
        alpha_beta = ones(T,2)
    else
        if minimum(alpha_beta)>1.5*tau_factor && sum(alpha_beta)>=20.0*tau_factor
            alpha_beta ./= tau_factor
        end
    end
    #println("Total: a= ",alpha_beta[1], "  b= ",alpha_beta[2])
    LinearTransformedBeta(alpha_beta[1], alpha_beta[2], xmin=xmin, xmax=xmax)
end

function make_proposal_dist_multidim_beta(theta::AbstractArray{Float64,2}, weights::AbstractArray{Float64,1},  tau_factor::Float64; verbose::Bool = false)
    global sim_param_closure
    local limitP::Array{Float64,1} = get_any(EvalSysSimModel.sim_param_closure, "p_lim_arr", Array{Float64,1})
    local p_dim = length(limitP)-1
    local r_dim = length(get_any(EvalSysSimModel.sim_param_closure, "r_lim_arr", Array{Float64,1}))-1

    theta_mean =  sum(theta.*weights',dims=2) # weighted mean for parameters
    theta_var = ABC.var_weighted(theta'.-theta_mean',weights)  # scaled, weighted covar for parameters
    tau_factor_indiv = fill(tau_factor,length(theta_var))

    dist_arr = ContinuousDistribution[]
    for j in 1:p_dim
        max_col_rate = 3*log(limitP[j+1]/limitP[j])/log(2)
        col_startidx = (j-1)*r_dim+1
        #tau_factor_indiv[col_startidx] = 2.0

        #=
        println("mean= ",theta_mean)
        println("var= ",theta_var)
        println("tau= ",tau_factor_indiv)
        for i in 1:length(theta_mean)
            println("a= ",alpha(theta_mean[i],tau_factor*theta_var[i]), "  b= ",beta(theta_mean[i],tau_factor*theta_var[i]))
        end
        =#

        dist_arr = vcat(dist_arr, ContinuousDistribution[make_beta_transformed(theta[i,:], weights, xmin=0.0, xmax=max_col_rate, xmean=theta_mean[i]/max_col_rate, xvar=theta_var[i]/max_col_rate^2, tau_factor=tau_factor_indiv[i]) for i in (col_startidx):(col_startidx+r_dim-1)])
    end

dist = CompositeDist(dist_arr)
end

function make_proposal_dist_multidim_beta(pop::abc_population_type, tau_factor::Float64; verbose::Bool = false)
    make_proposal_dist_multidim_beta(pop.theta, pop.weights, tau_factor, verbose=verbose)
end

function make_proposal_dist_multidim_beta_dirichlet(theta::AbstractArray{Float64,2}, weights::AbstractArray{Float64,1},  tau_factor::Float64; verbose::Bool = false)
    global sim_param_closure
    local limitP::Array{Float64,1} = get_any(EvalSysSimModel.sim_param_closure, "p_lim_arr", Array{Float64,1})
    local p_dim = length(limitP)-1
    local r_dim = length(get_any(EvalSysSimModel.sim_param_closure, "r_lim_arr", Array{Float64,1}))-1

    theta_mean =  sum(theta.*weights',dims=2) # weighted mean for parameters
    theta_var = ABC.var_weighted(theta'.-theta_mean',weights)  # scaled, weighted covar for parameters
    tau_factor_indiv = fill(tau_factor,length(theta_var))

    dist_arr = ContinuousDistribution[]
    for j in 1:p_dim
        max_col_rate = 3*log(limitP[j+1]/limitP[j])/log(2)
        col_startidx = (j-1)*(r_dim+1)+1
        #tau_factor_indiv[col_startidx] = 2.0

        # if verbose
        #     println("total: ",theta_mean[col_startidx]," ",theta_var[col_startidx])
        # end
        # for i in (col_startidx+1):(col_startidx+r_dim)
        #     mean_ratio = sum(theta[col_startidx,:].*theta[i,:].*weights) /(theta_mean[col_startidx]*theta_mean[i]) # weighted mean for parameters
        #     var_ratio = var_weighted(vec(theta[col_startidx,:].*theta[i,:]).-(theta_mean[col_startidx]*theta_mean[i]),weights)/(2 * theta_mean[col_startidx] * theta_var[i]) # scaled, weighted covar for parameters
        #     if verbose
        #         println("i=",i,": ",theta_mean[i]," ",theta_var[i]," ratios: ",mean_ratio, " ",var_ratio)
        #     end
        #     var_ratio  = var_ratio  >= one(var_ratio)  ? var_ratio  : one(var_ratio)
        #     tau_factor_indiv[i] = tau_factor*var_ratio
        # end
        # if verbose
        #     flush(stdout)
        # end

        #=
        println("mean= ",theta_mean)
        println("var= ",theta_var)
        println("tau= ",tau_factor_indiv)
        for i in 1:length(theta_mean)
            println("a= ",alpha(theta_mean[i],tau_factor*theta_var[i]), "  b= ",beta(theta_mean[i],tau_factor*theta_var[i]))
        end
        =#

        dist_arr = vcat(dist_arr, make_beta_transformed(theta[col_startidx,:], weights, xmin=0.0, xmax=max_col_rate, xmean=theta_mean[col_startidx]/max_col_rate, xvar=theta_var[col_startidx]/max_col_rate^2, tau_factor=tau_factor_indiv[col_startidx]), ContinuousDistribution[ make_beta(theta[i,:], weights, xmean=theta_mean[i], xvar=theta_var[i], tau_factor=tau_factor_indiv[i]) for i in (col_startidx+1):(col_startidx+r_dim)])
    end

dist = CompositeDist(dist_arr)
end

function make_proposal_dist_multidim_beta_dirichlet(pop::abc_population_type, tau_factor::Float64; verbose::Bool = false)
    make_proposal_dist_multidim_beta_dirichlet(pop.theta, pop.weights, tau_factor, verbose=verbose)
end


@testset "get_edge_normal" begin
    mmesh = Mesh(2,2,1.0)
    out = get_edge_normal(mmesh)
    bdedge = bcedge(mmesh)
    n1 = mmesh.nodes[bdedge[:,1], :]
    n2 = mmesh.nodes[bdedge[:,2], :]
    @test out≈[ -0.0  -1.0
        0.0   1.0
        0.0   1.0
        1.0   0.0
    -1.0  -0.0
    -1.0  -0.0
    -0.0  -1.0
        1.0   0.0]
end


@testset "Test Saving and Loading PLS1 Models" begin

	Xtr        = [1 -2; 2 -4; 4.0 -6]
	Ytr        = [-2; -4; -6.0]
	Xt         = [6 -8; 8 -10; 10.0 -12]
	model1    = PLSRegressor.fit(Xtr,Ytr,nfactors=2)
	pred1     = PLSRegressor.predict(model1,Xt)

	PLSRegressor.save(model1)
	model2    = PLSRegressor.load()

	pred2     = PLSRegressor.predict(model2,Xt)
    rm(PLSRegressor.MODEL_FILENAME)
	@test all(pred1 .== pred2)

end

@testset "PLS1 Pediction Tests (in sample)" begin

	@testset "Single Column Prediction Test" begin

		X        = [1; 2; 3.0][:,:]
		Y        = [1; 2; 3.0]
		model    = PLSRegressor.fit(X,Y,nfactors=1)
		pred     = PLSRegressor.predict(model,X)
		@test isequal(round.(pred),[1; 2; 3.0])

	end

	@testset "Constant Values Prediction Tests (Ax + b) | A=0, b=1 " begin

		X        = [1 3;2 1;3 2.0]
		Y        = [1; 1; 1.0]
		try
			PLSRegressor.fit(X,Y,nfactors=2)
		catch
			@test true
		end

	end

	@testset "Linear Prediction Tests " begin

		X        = [1 2; 2 4; 4.0 6]
		Y        = [2; 4; 6.0]
		model    = PLSRegressor.fit(X,Y,nfactors=2)
		pred     = PLSRegressor.predict(model,X)
		@test isequal(round.(pred),[2; 4; 6.0])

		X           = [1 -2; 2 -4; 4.0 -6]
		Y           = [-2; -4; -6.0]
		model       = PLSRegressor.fit(X,Y,nfactors=2)
		pred        = PLSRegressor.predict(model,X)
		@test isequal(round.(pred),[-2; -4; -6.0])

	end

	@testset "Linear Prediction Tests (Ax + b)" begin

		Xtr        = [1 2; 2 4; 4.0 6]
		Ytr        = [2; 4; 6.0]
		Xt         = [6 8; 8 10; 10.0 12] # same sample
		model    = PLSRegressor.fit(Xtr,Ytr,nfactors=2)
		pred     = PLSRegressor.predict(model,Xt)
		@test isequal(round.(pred),[8; 10; 12.0])

		Xtr        = [1 2; 2 4.0; 4.0 6; 6 8]
		Ytr        = [2; 4; 6.0; 8]
		Xt         = [1 2; 2 4.0] # a subsample

		model    = PLSRegressor.fit(Xtr,Ytr,nfactors=2,centralize=true)
		pred     = PLSRegressor.predict(model,Xt)
		@test isequal(round.(pred),[2; 4])

	end

end;

@testset "PLS1 Pediction Tests (out of sample)" begin

	@testset "Linear Prediction Tests (Ax + b) | A>0" begin

		Xtr        = [1 2; 2 4; 4.0 6]
		Ytr        = [2; 4; 6.0]
		Xt         = [6 8; 8 10; 10.0 12]
		model    = PLSRegressor.fit(Xtr,Ytr,nfactors=2)
		pred     = PLSRegressor.predict(model,Xt)
		@test isequal(round.(pred),[8; 10; 12.0])

		Xtr        = [1 2; 2 4; 4.0 6]
		Ytr        = [4; 6; 8.0]
		Xt         = [6 8; 8 10; 10.0 12]
		model    = PLSRegressor.fit(Xtr,Ytr,nfactors=2)
		pred     = PLSRegressor.predict(model,Xt)
		@test isequal(round.(pred),[10; 12; 14.0])

	end

	@testset "Linear Prediction Tests (Ax + b) | A<0" begin

		Xtr        = [1 -2; 2 -4; 4.0 -6]
		Ytr        = [-2; -4; -6.0]
		Xt         = [6 -8; 8 -10; 10.0 -12]
		model    = PLSRegressor.fit(Xtr,Ytr,nfactors=2)
		pred     = PLSRegressor.predict(model,Xt)
		@test isequal(round.(pred),[-8; -10; -12.0])

		Xtr        = [1 -2; 2 -4; 4.0 -6]
		Ytr        = [-4; -6; -8.0]
		Xt         = [6 -8; 8 -10; 10.0 -12]
		model    = PLSRegressor.fit(Xtr,Ytr,nfactors=2)
		pred     = PLSRegressor.predict(model,Xt)
		@test isequal(round.(pred),[-10; -12; -14.0])

	end

end;


using Test, BenchmarkTools
using StaticKernels

using StaticKernels: Window

BenchmarkTools.DEFAULT_PARAMETERS.seconds = 0.1

@testset "consistency" begin
    a = rand(10, 10)
    a2 = rand(10, 10)

    @testset "window" begin
        b = rand(size(a)...)
        c = CartesianIndex(2, 2)
        k = Kernel{(-1:1, -1:1)}(identity)
        w = Window{axes(k)}(k, a, c)

        # AbstractArray interface
        @test ndims(w) == ndims(k)
        @test eltype(w) == eltype(a)

        # getindex / setindex
        for i in CartesianIndices(w)
            @inferred w[i]
            @test w[i] == a[c + i]
            w[i] = b[c + i]
            @test a[c + i] == w[i]
        end

        aw = a[1:3, 1:3]
        # iteration
        @test_broken sum(v for v in w) == sum(aw)

        # mapreduce
        @test sum(w) == sum(aw)
        @test minimum(w) == minimum(aw)

        # wrong kernel axes
        let a = rand(1)
            k = Kernel{(0:0,)}(w -> w[1])
            @test_throws MethodError map(k, a)
        end
    end

    @testset "extended array" begin
        ae = extend(a, StaticKernels.ExtensionNothing())
        #@test isnothing(ae[0,0])
    end

    @testset "kernel" begin
        k = Kernel{(-1:1,-1:1)}(@inline function(w) sum(Tuple(w)) end)
        k2 = Kernel{(-1:1,-1:1)}(@inline function(w1, w2) sum(Tuple(w1)) + prod(Tuple(w2)) end)
        @test eltype(k, a) == eltype(a)
        @test eltype(k2, a, a2) == eltype(a)
    end

    @testset "map" begin
        k = Kernel{(1:1, -1:-1)}(w -> w[1, -1])
        @test map(k, a) == a

        k = Kernel{(-1:1,0:1)}(w -> w[-1,0] + w[1,1])
        @test map(k, a) == a[1:end-2,1:end-1] .+ a[3:end,2:end]

        k = Kernel{(-1:1,0:1)}((w1, w2) -> w1[-1,0] + w2[1,1])
        @test map(k, a, a2) == a[1:end-2,1:end-1] .+ a2[3:end,2:end]
    end

    @testset "mapreduce" begin
        k = Kernel{(-1:1,0:1)}(w -> w[-1,0] + w[1,1])
        @test mapreduce(k, +, a) ≈ sum(a[1:end-2,1:end-1] .+ a[3:end,2:end])

        k = Kernel{(-1:1,0:1)}((w1, w2) -> w1[-1,0] + w2[1,1])
        @test mapreduce(k, +, a, a2) ≈ sum(a[1:end-2,1:end-1] .+ a2[3:end,2:end])

        k = Kernel{(0:1, 0:0)}(w -> sum(Tuple(w)))
        @test sum(k, a) ≈ sum(a[1:end-1,:]) + sum(a[2:end,:])
    end

    @testset "kernel macro" begin
        b = vec(a)

        k1 = @kernel w -> w[0]
        k2 = Kernel{(0:0,)}(w -> w[0])
        @test map(k1, b) == map(k2, b)

        k1 = @kernel w -> w[1] + w[-2]
        k2 = Kernel{(-2:1,)}(w -> w[1] + w[-2])
        @test map(k1, b) == map(k2, b)

        k1 = @kernel w -> w[1,2] + w[-3,5]
        k2 = Kernel{(-3:1,2:5)}(w -> w[1,2] + w[-3,5])
        @test map(k1, a) == map(k2, a)

        @test_throws Exception @macroexpand @kernel w -> w
        @test_throws Exception @macroexpand @kernel w -> w[1] + w[2,3]
    end

    @testset "gradient" begin
        grad = Kernel{(0:1,0:1)}(w -> (w[1,0] - w[0,0], w[0,1] - w[0,0]))

        grada = map(grad, a)
        gx = axes(grad, a)
        @test diff(a, dims=1)[gx...] ≈ [x[1] for x in grada[gx...]]
        @test diff(a, dims=2)[gx...] ≈ [x[2] for x in grada[gx...]]
    end

    @testset "extensions" begin
        ae = extend(a, StaticKernels.ExtensionNone())
        k = Kernel{(-1:1, -1:1)}(w -> sum(Tuple(w)))
        @test size(map(k, ae)) == size(k, ae)

        ae = extend(a, StaticKernels.ExtensionNothing())
        k = Kernel{(-1:1, -1:1)}(w -> w[1,0] + w[0,0])
        @test_throws MethodError map(k, ae)
        k = Kernel{(-1:1, -1:1)}(w -> something(w[1,0], 0))
        @test size(map(k, ae)) == size(k, ae)
        @test all(map(k, ae)[end, :] .== 0)

        ae = extend(a, StaticKernels.ExtensionReplicate())
        k = Kernel{(-1:1, -1:1)}(w -> w[1,0])
        @test size(map(k, ae)) == size(k, ae)
        @test all(map(k, ae)[end, :] .== ae[end, :])

        ae = extend(a, StaticKernels.ExtensionCircular())
        k = Kernel{(-1:1, -1:1)}(w -> w[1,0])
        @test size(map(k, ae)) == size(k, ae)
        @test all(map(k, ae)[end, :] .== ae[begin, :])

        ae = extend(a, StaticKernels.ExtensionSymmetric())
        k = Kernel{(-1:1, -1:1)}(w -> w[1,0])
        @test size(map(k, ae)) == size(k, ae)
        @test all(map(k, ae)[end, :] .== ae[end-1, :])

        ae = extend(a, StaticKernels.ExtensionConstant(0.))
        k = Kernel{(-1:1, -1:1)}(w -> w[1,0])
        @test eltype(k, ae) == eltype(ae)
        @test size(map(k, ae)) == size(k, ae)
        @test all(map(k, ae)[end, :] .== 0)

        ae = extend(a, StaticKernels.ExtensionConstant(missing))
        k = Kernel{(-1:1, -1:1)}(w -> w[1,0])
        @test eltype(k, ae) == Union{Missing,eltype(ae)}
    end
end

@testset "type stability" begin
    a = rand(100)

    @testset "window" begin
        k = Kernel{(0:0,)}(w -> w)

        @test eltype(k, a) <: Window
        @test isconcretetype(eltype(k, a))
    end

    @testset "kernel evaluation" begin
        k = Kernel{(0:0,)}(w -> w[0])
        @test eltype(k, a) == eltype(a)

        k = Kernel{(0:0,)}((w1, w2) -> w1[0] + w2[0])
        @test eltype(k, a, a) == eltype(a)
        @test eltype(k, a, round.(Int, a)) == Base.promote_type(eltype(a), Int)

        k = Kernel{(0:0,)}(w -> Tuple(w))
        @test eltype(k, a) <: Tuple
        @test isconcretetype(eltype(k, a))
    end
end

@testset "memory allocations" begin
    a = rand(100)
    a2 = rand(100)
    ks = [
        (Kernel{(0:0,)}(w -> w[0]), StaticKernels.ExtensionNone()),
        (Kernel{(0:1,)}(w -> something(w[1], 0.)), StaticKernels.ExtensionNothing()),
        (Kernel{(0:1,)}(w -> w[1]), StaticKernels.ExtensionReplicate()),
        (Kernel{(0:1,)}(w -> w[1]), StaticKernels.ExtensionCircular()),
        (Kernel{(0:1,)}(w -> w[1]), StaticKernels.ExtensionSymmetric()),
        (Kernel{(0:1,)}(w -> w[1]), StaticKernels.ExtensionConstant(0.)),
        (Kernel{(0:1,)}(w -> Tuple(w)), StaticKernels.ExtensionNone())]

    @testset "map! $(x[2])" for x in ks
        k, extension = x
        ae = extend(a, extension)
        b = similar(ae, eltype(k, ae), size(k, ae))
        @test 0 == @ballocated map!($k, $b, $ae)
    end

    @testset "sum $(x[2])" for x in ks[1:6]
        k, extension = x
        ae = extend(a, extension)
        @test eltype(k, ae) == eltype(a)
        @test 0 == @ballocated sum($k, $ae)
    end

    @testset "multiargument" begin
        k = Kernel{(-1:1,)}((w1, w2) -> w1[-1] + w2[1])
        b = similar(a, eltype(k, a, a2), size(k, a))
        @test 0 == @ballocated map!($k, $b, $a, $a2)
        @test 0 == @ballocated mapreduce($k, +, $a, $a2)
    end
end

@testset "performance" begin
    a = rand(1000000)
    a2 = rand(1000, 1000)
    a2ext = extend(rand(1000, 1000), StaticKernels.ExtensionConstant(0))

    @testset "Base.diff" begin
        k = Kernel{(0:1,)}(@inline function(w) @inbounds w[1] - w[0] end)

        @test 1.3 > @belapsed(map($k, $a)) / @belapsed(diff($a))
    end

    @testset "Base.map" begin
        k = Kernel{(0:0,)}(@inline function(w) @inbounds w[0] end)
        b = similar(a, size(k, a))

        @test 1.1 > @belapsed(map!($k, $b, $a)) / @belapsed(map!(identity, $b, $a))
    end

    @testset "window mapreduce" begin
        k1 = Kernel{(-1:1,)}(@inline function(w) @inbounds sum(w) end)
        k2 = Kernel{(-1:1,)}(@inline function(w) @inbounds w[-1] + w[0] + w[1] end)
        b = similar(a, size(k1, a))

        @test 1.1 > @belapsed(map!($k2, $b, $a)) / @belapsed(map!($k1, $b, $a))
    end

    @testset "extension" begin
        k = Kernel{(-1:1,-1:1)}(
            @inline function(w) @inbounds w[0,-1] + w[-1,0] - 4*w[0,0] + w[1,0] + w[0,1] end)
        b2 = similar(a2, size(k, a2))
        b2ext = similar(a2ext, size(k, a2ext))

        @test 1.1 > @belapsed(map!($k, $b2ext, $a2ext)) / @belapsed(map!($k, $b2, $a2))
    end
end


function next_permutation{Container}(arr::Container)

	len=length(arr)
	i=len
	while i>1 && arr[i-1]>=arr[i]
		i-=1
	end
	if i<=1
		return false
	end
	begin
		j=len
		while arr[j]<=arr[i-1]
			j-=1
		end
		arr[i-1],arr[j]=arr[j],arr[i-1]
	end
	begin
	    j=len
	    while i<j
	    	arr[i],arr[j]=arr[j],arr[i]
	    	i+=1
	    	j-=1
	    end
	end

	    return true

	end

"""
# test
   a=collect(1:5)
   while next_permutation(a)
       println(a)
   end

"""


module CatalystTest
  using AlgebraicPetri
  using Catalyst
  using Test

  # Test with SIR model
  sir = PetriNet(3, (1,2)=>(2,2), (2)=>(3))
  sir_rxn = ReactionSystem(sir)
  @test sir_rxn isa ReactionSystem
end


"""
#/* compute Laplace coefficients and Leverrier derivative/
#          j
#     j   d     i
#    a   ---   b (a)
#          j    s
#        da
#
#   by series summation */
#/* Code due to Jack Wisdom */
"""
function laplace_wisdom(s::Rational,i::Integer,j::Integer,a::Number)
# function laplace_wisdom,s,i,j,a  IDL
# double laplace(double s, int i, int j, double a);  c

##define LAPLACE_EPS 1.0e-12
const LAPLACE_EPS = convert(eltype(a),1.0e-12)

#if (i lt 0) then i = -i
i=abs(i)

if j <= i     #/* compute first term in sum */
  factor4 = one(a)
  for k=0:j-1
    factor4 *= i - k
  end
  lap_coef_sum = factor4
  q0 = 0
else
  q0 = fld(j + 1 - i,2)
  lap_coef_sum = zero(a)
  factor4 = one(a)
end

#  /* compute factors for terms in lap_coef_sum */

factor1 = s
factor2 = s + i
factor3 = i + 1
for q=1:q0-1       #/* no contribution for q = 0 */
  factor1 *= s + q
  factor2 *= s + i + q
  factor3 *= i+q+1
end
if q0 > 1
  q=q0
else
  q=1
end
#println(j+1-i,q0)
term = a*a * factor1 * factor2 / (factor3 * q)

#  /* sum series */

while  (term*factor4) > LAPLACE_EPS
  factor4 = one(a)
  for k=0:j-1
    factor4 *= (2*q + i - k)
  end
  lap_coef_sum += term * factor4
  factor1 += 1
  factor2 += 1
  factor3 += 1
  q = q+1
  term *= a*a * factor1 * factor2 / (factor3 * q)
end

#  /* fix coefficient */

for k=0:i-1
  lap_coef_sum *= (s+k)/(k+1)
end

apower = (q0 <= 0) ?  i : 2*q0 + i - 2
lap_coef_sum *= 2 * a^apower
# Return the Laplace Coefficient:
return lap_coef_sum
end


# # tests for lasso: 1/2\|Ax-b\|^2 + λ \|x\|_1

@testset "Lasso ($T)" for T in [Float32, Float64, ComplexF32, ComplexF64]
    using Test
    using LinearAlgebra
    using CIAOAlgorithms
    using ProximalOperators
    using Base.Iterators: take
    using Random

    Random.seed!(0)
    R = real(T)

    # problem definition
    N, n = 6, 3 # A in R^{N x n}
    p = 2 # nonzeros in the solution

    y_star = rand(R, N)
    y_star ./= norm(y_star) # y^star
    C = rand(R, N, n) .* 2 .- 1
    CTy = abs.(C' * y_star)
    # indices with decreasing order by abs
    perm = sortperm(CTy, rev = true)

    rho, λ = R(10), R(1)
    alpha = zeros(T, n)
    for i = 1:n
        if i <= p
            alpha[perm[i]] = λ / CTy[perm[i]]
        else
            alpha[perm[i]] = (CTy[perm[i]] < 0.1 * λ) ? λ : λ * rand() / CTy[perm[i]]
        end
    end
    A = C * diagm(0 => alpha)   # scaling the columns of Cin
    # generate the solution
    x_star = zeros(T, n)
    for i = 1:n
        if i <= p
            x_star[perm[i]] = rand() * rho / sqrt(p) * sign(dot(A[:, perm[i]], y_star))
        end
    end
    b = A * x_star + y_star

    # cost function
    cost_lasso(x) = norm(A * x - b)^2 / 2 + λ * norm(x, 1)

    f_star = cost_lasso(x_star)

    # preparations for the solver
    F = Vector{LeastSquares}(undef, 0)
    L = Vector{R}(undef, 0)
    for i = 1:N
        tempA = A[i:i, :]
        f = LeastSquares(tempA, b[i:i], R(N))
        Lf = opnorm(tempA)^2 * N
        push!(F, f)
        push!(L, Lf)
    end
    g = NormL1(λ)
    x0 = zeros(T, n)

    maxit = 1000
    tol = 1e-4

    @testset "Finito" begin
        # sweeping 1, 2, 3 for randomined, cyclic and shuffled sampling strategies, respectively.

        ## test the solver
        # basic finito
        @testset "basic Finito" for sweeping in collect(1:3)
            solver = CIAOAlgorithms.Finito{R}(maxit = maxit, sweeping = sweeping)
            x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
            @test cost_lasso(x_finito) - f_star < tol
            @test eltype(x_finito) == T
        end

        # limited memory finito
        @testset "LFinito" for sweeping in collect(2:3)
            # @testset "cyclical" begin
            solver =
                CIAOAlgorithms.Finito{R}(maxit = maxit, sweeping = sweeping, LFinito = true)
            x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
            @test cost_lasso(x_finito) - f_star < tol
            @test eltype(x_finito) == T
        end

        # # adaptive variant
        @testset "adaptive finito" for sweeping in collect(1:3)
            solver = CIAOAlgorithms.Finito{R}(
                maxit = maxit,
                tol = R(1e-5),
                sweeping = sweeping,
                adaptive = true,
            )
            x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
            @test cost_lasso(x_finito) - f_star < tol
            @test eltype(x_finito) == T
        end

        # basic finito with minibatch
        vec_ref = [(1, 2), (2, 2), (3, 3)] # different samplings and batch sizes
        @testset "Finito_minibatch" for (sweeping, batch) in vec_ref
            solver = CIAOAlgorithms.Finito{R}(
                maxit = maxit,
                sweeping = sweeping,
                minibatch = (true, batch),
            )
            x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
            @test cost_lasso(x_finito) - f_star < tol
            @test eltype(x_finito) == T
        end

        # limited memory finito with minibatch
        vec_ref = [(2, 1), (2, 2), (3, 3)] # different samplings and batch sizes
        @testset "LFinito_minibatch" for (sweeping, batch) in vec_ref
            solver = CIAOAlgorithms.Finito{R}(
                maxit = maxit,
                sweeping = sweeping,
                LFinito = true,
                minibatch = (true, batch),
            )
            x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
            @test cost_lasso(x_finito) - f_star < tol
            @test eltype(x_finito) == T
        end

        # test with user defined stepsizes
        @testset "γ and L as scalars" begin
            @testset "randomized" begin
                γ = N / maximum(L)
                solver = CIAOAlgorithms.Finito{R}(maxit = maxit, γ = γ)
                x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
                @test cost_lasso(x_finito) - f_star < tol
            end
            @testset "cyclic" begin
                solver = CIAOAlgorithms.Finito{R}(maxit = maxit)
                x_finito, it_finito = solver(x0, F = F, g = g, L = maximum(L), N = N)
                @test cost_lasso(x_finito) - f_star < tol
            end
        end

        ## test the iterator
        vec_ref = [(1, false, false), (2, false, false), (3, false, true), (3, true, false)]
        @testset "the iterator" for (sweeping, LFinito, adaptive) in vec_ref
            solver = CIAOAlgorithms.Finito{R}(
                sweeping = sweeping,
                LFinito = LFinito,
                adaptive = adaptive,
            )
            iter = CIAOAlgorithms.iterator(solver, x0, F = F, g = g, L = L, N = N)
            @test iter.x0 === x0

            for state in take(iter, 2)
                @test solution(state) === state.z
                @test eltype(solution(state)) == T
            end
        end
    end

    @testset "SVRG" begin

        ## test the solver
        γ = 1 / (7 * maximum(L))
        @testset "SVRG-Base" begin
            solver = CIAOAlgorithms.SVRG{R}(maxit = maxit, γ = γ)
            x_SVRG, it_SVRG = solver(x0, F = F, g = g, N = N)
            @test cost_lasso(x_SVRG) - f_star < tol
            @test eltype(x_SVRG) == T
        end
        @testset "SVRG++" begin
            solver = CIAOAlgorithms.SVRG{R}(maxit = 16, γ = γ, m = 1, plus = true)
            x_SVRG, it_SVRG = solver(x0, F = F, g = g, N = N)
            @test cost_lasso(x_SVRG) - f_star < tol
            @test eltype(x_SVRG) == T
        end

        # test the iterator
        @testset "the iterator" begin
            solver = CIAOAlgorithms.SVRG{R}(γ = γ)
            iter = CIAOAlgorithms.iterator(solver, x0, F = F, g = g, N = N)
            @test iter.x0 === x0

            for state in take(iter, 2)
                @test solution(state) === state.z_full
                @test eltype(solution(state)) == T
            end
            next = iterate(iter) # next = (state, state)
            # one iteration with the solver
            solver = CIAOAlgorithms.SVRG{R}(γ = γ, maxit = 1)
            x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
            @test solution(next[2]) == x_finito
        end
    end

    @testset "SAGA" begin

        ## test the solver
        @testset "SAGA-Base" begin
            solver = CIAOAlgorithms.SAGA{R}(maxit = maxit)
            x_SAGA, it_SAGA = solver(x0, F = F, g = g, N = N, L = L)
            @test cost_lasso(x_SAGA) - f_star < tol
            @test eltype(x_SAGA) == T
        end
        @testset "SAGA-stepsize" begin
            γ = 1 / (3 * maximum(L))
            solver = CIAOAlgorithms.SAGA{R}(maxit = maxit, γ = γ)
            x_SAGA, it_SAGA = solver(x0, F = F, g = g, N = N)
            @test cost_lasso(x_SAGA) - f_star < tol
            @test eltype(x_SAGA) == T
        end

        # test the iterator
        @testset "the iterator" begin
            γ = 1 / (3 * maximum(L))
            solver = CIAOAlgorithms.SAGA{R}(γ = γ)
            iter = CIAOAlgorithms.iterator(solver, x0, F = F, g = g, N = N)
            @test iter.x0 === x0

            for state in take(iter, 2)
                @test solution(state) === state.z
                @test eltype(solution(state)) == T
            end
            next = iterate(iter) # next = (state, state)
            # one iteration with the solver
            solver = CIAOAlgorithms.SAGA{R}(γ = γ, maxit = 1)
            x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
            @test solution(next[2]) == x_finito
        end
    end

    @testset "SAG" begin
        # note that proximal SAG may not be theoretically convergent
        maxit = 10000
        ## test the solver
        @testset "SAG-Base" begin
            solver = CIAOAlgorithms.SAG(R, maxit = maxit)
            x_SAG, it_SAG = solver(x0, F = F, g = g, N = N, L = L)
            @test cost_lasso(x_SAG) - f_star < tol
            @test eltype(x_SAG) == T
        end
        @testset "SAG" begin
            γ = 1 / (16 * maximum(L))
            solver = CIAOAlgorithms.SAG(R, maxit = maxit, γ = γ)
            x_SAG, it_SAG = solver(x0, F = F, g = g, N = N)
            @test cost_lasso(x_SAG) - f_star < tol
            @test eltype(x_SAG) == T
        end

        # test the iterator
        @testset "the iterator" begin
            γ = 1 / (16 * maximum(L))
            solver = CIAOAlgorithms.SAG(R, γ = γ)
            iter = CIAOAlgorithms.iterator(solver, x0, F = F, g = g, N = N)
            @test iter.x0 === x0

            for state in take(iter, 2)
                @test solution(state) === state.z
                @test eltype(solution(state)) == T
            end
            next = iterate(iter) # next = (state, state)
            # one iteration with the solver
            solver = CIAOAlgorithms.SAG(R, γ = γ, maxit = 1)
            x_finito, it_finito = solver(x0, F = F, g = g, L = L, N = N)
            @test solution(next[2]) == x_finito
        end
    end
end


"""
    α,α⁺ = wigner(state <: State,N)

Generate `N` samples from wigner phase-space distribution for `state`.

Moments of the Wigner distribution generate symmetrically ordered quantum operator averages.
"""
function wigner(state::Coherent,N)
    @unpack β = state
    α = β .+ randnc(N)/sqrt(2)
    α⁺ = conj(α)
    return α, α⁺
end

"""
    α,ᾱ = husimiQ(state <: State,N)

Generate `N` samples from the Husimi-Q phase-space distribution for `state`.

Moments of the Husimi-Q distribution generate quantum operator averages that are anti-normally ordered.
"""
function husimiQ(state::Coherent,N)
    @unpack β = state
    α = β .+ randnc(N)
    α⁺ = conj(α)
    return α, α⁺
end

"""
    α,α⁺ = positiveP(state <: State,N)

Generate `N` samples from the positive-P (+P) phase-space distribution for `state`.

Moments of the +P distribution generate quantum operator averages that are normally ordered.

In general the two random variates α,α⁺ are statistically independent for the +P distribution.
"""
function positiveP(state::T,N) where T
    μ,μ⁺ = husimiQ(state,N)
    γ = randnc(N)
    α = μ .+ γ
    α⁺ = conj.(μ .- γ)
    return α, α⁺
end

function positiveP(state::Coherent,N)
    @unpack β = state
    α = β*ones(N)
    α⁺ = conj(α)
    return α,α⁺
end

"""
    α,α⁺ = glauberP(state <: State,N)

Generate `N` samples from the Glauber-Sudarshan-P phase-space distribution for `state`.

Moments of the Glauber-Sudarshan-P distribution generate quantum operator averages that are normally ordered.
"""
glauberP(state::Coherent,N) = positiveP(state,N)

function glauberP(state::Thermal,N)
    @unpack β,n̄ = state
    α = β .+ sqrt(n̄)*randnc(N)
    α⁺ = conj(α)
    return α, α⁺
end

function wigner(state::Thermal,N)
    @unpack β,n̄ = state
    α = β .+ sqrt(n̄ + .5)*randnc(N)
    α⁺ = conj(α)
    return α, α⁺
end

function husimiQ(state::Thermal,N)
    @unpack β,n̄ = state
    α = β .+ sqrt(n̄ + 1.0)*randnc(N)
    α⁺ = conj(α)
    return α, α⁺
end

function husimiQ(state::Squeezed,N)
    @unpack β,ϵ = state
    r = abs(ϵ)
    ϕ = angle(ϵ)/2
    ν = sqrt(exp(-r)*cosh(r)/2)*randn(N) .+ im*sqrt(exp(r)*cosh(r)/2)*randn(N)
    α = β .+ exp(im*ϕ)*ν
    α⁺ = conj(α)
    return α, α⁺
end

function wigner(state::Squeezed,N)
    @unpack β,ϵ = state
    r = abs(ϵ)
    ϕ = angle(ϵ)/2
    α = β .+ 0.5*(randn(N)*exp(-r) .+ im*randn(N)*exp(r))*exp(-im*ϕ)
    α⁺ = conj(α)
    return α, α⁺
end

function husimiQ(state::Crescent,N)
    @unpack β,ϵ,q = state
    r = abs(ϵ)
    ϕ = angle(ϵ)/2
    α = β .+ (randn(N)*exp(-r) .+ im*randn(N)*exp(r))*exp(-im*ϕ)/sqrt(2)
    α = α.*exp.(im*q*randn(N))
    α⁺ = conj(α)
    return α,α⁺
end

function wigner(state::Crescent,N)
    @unpack β,ϵ,q = state
    r = abs(ϵ)
    ϕ = angle(ϵ)/2
    α = β .+ 0.5*(randn(N)*exp(-r) .+ im*randn(N)*exp(r))*exp(-im*ϕ)
    α = α.*exp.(im*q*randn(N))
    α⁺ = conj(α)
    return α, α⁺
end

function husimiQ(state::Fock,N)
    @unpack n = state
    d = Gamma(n+1,1)
    z = rand(d,N)
    α = sqrt.(z).*exp.(2π*im*rand(N))
    α⁺ = conj(α)
    return α, α⁺
end

function wigner(state::Fock,N)
    @unpack n = state
    n < 10 && @warn "Fock state sampling for W is only valid for n ≫ 1."
    p = 0.5*sqrt(2*n+1+2*sqrt(n^2+n))
    q = 1/(4*p)
    α = (p .+ q*randn(N)).*exp.(2π*im*rand(N))
    α⁺ = conj(α)
    return α, α⁺
end

"""
    α,α⁺ = positiveW(state <: State,N)

Generate `N` samples from the positive-W (+W) phase-space distribution for `state`.

Implemented states are

- `Fock(N)`

The moments of the +W distribution generate quantum operator averages that are symmmetrically ordered.
"""
function positiveW(state::Fock,N)
    @unpack n = state
    if n<=320
    x1 = max(0,sqrt(n)-5); x2 = sqrt(n)+5
    (n==0||n==1) ? Pmax=0.71 : Pmax=0.6
    z = reject(x->plaguerre.(x,n),[x1,x2],N,Pmax)
    μ = z.*exp.(2π*im*rand(N))
    γ = randnc(N)
    α = μ .+ γ
    α⁺ = conj(μ .- γ)
    return α, α⁺
    else
    x1 = max(0,sqrt(n)-5); x2 = sqrt(n)+5
    z = reject(x->plaguerre_asymptotic.(x,n),[x1,x2],N,0.6)
    μ = z.*exp.(2π*im*rand(N))
    γ = randnc(N)
    α = μ .+ γ
    α⁺ = conj(μ .- γ)
    return α, α⁺
end
end

function glauberP(state::Bogoliubov,N)
    @unpack u,v,n̄ = state
    β,β⁺ = glauberP(Thermal(0.0,n̄),N)
    α = u*β + conj(v)*β⁺
    α⁺ = conj.(α)
    return α,α⁺
end

function wigner(state::Bogoliubov,N)
    @unpack u,v,n̄ = state
    β,β⁺ = wigner(Thermal(0.0,n̄),N)
    α = u*β + conj(v)*β⁺
    α⁺ = conj.(α)
    return α,α⁺
end

function husimiQ(state::Bogoliubov,N)
    @unpack u,v,n̄ = state
    β,β⁺ = wigner(Thermal(0.0,n̄),N)
    α = u.*β .+ conj(v).*β⁺ .+ randnc(N)/sqrt(2)  # convolve with Gaussian
    α⁺ = conj.(α)
    return α,α⁺
end

# #TODO
# function positiveW(state::Coherent,N)
#     @unpack β = state
#     α = β .+ randnc(N)/sqrt(2)
#     α⁺ = conj(α)
#     return α, α⁺
# end


# File: fSkew.jl
# Author:
# Date: 29-June-2020

# SKEW
function m3 = fSkew(x,flag,f)
# --------------------------------------------------------------------------
# x:     returns
# flag:  0 = sample, 1 = population
# f:     reporting frequency
# m3:    skew (sample or population)
# --------------------------------------------------------------------------

    m3 = skewness(x,flag)./sqrt(f);
end


@testset "Algebraic Connectivity: Optimal solution tests" begin

    file_path = joinpath(@__DIR__,"..", "examples/solver.jl")
    include(file_path)

    params = Dict{String, Any}(
        "num_nodes" => 5,
        "instance" => 1,
        "data_type" => "old",

        "solution_type" => "exact",
        "presolve" => true,
        "optimizer_log" => true,
        "relax_integrality" => false
    )

    result_lo = LaplacianOpt.run_LOpt_model(params, glpk_optimizer)

    @test result_lo["termination_status"] == MOI.OPTIMAL
    @test result_lo["primal_status"] == MOI.FEASIBLE_POINT
    @test isapprox(result_lo["objective"], 11.1592, atol=1E-4)
    @test isapprox(result_lo["solution"]["z_var"][1,4], 1.0)
    @test isapprox(result_lo["solution"]["z_var"][4,1], 1.0)
    @test isapprox(result_lo["solution"]["z_var"][2,4], 1.0)
    @test isapprox(result_lo["solution"]["z_var"][4,2], 1.0)
    @test isapprox(result_lo["solution"]["z_var"][3,4], 1.0)
    @test isapprox(result_lo["solution"]["z_var"][4,3], 1.0)
    @test isapprox(result_lo["solution"]["z_var"][3,5], 1.0)
    @test isapprox(result_lo["solution"]["z_var"][5,3], 1.0)

    @test LA.issymmetric(result_lo["solution"]["W_var"])

    L_val = result_lo["solution"]["W_var"]

    for i=1:params["num_nodes"]
        L_val[i,i] += result_lo["solution"]["γ_var"] * (params["num_nodes"] - 1)/params["num_nodes"]
    end

    for i=1:(params["num_nodes"] - 1)
        for j=(i+1):params["num_nodes"]
            L_val[i,j] -= result_lo["solution"]["γ_var"] / params["num_nodes"]
            L_val[j,i] = L_val[i,j]
        end
    end

    @test isapprox(LA.eigvals(L_val)[2], result_lo["objective"])
    @test isapprox(LA.eigvals(L_val)[2], result_lo["solution"]["γ_var"])
end

@testset "Max Span Tree: Optimal solution tests" begin

    file_path = joinpath(@__DIR__,"..", "examples/solver.jl")
    include(file_path)

    params = Dict{String, Any}(
        "num_nodes" => 5,
        "instance" => 1
        )

    result_mst = LaplacianOpt.run_MaxSpanTree_model(params, glpk_optimizer)

    @test result_mst["termination_status"] == MOI.OPTIMAL
    @test result_mst["primal_status"] == MOI.FEASIBLE_POINT
    @test isapprox(result_mst["objective"], 113.7588, atol=1E-4)
    @test isapprox(result_mst["solution"]["z_var"][1,2], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][2,1], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][2,5], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][5,2], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][3,4], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][4,3], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][3,5], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][5,3], 1.0)

end

@testset "Max Span Tree: Lazy callback tests" begin

    file_path = joinpath(@__DIR__,"..", "examples/solver.jl")
    include(file_path)

    params = Dict{String, Any}(
        "num_nodes" => 15,
        "instance" => 5,
        "data_type" => "old",
        "eigen_cuts_full" => false,
        "topology_flow_cuts" => true
        )

    result_mst = LaplacianOpt.run_MaxSpanTree_model(params, glpk_optimizer, lazy_callback=true)

    @test result_mst["termination_status"] == MOI.OPTIMAL
    @test result_mst["primal_status"] == MOI.FEASIBLE_POINT
    @test isapprox(result_mst["objective"], 3576.56202331, atol=1E-6)
    @test isapprox(result_mst["solution"]["z_var"][1,4], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][2,4], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][3,4], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][4,15], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][7,15], 1.0)
    @test isapprox(result_mst["solution"]["z_var"][8,15], 1.0)
end

@testset "SOC relaxations - 1: constraint_soc_linearized_cuts_on_2minors" begin

    file_path = joinpath(@__DIR__,"..", "examples/solver.jl")
    include(file_path)

    params_1 = Dict{String, Any}(
        "num_nodes" => 5,
        "instance" => 5,
        "eigen_cuts_full" => false,
        "soc_linearized_cuts" => true,
        "topology_flow_cuts" => true
    )

    result_1 = LaplacianOpt.run_LOpt_model(params_1, glpk_optimizer)

    @test result_1["termination_status"] == MOI.OPTIMAL
    @test result_1["primal_status"] == MOI.FEASIBLE_POINT
    @test isapprox(result_1["objective"], 15.992792245, atol=1E-6)
    @test isapprox(result_1["solution"]["z_var"][1,5], 1.0)
    @test isapprox(result_1["solution"]["z_var"][2,5], 1.0)
    @test isapprox(result_1["solution"]["z_var"][3,4], 1.0)
    @test isapprox(result_1["solution"]["z_var"][3,5], 1.0)
    @test isapprox(result_1["solution"]["z_var"][4,3], 1.0)

    params_2 = Dict{String, Any}(
        "num_nodes" => 5,
        "instance" => 5,
        "eigen_cuts_full" => true,
        "soc_linearized_cuts" => true
    )

    result_2 = LaplacianOpt.run_LOpt_model(params_2, glpk_optimizer)

    @test result_2["termination_status"] == MOI.OPTIMAL
    @test result_2["primal_status"] == MOI.FEASIBLE_POINT
    @test result_2["objective"] <= result_1["objective"]
    @test isapprox(result_2["objective"], 13.065372790, atol=1E-6)
    @test isapprox(result_1["solution"]["z_var"], result_2["solution"]["z_var"], atol = 1E-6)
end


# Text provided under a Creative Commons Attribution license, CC-BY.  All code is made available under the FSF-approved BSD-3 license.  (c) Lorena A. Barba, Gilbert F. Forsyth 2017. Thanks to NSF for support via CAREER award #1149784.
# [@LorenaABarba](https://twitter.com/LorenaABarba)
# Translation to Julia by [@miguelraz_](https://twitter.com/miguelraz_)

# 12 steps to Navier–Stokes
# =====
# ***

# This lesson complements the first interactive module of the online [CFD Python](https://github.com/barbagroup/CFDPython) class,
# by Prof. Lorena A. Barba, called **12 Steps to Navier–Stokes.** The interactive module starts with
# simple exercises in 1D that at first use little of the power of Python.
# We now present some new ways of doing the same things that are more efficient and produce prettier code, but in Julia 🚀 ,
# which is hosted at the [CFD Julia](TODO LINK) repo

# This lesson was written with BU graduate student Gilbert Forsyth.

# Defining Functions in Julia
# ----

# In steps 1 through 8, we wrote Python code that is meant to run from top to bottom.
# We were able to reuse code (to great effect!) by copying and pasting, to incrementally build a solver for the Burgers' equation.
# But moving forward there are more efficient ways to write our Python codes.
# In this lesson, we are going to introduce *function definitions*,
# which will allow us more flexibility in reusing and also in organizing our code.

# We'll begin with a trivial example: a function which adds two numbers.

# To create a function in Julia, we start with the following function block:

function simpleadd(a,b)
    return a + b
end

# The `return` statement tells Julia what data to return in response to being called.
# In Julia, if you omit the `return` inside the body of a funciton, the last evaluated expression is implicitly returned.
# Now we can try calling our `simpleadd` function:

simpleadd(3, 4)

# For simple functions, we can use the inline function syntax:
simpleadd(a,b) = a + b

# Where both are equivalent.

# Of course, there can be much more happening between the `function` line and the `return` line.
# In this way, one can build code in a *modular* way.
# Let's try a function which returns the `n`-th number in the Fibonacci sequence.

function fibonacci(n)
    a, b = 0, 1
    for i in 1:n
        a, b = b, a + b
    end
    return a
end

fibonacci(7)

# Once defined, the function `fibonacci` can be called like any of the built-in Python functions that we've already used.
# For exmaple, we might want to print out the Fibonacci sequence up through the `n`-th value:

for n in 1:10
    println(fibonacci(n))
end

# We will use the capacity of defining our own functions in Julia to help us build code that is easier to reuse, easier to maintain, easier to share!

##### Exercise

# (Pending.)

# Learn more
# -----
# ***

# Remember our short detour on using [array operations with Julia](./07_Step_5.jl)?

# Well, there are a few more ways to make your scientific codes in Julia run faster.
# The original tutorial said...
# > We recommend the article on the Technical Discovery blog about [Speeding Up Python](http://technicaldiscovery.blogspot.com/2011/06/speeding-up-python-numpy-cython-and.html) (June 20, 2011), which talks about NumPy, Cython and Weave. It uses as example the Laplace equation (which we will solve in [Step 9](./12_Step_9.ipynb)) and makes neat use of defined functions.
#  We *encourage* instead that you read [the performance tips section of the Julia manual](https://docs.julialang.org/en/v1/manual/performance-tips/).

# From the original tutorial, it says...
# > But a recent new way to get fast Python codes is [Numba](http://numba.pydata.org).
# Which is actually a good comparison to how Julia's JIT operates internally, but unfortunately breaks down in many cases
# [that we are interested in](http://www.stochasticlifestyle.com/why-numba-and-cython-are-not-substitutes-for-julia/)


function is_orthogonal(S, tol=1e-6)
    n = LinearAlgebra.checksquare(S)
    for i in 1:n
        for j in 1:n
            if i != j
                s = LinearAlgebra.dot(S[:, i], S[:, j])
                if abs(s) > tol
                    return false
                end
            end
        end
    end
    return true
end

function _projcoef(A, i, v, w = A[i, :])
    # `LinearAlgebra.dot(v, w)` would not work as it is computing the adjoint
    # of `v`.
    # If `v = α * w + ...` then
    # `dot(w, v) = conj(w) (α * w + ...) = α`
    # However,
    # `dot(v, w) = conj(α * w + ...) w = conj(α)`
    return LinearAlgebra.dot(w, v) / LinearAlgebra.dot(w, w)
end

struct _OrthogonalMatrix end
struct _RowEchelonMatrix end

function __linsolve(A::AbstractMatrix{T}, b::Vector{T}, ::_OrthogonalMatrix) where {T}
    return map(1:size(A, 1)) do i
        return _projcoef(A, i, b)
    end
end

function __linsolve(A::Matrix{T}, b::Vector{T}, ::_RowEchelonMatrix) where {T}
    j = 0
    return map(1:size(A, 1)) do i
        while j < size(A, 2)
            j += 1
            if isone(A[i, j]) && all(k -> k == i || iszero(A[k, j]), 1:size(A, 1))
                return b[j]
            end
        end
        error("Not in row_echelon_form, cannot find for `$i`th entry.")
    end
end

using SparseArrays
function _linsolve(A::AbstractMatrix{T}, b::Vector{T}, form) where {T}
    x = __linsolve(A, b, form)
    @assert transpose(A) * x ≈ b
    return x
end


using SparseArrays

abstract type GraphDataType end

mutable struct AdjacencyList <: GraphDataType
    flist::Vector{Array{Int64}}
    flist_w::Vector{Array{Float64}}
    blist::Vector{Array{Int64}}
    blist_w::Vector{Array{Float64}}

    directed::Bool
end

function Base.show(io::IO, p::AdjacencyList)
    dire = "directed"
    if !p.directed
        dire = "undirected"
    end

    print(io, "AdjacencyList: $dire")
end

function AdjacencyList(mat::SparseMatrixCSC)

    flist = Array{Int64}[]
    flist_w = Array{Float64}[]
    blist = Array{Int64}[]
    blist_w = Array{Float64}[]
    directed = false

    for i = 1:mat.m
        vec = findall(x -> x > 0, mat[i, :])
        push!(flist, vec)
        push!(flist_w, mat[i, vec'])
    end

    for i = 1:mat.n
        vec = findall(x -> x > 0, mat[:, i])
        push!(blist, vec)
        push!(blist_w, mat[vec', i])
    end

    if mat != mat'
        directed = true
    end

    return AdjacencyList(flist, flist_w, blist, blist_w, directed)

end

import Graphs.LinAlg.adjacency_matrix
function adjacency_matrix(adj_list::AdjacencyList)
    n = length(adj_list.flist)
    mat = zeros(n, n)
    for i = 1:n
        row = adj_list.flist[i]
        j = 1
        for r in row
            mat[i, r] = adj_list.flist_w[i][j]
            j = j + 1
        end
    end

    return SparseMatrixCSC(mat)

end

function edge_list(adj_matrix::SparseMatrixCSC)
    b = []
    weigth = 0
    for c in 1:adj_matrix.m
        for r in 1:adj_matrix.n
            weight = adj_matrix[r, c]
            if weight > 0
                push!(b, [r, c, weight])
            end
        end
    end
    b
end

function edge_list_to_matrix(adj_list)

    dim = 0.0
    for vec in adj_list
        m = maximum([vec[1], vec[2]])
        if m > dim
            dim = m
        end
    end
    dim = trunc(Int, dim)

    adj_mat = zeros(dim, dim)
    for node in adj_list
        if length(node) > 2
            adj_mat[trunc(Int, node[1]), trunc(Int, node[2])] = node[3]
        else
            adj_mat[trunc(Int, node[1]), trunc(Int, node[2])] = 1
        end
    end
    return SparseMatrixCSC(adj_mat)
end


using Plots
using Statistics
using StatsBase: sample
using Distributions
using Revise
using EvoTrees

features = rand(Int(1.25e4), 1)
# prepare a dataset
# features = rand(100, 10)
X = features
Y = randn(size(X, 1)) .* 0.1
Y[X[:,1] .< 0.2] .*= 2
Y[(X[:,1] .>= 0.4) .& (X[:,1] .< 0.6)] .*= 5
Y[(X[:,1] .>= 0.9)] .*= 5
𝑖 = collect(1:size(X,1))

Y .*= 0.01

# train-eval split
𝑖_sample = sample(𝑖, size(𝑖, 1), replace = false)
train_size = 0.8
𝑖_train = 𝑖_sample[1:floor(Int, train_size * size(𝑖, 1))]
𝑖_eval = 𝑖_sample[floor(Int, train_size * size(𝑖, 1))+1:end]

X_train, X_eval = X[𝑖_train, :], X[𝑖_eval, :]
Y_train, Y_eval = Y[𝑖_train], Y[𝑖_eval]

# train model
params1 = EvoTreeGaussian(
    loss=:gaussian, metric=:gaussian,
    nrounds=200,
    λ = 1.0, γ=1.0, η=0.5,
    max_depth = 4, min_weight = 100.0,
    rowsample=1.0, colsample=1.0, nbins=64)

@time model = fit_evotree(params1, X_train, Y_train, X_eval=X_eval, Y_eval=Y_eval, print_every_n = 10);
# @time model = fit_evotree(params1, X_train, Y_train, print_every_n = 10);
@time pred_train = EvoTrees.predict(model, X_train)
@time pred_train_gauss = EvoTrees.predict(params1, model, X_train)

pred_gauss = [Distributions.Normal(pred_train[i,1], pred_train[i,2]) for i in 1:size(pred_train,1)]
pred_q90 = quantile.(pred_gauss, 0.9)
pred_q10 = quantile.(pred_gauss, 0.1)

mean(Y_train .< pred_q90)
mean(Y_train .< pred_q10)

x_perm = sortperm(X_train[:,1])
plot(X_train[:, 1], Y_train, ms = 1, mcolor = "gray", mscolor = "lightgray", background_color = RGB(1, 1, 1), seriestype=:scatter, xaxis = ("feature"), yaxis = ("target"), legend = true, label = "")
plot!(X_train[:,1][x_perm], pred_train[x_perm, 1], color = "navy", linewidth = 1.5, label = "mu")
plot!(X_train[:,1][x_perm], pred_train[x_perm, 2], color = "blue", linewidth = 1.5, label = "sigma")
plot!(X_train[:,1][x_perm], pred_q10[x_perm, 1], color = "darkred", linewidth = 1.5, label = "q10")
plot!(X_train[:,1][x_perm], pred_q90[x_perm, 1], color = "green", linewidth = 1.5, label = "q90")
savefig("figures/regression_gaussian_v1.png")

# compare with zygote
using Zygote

pred = [0.0, log(1.0)]
target = 0.1

δ1 = (target - pred[1]) / max(1e-8, exp(2*pred[2]))
δ2 = (1 - (pred[1] - target)^2 / max(1e-8, exp(2*pred[2])))

δ²1 = 1 / max(1e-8, exp(2*pred[2]))
δ²2 = 2 / max(1e-8, exp(2*pred[2])) * (pred[1] - target)^2

lpdf(x,μ,σ) = -log(σ) - log(2π)/2 - 1/2*((x-μ)/σ)^2
lpdf(0, pred[1], pred[2])

lpdf2(x,μ,lσ) = -log(exp(lσ)) - log(2π)/2 - 1/2*((x-μ)/exp(lσ))^2
lpdf2(0, pred[1], pred[2])

n1 = Normal(0, 1)
Distributions.logpdf(n1, 0)

# gradient(lpdf, target, pred[1], pred[2])[2:end]
gradient(lpdf2, target, pred[1], pred[2])[2:end]
Zygote.hessian(lpdf2, target, pred[1], pred[2])

gradient_lpdf(x,pred) = gradient(lpdf2, x, pred[1], pred[2])[3]
hessian_lpdf(x,pred) = gradient(gradient_lpdf, x, pred)[1]
gradient_lpdf(target, pred)
hessian_lpdf(target, pred)


using Distributions
using Test, ForwardDiff

# Currently, most of the tests for NegativeBinomail are in the "ref" folder.
# Eventually, we might want to consolidate the tests here

mydiffp(r, p, k) = r/p - k/(1 - p)

@testset "NegativeBinomial r=$r, p=$p, k=$k" for
    p in exp10.(-10:0) .- eps(), # avoid p==1 since it's not differentiable
        r in exp10.(range(-10, stop=2, length=25)),
            k in (0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024)

    @test ForwardDiff.derivative(_p -> logpdf(NegativeBinomial(r, _p), k), p) ≈ mydiffp(r, p, k) rtol=1e-12 atol=1e-12
end

@testset "Check the corner case p==1" begin
    @test logpdf(NegativeBinomial(0.5, 1.0), 0) === 0.0
    @test logpdf(NegativeBinomial(0.5, 1.0), 1) === -Inf
end


# Source for Cartesian package. License is MIT https://github.com/ja72/Cartesian.jl/blob/master/LICENSE

"""

Definitions for cartesian vectors and matrices used in 3D mechanics.
The repository is located at https://github.com/ja72/Cartesian.jl

    $(EXPORTS)

    $(SIGNATURES)

    $(METHODLIST)

"""
module Cartesian
using LinearAlgebra
using StaticArrays
using Rotations
using DocStringExtensions

using Base: @propagate_inbounds

import Base.convert, Base.*
import LinearAlgebra.cross, LinearAlgebra.dot, LinearAlgebra.normalize, LinearAlgebra.norm

export
    Vector3,
    Matrix3,
    dot,
    cross,
    cross2,
    slerp,
    outer,
    inv,
    solve

include("Vector3.jl")
include("Matrix3.jl")

end


include("Misc/acb_root_ctx.jl")
include("Misc/fmpr.jl")
include("Misc/FactoredElem.jl")
include("Misc/Integer.jl")
include("Misc/coprime.jl")
include("Misc/Magma.jl")
include("Misc/Matrix.jl")
include("Misc/NumberField.jl")
include("Misc/Poly.jl")
include("Misc/Residue.jl")
include("Misc/ResidueRingPoly.jl")
include("Misc/pAdic.jl")
include("Misc/FiniteField.jl")
include("Misc/Plesken.jl")
include("Misc/Arb.jl")
include("Misc/psibound.jl")
include("Misc/AbelianGrp.jl")
include("Misc/UnitsModM.jl")
include("Misc/ZZModUInt.jl")


@with_kw struct ArmijoGoldstein <: ManifoldLinesearch
    ω₁::Float64 = 1e-4
    ω₂::Float64 = 0.99
    τₑ::Float64 = 1.5        # expansion factor when interval is unbounded
    maxit::Int = 50
end

function linesearch(ls::ArmijoGoldstein, state, linesearchstate, pb::CompositeProblem, M::Manifold, x, ∇fₘ, d; hist=Dict())
    @unpack ω₁, ω₂, τₑ = ls

    # TODO: replace 0 by -ϵ, ϵ>0 for descent dirction criterion.
    # TODO: extrapolation step for gradient...
    if inner(M, x, ∇fₘ, d) / (norm(M, x, ∇fₘ)*norm(M, x, d)) > 0
        @warn "ArmijoGoldstein: non negative direction provided, taking opposite direction." inner(M, x, ∇fₘ, d), norm(M, x, ∇fₘ), norm(M, x, d)
        d = -d
    end

    α = 1
    α_low, α_up = 0, Inf

    F_x = F(pb, x)
    F_cand = Inf
    x_cand = deepcopy(x)
    dh_0 = inner(M, x, ∇fₘ, d)

    it_ls = 0
    validpoint = false
    while !validpoint
        x_cand = retract(M, x, α*d); state.ncalls_retr += 1
        F_cand = F(pb, x_cand); state.ncalls_f += 1; state.ncalls_g += 1

        if F_x > F_cand > F_x - 3*eps(F_cand)
            @warn "Linesearch: reached function conditionning of funtion here, #it ls: $it_ls"
            # @printf "F_x        : %.16e\n" F_x
            # @printf "F_cand     : %.16e\n" F_cand
            # @printf "eps(F_cand): %.16e\n" eps(F_cand)
            break
        end

        if (F_x + ω₁*α*dh_0 ≥ F_cand) && (F_cand ≥ F_x + ω₂*α*dh_0)
            # α = 1 should be accepted for superlinear convergence.
            validpoint = true
            break
        end

        if F_x + ω₁*α*dh_0 < F_cand
            α_up = α

            α = α_up*0.1 + α_low*0.9    # NOTE : Might do interpolation here?
        elseif F_cand < F_x + ω₂*α*dh_0
            α_low = α

            if isinf(α_up)
                α = τₑ * α_low
            else
                α = α_up*0.9 + α_low*0.1
            end
        else
            validpoint = true
        end

        it_ls += 1
        (it_ls > ls.maxit) && (break)
    end

    if F_cand > F_x + ω₁*α*dh_0
        @debug "Linesearch: no suficient decrease" F_cand F_x + ω₁*α*dh_0
    end
    if F_x + ω₂*α*dh_0 > F_cand
        @debug "Linesearch: too small step" F_x + ω₂*α*dh_0 F_cand
    end

    state.niter_manls += it_ls
    hist[:niter] = it_ls
    hist[:end_fval] = F_cand

    return x_cand
end


using DataStructures

const data = parse.(Int, reduce(hcat, collect.(readlines())))

neighbours(pos; expand = 1) =
    filter([
        CartesianIndex(pos.I .- n)
        for n ∈ ((-1, 0), (1, 0), (0, -1), (0, 1))
    ]) do I
        1 <= I.I[1] <= size(data, 1) * expand && 1 <= I.I[2] <= size(data, 2) * expand
    end

div1mod1(x, y) = (div(x, y) - (mod(x, y) == 0 ? 1 : 0), mod1(x, y))

function get_val(data, pos::CartesianIndex)
    qx, rx = div1mod1(pos[1], size(data, 1))
    qy, ry = div1mod1(pos[2], size(data, 2))

    reduced_pos = CartesianIndex(rx, ry)
    mod1(data[reduced_pos] + qx + qy, 9)
end

function dijkstra(start, finish; expand = 1)
    q = PriorityQueue{CartesianIndex,Int}(Base.Order.Forward)
    seen = Set{CartesianIndex}()

    enqueue!(q, start => 0)

    while !isempty(q)
        pos, d = dequeue_pair!(q)
        if pos ∈ seen
            continue
        else
            push!(seen, pos)
        end
        if pos == finish
            return d
        end
        for nei in neighbours(pos; expand)
            if nei ∉ seen
                if nei ∉ keys(q)
                    enqueue!(q, nei => d + get_val(data, nei))
                else
                    q[nei] = min(q[nei], d + get_val(data, nei))
                end
            end
        end
    end
    return nothing
end

# Part One
dijkstra(CartesianIndex(1, 1), CartesianIndex(size(data)...)) |> println

# Part Two
dijkstra(CartesianIndex(1, 1), CartesianIndex(size(data)...) * 5; expand = 5) |> println


struct BradDeconvolveR <: EmpiricalBayesEstimator
    prior_grid::Vector{Float64}
    g_prior::Vector{Float64}
    g_bias::Vector{Float64}
    g_cov::Matrix{Float64}
end

struct SmoothedBradDeconvolveR <: EmpiricalBayesEstimator
    unsmoothed::BradDeconvolveR
    prior_σ::Float64
    prior_grid::Vector{Float64}
end

function fit(::Type{BradDeconvolveR}, Xs; c0=0.01,
           deltaAt = :nothing, pDegree=5, prior_grid=collect(-3.6:0.02:3.6))
    R"library(deconvolveR)"
    if deltaAt == :nothing
        R"result <- deconv(tau = $(prior_grid), X = $(Xs), c0=$(c0),
                   deltaAt = NULL, family = 'Normal', pDegree = $(pDegree))"
    else
        R"result <- deconv(tau = $(prior_grid), X = $(Xs), c0=$(c0),
                       deltaAt = $(deltaAt), family = 'Normal', pDegree = $(pDegree))"
    end
    R"result_mat <- result$stats"
    R"result_cov <- result$cov.g"
    @rget result_mat
    @rget result_cov
    g_prior = result_mat[:,2]
    g_bias = result_mat[:,6]
    g_cov = result_cov
    BradDeconvolveR(prior_grid, g_prior, g_bias, g_cov)
end

function estimate(brad::BradDeconvolveR, target::LinearInferenceTarget; debias=false)
    gs = brad.g_prior
    g_bias = debias ? brad.g_bias : zero(gs)
    lin_coef = riesz_representer.(target,  brad.prior_grid)
    lin_coef'*(gs .+ g_bias)
end

function StatsBase.confint(brad::BradDeconvolveR, target::LinearInferenceTarget, alpha::Float64=0.1; kwargs...)
    point_est = estimate(brad, target; kwargs...)
    lin_coef = riesz_representer.(target,  brad.prior_grid)
    sd = sqrt(lin_coef'*brad.g_cov*lin_coef)
    q = quantile(Normal(), 1-alpha/2)
    (point_est - q*sd, point_est + q*sd)
end

# actually make the below generic for any type of EBayes thing
function estimate(brad::Union{BradDeconvolveR, SmoothedBradDeconvolveR}, target::PosteriorTarget; kwargs...)
    num = estimate(brad, target.num; kwargs...)
    denom = estimate(brad, target.denom; kwargs...)
    num/denom
end

function StatsBase.confint(brad::BradDeconvolveR, target::PosteriorTarget, alpha::Float64=0.1; debias=false)
    num_est = estimate(brad, target.num; debias=debias)
    denom_est = estimate(brad, target.denom; debias=debias)
    point_est = num_est/denom_est

    num_coef = riesz_representer.(target.num,  brad.prior_grid)
    denom_coef = riesz_representer.(target.denom,  brad.prior_grid)

    delta_method_coef = num_coef./denom_est .- denom_coef.*num_est./(denom_est^2)

    sd = sqrt(delta_method_coef'*brad.g_cov*delta_method_coef)

    q = quantile(Normal(), 1-alpha/2)

    (point_est - q*sd, point_est + q*sd)
end

function estimate(brad::Union{BradDeconvolveR, SmoothedBradDeconvolveR},
                  target::InferenceTarget, Xs; debias=false)
    estimate(brad, target; debias=debias)
end

#----------------------------------------------------------
# Fitting and estimation functions for SmoothedDeconvolveR
#----------------------------------------------------------

function fit(::Type{SmoothedBradDeconvolveR}, Xs, prior_σ = 0.02;
              prior_grid=collect(-3.6:0.02:3.6), kwargs...)
    marginal_σ = sqrt(1.0 + prior_σ^2)
    Xs_rescaled = Xs./marginal_σ
    deconv = fit(BradDeconvolveR, Xs_rescaled;
                 prior_grid = prior_grid./marginal_σ, kwargs...)
    SmoothedBradDeconvolveR(deconv, prior_σ, prior_grid)
end

function MixtureModel(smooth_g::SmoothedBradDeconvolveR)
   prior_σ = smooth_g.prior_σ
   normals = [Normal(μ, prior_σ) for μ in smooth_g.prior_grid]
   MixtureModel(normals, smooth_g.unsmoothed.g_prior)
end

function smoothed_riesz(smooth_g::SmoothedBradDeconvolveR, target::LinearInferenceTarget)
    mm = MixtureModel(smooth_g)
    posterior_stats.(mm.components, target)
end
function estimate(brad::SmoothedBradDeconvolveR, target::LinearInferenceTarget; debias=false)
    gs = brad.unsmoothed.g_prior
    g_bias = debias ? brad.unsmoothed.g_bias : zero(gs)
    lin_coef = smoothed_riesz(brad, target)
    lin_coef'*(gs .+ g_bias)
end

function confint(brad::SmoothedBradDeconvolveR, target::LinearInferenceTarget, alpha::Float64=0.1; kwargs...)
    point_est = estimate(brad, target; kwargs...)
    lin_coef = smoothed_riesz(brad,  target)
    sd = sqrt(lin_coef'*brad.unsmoothed.g_cov*lin_coef)
    q = quantile(Normal(), 1-alpha/2)
    (point_est - q*sd, point_est + q*sd)
end

function confint(brad::SmoothedBradDeconvolveR, target::PosteriorTarget, alpha::Float64=0.1; debias=false)
    num_est = estimate(brad, target.num; debias=debias)
    denom_est = estimate(brad, target.denom; debias=debias)
    point_est = num_est/denom_est

    num_coef = smoothed_riesz(brad, target.num)
    denom_coef = smoothed_riesz(brad,  target.denom)

    delta_method_coef = num_coef./denom_est .- denom_coef.*num_est./(denom_est^2)

    var_tmp = delta_method_coef'*brad.unsmoothed.g_cov*delta_method_coef

    sd = var_tmp >= 0 ? sqrt(var_tmp) : Inf

    q = quantile(Normal(), 1-alpha/2)

    (point_est - q*sd, point_est + q*sd)
end

function maxbias(brad::SmoothedBradDeconvolveR, target)
    zero(Float64)
end


###############################################################################
#
# Plot linear regression on axis
#
###############################################################################
"""
    plot_line_regress!(
                ax::PyObject,
                xs::Array,
                ys::Array;
                linestyle::String = "-",
                intercept::Bool = true,
                interval::Bool = false,
                color::String = "red",
                alpha::Number = 0.3
    )

Plor linear regression and confidence interval on the axis, given
- `ax` Given axis
- `xs` Array of x
- `ys` Array of y
- `linestyle` Optional. Line style for the regression curve ("-" by default)
- `intercept` Optional: if true, fit the data with an intercept
- `interval` Optional: if true, plot the confidence interval of fitted y
- `color` Color the fitted curve
- `alpha` Transparency of the confidence interval (same color as curve)
"""
function plot_line_regress!(
            ax::PyObject,
            xs::Array,
            ys::Array;
            linestyle::String = "-",
            intercept::Bool = true,
            interval::Bool = false,
            color::String = "red",
            alpha::Number = 0.3
)
    # make linear regression
    lr = line_regress(xs, ys, intercept=intercept, sorting=true);

    # plot the fittings
    ax.plot(lr.df.X, lr.df.predY, linestyle=linestyle, color=color);
    if interval
        ax.fill_between(lr.df.X,
                        lr.df.lowerY,
                        lr.df.upperY,
                        facecolor=color,
                        alpha=alpha);
    end

    return nothing
end


# Show near evaluation inside starfish

push!(LOAD_PATH, string(pwd(),"/src"))

using Revise
using PyPlot
import IterativeSolvers
import LinearMaps
import AnalyticDomains
using ModifiedStokesSolver

include("../src/plottools.jl")

# Params
alpha = 5.0
numpanels = 100
panelorder = 16
solve_inteq = true
# Discretize
curve = AnalyticDomains.starfish(n_arms=5, amplitude=0.3)
println("* Discretizing")
@time dcurve = discretize(curve, numpanels, panelorder,
                          equal_arclength=true)
N = dcurve.numpoints

if solve_inteq
    # Setup a problem
    xsrc = [5.0, 5.0]
    srand(1)
    qsrc = 1-2*rand(2)
    nsrc = 1-2*rand(2)
    fsrc = 1-2*rand(2)
    ubc(x) = stresslet(x-xsrc, qsrc, nsrc, alpha) +
        stokeslet(x-xsrc, fsrc, alpha)
    # Right hand side
    rhs = zeros(2*N)
    for i=1:dcurve.numpoints
        rhs[2(i-1)+(1:2)] = ubc(dcurve.points[:,i])
    end

    # FMM lhs
    @time flhs = system_matvec(dcurve, alpha)
    LHS = LinearMaps.LinearMap(flhs, 2*N)

    # GMRES solve
    println("* GMRES solve")
    maxiter = 50
    tol = 1e-15
    @time sol, gmlog = IterativeSolvers.gmres(LHS, rhs; tol=tol, restart=maxiter, maxiter=maxiter, verbose=false, log=true)
    println( (gmlog.isconverged ? "  Converged" : "Did NOT converge"),
             " in $(gmlog.iters) iterations, residual=",
             gmlog.data[:resnorm][end])
    density = reshape(sol, 2, N)
    gmlog.isconverged
else
    # Just compare to a unit density on a refined curve
    println("* Discretizing ref curve")
    refmul = 5
    @time refgrid = discretize(curve, numpanels, refmul*panelorder, equal_arclength = true)
    density = ones(size(dcurve.points))
    refden = ones(size(refgrid.points))
    ubc(zt) = doublelayer(refgrid, refden, zt, alpha; specquad=false)
end

## POST PROCESS
xmax = maximum(dcurve.points[1,:])
xmin = minimum(dcurve.points[1,:])
ymax = maximum(dcurve.points[2,:])
ymin = minimum(dcurve.points[2,:])

xmin = 0.0
ymin = 0.0
xmin = 0.4
ymax = 0.6

N = 200
# Points sent to pcolor
xplot = linspace(xmin, xmax, N)
yplot = linspace(ymin, ymax, N)
# Create ndgrid using centroids
xcen = (xplot[1:end-1] + xplot[2:end])/2
ycen = (yplot[1:end-1] + yplot[2:end])/2
X, Y = ndgrid(xcen, ycen)
# Vector of grid points
zt = [vec(X) vec(Y)]'
# Get interior point mask
interior = interior_points(dcurve, zt)
numeval = sum(interior)
zt_interior = zt[:, interior]

println("* Computing near weights")
@time weights = ModifiedStokesSolver.doublelayer_near_weights(dcurve, zt_interior, alpha)
@time weights = ModifiedStokesSolver.doublelayer_near_weights(dcurve, zt_interior, alpha)

println("* Computing field on grid, using precomputed weights")
@time u = doublelayer_fast(dcurve, density, zt_interior, alpha; weights=weights)
@time u = doublelayer_fast(dcurve, density, zt_interior, alpha; weights=weights)

println("* Computing field on grid, on the fly")
@time uotf = doublelayer_fast(dcurve, density, zt_interior, alpha)
@time uotf = doublelayer_fast(dcurve, density, zt_interior, alpha)

println("* Computing field on grid, without specquad")
@time udummy = doublelayer_fast(dcurve, density, zt_interior, alpha; specquad=false)
@time udummy = doublelayer_fast(dcurve, density, zt_interior, alpha; specquad=false)

println("* Difference between on the fly and precomputed weights:")
println("  ", norm(vec(u)-vec(uotf),Inf))

println("* Computing error")
# Reference
uref = Array{Float64}(2, numeval)
for i=1:numeval
    uref[:,i] = ubc(zt_interior[:, i])
end
unorm = norm(vec(uref), Inf)
# Error
err = u .- uref
maxerr = Array{Float64}(numeval)
for i=1:numeval
    maxerr[i] = norm(err[:, i], Inf)
end
println("Maxerr on grid: ", norm(vec(maxerr), Inf))
# Put on grid
E = zeros(N-1,N-1)
E[interior] = maxerr
Erel = E / unorm
logE = log10.(Erel+1e-100)

println("* Displaying")
clf()

interior_pcolor(X, Y, logE, interior, vmin=-16, vmax=0)

cbar = colorbar()
cbar[:set_label]("Rel. error")
plot(dcurve.points[1,:], dcurve.points[2,:], ".-k")
axis("image")
axis([xmin, xmax, ymin, ymax])
show()