#!/usr/bin/env julia

# Copyright 2022 John T. Foster
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module project2

using MPI
using RecipesBase
using Plots

struct Grid{T}
    dx::T
    dy::T
    xrange::NTuple{2, T}
    yrange::NTuple{2, T}
    u::Matrix{T}
end

function Grid(nx::Integer=10, ny::Integer=10,
              xrange::NTuple{2, Real}=(0.0, 1.0),
              yrange::NTuple{2, Real}=(0.0, 1.0))
    xmin, xmax = xrange
    ymin, ymax = yrange
    dx = (xmax - xmin) / (nx - 1)
    dy = (ymax - ymin) / (ny - 1)
    t = typeof(dx)
    u = zeros(t, (nx, ny))
    Grid{t}(dx, dy, xrange, yrange, u)
end

function set_boundary_condition!(g::Grid, f::Function=(x,y) -> 0.0;
        sides::Union{String, Tuple{Vararg{String}}})

    xmin, xmax = g.xrange
    ymin, ymax = g.yrange
    nx = size(g.u)[2]
    ny = size(g.u)[1]

    if sides == "all"
        sides = ("bottom", "top", "left", "right")
    elseif typeof(sides) == String
        sides = (sides,)
    end

    for side in sides

        if side == "left"
            g.u[:, 1] = f.(xmin, LinRange(ymin, ymax, ny))
        elseif side == "right"
            g.u[:, end] = f.(xmax, LinRange(ymin, ymax, ny))
        elseif side == "bottom"
            g.u[1, :] = f.(LinRange(xmin, xmax, nx), ymin)
        elseif side == "top"
            g.u[end, :] = f.(LinRange(xmin, xmax, nx), ymax)
        end
    end

end

function reset!(g::Grid)
    g.u .= zero(typeof(g.dx))
end

function iterate!(g::Grid)

    u = g.u
    nx, ny = size(u)

    dx2, dy2 = g.dx^2, g.dy^2

    err = 0.0

    for i = 2:(nx-1)
        for j = 2:(ny-1)

            tmp = u[i, j]

            u[i,j] = ((u[i-1, j] + u[i+1, j]) * dy2 +
                      (u[i, j-1] + u[i, j+1]) * dx2) / (dx2 + dy2) / 2

            diff = u[i,j] - tmp

            err += diff * diff
        end
    end

    sqrt(err)
end

function solve!(g::Grid; max_iterations::Integer=10000,
                         tolerance::AbstractFloat=1.0e-12,
                         quiet::Bool=false)

    for i=1:max_iterations
        error = iterate!(g)
        if error < tolerance
            if !quiet
                println("Solution converged in $(i) iterations")
            end
            break
        end
    end
end

function partition_grid(g::Union{Grid, Nothing}, comm::MPI.Comm)::Grid
    # Return Grid{t}(dx, dy, xrange, yrange, my_u) on each rank
end

function solve!(my_g::Grid, comm::MPI.Comm; max_iterations::Integer=10000,
                                            tolerance::AbstractFloat=1.0e-12,
                                            quiet::Bool=false)
   #Returns nothing, should update my_g in place
end

function get_solution!(g::Union{Grid, Nothing}, my_g::Grid, comm)
   #Returns nothing, should update g in place
end

@recipe function f(g::Grid)
    xmin, xmax = g.xrange
    ymin, ymax = g.yrange
    nx = size(g.u)[2]
    ny = size(g.u)[1]
    LinRange(xmin, xmax, nx), LinRange(ymin, ymax, ny), g.u
end

function run_serial()
    g = Grid(101, 101)
    set_boundary_condition!(g, (x, y) -> 10, sides=("top", "bottom"))
    solve!(g)

    ENV["GKSwstype"]="nul"
    p = contourf(g, aspect_ratio=:equal, xlims=g.xrange);
    savefig(p, "laplace_serial.png");
end

function run_parallel()
    MPI.Init()
    comm = MPI.COMM_WORLD
    rank = MPI.Comm_rank(comm)

    ENV["GKSwstype"]="nul"

    if rank == 0
        g = Grid(101, 101)
        set_boundary_condition!(g, (x, y) -> 10, sides=("left", "right"))
    else
        g = nothing
    end
    my_g = partition_grid(g, comm)
    solve!(my_g, comm)
    get_solution!(g, my_g, comm)
    if rank == 0
        p = contourf(g, aspect_ratio=:equal, xlims=g.xrange);
        savefig(p, "laplace_parallel.png");
    end
end

export run_serial, run_parallel, set_boundary_condition!, partition_grid, Grid, solve!, get_solution!

end


export interpmat, getInterpolationMatrix
export getNodalInterpolationMatrix, getEdgeInterpolationMatrix, getFaceInterpolationMatrix, getCellCenteredInterpolationMatrix

"""
function jInv.Mesh.getInterpolationMatrix

computes bi/trilinear interpolation matrix P for cell-centered data
from Mesh1 to Mesh2. If I1 is a cell-centerd discretization of some function
on Mesh1 then, its interpolant on Mesh2 is given by

I2 = P*I1

Required Input:

	M1::AbstractTensorMesh
	M2::AbstractTensorMesh

    Note: kwargs are not used for AbstractTensorMesh

Example:

	In mesh-decoupling, we use different meshes for the inverse solution
	and the different forward problems.

	Mesh2Mesh = getInterpolationMatrix(Minv,Mfor)

"""
getInterpolationMatrix(M1::AbstractTensorMesh, M2::AbstractTensorMesh; kwargs...) =
							getCellCenteredInterpolationMatrix(M1, getCellCenteredGrid(M2))

function interpmat(x,y,Xr)
	nx = length(x); ny = length(y); np = size(Xr,1)

	# get cell sizes
	h1 = x[2:end] - x[1:end-1]
	h2 = y[2:end] - y[1:end-1]

	# find valid points
	valid1 = (x[1]-h1[1] .<=Xr[:,1]) .& (Xr[:,1] .<= x[end]+h1[end])
	valid2 = (y[1]-h2[1] .<=Xr[:,2]) .& (Xr[:,2] .<= y[end]+h2[end])
	valid =  findall( valid1 .& valid2  )

	if isempty(valid)
	    return spzeros(np,nx*ny)
	end

	J1,V1 = inter1D(Xr[:,1],x,valid)
	J2,V2 = inter1D(Xr[:,2],y,valid)

	IJ  = [  valid J1[:,1] J2[:,1]
	         valid J1[:,2] J2[:,1]
	         valid J1[:,1] J2[:,2]
	         valid J1[:,2] J2[:,2]
	        ]
	V = [
			V1[:,1].*V2[:,1]
			V1[:,2].*V2[:,1]
			V1[:,1].*V2[:,2]
			V1[:,2].*V2[:,2]
	]
	V  = V[LinearIndices(V)[findall(all(IJ[:,2:3] .> 0 ,dims=2))]]
	IJ = IJ[LinearIndices(IJ)[findall(all(IJ[:,2:3] .> 0 ,dims=2))],:]
	return sparse(IJ[:,1], LinearIndices((nx,ny))[CartesianIndex.(IJ[:,2],IJ[:,3])],V,np,nx*ny)
end

function interpmat(x,y,z,Xr)
	nx = length(x); ny = length(y); nz = length(z);
	np = size(Xr,1)

	# get cell sizes
	h1 = x[2:end] - x[1:end-1]
	h2 = y[2:end] - y[1:end-1]
	h3 = z[2:end] - z[1:end-1]

	# find valid points
	valid1 = (x[1]-h1[1] .<=Xr[:,1]) .& (Xr[:,1] .<= x[end]+h1[end])
	valid2 = (y[1]-h2[1] .<=Xr[:,2]) .& (Xr[:,2] .<= y[end]+h2[end])
	valid3 = (z[1]-h3[1] .<=Xr[:,3]) .& (Xr[:,3] .<= z[end]+h3[end])
	valid =  findall( valid1 .& valid2 .& valid3 )

	if isempty(valid)
	    return spzeros(np,nx*ny*nz)
	end

	J1,V1 = inter1D(Xr[:,1],x,valid)
	J2,V2 = inter1D(Xr[:,2],y,valid)
	J3,V3 = inter1D(Xr[:,3],z,valid)

	IJ  = [  valid J1[:,1] J2[:,1] J3[:,1]
	         valid J1[:,2] J2[:,1] J3[:,1]
	         valid J1[:,1] J2[:,2] J3[:,1]
	         valid J1[:,2] J2[:,2] J3[:,1]
	         valid J1[:,1] J2[:,1] J3[:,2]
	         valid J1[:,2] J2[:,1] J3[:,2]
	         valid J1[:,1] J2[:,2] J3[:,2]
	         valid J1[:,2] J2[:,2] J3[:,2]
	        ]
	V = [
			V1[:,1].*V2[:,1].*V3[:,1]
			V1[:,2].*V2[:,1].*V3[:,1]
			V1[:,1].*V2[:,2].*V3[:,1]
			V1[:,2].*V2[:,2].*V3[:,1]
			V1[:,1].*V2[:,1].*V3[:,2]
			V1[:,2].*V2[:,1].*V3[:,2]
			V1[:,1].*V2[:,2].*V3[:,2]
			V1[:,2].*V2[:,2].*V3[:,2]
	]
	V  = V[LinearIndices(V)[findall(all(IJ[:,2:4] .> 0 ,dims=2))]]
	IJ = IJ[LinearIndices(IJ)[findall(all(IJ[:,2:4] .> 0 ,dims=2))],:]
	return sparse(IJ[:,1], LinearIndices((nx,ny,nz))[CartesianIndex.(IJ[:,2],IJ[:,3],IJ[:,4])],V,np,nx*ny*nz)
end

function inter1D(x,ax,valid)
	# initialize
	J  = zeros(Int,length(valid),2)
	V  = zeros(length(valid),2)
	debit = valid
	for k=length(ax):-1:1; # go from right to left and search left neighbour
	    p =  (x[debit].-ax[k]) .>=0
		Ip = findall(p)
	    J[debit[Ip],1] .= k
		# check if right neighbour is in domain
	    if k < length(ax)
	        hk = ax[k+1]-ax[k]
	        J[debit[Ip],2] .= k+1
	        V[debit[Ip],1] .= (ax[k+1].-x[debit[Ip]])./hk
	        V[debit[Ip],2] .= (x[debit[Ip]].-ax[k])./hk
	    else
	        hk = ax[k]-ax[k-1]
	        V[debit[Ip],1] = (ax[k]+hk.-x[debit[Ip]])./hk
	    end
		debit = debit[findall(.!p)]
	end
	if !isempty(debit)
	    # check for points between ax[1]-h[1] and ax[1]
	    hk = ax[2]-ax[1]
	    P  = x[debit].-(ax[1]-hk)
	    p  = findall( P .>=0 )
	    J[debit[p],2] .= 1
	    V[debit[p],2] .= (x[debit[p],1].-(ax[1]-hk))/hk
		debit = debit[findall(P.<0)]
	end
	if !isempty(debit); error("something is wrong here!"); end;
	return J,V
end

function getNodalInterpolationMatrix(Mesh::AbstractTensorMesh, x)

	# axes
	xn,yn,zn = getNodalAxes(Mesh)

	# interpolate
	Q = interpmat(xn, yn, zn, x)

end

function getEdgeInterpolationMatrix(Mesh::AbstractTensorMesh, x)

	# axes
	xn,yn,zn = getNodalAxes(Mesh)
	xc,yc,zc = getCellCenteredAxes(Mesh)

	# interpolate
	Qx = interpmat(xc, yn, zn, x)
	Qy = interpmat(xn, yc, zn, x)
	Qz = interpmat(xn, yn, zc, x)

	Q  = blockdiag(blockdiag(Qx,Qy),Qz)

	return Q

end

function getFaceInterpolationMatrix(Mesh::AbstractTensorMesh, x)

	# axes
	xn,yn,zn = getNodalAxes(Mesh)
	xc,yc,zc = getCellCenteredAxes(Mesh)

	# interpolate
	Qx = interpmat(xn, yc, zc, x)
	Qy = interpmat(xc, yn, zc, x)
	Qz = interpmat(xc, yc, zn, x)

	Q  = blockdiag(blockdiag(Qx,Qy),Qz)

	return Q

end

function getCellCenteredInterpolationMatrix(Mesh::AbstractTensorMesh, x)
	axes = getCellCenteredAxes(Mesh)
	if Mesh.dim==2
		return interpmat(axes[1],axes[2],x)
	elseif Mesh.dim==3
		return interpmat(axes[1],axes[2],axes[3],x)
	end
end


using LinearAlgebra

Δx = 0.04
Lx = 4.0
xlim = (-Lx/2,Lx/2)
ylim = (-Lx/2,Lx/2)
g = PhysicalGrid(xlim,ylim,Δx)

RadC = 1.0
Δs = 1.4*cellsize(g)
body = Circle(RadC,Δs)
X = VectorData(collect(body,endpoints=true))

w = Nodes(Dual,size(g))
ϕ = Nodes(Primal,size(g))
q = Edges(Primal,size(g))
f = ScalarData(X)

angs(n) = range(0,2π,length=n+1)[1:n]

_size(::CartesianGrids.Laplacian{NX,NY}) where {NX,NY} = NX, NY

@testset "Forming a cache" begin
  scache1 = SurfaceScalarCache(body,g,scaling=GridScaling)
  scache2 = SurfaceScalarCache(X,g,scaling=GridScaling)
  @test normals(scache1) ≈ normals(scache2)
  @test areas(scache1) ≈ areas(scache2)
  @test points(scache1) ≈ points(scache2)

  @test size(g) == _size(scache1.L)
end

@testset "Scalar surface ops" begin
  scache = SurfaceScalarCache(body,g,scaling=GridScaling)

  gd = similar_grid(scache)
  @test typeof(gd) == typeof(scache.gdata_cache)

  sd = similar_surface(scache)
  @test typeof(sd) == typeof(scache.sdata_cache)

  f .= sin.(angs(length(body)) .- π/4)
  regularize_normal!(q,f,scache)
  normal_interpolate!(f,q,scache)
  @test maximum(f) ≈ 11 atol = 1e-0
  @test minimum(f) ≈ -11 atol = 1e-0

  A = create_CLinvCT(scache,scale=cellsize(g))

  @test maximum(abs.(eigvals(A))) ≈ 0.2 atol = 1e-1

  A = create_GLinvD(scache,scale=cellsize(g))
  @test maximum(abs.(eigvals(A))) ≈ 0.45 atol = 1e-1

  A = create_nRTRn(scache)
  @test maximum(svdvals(A)) ≈ 11 atol = 1e-0

  # Create additional regularization and interpolation ops
  w = ones_gridcurl(scache)
  Rn = RegularizationMatrix(scache,scache.sdata_cache,w)
  En = InterpolationMatrix(scache,w,scache.sdata_cache)

  f .= En*w

  vd = zeros_gridgradcurl(scache)
  @test typeof(vd) <: Edges{Dual}

  f2 = zeros_griddiv(scache)
  @test typeof(f2) <: Nothing

  f2 = similar_griddiv(scache)
  @test typeof(f2) <: Nothing

  f2 = ones_griddiv(scache)
  @test typeof(f2) <: Nothing

  f2 = x_griddiv(scache)
  @test typeof(f2) <: Nothing

  f2 = y_griddiv(scache)
  @test typeof(f2) <: Nothing

  @test isnothing(scache.Rcurl)
  @test isnothing(scache.Rdiv)

end

@testset "Complex cache" begin
  scache = SurfaceScalarCache(body,g,scaling=GridScaling,dtype=ComplexF64)

  @test eltype(similar_grid(scache)) == ComplexF64
  @test eltype(similar_gridcurl(scache)) == ComplexF64
  @test eltype(similar_gridgrad(scache)) == ComplexF64
  @test eltype(similar_gridgradcurl(scache)) == ComplexF64
  @test eltype(similar_surface(scache)) == ComplexF64

end

dq = EdgeGradient(Primal,size(g))
vs = VectorData(X)

@testset "Vector surface ops" begin
  vcache = SurfaceVectorCache(body,g,scaling=GridScaling)

  vs.u .= sin.(angs(length(body)) .- π/4)

  regularize_normal!(dq,vs,vcache)
  normal_interpolate!(vs,dq,vcache)

  fill!(dq,0.0)
  vs.u .= sin.(angs(length(body)) .- π/4)
  regularize_normal_symm!(dq,vs,vcache)
  normal_interpolate_symm!(vs,dq,vcache)
  @test minimum(vs.u) ≈ -22.5 atol = 1e-0
  @test maximum(vs.u) ≈ 22.5 atol = 1e-0

  surface_divergence!(q,vs,vcache)
  surface_grad!(vs,q,vcache)

  surface_divergence_symm!(q,vs,vcache)
  surface_grad_symm!(vs,q,vcache)

  A = create_GLinvD(vcache,scale=cellsize(g))
  @test maximum(abs.(eigvals(A))) ≈ 0.45 atol = 1e-1

  f2 = zeros_griddiv(vcache)
  @test typeof(f2) <: Nodes{Primal}

  f2 = similar_griddiv(vcache)
  @test typeof(f2) <: Nodes{Primal}

  f2 = ones_griddiv(vcache)
  @test typeof(f2) <: Nodes{Primal}

  f2 = x_griddiv(vcache)
  @test typeof(f2) <: Nodes{Primal}

  f2 = y_griddiv(vcache)
  @test typeof(f2) <: Nodes{Primal}

  @test typeof(vcache.Rcurl) <: RegularizationMatrix
  @test typeof(vcache.Rdiv) <: RegularizationMatrix

  v2 = zeros_grid(vcache)
  f2 = ones_surfacescalar(vcache)

  regularize_normal_cross!(v2,f2,vcache)

  vs2 = ones_surface(vcache,2)
  w2 = zeros_gridcurl(vcache)

  regularize_normal_cross!(w2,vs2,vcache)

end

@testset "Masks" begin

  scache = SurfaceScalarCache(body,g,scaling=GridScaling)

  inner = mask(scache)

  oc = similar(ϕ)
  oc .= 1
  @test abs(dot(oc,inner,g) - π*RadC^2) < 2e-3

  outer = complementary_mask(scache)

end

@testset "Grid operations" begin
  vcache = SurfaceVectorCache(body,g,scaling=GridScaling)

  q .= randn(size(q))
  cv_cache = ConvectiveDerivativeCache(EdgeGradient(Primal,q))
  cv_cache2 = ConvectiveDerivativeCache(vcache)
  qdq = similar(q)
  qdq .= 0.0
  convective_derivative!(qdq,q,vcache,cv_cache2)

  qdq2 = convective_derivative(q,vcache)

  @test qdq == qdq2

  w = zeros_gridcurl(vcache)
  v = zeros_grid(vcache)
  v .= randn(size(v))
  curl!(w,v)
  vdw = zeros_gridcurl(vcache)

  cd_cache = ConvectiveDerivativeCache(zeros_gridgradcurl(vcache))
  convective_derivative!(vdw,v,w,vcache,cd_cache)

end

@testset "Convenience fields" begin
  scache = SurfaceScalarCache(body,g,scaling=GridScaling)

  x = x_grid(scache)
  y = y_grid(scache)
  @test typeof(x) == typeof(y) == typeof(scache.gdata_cache)

  x = x_gridcurl(scache)
  y = y_gridcurl(scache)
  @test typeof(x) == typeof(y) == typeof(scache.gcurl_cache)

  x = x_gridgrad(scache)
  y = y_gridgrad(scache)
  @test typeof(x) == typeof(y) == typeof(scache.gsnorm_cache)

  vcache = SurfaceVectorCache(body,g,scaling=GridScaling)

  x = x_grid(vcache)
  y = y_grid(vcache)
  @test typeof(x) == typeof(y) == typeof(vcache.gdata_cache)

  x = x_gridcurl(vcache)
  y = y_gridcurl(vcache)
  @test typeof(x) == typeof(y) == typeof(vcache.gcurl_cache)

  x = x_gridgrad(vcache)
  y = y_gridgrad(vcache)
  @test typeof(x) == typeof(y) == typeof(vcache.gsnorm_cache)

end

@testset "Helmholtz decomposition" begin

  vcache = SurfaceVectorCache(body,g,scaling=GridScaling)
  veccache = VectorFieldCache(vcache)

  w = zeros_gridcurl(vcache)
  ψ = zeros_gridcurl(vcache)
  d = zeros_griddiv(vcache)
  ϕ = zeros_griddiv(vcache)
  v = zeros_grid(vcache)
  dv = zeros_surface(vcache)

  w .= randn(size(w))
  d .= randn(size(d))

  dv .= randn(size(dv))

  vecfield_helmholtz!(v,w,d,dv,(0.0,0.0),vcache,veccache)

  # Test that curl of the resulting vector field is zero
  w2 = zeros_gridcurl(vcache)
  curl!(w2,v,vcache)
  masked_w = zeros_gridcurl(vcache)
  masked_curlv_from_curlv_masked!(masked_w,w2,dv,vcache,veccache.wcache)
  @test maximum(abs.(masked_w[2:end-1,2:end-1] - w[2:end-1,2:end-1])) < 1e-8

  # Test that divergence of the resulting vector field is zero
  d2 = zeros_griddiv(vcache)
  divergence!(d2,v,vcache)
  masked_d = zeros_griddiv(vcache)
  masked_divv_from_divv_masked!(masked_d,d2,dv,vcache,veccache.dcache)
  @test maximum(abs.(masked_d[2:end-1,2:end-1]-d[2:end-1,2:end-1])) < 1e-8

  vectorpotential_from_masked_curlv!(ψ,w,dv,vcache,veccache.wcache)
  scalarpotential_from_masked_divv!(ϕ,d,dv,vcache,veccache.dcache)

  # Tests that no immersed points lead to no problems
  vcache = SurfaceVectorCache(g,scaling=GridScaling)
  veccache = VectorFieldCache(vcache)

  w = zeros_gridcurl(vcache)
  ψ = zeros_gridcurl(vcache)
  d = zeros_griddiv(vcache)
  ϕ = zeros_griddiv(vcache)
  v = zeros_grid(vcache)
  dv = zeros_surface(vcache)

  vectorpotential_from_masked_curlv!(ψ,w,dv,vcache,veccache.wcache)
  scalarpotential_from_masked_divv!(ϕ,d,dv,vcache,veccache.dcache)
  vecfield_helmholtz!(v,w,d,dv,(0.0,0.0),vcache,veccache)
  @test maximum(abs.(ψ)) == 0.0
  @test maximum(abs.(ϕ)) == 0.0
  @test maximum(abs.(v)) == 0.0

end

@testset "Problem specification" begin

  prob = BasicScalarILMProblem(g,scaling=GridScaling)
  @test typeof(prob.bodies) <: BodyList

  prob = BasicScalarILMProblem(g,body,scaling=GridScaling)
  @test typeof(prob.bodies) <: BodyList

  sys = construct_system(prob)

  scache = SurfaceScalarCache(body,g,scaling=GridScaling)

  @test typeof(sys.base_cache) == typeof(scache)

  vprob = BasicVectorILMProblem(g,body,scaling=GridScaling)
  vsys = construct_system(vprob)

  vcache = SurfaceVectorCache(body,g,scaling=GridScaling)

  @test typeof(vsys.base_cache) == typeof(vcache)

  p = rand()
  prob = BasicScalarILMProblem(g,body,scaling=GridScaling,phys_params=p)
  sys = construct_system(prob)
  @test sys.phys_params == p

  Re = rand()
  p = Dict("Re"=>Re)
  prob = BasicScalarILMProblem(g,body,scaling=GridScaling,phys_params=p)
  sys = construct_system(prob)
  @test sys.phys_params["Re"] == Re

  my_bc_func(x) = x
  prob = BasicScalarILMProblem(g,body,scaling=GridScaling,bc=my_bc_func)
  sys = construct_system(prob)
  @test sys.bc == my_bc_func

  my_f_func(x) = x
  prob = BasicScalarILMProblem(g,body,scaling=GridScaling,forcing=my_f_func)
  sys = construct_system(prob)
  @test sys.forcing == my_f_func

  prob = BasicScalarILMProblem(g,body,scaling=GridScaling,dtype=ComplexF64)
  sys = construct_system(prob)
  @test eltype(sys.base_cache.gdata_cache) == ComplexF64

  prob = BasicVectorILMProblem(g,body,scaling=GridScaling,dtype=ComplexF64)
  sys = construct_system(prob)
  @test eltype(sys.base_cache.gdata_cache) == ComplexF64

end

@testset "Problem regeneration" begin

  my_f_func(x) = x
  prob = BasicScalarILMProblem(g,body,scaling=GridScaling,forcing=my_f_func)
  sys = construct_system(prob)

  prob2 = ImmersedLayers.regenerate_problem(sys,sys.base_cache.bl)
  @test prob.g == prob2.g
  @test prob.bodies == prob2.bodies
  @test prob.phys_params == prob2.phys_params
  @test prob.bc == prob2.bc
  @test prob.forcing == prob2.forcing
  @test prob.timestep_func == prob2.timestep_func

end

@testset "Forcing regions" begin
  Δs = 1.4*cellsize(g)

  scache = SurfaceScalarCache(body,g,scaling=GridScaling)

  phys_params = Dict("areaheater1_flux" => 3.0,
                      "lineheater_flux" => -2.0,
                      "areaheater2_temp" => 1.5,
                       "areaheater2_coeff" => 2.0)

  fregion1 = Circle(0.2,Δs)
  T = RigidTransform((0.5,0.0),0.0)
  T(fregion1)
  function model1!(σ,T,t,fr::AreaRegionCache,phys_params)
      σ .= phys_params["areaheater1_flux"]
   end
   afm = AreaForcingModel(fregion1,model1!)

   fregion2 = Square(0.5,Δs)
   T = RigidTransform((0.0,1.0),0.0)
   T(fregion2)
   function model2!(σ,T,t,fr::LineRegionCache,phys_params)
     σ .= phys_params["lineheater_flux"]
    end
   lfm = LineForcingModel(fregion2,model2!)

   fregion3 = Polygon([0.0,1.0,0.5],[0.0,0.0,0.5],Δs)
   T = RigidTransform((-1.0,-1.0),π/4)
   T(fregion3)
   function model3!(σ,T,t,fr::AreaRegionCache,phys_params)
     σ .= phys_params["areaheater2_coeff"]*(phys_params["areaheater2_temp"] - T)
   end
   afm2 = AreaForcingModel(fregion3,model3!)

   pts = VectorData([-1.2,0.5],[0.5,0.5])
   str = [1.0,-1.0]

   function model4!(σ,T,t,fr::PointRegionCache,phys_params)
     σ .= str
     return σ
   end
   pfm = PointForcingModel(pts,model4!;ddftype=CartesianGrids.M4prime);

   fcache = ForcingModelAndRegion([afm,lfm,afm2,pfm],scache)

   @test typeof(fcache[1].region_cache.mask) <: ScalarGridData

   @test typeof(fcache[4].region_cache.cache.regop.ddf) == DDF{CartesianGrids.M4prime, 1.0}

   T = zeros_grid(scache)
   dT = similar_grid(scache)
   t = 0.0

   apply_forcing!(dT,T,t,fcache,phys_params)

   function point_function(T,t,fr::PointRegionCache,phys_params)
     return VectorData([-1.2,0.5],[0.5,0.5])
   end
   pfm2 = PointForcingModel(point_function,model4!;ddftype=CartesianGrids.M4prime);

   fcache = ForcingModelAndRegion(pfm,scache)
   fcache2 = ForcingModelAndRegion(pfm2,scache)

   T2 = zeros_grid(scache)
   dT2 = similar_grid(scache)
   t = 0.0

   apply_forcing!(dT,T,t,fcache,phys_params)
   apply_forcing!(dT2,T2,t,fcache2,phys_params)

   @test dT == dT2

   str = [1.0,-1.0,2.0]

   @test_throws DimensionMismatch apply_forcing!(dT2,T2,t,fcache2,phys_params)

end


#= Instructions:

- Pkg.add("PkgBenchmark.jl")
- using PkgBenchmark

- results = benchmarkpkg("IntervalArithmetic")
- showall(results)

- results = judge("IntervalArithmetic", "v0.9.1")  # compare current version to that tag
- showall(results)
=#

using IntervalArithmetic

@benchgroup "Constructors" begin
    @bench "Interval" Interval(1, 2)
end

@benchgroup "@interval" begin

    @bench "@interval" @interval(0.1)
    @bench "pi" @interval(pi)
    @bench "expression" @interval(sin(0.1) + cos(0.2))
end

@benchgroup "Arithmetic" begin

    a = Interval(1, 2)
    b = Interval(3, 4)

    for op in (+, -, *, /)
        @bench string(op) $(op)($a, $b)
    end
end

@benchgroup "Elementary functions" begin
    for op in (exp, log, sin, tan)
        @bench string(op) $(op)($a)
    end
end

@benchgroup "Sum" begin

    sum1(N) = sum(Interval(i, i+1) for i in 1:N)
    sum2(N) = (one = Interval(1.0); sum(one / (i^2) for i in 1:N) )

    @bench "Sum1" sum1(1000)
    @bench "Sum2" sum2(1000)
end


#================================================================

MonteCarloHash for hashing function spaces.

================================================================#

#========================
Global constants
========================#

const _MONTECARLOHASH_DEFAULT_N_SAMPLES = 1024

#========================
Typedefs
========================#

struct MonteCarloHash{F, H <: LSHFunction, D, T, S} <: LSHFunction
    similarity :: F

    discrete_hashfn :: H
    μ :: D

    # TODO: make typeof(volume) and typeof(p) match the element type
    # returned by the sampler μ.

    # The volume of the L^p space we're embedding, defined as
    #
    #   volume = ∫_Ω dx
    #
    volume :: T

    # The order of the L^p space that we're embedding
    p :: T

    n_samples :: Int64
    sample_points :: Vector{S}

    ### Internal constructors
    function MonteCarloHash(similarity::F, discrete_hashfn::H, μ, volume,
                            p, n_samples) where {F, H<:LSHFunction}
        sample_points = [μ() for ii = 1:n_samples]

        T = eltype(μ())
        volume = T(volume)
        p = T(p)

        new{F,H,typeof(μ),T,eltype(sample_points)}(
            similarity, discrete_hashfn, μ, volume,
            p, n_samples, sample_points
        )
    end
end

### External MonteCarloHash constructors
const _valid_MonteCarloHash_similarities = (
    # Function space similarities
    (L1, L2, cossim),
    # Discrete-space similarities corresponding to function space similarities
    (ℓ1, ℓ2, cossim),
    # Order of L^p space that the similarity applies to
    (1, 2, 2),
)

# TODO: restrict similarities. E.g. Jaccard should not be an available similarity
@doc """
    MonteCarloHash(sim, ω, args...; volume=1.0, n_samples=$(_MONTECARLOHASH_DEFAULT_N_SAMPLES), kws...)

Samples a hash function from an LSH family for the similarity `sim` defined over the function space ``L^p_{\\mu}(\\Omega)``. `sim` may be one of the following:
$(
join(
    ["- `" * sim * "`" for sim in (_valid_MonteCarloHash_similarities[1] .|>
                                   string |>
                                   collect |>
                                   sort!)
    ],
    "\n"
)
)

Given an input function ``f\\in L^p_{\\mu}(\\Omega)``, `MonteCarloHash` works by sampling ``f`` at some randomly-selected points in ``\\Omega``, and then hashing those samples.

# Arguments
- `sim`: the similarity statistic you want to hash on.
- `ω`: a function that takes no inputs and samples a single point from ``\\Omega``. Alternatively, it can be viewed as a random variable with probability measure

```math
\\frac{\\mu}{\\text{vol}_{\\mu}(\\Omega)} = \\frac{\\mu}{\\int_{\\Omega} d\\mu}
```

- `args...`: arguments to pass on when building the `LSHFunction` instance underlying the returned `MonteCarloHash` struct.
- `volume::Real` (default: `1.0`): the volume of the space ``\\Omega``, defined as

```math
\\text{vol}_{\\mu}(\\Omega) = \\int_{\\Omega} d\\mu
```

- `n_samples::Integer` (default: `$(_MONTECARLOHASH_DEFAULT_N_SAMPLES)`): the number of points to sample from each function that is hashed by the `MonteCarloHash`. Larger values of `n_samples` tend to capture the input function better and will thus be more likely to achieve desirable collision probabilities.
- `kws...`: keyword arguments to pass on when building the `LSHFunction` instance underlying the returned `MonteCarloHash` struct.

# Examples
Create a hash function for cosine similarity for functions in ``L^2([-1,1])``:

```jldoctest; setup = :(using LSHFunctions)
julia> μ() = 2*rand()-1;   # μ samples a random point from [-1,1]

julia> hashfn = MonteCarloHash(cossim, μ, 50; volume=2.0);

julia> n_hashes(hashfn)
50

julia> similarity(hashfn) == cossim
true

julia> hashtype(hashfn)
$(cossim |> LSHFunction |> hashtype)
```

Create a hash function for ``L^2`` distance in the function space ``L^2([0,2\\pi])``. Hash the functions `f(x) = cos(x)` and `f(x) = x/(2π)` using the returned `MonteCarloHash`.

```jldoctest; setup = :(using LSHFunctions, Random; Random.seed!(0))
julia> μ() = 2π * rand(); # μ samples a random point from [0,2π]

julia> hashfn = MonteCarloHash(L2, μ, 3; volume=2π);

julia> hashfn(cos)
3-element Array{Int32,1}:
 -1
  3
  0

julia> hashfn(x -> x/(2π))
3-element Array{Int32,1}:
 -1
 -2
 -1
```

Create a hash function with a different number of sample points.

```jldoctest; setup = :(using LSHFunctions; μ() = rand())
julia> μ() = rand();  # Samples a random point from [0,1]

julia> hashfn = MonteCarloHash(cossim, μ; volume=1.0, n_samples=512);

julia> length(hashfn.sample_points)
512
```
"""
MonteCarloHash(similarity, args...; kws...) =
    MonteCarloHash(SimilarityFunction(similarity), args...; kws...)

for (fn_space_simfn, simfn, p) in zip(_valid_MonteCarloHash_similarities...)
    quote
        # Add dispatch for case in which we specify the similarity function
        # to be $fn_space_simfn
        function MonteCarloHash(
                    sim::SimilarityFunction{$fn_space_simfn},
                    μ,
                    args...;
                    n_samples::Integer=_MONTECARLOHASH_DEFAULT_N_SAMPLES,
                    volume=1.0,
                    kws...)

            discrete_hashfn = LSHFunction($simfn, args...; kws...)
            MonteCarloHash($fn_space_simfn, discrete_hashfn, μ, volume,
                           $p, n_samples)
        end
    end |> eval
end

# Implementation of MonteCarloHash for invalid similarity functions. Just throws
# an error.
# Necessary because otherwise the first MonteCarloHash constructor will go into
# infinite recursion if it receives an invalid similarity function.
function MonteCarloHash(sim::SimilarityFunction, args...; kws...)
    valid_sims = _valid_MonteCarloHash_similarities[1] .|>
                 string  |>
                 collect |>
                 sort!
    valid_sims = join(valid_sims, ", ", " or ")
    ErrorException("similarity must be $(valid_sims)") |> throw
end

#========================
MonteCarloHash helper functions
========================#

# Embed an input function f in the discrete space
function get_samples(hashfn::MonteCarloHash, f)
    α = (hashfn.volume / hashfn.n_samples)^(1/hashfn.p)
    α * f.(hashfn.sample_points)
end

#========================
LSHFunction API compliance
========================#

hashtype(hashfn::MonteCarloHash) =
    hashtype(hashfn.discrete_hashfn)

similarity(hashfn::MonteCarloHash) = hashfn.similarity

n_hashes(hashfn::MonteCarloHash) =
    n_hashes(hashfn.discrete_hashfn)

# TODO: this may not be true
collision_probability(hashfn::MonteCarloHash, args...; kws...) =
    collision_probability(hashfn.discrete_hashfn, args...; kws...)

#========================
SymmetricLSHFunction API compliance
========================#

(hashfn::MonteCarloHash)(f) = index_hash(hashfn, f)

#========================
AsymmetricLSHFunction API compliance
========================#

function index_hash(hashfn::MonteCarloHash, f)
    samples = get_samples(hashfn, f)
    index_hash(hashfn.discrete_hashfn, samples)
end

function query_hash(hashfn::MonteCarloHash, f)
    samples = get_samples(hashfn, f)
    query_hash(hashfn.discrete_hashfn, samples)
end

#========================
MonteCarloHash API
========================#

# Compute the similarity between f and g in the embedded space
function embedded_similarity(hashfn::MonteCarloHash, f, g)
    samples_f = get_samples(hashfn, f)
    samples_g = get_samples(hashfn, g)
    simfun = similarity(hashfn)
    simfun(samples_f, samples_g)
end


# # Cyclic symmetry for Sums of Hermitian Squares

#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/Symmetry/cyclic.ipynb)
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/Symmetry/cyclic.ipynb)

using Test #src

# We start by defining the Cyclic group.

using GroupsCore
import PermutationGroups

struct CyclicElem <: GroupElement
    n::Int
    id::Int
end
Base.:(==)(a::CyclicElem, b::CyclicElem) = a.n == b.n && a.id == b.id
Base.inv(el::CyclicElem) = CyclicElem(el.n, (el.n - el.id) % el.n)

function Base.:*(a::CyclicElem, b::CyclicElem)
    return CyclicElem(a.n, (a.id + b.id) % a.n)
end
Base.:^(el::CyclicElem, k::Integer) = CyclicElem(el.n, (el.id * k) % el.n)

Base.conj(a::CyclicElem, b::CyclicElem) = inv(b) * a * b
Base.:^(a::CyclicElem, b::CyclicElem) = conj(a, b)

function PermutationGroups.order(el::CyclicElem)
    return div(el.n, gcd(el.n, el.id))
end

struct CyclicGroup <: Group
    n::Int
end
Base.eltype(::CyclicGroup) = CyclicElem
Base.one(c::Union{CyclicGroup, CyclicElem}) = CyclicElem(c.n, 0)
PermutationGroups.gens(c::CyclicGroup) = [CyclicElem(c.n, 1)]
PermutationGroups.order(::Type{T}, c::CyclicGroup) where {T} = convert(T, c.n)
function Base.iterate(c::CyclicGroup, prev::CyclicElem=CyclicElem(c.n, -1))
    id = prev.id + 1
    if id >= c.n
        return nothing
    else
        next = CyclicElem(c.n, id)
        return next, next
    end
end

# Now we define that the cyclic group acts on monomial by permuting variables
# cyclically. So for instance, `CyclicElem(3, 1)` would transform
# `x_1^3*x_2*x_3^4` into `x_1^4*x_2^3*x_3`.

import MultivariatePolynomials
const MP = MultivariatePolynomials

using SumOfSquares

struct Action{V<:MP.AbstractVariable} <: Symmetry.OnMonomials
    variables::Vector{V}
end
Symmetry.SymbolicWedderburn.coeff_type(::Action) = Float64
function Symmetry.SymbolicWedderburn.action(a::Action, el::CyclicElem, mono::MP.AbstractMonomial)
    return prod(MP.powers(mono), init=MP.constantmonomial(mono)) do (var, exp)
        index = findfirst(isequal(var), a.variables)
        new_index = mod1(index + el.id, el.n)
        return a.variables[new_index]^exp
    end
end

using DynamicPolynomials
@polyvar x[1:3]
action = Action(x)
g = CyclicElem(3, 1)
@test Symmetry.SymbolicWedderburn.action(action, g, x[1]^3 * x[2] * x[3]^4) == x[1]^4 * x[2]^3 * x[3] #src
Symmetry.SymbolicWedderburn.action(action, g, x[1]^3 * x[2] * x[3]^4)

# The following polynomial `poly` is invariant under the action of the group `G`.

N = 3
G = CyclicGroup(N)
poly = sum(x[i] * x[mod1(i + 1, N)] for i in 1:N) + sum(x.^2)
@test Symmetry.SymbolicWedderburn.action(action, g, poly) == poly #src
Symmetry.SymbolicWedderburn.action(action, g, poly)

# Let's now find the minimum of `p` by exploiting this symmetry.

import CSDP
solver = CSDP.Optimizer
model = Model(solver)
@variable(model, t)
@objective(model, Max, t)
pattern = Symmetry.Pattern(G, action)
con_ref = @constraint(model, poly - t in SOSCone(), symmetry = pattern)
optimize!(model)
@test termination_status(model) == MOI.OPTIMAL #src
@test objective_value(model) ≈ 0.0 atol=1e-4 #src
solution_summary(model)

# Let's look at the symmetry adapted basis used.

gram = gram_matrix(con_ref).sub_gram_matrices #src
@test length(gram) == 3                       #src
@test gram[1].Q ≈ [0 0; 0 2] #src
@test length(gram[1].basis.polynomials) == 2 #src
@test gram[1].basis.polynomials[1] == 1 #src
@test gram[1].basis.polynomials[2] ≈ -sum(x)/√3 #src
@test gram[2].Q ≈ [0.5;;] #src
@test length(gram[2].basis.polynomials) == 1 #src
@test gram[2].basis.polynomials[1] ≈ (-2x[1] + x[2] + x[3])/√6 #src
@test gram[3].Q == gram[2].Q #src
@test length(gram[3].basis.polynomials) == 1 #src
@test gram[3].basis.polynomials[1] ≈ (-x[2] + x[3])/√2 #src
for gram in gram_matrix(con_ref).sub_gram_matrices
    println(gram.basis.polynomials)
    display(gram.Q)
end

# Let's look into more details at the last two elements of the basis.

basis = [(-2x[1] + x[2] + x[3])/√6, (-x[2] + x[3])/√2]

# This actually constitutes the basis for an invariant subspace corresponding
# to a group character of degree 2 and multiplicity 1.
# This means that it decomposes the semidefinite matrix into 2 blocks of size
# 1-by-1 that are equal. Indeed, we see above that `gram.Q` is identically equal for both.
# As the group is generated by one element `g`, we can just verify it by verifying
# its invariance under `g`.
# The image of each element under the basis is:

image = [Symmetry.SymbolicWedderburn.action(action, g, p) for p in basis]

# We can see that they are both still in the same 2-dimensional subspace.

a = -1/2
b = -√3/2
@test all(image .≈ [a -b; b a] * basis) #src
[a -b; b a] * basis

# In fact, these last two basis comes from the real decomposition of a complex one.

import CSDP
solver = CSDP.Optimizer
model = Model(solver)
add_bridge(model, SumOfSquares.COI.Bridges.Variable.HermitianToSymmetricPSDBridge)
add_bridge(model, SumOfSquares.COI.Bridges.Constraint.SplitZeroBridge)
MOI.Bridges.add_bridge(backend(model).optimizer, PolyJuMP.ZeroPolynomialBridge{Complex{Float64}})
MOI.Bridges.add_bridge(backend(model).optimizer, SumOfSquares.Bridges.Constraint.SOSPolynomialBridge{Complex{Float64}})
@variable(model, t)
@objective(model, Max, t)
pattern = Symmetry.Pattern(G, action)
cone = SumOfSquares.NonnegPolyInnerCone{SumOfSquares.COI.HermitianPositiveSemidefiniteConeTriangle}()
pp = (1.0 + 0.0im) * poly - (1.0 + 0.0im) * t
con_ref = @constraint(model, pp in cone, symmetry = pattern)
optimize!(model)
@test termination_status(model) == MOI.OPTIMAL #src
@test objective_value(model) ≈ 0.0 atol=1e-4 #src
solution_summary(model)

gram = gram_matrix(con_ref).sub_gram_matrices #src
@test length(gram) == 3                       #src
@test gram[1].Q ≈ [0 0; 0 2] #src
@test length(gram[1].basis.polynomials) == 2 #src
@test gram[1].basis.polynomials[1] == 1 #src
@test gram[1].basis.polynomials[2] ≈ -sum(x)/√3 #src
@test gram[2].Q ≈ [0.5;;] #src
@test length(gram[2].basis.polynomials) == 1 #src
@test gram[2].basis.polynomials[1] ≈ (basis[1] + basis[2] * im) / √2  #src
@test gram[3].Q ≈ [0.5;;] #src
@test length(gram[3].basis.polynomials) == 1 #src
@test gram[3].basis.polynomials[1] ≈ (basis[1] - basis[2] * im) / √2 #src
for gram in gram_matrix(con_ref).sub_gram_matrices
    println(gram.basis.polynomials)
    display(gram.Q)
end

# We can see that the real invariant subspace was in fact coming from two complex conjugate complex invariant subspaces:

complex_basis = basis[1] + im * basis[2]
image = Symmetry.SymbolicWedderburn.action(action, g, complex_basis)

# And there is a direct correspondance between the representation of the real and complex versions:

@test all(image .≈ (a + b * im) * complex_basis) #src
(a + b * im) * complex_basis


using AXMethods
using Test

@testset "AXLeastSquares.jl" begin
    # Generate example data
    n_i = 1000
    X   = 2 * randn((n_i, 2))
    y   = X * [1, -2] + randn((n_i, 1))

    # Estimate the least square regression
    ls_fit = AXLeastSquares(y, X)

    # Check the methods
    β_hat = coef(ls_fit)
    y_hat = predict(ls_fit)
    se_hat = infer(ls_fit, print_df = false)
    se_robust_hat = infer(ls_fit, heteroskedastic = true, print_df = false)

    # Let's check that everything is of correct type.
    @test typeof(ls_fit) == AXLeastSquares
    @test typeof(β_hat) == Array{Float64,2}
    @test typeof(y_hat) == Array{Float64,2}
    @test typeof(se_hat) == NamedTuple{(:β, :se, :t, :p),
                                       Tuple{Array{Float64,2},
                                             Array{Float64,1},
                                             Array{Float64,2},
                                             Array{Float64,2}}}
    @test typeof(se_robust_hat) == NamedTuple{(:β, :se, :t, :p),
                                          Tuple{Array{Float64,2},
                                                Array{Float64,1},
                                                Array{Float64,2},
                                                Array{Float64,2}}}
end

@testset "AX2StageLeastSquares.jl" begin
    # Generate example data
    n_i = 1000
    ν = randn((n_i, 1))
    instrument = 2 * randn((n_i, 2))
    D = instrument * [1, -2] + ν
    y = D + 0.3 * ν + randn((n_i, 1))

    # Estimate the least square regression
    tsls_fit = AX2StageLeastSquares(y, D, instrument)

    # Check the methods
    β_hat = coef(tsls_fit)
    y_hat = predict(tsls_fit)
    se_hat = infer(tsls_fit, print_df = false)
    se_hc_hat = infer(tsls_fit, heteroskedastic = true, print_df = false)

    # Let's check that everything is of correct type.
    @test typeof(tsls_fit) == AX2StageLeastSquares
    @test typeof(β_hat) == Array{Float64,2}
    @test typeof(y_hat) == Array{Float64,2}
    @test typeof(se_hat) == NamedTuple{(:β, :se, :t, :p),
                                       Tuple{Array{Float64,2},
                                             Array{Float64,1},
                                             Array{Float64,2},
                                             Array{Float64,2}}}
    @test typeof(se_hc_hat) == NamedTuple{(:β, :se, :t, :p),
                                          Tuple{Array{Float64,2},
                                                Array{Float64,1},
                                                Array{Float64,2},
                                                Array{Float64,2}}}
end


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 5f1fe571-3357-49cf-93d6-49ae6b4b9a05
begin
	using OrdinaryDiffEq
	using Plots, LaTeXStrings
	using PlutoUI
	# using CSV
	# using DataFrames, CategoricalArrays
	# using Turing
	# using Dates
end

# ╔═╡ 9997fcae-e8c0-11eb-29d3-19aa2548bb51
md"""
# Within-host modelling of SARS-CoV-2

*C. Contreras, J. Newby, T. Hillen.*
© 2021
"""

# ╔═╡ f02367f9-77ec-4af5-ab95-c216cc61773f

# ╔═╡ a3756580-3ac1-40b5-94ea-9e164c9065e5
md"Importing modules"

# ╔═╡ 4a72dcd8-bb16-4d63-a8a8-e89865b3ff95
md"""
Text
"""

# ╔═╡ 7541a4c5-e8bd-4700-880d-8d7e1b05cdf9

# ╔═╡ 9087b209-8385-4da8-8cfd-6eb7f4a2a0aa
begin
	md"Plot configuration"
	plotly()
	figdir = "figures/"
	# ENV["COLUMNS"]=120
	# ENV["LINES"]=20
end

# ╔═╡ 8e990cf8-3fee-4fde-b044-0e95c90a19a8
md"""
## Standard virus load function

The standard virus load function with three main phases given by

$$V(t)=v_1(t)v_2(t)v_3(t),$$

where $v _1$ describes the initial growth phase between $a _1$ and $a _2$ , $v _2$ the intermediate slow decay
phase between $a _2$ and $b _1$, and $v _3$ the final decay phase between $b _1$ and $b _2$. These functions are given as sigmoid and exponential functions, respectively

$$v_1(t) = 1 + \frac{V_{\max} - 1}{2} \left[ \tanh \left( \frac{6}{a_2 - a_1} \left( t - \frac{a_1 + a_2}{2} \right) \right) - \tanh  \left( -3\frac{a_2 + a_1}{a_2 - a_1} \right) \right]$$

$$v_2(t) = \left\{\begin{aligned}
        & 1 & t < a_2 \\
        & e^{-\alpha (t-a_2)} & t \geq a_2 \\
    \end{aligned} \right.$$

$$v_3(t) = 1 - \frac{1 - V_{\min}}{2} \left[ \tanh \left( \frac{6}{b_2 - b_1} \left( t - \frac{b_1 + b_2}{2} \right) \right) - \tanh  \left( -3\frac{b_2 + b_1}{b_2 - b_1} \right) \right],$$

.
"""

# ╔═╡ 54a73741-2b04-4bb5-9bcb-b594b451d9ec
begin
	H(x) = 0.5*(tanh(1e3*x) + 1.)
	v₁(t, a₁, a₂, Vmax) = 1. + (Vmax - 1.0)*(tanh(6.0*(t - (a₁ + a₂)/2)/(a₂ - a₁)) - tanh(-3.0*(a₂ + a₁)/(a₂ - a₁)))/2
	v₂(t, a₂, α) = 1 - H(t - a₂) + H(t - a₂)*exp(-α*(t - a₂))
	v₃(t, b₁, b₂, Vmin) = 1 - (1.0 - Vmin)*(tanh(6.0*(t - (b₁ + b₂)/2)/(b₂ - b₁)) - tanh(-3.0*(b₂ + b₁)/(b₂ - b₁)))/2
	function VLF(t, a₁, a₂, b₁, b₂, α, Vmax, Vmin)
		v₁(t, a₁, a₂, Vmax)*v₂(t, a₂, α)*v₃(t, b₁, b₂, Vmin)
	end
end

# ╔═╡ 568527c6-0760-405e-a056-98a22f95454c
md"""
## Kinetic model

The within-host model for COVID-19 infections dynamics on the lung epithilium, $E$, is given by

$$\begin{aligned}
    \frac{d E}{dt} & = \mu_E E(1-E) - \delta_E T \frac{V}{V_{max}}.
\end{aligned}$$
"""

# ╔═╡ dea4b784-87c7-4497-b11b-b1ca1cc83c71
function model!(du, u, p, t)
    E, = u
    a₁, a₂, b₁, b₂, α, Vmax, Vmin, μ_E, δ_E = p
    # Lung damage
    du[1] = dE = μ_E*E*(1.0 - E) - δ_E*E*VLF(t, a₁, a₂, b₁, b₂, α, Vmax, Vmin)/Vmax
	# Healthy lung epithelium
end

# ╔═╡ 5a3929ad-5719-4021-993c-acc31db4a674
begin
	a₁_slider = @bind a₁ Slider(0.0:0.1:4.0, default=0.4)
	α_slider = @bind α Slider(0.0:0.1:1.0, default=0.1)
	md"""Parameter

	a1: $(a₁_slider)

	alpha: $(α_slider)"""
end

# ╔═╡ b330f75d-125c-4a49-a830-5672ed18c56b
begin
	# initial
	u0 = [
		1.0   # E: Healthy lung epithilium
	]
	tspan = (0.0, 40.0)
	pars = [
		a₁     # a₁
		4.0     # a₂
		13.0    # b₁
		19.0    # b₂
		α     # α
		1e3  # Vmax
		1e-8    # Vmin
		0.01    # μ_E
		0.2     # δ_E
		]
	parnames = [:a₁, :a₂, :b₁, :b₂, :α, :Vmax, :Vmin, :μ_E, :δ_E]
	thresholds = (E = 0.3,);
	prob = ODEProblem(model!, u0, tspan, pars)
end

# ╔═╡ 2f80e9cb-9bd6-4189-9d84-20d5e1c6b0e3
function plot_sol!(sol; kwargs...)
    tt = range(0.0, tspan[2], length=100)
    pars = sol.prob.p
    vv = VLF.(sol.t, pars[1], pars[2], pars[3], pars[4], pars[5], pars[6], pars[7])
    plot!(sol.t, vv, lw=3, xaxis=("Time (days)"), yaxis=("Viral load", extrema(vv).*1.05), subplot=1; kwargs...)
    plot!(sol, vars=1, lw=3, xaxis=("Time (days)"), yaxis=("Healthy lung epithelium", (0, 1.1)), subplot=2; kwargs...)
    plot!(collect(extrema(sol.t)), [thresholds.E, thresholds.E], color=:black, lw=2, subplot=2)
end

# ╔═╡ 364206c0-954b-45b1-9f20-e5031c34de16
function plot_sol(sol; kwargs...)
    plot(layout=2, legend=:none, grid=:none, size=(600,300))
    plot_sol!(sol; kwargs...)
end

# ╔═╡ 8864fe08-3dd3-4731-ac26-7ae256835f03
begin
	sol = solve(prob, Tsit5(), dtmax=1e-1)
	plot_sol(sol)
end

# ╔═╡ 5df4f25f-ff30-46ea-b7d1-923001d900fa
md"""
$(a₁)
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
LaTeXStrings = "~1.2.1"
OrdinaryDiffEq = "~5.60.1"
Plots = "~1.19.2"
PlutoUI = "~0.7.9"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[ArnoldiMethod]]
deps = ["LinearAlgebra", "Random", "StaticArrays"]
git-tree-sha1 = "f87e559f87a45bece9c9ed97458d3afe98b1ebb9"
uuid = "ec485272-7323-5ecc-a04f-4719b315124d"
version = "0.1.0"

[[ArrayInterface]]
deps = ["IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "045ff5e1bc8c6fb1ecb28694abba0a0d55b5f4f5"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "3.1.17"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c3598e525718abcc440f69cc6d5f60dda0a1b61e"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.6+5"

[[Cairo_jll]]
deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "e2f47f6d8337369411569fd45ae5753ca10394c6"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.0+6"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "f53ca8d41e4753c41cdafa6ec5f7ce914b34be54"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "0.10.13"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random", "StaticArrays"]
git-tree-sha1 = "ed268efe58512df8c7e224d2e170afd76dd6a417"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.13.0"

[[ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[CommonSolve]]
git-tree-sha1 = "68a0743f578349ada8bc911a5cbd5a2ef6ed6d1f"
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
version = "0.2.0"

[[CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "dc7dedc2c2aa9faf59a55c622760a25cbefbe941"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.31.0"

[[CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[ConstructionBase]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f74e9d5388b8620b4cee35d4c5a618dd4dc547f4"
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
version = "1.3.0"

[[Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"

[[DataAPI]]
git-tree-sha1 = "ee400abb2298bd13bfc3df1c412ed228061a2385"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.7.0"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "4437b64df1e0adccc3e5d1adbc3ac741095e4677"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.9"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[DiffEqBase]]
deps = ["ArrayInterface", "ChainRulesCore", "DataStructures", "DocStringExtensions", "FastBroadcast", "FunctionWrappers", "IterativeSolvers", "LabelledArrays", "LinearAlgebra", "Logging", "MuladdMacro", "NonlinearSolve", "Parameters", "Printf", "RecursiveArrayTools", "RecursiveFactorization", "Reexport", "Requires", "SciMLBase", "Setfield", "SparseArrays", "StaticArrays", "Statistics", "SuiteSparse", "ZygoteRules"]
git-tree-sha1 = "932153f62d0508e59733e0fc33361470d293a889"
uuid = "2b5f629d-d688-5b77-993f-72d75c75574e"
version = "6.68.1"

[[DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"

[[DiffRules]]
deps = ["NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "214c3fcac57755cfda163d91c58893a8723f93e9"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.0.2"

[[Distances]]
deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
git-tree-sha1 = "abe4ad222b26af3337262b8afb28fab8d215e9f8"
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
version = "0.10.3"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "92d8f9f208637e8d2d28c664051a00569c01493d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.1.5+1"

[[Expat_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b3bfd02e98aedfa5cf885665493c5598c350cd2f"
uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
version = "2.2.10+0"

[[ExponentialUtilities]]
deps = ["ArrayInterface", "LinearAlgebra", "Printf", "Requires", "SparseArrays"]
git-tree-sha1 = "ad435656c49da7615152b856c0f9abe75b0b5dc9"
uuid = "d4d017d3-3776-5f7e-afef-a10c40355c18"
version = "1.8.4"

[[FFMPEG]]
deps = ["FFMPEG_jll"]
git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
version = "0.4.1"

[[FFMPEG_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "LibVPX_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
git-tree-sha1 = "3cc57ad0a213808473eafef4845a74766242e05f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.3.1+4"

[[FastBroadcast]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "26be48918640ce002f5833e8fc537b2ba7ed0234"
uuid = "7034ab61-46d4-4ed7-9d0f-46aef9175898"
version = "0.1.8"

[[FastClosures]]
git-tree-sha1 = "acebe244d53ee1b461970f8910c235b259e772ef"
uuid = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a"
version = "0.3.2"

[[FiniteDiff]]
deps = ["ArrayInterface", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays"]
git-tree-sha1 = "f6f80c8f934efd49a286bb5315360be66956dfc4"
uuid = "6a86dc24-6348-571c-b903-95158fe2bd41"
version = "2.8.0"

[[FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "35895cf184ceaab11fd778b4590144034a167a2f"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.1+14"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "e2af66012e08966366a43251e1fd421522908be6"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.18"

[[FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "cbd58c9deb1d304f5a245a0b7eb841a2560cfec6"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.1+5"

[[FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91"
uuid = "559328eb-81f9-559d-9380-de523a88c83c"
version = "1.0.10+0"

[[FunctionWrappers]]
git-tree-sha1 = "241552bc2209f0fa068b6415b1942cc0aa486bcc"
uuid = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e"
version = "1.1.2"

[[Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[GLFW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"]
git-tree-sha1 = "dba1e8614e98949abfa60480b13653813d8f0157"
uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89"
version = "3.3.5+0"

[[GR]]
deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test", "UUIDs"]
git-tree-sha1 = "9f473cdf6e2eb360c576f9822e7c765dd9d26dbc"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.58.0"

[[GR_jll]]
deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Pkg", "Qt5Base_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "eaf96e05a880f3db5ded5a5a8a7817ecba3c7392"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.58.0+0"

[[GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "15ff9a14b9e1218958d3530cc288cf31465d9ae2"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.3.13"

[[Gettext_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
version = "0.21.0+0"

[[Glib_jll]]
deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "47ce50b742921377301e15005c96e979574e130b"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.1+0"

[[Grisu]]
git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
version = "1.0.2"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "c6a1fff2fd4b1da29d3dccaffb1e1001244d844e"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.12"

[[Hwloc]]
deps = ["Hwloc_jll"]
git-tree-sha1 = "92d99146066c5c6888d5a3abc871e6a214388b91"
uuid = "0e44f5e4-bd66-52a0-8798-143a42290a1d"
version = "2.0.0"

[[Hwloc_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3395d4d4aeb3c9d31f5929d32760d8baeee88aaf"
uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
version = "2.5.0+0"

[[IfElse]]
git-tree-sha1 = "28e837ff3e7a6c3cdb252ce49fb412c8eb3caeef"
uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
version = "0.1.0"

[[Inflate]]
git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.2"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[IterativeSolvers]]
deps = ["LinearAlgebra", "Printf", "Random", "RecipesBase", "SparseArrays"]
git-tree-sha1 = "1a8c6237e78b714e901e406c096fc8a65528af7d"
uuid = "42fd0dbc-a981-5370-80f2-aaf504508153"
version = "0.9.1"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.1"

[[JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d735490ac75c5cb9f1b00d8b5509c11984dc6943"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.0+0"

[[LAME_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c"
uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
version = "3.100.1+0"

[[LZO_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6"
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
version = "2.10.1+0"

[[LaTeXStrings]]
git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.2.1"

[[LabelledArrays]]
deps = ["ArrayInterface", "LinearAlgebra", "MacroTools", "StaticArrays"]
git-tree-sha1 = "5e38cfdd771c34821ade5515f782fe00865d60b3"
uuid = "2ee39098-c373-598a-b85f-a56591580800"
version = "1.6.2"

[[Latexify]]
deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"]
git-tree-sha1 = "a4b12a1bd2ebade87891ab7e36fdbce582301a92"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
version = "0.15.6"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[LibVPX_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "12ee7e23fa4d18361e7c2cde8f8337d4c3101bc7"
uuid = "dd192d2f-8180-539f-9fb4-cc70b1dcf69a"
version = "1.10.0+0"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[Libffi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "761a393aeccd6aa92ec3515e428c26bf99575b3b"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+0"

[[Libgcrypt_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"]
git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae"
uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
version = "1.8.7+0"

[[Libglvnd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
git-tree-sha1 = "7739f837d6447403596a75d19ed01fd08d6f56bf"
uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
version = "1.3.0+3"

[[Libgpg_error_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9"
uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
version = "1.42.0+0"

[[Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[Libmount_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73"
uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
version = "2.35.0+0"

[[Libtiff_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"]
git-tree-sha1 = "340e257aada13f95f98ee352d316c3bed37c8ab9"
uuid = "89763e89-9b03-5906-acba-b20f662cd828"
version = "4.3.0+0"

[[Libuuid_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066"
uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
version = "2.36.0+0"

[[LightGraphs]]
deps = ["ArnoldiMethod", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"]
git-tree-sha1 = "432428df5f360964040ed60418dd5601ecd240b6"
uuid = "093fc24a-ae57-5d10-9952-331d41423f4d"
version = "1.3.5"

[[LineSearches]]
deps = ["LinearAlgebra", "NLSolversBase", "NaNMath", "Parameters", "Printf"]
git-tree-sha1 = "f27132e551e959b3667d8c93eae90973225032dd"
uuid = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
version = "7.1.1"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[LogExpFunctions]]
deps = ["DocStringExtensions", "LinearAlgebra"]
git-tree-sha1 = "7bd5f6565d80b6bf753738d2bc40a5dfea072070"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.2.5"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[LoopVectorization]]
deps = ["ArrayInterface", "DocStringExtensions", "IfElse", "LinearAlgebra", "OffsetArrays", "Polyester", "Requires", "SLEEFPirates", "Static", "StrideArraysCore", "ThreadingUtilities", "UnPack", "VectorizationBase"]
git-tree-sha1 = "e03414d325902b1690d02a528ef46f9b29daaa73"
uuid = "bdcacae8-1622-11e9-2a5c-532679323890"
version = "0.12.51"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "6a8a2a625ab0dea913aba95c11370589e0239ff0"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.6"

[[ManualMemory]]
git-tree-sha1 = "71c64ebe61a12bad0911f8fc4f91df8a448c604c"
uuid = "d125e4d3-2237-4719-b19c-fa641b8a4667"
version = "0.1.4"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Measures]]
git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f"
uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
version = "0.3.1"

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "4ea90bd5d3985ae1f9a908bd4500ae88921c5ce7"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.0"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[MuladdMacro]]
git-tree-sha1 = "c6190f9a7fc5d9d5915ab29f2134421b12d24a68"
uuid = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221"
version = "0.2.2"

[[NLSolversBase]]
deps = ["DiffResults", "Distributed", "FiniteDiff", "ForwardDiff"]
git-tree-sha1 = "50608f411a1e178e0129eab4110bd56efd08816f"
uuid = "d41bc354-129a-5804-8e4c-c37616107c6c"
version = "7.8.0"

[[NLsolve]]
deps = ["Distances", "LineSearches", "LinearAlgebra", "NLSolversBase", "Printf", "Reexport"]
git-tree-sha1 = "019f12e9a1a7880459d0173c182e6a99365d7ac1"
uuid = "2774e3e8-f4cf-5e23-947b-6d7e65073b56"
version = "4.5.1"

[[NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[NonlinearSolve]]
deps = ["ArrayInterface", "FiniteDiff", "ForwardDiff", "IterativeSolvers", "LinearAlgebra", "RecursiveArrayTools", "RecursiveFactorization", "Reexport", "SciMLBase", "Setfield", "StaticArrays", "UnPack"]
git-tree-sha1 = "ef18e47df4f3917af35be5e5d7f5d97e8a83b0ec"
uuid = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
version = "0.3.8"

[[OffsetArrays]]
deps = ["Adapt"]
git-tree-sha1 = "4f825c6da64aebaa22cc058ecfceed1ab9af1c7e"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.10.3"

[[Ogg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7937eda4681660b4d6aeeecc2f7e1c81c8ee4e2f"
uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
version = "1.3.5+0"

[[OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "15003dcb7d8db3c6c857fda14891a539a8f2705a"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.10+0"

[[OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[Opus_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
uuid = "91d4177d-7536-5919-b921-800302f37372"
version = "1.3.2+0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[OrdinaryDiffEq]]
deps = ["Adapt", "ArrayInterface", "DataStructures", "DiffEqBase", "DocStringExtensions", "ExponentialUtilities", "FastClosures", "FiniteDiff", "ForwardDiff", "LinearAlgebra", "Logging", "MacroTools", "MuladdMacro", "NLsolve", "Polyester", "RecursiveArrayTools", "Reexport", "SparseArrays", "SparseDiffTools", "StaticArrays", "UnPack"]
git-tree-sha1 = "379ed10814cba5087978bce9df31161a496620d3"
uuid = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
version = "5.60.1"

[[PCRE_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488"
uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc"
version = "8.44.0+0"

[[Parameters]]
deps = ["OrderedCollections", "UnPack"]
git-tree-sha1 = "2276ac65f1e236e0a6ea70baff3f62ad4c625345"
uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a"
version = "0.12.2"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "1.1.0"

[[Pixman_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29"
uuid = "30392449-352a-5448-841d-b1acce4e97dc"
version = "0.40.1+0"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PlotThemes]]
deps = ["PlotUtils", "Requires", "Statistics"]
git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d"
uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
version = "2.0.1"

[[PlotUtils]]
deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"]
git-tree-sha1 = "501c20a63a34ac1d015d5304da0e645f42d91c9f"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.0.11"

[[Plots]]
deps = ["Base64", "Contour", "Dates", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs"]
git-tree-sha1 = "f3d4d35b8cb87adc844c05c722f505776ac29988"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.19.2"

[[PlutoUI]]
deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.9"

[[Polyester]]
deps = ["ArrayInterface", "IfElse", "ManualMemory", "Requires", "Static", "StrideArraysCore", "ThreadingUtilities", "VectorizationBase"]
git-tree-sha1 = "f5a74523ebc205723baefb95874f708741199d70"
uuid = "f517fe37-dbe3-4b94-8317-1923a5111588"
version = "0.3.4"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[Qt5Base_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "xkbcommon_jll"]
git-tree-sha1 = "ad368663a5e20dbb8d6dc2fddeefe4dae0781ae8"
uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1"
version = "5.15.3+0"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[RecipesBase]]
git-tree-sha1 = "b3fb709f3c97bfc6e948be68beeecb55a0b340ae"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.1"

[[RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "2a7a2469ed5d94a98dea0e85c46fa653d76be0cd"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.3.4"

[[RecursiveArrayTools]]
deps = ["ArrayInterface", "ChainRulesCore", "DocStringExtensions", "LinearAlgebra", "RecipesBase", "Requires", "StaticArrays", "Statistics", "ZygoteRules"]
git-tree-sha1 = "2a76e8f24c67f3ebecaccefa8d4abd27db828407"
uuid = "731186ca-8d62-57ce-b412-fbd966d074cd"
version = "2.14.9"

[[RecursiveFactorization]]
deps = ["LinearAlgebra", "LoopVectorization"]
git-tree-sha1 = "2e1a88c083ebe8ba69bc0b0084d4b4ba4aa35ae0"
uuid = "f2c3362d-daeb-58d1-803e-2bc74f2840b4"
version = "0.1.13"

[[Reexport]]
git-tree-sha1 = "5f6c21241f0f655da3952fd60aa18477cf96c220"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.1.0"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[SLEEFPirates]]
deps = ["IfElse", "Static", "VectorizationBase"]
git-tree-sha1 = "da6d214ffc85b1292f300649ef86d3c4f9aaf25d"
uuid = "476501e8-09a2-5ece-8869-fb82de89a1fa"
version = "0.6.22"

[[SciMLBase]]
deps = ["ArrayInterface", "CommonSolve", "ConstructionBase", "Distributed", "DocStringExtensions", "IteratorInterfaceExtensions", "LinearAlgebra", "Logging", "RecipesBase", "RecursiveArrayTools", "StaticArrays", "Statistics", "Tables", "TreeViews"]
git-tree-sha1 = "932aaae93e81686e6473f27b5a11c403576a2183"
uuid = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
version = "1.18.0"

[[Scratch]]
deps = ["Dates"]
git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.1.0"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[Setfield]]
deps = ["ConstructionBase", "Future", "MacroTools", "Requires"]
git-tree-sha1 = "d5640fc570fb1b6c54512f0bd3853866bd298b3e"
uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46"
version = "0.7.0"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Showoff]]
deps = ["Dates", "Grisu"]
git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
version = "1.0.3"

[[SimpleTraits]]
deps = ["InteractiveUtils", "MacroTools"]
git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
version = "0.9.4"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[SparseDiffTools]]
deps = ["Adapt", "ArrayInterface", "Compat", "DataStructures", "FiniteDiff", "ForwardDiff", "LightGraphs", "LinearAlgebra", "Requires", "SparseArrays", "StaticArrays", "VertexSafeGraphs"]
git-tree-sha1 = "cc8703d09d1e7337fb794dbf6c9634afaa4f49c7"
uuid = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
version = "1.14.1"

[[SpecialFunctions]]
deps = ["ChainRulesCore", "LogExpFunctions", "OpenSpecFun_jll"]
git-tree-sha1 = "a50550fa3164a8c46747e62063b4d774ac1bcf49"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.5.1"

[[Static]]
deps = ["IfElse"]
git-tree-sha1 = "2740ea27b66a41f9d213561a04573da5d3823d4b"
uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
version = "0.2.5"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "1b9a0f17ee0adde9e538227de093467348992397"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.7"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[StatsAPI]]
git-tree-sha1 = "1958272568dc176a1d881acb797beb909c785510"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.0.0"

[[StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "2f6792d523d7448bbe2fec99eca9218f06cc746d"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.8"

[[StrideArraysCore]]
deps = ["ArrayInterface", "ManualMemory", "Requires", "ThreadingUtilities", "VectorizationBase"]
git-tree-sha1 = "2525850b6887e217c3cc149ab29eb2a7a6360d37"
uuid = "7792a7ef-975c-4747-a70f-980b88e8d1da"
version = "0.1.16"

[[StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "000e168f5cc9aded17b6999a560b7c11dda69095"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.0"

[[SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "8ed4a3ea724dac32670b062be3ef1c1de6773ae8"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.4.4"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[ThreadingUtilities]]
deps = ["ManualMemory"]
git-tree-sha1 = "593009385e6536dc50baedc2c8bc1a3717ef358f"
uuid = "8290d209-cae3-49c0-8002-c8c24d57dab5"
version = "0.4.5"

[[TreeViews]]
deps = ["Test"]
git-tree-sha1 = "8d0d7a3fe2f30d6a7f833a5f19f7c7a5b396eae6"
uuid = "a2a6695c-b41b-5b7d-aed9-dbfdeacea5d7"
version = "0.3.0"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[UnPack]]
git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b"
uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
version = "1.0.2"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[VectorizationBase]]
deps = ["ArrayInterface", "Hwloc", "IfElse", "Libdl", "LinearAlgebra", "Static"]
git-tree-sha1 = "b609848b41158b8cc13a72dc6deb9511f9a96187"
uuid = "3d5dd08c-fd9d-11e8-17fa-ed2836048c2f"
version = "0.20.22"

[[VertexSafeGraphs]]
deps = ["LightGraphs"]
git-tree-sha1 = "b9b450c99a3ca1cc1c6836f560d8d887bcbe356e"
uuid = "19fa3120-7c27-5ec5-8db8-b0b0aa330d6f"
version = "0.1.2"

[[Wayland_jll]]
deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "3e61f0b86f90dacb0bc0e73a0c5a83f6a8636e23"
uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89"
version = "1.19.0+0"

[[Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll"]
git-tree-sha1 = "2839f1c1296940218e35df0bbb220f2a79686670"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.18.0+4"

[[XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"

[[XSLT_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
version = "1.1.34+0"

[[Xorg_libX11_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
git-tree-sha1 = "5be649d550f3f4b95308bf0183b82e2582876527"
uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
version = "1.6.9+4"

[[Xorg_libXau_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e"
uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
version = "1.0.9+4"

[[Xorg_libXcursor_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd"
uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724"
version = "1.2.0+4"

[[Xorg_libXdmcp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4"
uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
version = "1.1.3+4"

[[Xorg_libXext_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3"
uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
version = "1.3.4+4"

[[Xorg_libXfixes_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4"
uuid = "d091e8ba-531a-589c-9de9-94069b037ed8"
version = "5.0.3+4"

[[Xorg_libXi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"]
git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246"
uuid = "a51aa0fd-4e3c-5386-b890-e753decda492"
version = "1.7.10+4"

[[Xorg_libXinerama_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"]
git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123"
uuid = "d1454406-59df-5ea1-beac-c340f2130bc3"
version = "1.1.4+4"

[[Xorg_libXrandr_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631"
uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484"
version = "1.5.2+4"

[[Xorg_libXrender_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96"
uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
version = "0.9.10+4"

[[Xorg_libpthread_stubs_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb"
uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
version = "0.1.0+3"

[[Xorg_libxcb_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
git-tree-sha1 = "daf17f441228e7a3833846cd048892861cff16d6"
uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
version = "1.13.0+3"

[[Xorg_libxkbfile_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "926af861744212db0eb001d9e40b5d16292080b2"
uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a"
version = "1.1.0+4"

[[Xorg_xcb_util_image_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "0fab0a40349ba1cba2c1da699243396ff8e94b97"
uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b"
version = "0.4.0+1"

[[Xorg_xcb_util_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll"]
git-tree-sha1 = "e7fd7b2881fa2eaa72717420894d3938177862d1"
uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5"
version = "0.4.0+1"

[[Xorg_xcb_util_keysyms_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "d1151e2c45a544f32441a567d1690e701ec89b00"
uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7"
version = "0.4.0+1"

[[Xorg_xcb_util_renderutil_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "dfd7a8f38d4613b6a575253b3174dd991ca6183e"
uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e"
version = "0.3.9+1"

[[Xorg_xcb_util_wm_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "e78d10aab01a4a154142c5006ed44fd9e8e31b67"
uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361"
version = "0.4.1+1"

[[Xorg_xkbcomp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxkbfile_jll"]
git-tree-sha1 = "4bcbf660f6c2e714f87e960a171b119d06ee163b"
uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4"
version = "1.4.2+4"

[[Xorg_xkeyboard_config_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xkbcomp_jll"]
git-tree-sha1 = "5c8424f8a67c3f2209646d4425f3d415fee5931d"
uuid = "33bec58e-1273-512f-9401-5d533626f822"
version = "2.27.0+4"

[[Xorg_xtrans_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845"
uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
version = "1.4.0+3"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.0+0"

[[ZygoteRules]]
deps = ["MacroTools"]
git-tree-sha1 = "9e7a1e8ca60b742e508a315c17eef5211e7fbfd7"
uuid = "700de1a5-db45-46bc-99cf-38207098b444"
version = "0.2.1"

[[libass_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "acc685bcf777b2202a904cdcb49ad34c2fa1880c"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.14.0+4"

[[libfdk_aac_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7a5780a0d9c6864184b3a2eeeb833a0c871f00ab"
uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
version = "0.1.6+4"

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"

[[libvorbis_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
git-tree-sha1 = "c45f4e40e7aafe9d086379e5578947ec8b95a8fb"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+0"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"

[[x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d713c1ce4deac133e3334ee12f4adff07f81778f"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2020.7.14+2"

[[x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "487da2f8f2f0c8ee0e83f39d13037d6bbf0a45ab"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.0.0+3"

[[xkbcommon_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"]
git-tree-sha1 = "ece2350174195bb31de1a63bea3a41ae1aa593b6"
uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd"
version = "0.9.1+5"
"""

# ╔═╡ Cell order:
# ╟─9997fcae-e8c0-11eb-29d3-19aa2548bb51
# ╠═f02367f9-77ec-4af5-ab95-c216cc61773f
# ╟─a3756580-3ac1-40b5-94ea-9e164c9065e5
# ╠═5f1fe571-3357-49cf-93d6-49ae6b4b9a05
# ╠═4a72dcd8-bb16-4d63-a8a8-e89865b3ff95
# ╠═7541a4c5-e8bd-4700-880d-8d7e1b05cdf9
# ╠═9087b209-8385-4da8-8cfd-6eb7f4a2a0aa
# ╠═8e990cf8-3fee-4fde-b044-0e95c90a19a8
# ╠═54a73741-2b04-4bb5-9bcb-b594b451d9ec
# ╠═568527c6-0760-405e-a056-98a22f95454c
# ╠═dea4b784-87c7-4497-b11b-b1ca1cc83c71
# ╠═b330f75d-125c-4a49-a830-5672ed18c56b
# ╠═364206c0-954b-45b1-9f20-e5031c34de16
# ╠═2f80e9cb-9bd6-4189-9d84-20d5e1c6b0e3
# ╠═8864fe08-3dd3-4731-ac26-7ae256835f03
# ╠═5a3929ad-5719-4021-993c-acc31db4a674
# ╠═5df4f25f-ff30-46ea-b7d1-923001d900fa
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


countbulls(a, b) = sum([a[i] == b[i] for i in 1:length(a)])
countcows(a, b) = sum([a[i] == b[j] for i in 1:length(a), j in 1:length(b) if i != j])
validate(a, b) = typeof(a) == Int && typeof(b) == Int && a >= 0 && b >= 0 && a + b < 5

function doguess()
    poss = [a for a in collect(Base.product(1:9,1:9,1:9,1:9)) if length(unique(a))[1]==4]
    while(length(poss) > 0)
        ans = rand(poss, 1)[1]
        while true
            println("My guess: $(ans[1])$(ans[2])$(ans[3])$(ans[4]). How many bulls and cows?")
            regres = match(r"\D*(\d+)\D*(\d+)", readline())
            bul, cow = parse(Int,regres[1]), parse(Int,regres[2])
            if(validate(bul, cow))
                break
            else
                println("Please enter an integer each for bulls and cows.")
            end
        end
        if(bul == 4)
            return ans
        end
        filter!(i -> (countbulls(ans,i), countcows(ans, i)) == (bul, cow), poss)
    end
    Base.throw("ERROR: No solutions found. Inconsistent scoring by other player?")
end

answ = doguess()
println("The winning pick: $(answ[1])$(answ[2])$(answ[3])$(answ[4])")


# Note:
# Another Possible poisson generator:
# https://github.com/JuliaStats/Distributions.jl/blob/master/src/univariate/discrete/poisson.jl

# Note on randjump():
# https://www.juliabloggers.com/basics-of-generating-random-numbers-in-julia/

using Random
using Future

# The output of the stream can connect 1 or more Connections.
mutable struct PoissonStream <: AbstractBitStream
    base::BaseData{UInt8}
    seed::Int64

    # The Interspike interval (ISI) counter is populated by a value.
    # When the counter reaches 0 a spike is placed on the output
    # for single pass.
    isi::UInt64

    # This equal to lamba rates.
    firing_rate::Float64

    rng::MersenneTwister

    function PoissonStream(seed::UInt64, firing_rate::Float64 = 0.002)
        o = new()
        o.base = BaseData{UInt8}()
        o.firing_rate = firing_rate
        o.seed = seed
        o.isi = 0
        reset!(o)
        step!(o)
        o
    end
end

# Create an event per interval of time, for example, spikes in a 1 sec span.
# A firing rate given in rate/ms, for example, 0.2 in 1ms (0.2/1)
# or 200 in 1sec (200/1000ms)
function next!(stream::PoissonStream)
    p = -log(1.0 - rand(stream.rng, 1)[1]) / stream.firing_rate
    UInt64(round(p))
end

function reset!(stream::PoissonStream)
    stream.rng = Future.randjump(MersenneTwister(stream.seed), big(10)^20)
    stream.isi = next!(stream)
end

function step!(stream::PoissonStream)
    # Check ISI counter
   	if stream.isi == 0
	    # Time to generate a spike
        stream.base.output = 1
        stream.isi = next!(stream)
    else
        stream.base.output = 0
        stream.isi -= 1
    end
end

function output(stream::PoissonStream)
    stream.base.output & 0x01
end

function output(stream::PoissonStream, id::Int64)
    stream.base.output & 0x01
end


export rand_supremacy2d

# control-Z entangler.
cz_entangler(n::Int, pairs) = chain(n, control(n, [ctrl], target=>Z) for (ctrl, target) in pairs)

"""
    rand_supremacy2d(nx::Int, ny::Int, depth::Int) -> AbstactBlock

The circuit proposed for realizing quantum supermacy in a near-term device.

References
-------------------------------
* Boixo, Sergio, et al. "Characterizing quantum supremacy in near-term devices." Nature Physics 14.6 (2018): 595-600.

!!! note

    Some restrictions are loosed, please check this circuit carefully.
"""
function rand_supremacy2d(nx::Int, ny::Int, depth::Int)
    nbits = nx*ny
    entanglers = map(pair->cz_entangler(nbits, pair), pair_supremacy(nx, ny))
    gateset = [ConstGate.T, SqrtX, SqrtY]
    c = chain(nbits, repeat(nbits, H, 1:nbits))
    pre_occ = (1:nbits...,)
    pre_gates = Vector{PrimitiveBlock}(fill(H,nbits))
    hastgate = zeros(Bool, nbits)
    for i=1:depth-2
        ent = entanglers[mod1(i,length(entanglers))]
        unit = chain(nbits, [ent])
        occ = occupied_locs(ent)
        for loc in setdiff(pre_occ, occ)
            g1 = hastgate[loc] ? rand(setdiff(gateset, (pre_gates[loc],))) : (hastgate[loc]=true; T)
            push!(unit, put(nbits, loc=>g1))
            pre_gates[loc] = g1
        end
        pre_occ = occ
        push!(c, unit)
    end
    depth!=1 && push!(c, repeat(nbits, H, 1:nbits))
    return c
end

# obtain supremacy pairing patterns
function pair_supremacy(nx::Int, ny::Int; periodic=false)
    Kx = [0.25 0.5; 0.25 -0.5]
    Ky = [0.5 0.25; -0.5 0.25]
    out = Vector[]
    li = LinearIndices((nx, ny))

    for (dx, dy, isxdirection) in [(2,0,true), (0,0,true), (0,3,false), (0,1,false),
                                    (3,0,true), (1,0,true), (0,0,false), (0,2,false)]
        res = Pair{Int, Int}[]
        for i = 1:nx, j=1:ny
            if (periodic || (isxdirection ? i<nx : j<ny)) && all(mod.((isxdirection ? Kx : Ky)*[i-dx-1, j-dy-1], 1) .≈ 0)
                i_ = isxdirection ? mod1(i+1, nx) : i
                j_ = isxdirection ? j : mod1(j+1, ny)
                push!(res, li[i, j] => li[i_, j_])
            end
        end
        push!(out, res)
    end

    return out
end


# * Multipole expansion

"""
    multipole_expand_scalar_product(a, b, P, Q, c, d)

Multipole-expand the matrix element `⟨ab|P⋅Q|cd⟩`, where the tensor
`P` acts on orbitals `a` & `c`, and the tensor `Q` acts on orbitals
`b` & `d`. The definition is taken from Eq. (13.1.26) of Varshalovich
(1988).
"""
function multipole_expand_scalar_product(a, b, P, Q, c, d, f::Int=1)
    multipole_terms = Pair{Int,Float64}[]

    couples(a, P, c) && couples(b, Q, d) ||
        return multipole_terms

    ks = ranks(a,P,c) ∩ ranks(b,Q,d)

    for k in ks
        X = P(k)⋅Q(k)
        v = dot((a,b), X, (c,d))
        iszero(v) && continue

        push!(multipole_terms, k => f*v)
    end

    multipole_terms
end

"""
    multipole_expand(integral::OrbitalMatrixElement{2,A,<:CoulombInteraction,B})

Multipole-expand the two-body integral resulting from the Coulomb
repulsion between two electrons.
"""
function multipole_expand(integral::OrbitalMatrixElement{2,<:Any,<:CoulombInteraction,<:Any})
    (a,b),g,(c,d) = integral.a,integral.o,integral.b

    terms = NBodyTerm[]

    for (k,coeff) in multipole_expand_scalar_product(a, b, CoulombTensor, CoulombTensor, c, d)
        # Remember: integral.a == (a,b), integral.b == (c,d) => two-body integral
        push!(terms, NBodyTerm([radial_integral(integral.a, (k,g), integral.b)], coeff))
    end

    NBodyMatrixElement(terms)
end

export multipole_expand_scalar_product, multipole_expand


export UCBExplorer

using Random
using Flux

Base.@kwdef mutable struct UCBExplorer{R<:AbstractRNG} <: AbstractExplorer
    c::Float64
    actioncounts::Vector{Float64}
    step::Int
    rng::R
    is_training::Bool = true
end

"""
    UCBExplorer(na; c=2.0, ϵ=1e-10, step=1, seed=nothing)

# Arguments
- `na` is the number of actions used to create a internal counter.
- `t` is used to store current time step.
- `c` is used to control the degree of exploration.
- `seed`, set the seed of inner RNG.
- `is_training=true`, in training mode, time step and counter will not be updated.
"""
UCBExplorer(na; c = 2.0, ϵ = 1e-10, step = 1, rng = Random.GLOBAL_RNG, is_training = true) =
    UCBExplorer(c, fill(ϵ, na), 1, rng, is_training)

function (p::UCBExplorer)(values::AbstractArray)
    v, inds = find_all_max(@. values + p.c * sqrt(log(p.step + 1) / p.actioncounts))
    action = sample(p.rng, inds)
    if p.is_training
        p.actioncounts[action] += 1
        p.step += 1
    end
    action
end