if get(ARGS, 1, "Array") == "CuArray"
    using CUDA
    const ArrayType = CUDA.CuArray
    CUDA.allowscalar(false)
else
    const ArrayType = Array
end

using Test
using NonlinearSolvers

@testset "NonlinearSolvers tests" begin
    function f!(F, x)
        t = Tuple(x)
        F_nt = ntuple(Val(length(F))) do i
            i == 1 && (F_i = (t[1] + 3) * (t[2]^3 - 7) + 18)
            i == 2 && (F_i = sin(t[2] * exp(t[1]) - 1))
            F_i
        end
        F .= F_nt
    end

    function j!(J, x)
        t = Tuple(x)
        u = exp(t[1]) * cos(t[2] * exp(t[1]) - 1)
        J_nt = ntuple(Val(length(J))) do i
            i == 1 && (J_ij = t[2]^3 - 7)
            i == 2 && (J_ij = t[2] * u)
            i == 3 && (J_ij = 3 * t[2]^2 * (t[1] + 3))
            i == 4 && (J_ij = u)
            return J_ij
        end
        J[:] .= J_nt
    end
    FT = Float64
    N = 2
    x_initial = FT[0.1; 1.2]
    correct_sol = FT[0, 1]

    @show ArrayType
    x_initial = ArrayType(x_initial)

    @testset "Compact solution converging" begin
        # Jacobian provided
        nls = NewtonsMethod(f!, j!, x_initial)
        sol = solve!(nls)
        @test all(isapprox.(correct_sol, Array(sol.root); atol = 10eps(FT)))
        @test sol.converged == true

        # Jacobian-free
        nls = NewtonsMethodAD(f!, x_initial)
        sol = solve!(nls)
        @test all(isapprox.(correct_sol, Array(sol.root); atol = 10eps(FT)))
        @test sol.converged == true
    end

    @testset "Compact solution non-converging" begin
        # Jacobian provided
        nls = NewtonsMethod(f!, j!, x_initial)
        sol = solve!(nls, CompactSolution(), SolutionTolerance(eps(FT)), 1)
        @test sol.converged == false

        # Jacobian-free
        nls = NewtonsMethodAD(f!, x_initial)
        sol = solve!(nls, CompactSolution(), SolutionTolerance(eps(FT)), 1)
        @test sol.converged == false
    end

    if x_initial isa Array
        @testset "Verbose solution" begin
            # Jacobian provided
            nls = NewtonsMethod(f!, j!, x_initial)
            sol = solve!(nls, VerboseSolution())
            @test all(isapprox.(correct_sol, Array(sol.root); atol = 10eps(FT)))
            @test sol.converged
            @test sol.err[end] < 10eps(FT)
            @test hasproperty(sol, :iter_performed)
            @test sol.root_history isa Vector{typeof(nls.x1)}
            @test sol.err_history isa Vector{typeof(nls.x1)}

            # Jacobian-free
            nls = NewtonsMethodAD(f!, x_initial)
            sol = solve!(nls, VerboseSolution())
            @test all(isapprox.(correct_sol, Array(sol.root); atol = 10eps(FT)))

            @test sol.converged
            @test sol.err[end] < 10eps(FT)
            @test hasproperty(sol, :iter_performed)
            @test sol.root_history isa Vector{typeof(nls.x1)}
            @test sol.err_history isa Vector{typeof(nls.x1)}
        end
    end
end


### A Pluto.jl notebook ###
# v0.14.6

using Markdown
using InteractiveUtils

# ╔═╡ 48aa5a70-b65d-11eb-1f4c-ed447341ce4f
begin
	using FFTW, DeconvOptim, TestImages, Napari, Noise
	using IndexFunArrays, FourierTools, Colors
	v = napari.view_image
end

# ╔═╡ ced8e77f-035e-4aae-b236-b5196028861f
md"## Load Packages"

# ╔═╡ 6e88b04a-dcb5-4bc9-a4f9-f5b7dfd9965e
FFTW.set_num_threads(1)

# ╔═╡ bf93f8bd-144b-4c90-892e-01df05956327
begin
	img = Float32.(testimage("resolution_test_512"))
end

# ╔═╡ 35262a15-628e-4639-a0ca-2b53ac7fbecc
md"## Simulate a simple PSF"

# ╔═╡ 594eb15c-7f38-4890-8f26-d35a9fe128ad
function simulate_psf(shape, radius, dtype=Float32)
	arr_ft = ones(dtype, shape)
	aperture = rr(shape) .< radius
	arr = abs2.(ift(arr_ft .* aperture))
	arr ./= sum(arr)
	return arr
end

# ╔═╡ ba837952-d782-46eb-846c-7893401a4d4b
psf = ifftshift(simulate_psf(size(img), 30));

# ╔═╡ ed5e859b-aa48-4d43-8378-367be437f17e
v(psf)

# ╔═╡ 3cf013a8-221d-40ca-91b6-0a48774d9317
md"### Create blurry measured image"

# ╔═╡ 085402ed-d42d-40f5-9f70-6c6c0483aa39
measured = poisson(FourierTools.conv(img, psf), 300)

# ╔═╡ eb362894-9ea4-4a0d-8723-7acd39abd567
v(measured)

# ╔═╡ 4464fa21-69d4-4447-ba27-7299d7e8d454
md"## DeconvOptim.jl"

# ╔═╡ 5f7222dc-c2c5-4fd5-879f-0dddc99c9c6e
reg = TV()

# ╔═╡ 16d79931-5ae0-4515-b1f3-f1b694da80c6
res, o = deconvolution(measured, psf, regularizer=reg, iterations=20, λ=0.02);

# ╔═╡ 01a50c2a-bb57-4625-980d-fd4c9d07044e
v(res)

# ╔═╡ Cell order:
# ╟─ced8e77f-035e-4aae-b236-b5196028861f
# ╠═48aa5a70-b65d-11eb-1f4c-ed447341ce4f
# ╠═6e88b04a-dcb5-4bc9-a4f9-f5b7dfd9965e
# ╠═bf93f8bd-144b-4c90-892e-01df05956327
# ╟─35262a15-628e-4639-a0ca-2b53ac7fbecc
# ╠═594eb15c-7f38-4890-8f26-d35a9fe128ad
# ╠═ba837952-d782-46eb-846c-7893401a4d4b
# ╠═ed5e859b-aa48-4d43-8378-367be437f17e
# ╟─3cf013a8-221d-40ca-91b6-0a48774d9317
# ╠═085402ed-d42d-40f5-9f70-6c6c0483aa39
# ╠═eb362894-9ea4-4a0d-8723-7acd39abd567
# ╟─4464fa21-69d4-4447-ba27-7299d7e8d454
# ╠═5f7222dc-c2c5-4fd5-879f-0dddc99c9c6e
# ╠═16d79931-5ae0-4515-b1f3-f1b694da80c6
# ╠═01a50c2a-bb57-4625-980d-fd4c9d07044e


# The purpose of this script is to compute the empirical mean squared error of
# various algorithms for line spetral estimation for separating three closeby
# sinusoids with frequencies [f_1, f_1 + delta, f_1 + 2*delta] for varying
# delta.
#
# Parameters:
# N: number of measurements per snapshot
# L: number of snapshots
# Niters: number of Monte Carlo iterations
# m: number of sparse measurements which can be used in some of the algorithms;
#    set m = N for full measurements
# SNR_db: signal-to-noise-ratio [dB] of the AWGN model
# f_true: location of the frequencies of the sinusoids

include("MUSIC.jl")
include("rootMUSIC.jl")
include("ESPRIT.jl")
include("TLSesprit.jl")
include("MPM.jl")
include("ANM.jl")
include("EMAC.jl")
include("periodogram.jl")
include("findMinWrapDist.jl")
include("findMinWrapDistUnique.jl")
include("WrapDist.jl")

using Plots, Random

N = 10
m = N
Niters = 3
L = 1
SNR_db = 30

SNR = 10^(SNR_db/10)

K = 3
f1 = 0.3
f_range = range(0.0,0.1,length = 5)
MSE = zeros(length(f_range),3)

A(f,N) = exp.(2*pi*1im*kron(f,(0:N-1)))

for ii = 1:length(f_range)
    f_true = [f1 f1+f_range[ii] f1 + 2*f_range[ii]]
    for jj = 1:Niters

        noise = (randn(N,L)+1im*randn(N,L))/ (sqrt(2)*10^(SNR_db/10))
        omega = sort(randperm(m)[1:N])
        S = exp.(2*pi*rand(K,L)*1im)

        Y1 = A(f_true,N)*S + noise
        Y2 = A(f_true,m)[omega,:]*S + noise
        #t0 = time_ns()
        #f_MUSIC = rootMUSIC(Y,K,SNR)
        #t1 = (time_ns()-t0)/1e9
        #MinWrapDist = findMinWrapDistUnique(f_true,f_MUSIC)
        #MSE[ii,1] += norm(MinWrapDist)^2

        t0 = time_ns()
        f_ESPRIT = rootMUSIC(Y1,K)
        t2 = (time_ns()-t0)/1e9
        MinWrapDist = findMinWrapDistUnique(f_true,f_ESPRIT)
        MSE[ii,1] += norm(MinWrapDist)^2

        #t0 = time_ns()
        #f_MPM = MPM(Y,K)
        #t3 = (time_ns()-t0)/1e9
        #MinWrapDist = findMinWrapDistUnique(f_true,f_MPM)
        #MSE[ii,3] += norm(MinWrapDist)^2

        f_EMAC = EMAC(Y2,m,K,omega,1/SNR)
        MinWrapDist = findMinWrapDistUnique(f_true,f_EMAC)
        MSE[ii,2] += norm(MinWrapDist)^2

        f_ANM = ANM(Y2,m,K,omega,1/SNR)
        MinWrapDist = findMinWrapDistUnique(f_true,f_ANM)
        MSE[ii,3] += norm(MinWrapDist)^2

    end
end

MSE /= Niters

plot(f_range, MSE[:,1],yaxis=:log,tickfontsize = 6,
label="root MUSIC",
#xlims = (0,1),
#xticks = round.(f_range.-f1; digits=2),
xlabel = "delta f",
ylabel = "MSE"
)
plot!(f_range,MSE[:,2],label="EMAC")
plot!(f_range,MSE[:,3],label="ANM")
#savefig("/home/thomas/atom-amd64/usr/share/atom/example/mse_delta_f.png")


using MatrixCompletion
import Distributions

function unit_test_relative_error(metric,input,reference;base_metric = metric)
    @test abs(
        provide(RelativeError(),input,reference;metric = metric,base_metric=base_metric)
              - metric(input-reference)/base_metric(reference)) <1e-5
end

function unit_test_absolute_error(metric,input,reference)
    @test abs(provide(AbsoluteError(),input,reference;metric=metric) -  metric(input-reference)) <1e-5
end

function unit_test_diagnostics(;input =nothing, reference = nothing)

end

@testset "$(format("Diagnostics: Absolute Error[LpMetric]"))" begin
    # test lp norm metric for arrays
    [unit_test_absolute_error(metric,rand(100),rand(100)) for metric in [x ->  LinearAlgebra.norm(x,p) for p in 1:0.5:10]]
    # test lp norm metric for metrices
    [unit_test_relative_error(metric,rand(100,100),rand(100,100)) for metric in [x ->  LinearAlgebra.norm(x,p) for p in 1:0.5:10]]
    # test within_radius metric for arrays
end

@testset "$(format("Diagnostics: Absolute Error[WithinRadius]"))" begin
    let
        for i = 1:10
            tc = rand(100) .+ 1000
            mask = rand(Distributions.Bernoulli(0.6),100)
            num_of_non_zeros = sum(mask)
            unit_test_absolute_error(x -> within_radius(x), mask,zeros(100))
            unit_test_absolute_error(x -> within_radius(x), tc .* mask, tc)
        end
    end
    # test within_radius metric for matrices
    let
        for i = 1:10
            tc = rand(100,100) .+ 1000
            mask = rand(Distributions.Bernoulli(0.6),100,100)
            num_of_non_zeros = sum(mask)
            unit_test_absolute_error(x -> within_radius(x), mask,zeros(100,100))
            unit_test_absolute_error(x -> within_radius(x), tc .* mask, tc)
        end
    end
end

@testset "$(format("Diagnostics: Relative Error[LpMetric]"))" begin
    # test lp norm metric for arrays
    [unit_test_relative_error(metric,rand(100),rand(100)) for metric in [x ->  LinearAlgebra.norm(x,p) for p in 1:0.5:10]]
    # test lp norm metric for matrices
    [unit_test_relative_error(metric,rand(100,100),rand(100,100)) for metric in [x ->  LinearAlgebra.norm(x,p) for p in 1:0.5:10]]
end

@testset "$(format("Diagnostics: Relative Error[WithinRadius]"))" begin
    # for arrays
    let
        for i = 1:10
            tc = rand(100) .+ 1000
            mask = rand(Distributions.Bernoulli(0.6),100)
            num_of_non_zeros = sum(mask)
            unit_test_relative_error(x -> within_radius(x), mask,zeros(100);base_metric = x -> LinearAlgebra.norm(mask,0))
            unit_test_relative_error(x -> within_radius(x), tc .* mask, tc;base_metric = x -> LinearAlgebra.norm(mask,0))
        end
    end
    # for matrices
    let
        for i = 1:10
            tc = rand(100,100) .+ 1000
            mask = rand(Distributions.Bernoulli(0.6),100,100)
            num_of_non_zeros = sum(mask)
            unit_test_relative_error(x -> within_radius(x), mask,zeros(100,100);base_metric = x -> LinearAlgebra.norm(mask,0))
            unit_test_relative_error(x -> within_radius(x), tc .* mask, tc;base_metric = x -> LinearAlgebra.norm(mask,0))
        end
    end
end

@testset "$(format("Diagnostics: Construction[For Arrays]"))" begin

    # test dispatcher
    @test isa(provide(Diagnostics{Gamma()}(),input_data = [1.1], reference = [1.1]),Dict)

    # test arg parser
    @test_throws DomainError provide(Diagnostics{Gamma()}())

    # test for arrays
    let
        input_data = [1,1,2,1,0] * 1.0
        reference = deepcopy(input_data)
        diagnostic = provide(Diagnostics{Gamma()}(),
                             input_data = input_data,reference = reference);
        @test diagnostic["relative-error[#within-radius(1e-5)]"] == sum(Int.(abs.(input_data - reference) .> 1e-5))/length(input_data)
        @test diagnostic["absolute-error[#within-radius(1e-5)]"] == sum(Int.(abs.(input_data - reference) .> 1e-5))
        @test diagnostic["relative-error[L1]"] == 0
        @test diagnostic["relative-error[L2]"] == 0
        @test diagnostic["absolute-error[L1]"] == 0
        @test diagnostic["absolute-error[L2]"] == 0
        @test LinearAlgebra.norm(diagnostic["error-matrix"] - [0,0,0,0,0],2) <1e-5
    end
    let
        input_data = [1,1,3,1,0] * 1.0
        reference  = [1,0,1,0,0] * 1.0
        diagnostic = provide(Diagnostics{Gamma()}(),
                             input_data = input_data,reference = reference);
        @test diagnostic["relative-error[#within-radius(1e-5)]"] == 3/5
        @test diagnostic["absolute-error[#within-radius(1e-5)]"] == 3
        @test diagnostic["relative-error[L1]"] == 2
        @test diagnostic["relative-error[L2]"] == sqrt(6) / sqrt(2)
        @test diagnostic["absolute-error[L1]"] == 4
        @test diagnostic["absolute-error[L2]"] == sqrt(6)
        @test LinearAlgebra.norm(diagnostic["error-matrix"] - [0,1,2,1,0],2) <1e-5
    end
end

@testset "$(format("Diagnostics: Construction[For Matrices]"))" begin

    # test dispatcher
    @test isa(provide(Diagnostics{Gamma()}(),input_data = ones(2,2), reference = ones(2,2)),Dict)
    # zeros
    let
        input_data = rand(10,10)
        reference = deepcopy(input_data)
        diagnostic = provide(Diagnostics{Gamma()}(),
                             input_data = input_data, reference = reference);
        @test diagnostic["relative-error[#within-radius(1e-5)]"] == 0
        @test diagnostic["absolute-error[#within-radius(1e-5)]"] == 0
        @test diagnostic["relative-error[L1]"] == 0
        @test diagnostic["relative-error[L2]"] == 0
        @test diagnostic["absolute-error[L1]"] == 0
        @test diagnostic["absolute-error[L2]"] == 0
        @test LinearAlgebra.norm(diagnostic["error-matrix"] - zeros(10,10) ,2) <1e-5
    end
    # given test
    let
        input_data = [1 0 2;
                      0 2 0;
                      0 0 1]
        reference = [1 0 0;
                     0 0 1;
                     0 0 1]
        diagnostic = provide(Diagnostics{Gamma()}(),
                             input_data = input_data,reference = reference);
        @test diagnostic["relative-error[#within-radius(1e-5)]"] == 3/9
        @test diagnostic["absolute-error[#within-radius(1e-5)]"] == 3
        @test diagnostic["relative-error[L1]"] == 5 /3
        @test diagnostic["relative-error[L2]"] == 3/sqrt(3)
        @test diagnostic["absolute-error[L1]"] == 5
        @test diagnostic["absolute-error[L2]"] == 3
        @test LinearAlgebra.norm(diagnostic["error-matrix"] - [0 0 2;0 2 1;0 0 0],2) <1e-5
    end
end


function horner(cfs::AbstractVector,z::T) where T
    ret = zero(T)
    for k = length(cfs):-1:1
        ret = ret .* z + cfs[k]
    end
    ret
end

## Evaluate at a point using Horner's method (above)
function evaluate(cfs::AbstractVector,S::MalmquistTakenaka,x)
    T = eltype(x)
    z = (-one(T)im)*(S.λ .- x)./(conj(S.λ) .- x)
    ret = (-one(T)im)*horner(cfs[1:2:end],z) ./ (conj(S.λ) .- x)
    ret = ret .+ horner(cfs[2:2:end],inv.(z)) ./ (S.λ .- x)
    ret *= sqrt(abs(imag(S.λ))/π)
    ret
end

function evaluate(cfs::AbstractVector,S::Weideman,x)
    T = eltype(x)
    z = (-one(T)im)*(S.λ .- x)./(conj(S.λ) .- x)
    ret = horner(cfs[1:2:end],z)
    ret = ret .+ horner([zero(T);cfs[2:2:end]],inv.(z))
    ret
end

## Reorders coefficients to be indexed by ℤ
function hatify(cfs::AbstractVector)
    newcfs = copy(cfs)
    n = length(cfs)
    newcfs[div(n,2)+mod(n,2)+1:n] = cfs[1:2:n]
    newcfs[div(n,2)+mod(n,2):-1:1] = cfs[2:2:n]
    -div(n,2)-mod(n,2):div(n,2)-1, newcfs
end

@recipe function f(F::Fun{Sp,T,Vector{T}}) where {Sp<:Union{MalmquistTakenaka,Weideman},T}
    S = space(F)
    λ = S.λ
    a = real(λ) - 30*imag(λ)
    b = real(λ) + 30*imag(λ)
    cfs = coefficients(F)
    if length(cfs) < 200
        pad!(cfs,200)
    end
    n = length(cfs)
    pts = points(S,n)
    pts = [pts[div(n,2)+2:n]; pts[1:div(n,2)+mod(n,2)]]
    vals = values(F)
    vals = [vals[div(n,2)+2:n]; vals[1:div(n,2)+mod(n,2)]]

    inds = findall(x -> a ≤ x ≤ b,pts)
    @series begin
        pts[inds],real(vals[inds])
    end
    if maximum(abs.(imag(vals[inds]))) ≥ 1e-6
        @series begin
            pts[inds],imag(vals[inds])
        end
    end
end


module FixPA
export rev_add

using SmoothedParticles

"""
    FixPA_eps

    Epsilon in the fixed-point arithmetic
"""
const FixPA_eps = 1/(2^30)

"""
    nom(x::Float64)::Int64

Value of ``x`` in the fixed-point arithmetic.
"""
function nom(x::Float64)::Int64
    return Int64(round(x/FixPA_eps))
end

"""
    rev_add(x::Float64, y::Float64)::Float64

Reversible addition of two floats.
"""
function rev_add(x::Float64, y::Float64)::Float64
    return FixPA_eps*(nom(x) + nom(y))
end

"""
    rev_add(x::RealVector, y::RealVector)::RealVector

Reversible addition of two vectors.
"""
function rev_add(x::RealVector, y::RealVector)::RealVector
    v1 = FixPA_eps*(nom(x[1]) + nom(y[1]))
    v2 = FixPA_eps*(nom(x[2]) + nom(y[2]))
    v3 = FixPA_eps*(nom(x[3]) + nom(y[3]))
    return RealVector(v1,v2,v3)
end

end


using DataFrames

export Mack, claims2cum, cum2claims, cum2futureclaims
"""
A reserve triangle with chain ladder information
"""
type Mack
  "Cummulative claims (upper triangle)"
  cum::Matrix{Real}
  "Time vector of development factors"
  f::Vector{Real}
  "Time vector of future claims payments"
  futureclaims::Vector{Real}
  "Vector of reseves"
  res::Vector{Real}
  """
  Total reserves: ``\mathit{tot\_res} =\sum_{i=1}^I \mathit{res}_i``
  """
  tot_res::Real
  "Vector of mean square errors"
  mse::Vector{Real}
  "Total mean square error"
  tot_mse::Real
end

function Mack(df_triang::DataFrame; cum::Bool = false)
Mack(convert(DataArray{Real, 2}, df_triang); cum = cum)
end

function Mack(da_triang::DataArray{Real, 2}; cum::Bool = false)
  Mack(convert(Array{Real,2}, da_triang, 0); cum = cum)
end

function Mack(triang::Array{Real,2}; cum::Bool = false)
  if cum
    C = deepcopy(triang)
  else
    C = claims2cum(triang)
  end
  # Number of accident / development years
  I = size(C)[1]
  # Development factors
  f = [sum(C[1:(I-k),k+1])/sum(C[1:(I-k),k]) for k in 1:(I-1)]
  # Ultimate claim amounts
  for k  in 2:I
    for l in (I+1-k+1):I
      C[k,l] = C[k,I+1-k] * prod(f[(I+1-k):(l-1)])
    end
  end
  futclaims = cum2futureclaims(C)
  # Reserves
  R = [C[k,I]-C[k,I+1-k] for k in 1:I]
  σ² =
    [1/(I-k-1) *
      C[1:(I-k),k] ⋅ (C[1:(I-k),k+1] ./ C[1:(I-k),k]-f[k]).^2
    for k in 1:(I-1)]
  σ²[I-1] = min(σ²[I-2]^2 / σ²[I-3], min(σ²[I-3],σ²[I-2]))
  # mean square error
  mse = zeros(Real, I)
  for i in 2:I
    mse[i] = 0
    for k in (I+1-i):(I-1)
      mse[i] += σ²[k]/f[k]^2 * (1/C[i,k] + 1/ sum(C[1:I-k,k]))
    end
    mse[i] *= C[i,I]^2
  end
  # total mean square error
  mse_total = 0.0
  for i in 2:I
    tmp = 0.0
    for k in collect(I+1-i : I-1)
      tmp += (2σ²[k] / f[k]^2) / sum(C[1 : I-k, k])
    end
    tmp *= C[i,I] * sum(C[i+1 : I, I])
    tmp += mse[i]
    mse_total += tmp
  end
  return Mack(C, f, futclaims, R, sum(R), mse, mse_total)
end

"""
    claims2cum(c::Array{Real,2})

    Convert a quadratic upper triangular reserve matrix to
    the correponding cumulative triangular reserve matrix.
    The (strictly) lower triangular part is set to 0
"""
function claims2cum(c::Array{Real,2})
  if size(c,1) ≠ size(c,2)
    error("claims2cum: Matrix is not quadratic")
  end
  cum = zeros(Real,size(c))
  for i in 1:size(c, 1)
    cum[i,1:size(c, 1)-i+1] = cumsum(c[i,1:size(c, 1)-i+1], 2)
  end
  cum
end

"""
    cum2claims(c::Array{Real,2})

    Convert a quadratic cumulative claims matrix into a
    claims matrix.
"""
function cum2claims(c::Array{Real,2})
  I = size(c,2)
  if size(c,1) ≠ I
    error("cum2claims: Matrix is not quadratic")
  end
  claims = zeros(Real,size(c))
  for i in 1:I
    claims[i,1] = c[i,1]
    for k in 2:I
     claims[i,k] = c[i,k]-c[i,k-1]
    end
  end
  claims
end

"""
    cum2futclaims(c::Array{Real,2})

    Calculate the future claims vector from the quadratic,
    (strictly) lower triangle of a projected cumulative claims
    matrix
"""
function cum2futureclaims(c::Array{Real,2})
  I = size(c,2)
  if size(c,1) ≠ I
    error("cumclaims: Matrix is not quadratic")
  end
  claims = cum2claims(c)
  futureclaims = zeros(Real,I-1)
  for k in 2:I
    futureclaims[k-1] = sum(claims[I-k+2:I,k])
  end
  futureclaims
end


__precompile__()
module FastTransforms

using Base, ToeplitzMatrices, Compat

import Base: *
import Compat: view

export cjt, icjt, jjt, plan_cjt, plan_icjt
export leg2cheb, cheb2leg, leg2chebu, ultra2ultra, jac2jac
export gaunt

# Other module methods and constants:
#export ChebyshevJacobiPlan, jac2cheb, cheb2jac
#export sqrtpi, pochhammer, stirlingseries, stirlingremainder, Aratio, Cratio, Anαβ
#export Cnmαβ, Cnαβ, Cnmλ, Cnλ, Λ, absf, findmindices!
#export clenshawcurtis, clenshawcurtis_plan, clenshawcurtisweights
#export fejer1, fejer_plan1, fejerweights1
#export fejer2, fejer_plan2, fejerweights2
#export RecurrencePlan, forward_recurrence!, backward_recurrence

include("fftBigFloat.jl")

include("specialfunctions.jl")
include("clenshawcurtis.jl")
include("fejer.jl")
include("recurrence.jl")

abstract FastTransformPlan{D,T}

include("ChebyshevJacobiPlan.jl")
include("jac2cheb.jl")
include("cheb2jac.jl")

include("ChebyshevUltrasphericalPlan.jl")
include("ultra2cheb.jl")
include("cheb2ultra.jl")

include("cjt.jl")

include("toeplitzhankel.jl")

leg2cheb(x...)=th_leg2cheb(x...)
cheb2leg(x...)=th_cheb2leg(x...)
leg2chebu(x...)=th_leg2chebu(x...)
ultra2ultra(x...)=th_ultra2ultra(x...)
jac2jac(x...)=th_jac2jac(x...)

include("gaunt.jl")

end # module


function cumulate_grad!(∇m, m, neigs)
    p = 1.
    @inbounds for si in neigs
        J, i = sign(si), abs(si)
        p *= (1-J*m[i]) / 2
    end
    @inbounds for si in neigs
        J, i = sign(si), abs(si)
        ∇m[i] += J * p/(1-J*m[i]) / (1-p)
        @assert isfinite(∇m[i]) "$(∇m[i]) $(m[i]) p=$p "
    end
end

clip(h::Vector{Float64}) = Int.(sign.(h))

function solve_gd(cnf::CNF;
                    maxiters = 5000, ϵ::Float64 = 1e-4,
                    γ::Float64 = 0.1, γstep::Float64=0.01, γmax = 0.5,
                    η = 0.01, # gd learning rate
                    seed::Int = -1,
                    infotime = 100)

    seed > 0 && srand(seed)
    N, M = cnf.N, cnf.M
    h = randn(N)
    Δ = 0.

    σ = clip(h)
    E = energy(cnf, σ)
    @printf("it=0 γ=%.3f\t  E=%d   \tΔ=%f\n", γ, E, Δ)
    for it=1:maxiters
        m = tanh.(γ .* h)
        ∇m = zeros(N)
        for μ=1:M
            cumulate_grad!(∇m, m, cnf.clauses[μ])
        end

        h .+= γ .* η .* (1 .- m.^2) .* ∇m
        h ./= norm(h)/√N

        σ = clip(h)
        E = energy(cnf, σ)
        # it%infotime == 0 && @printf("\rit=%d γ=%.3f\t  E=%d   \tΔ=%f", it, γ, E, Δ)
        if it % infotime == 0
            @printf("it=%d γ=%.3f\t  E=%d \n", it, γ, E)
            γ += γstep
        end

        E == 0 && break
    end
    @printf("γ=%.3f\t  E=%d\n", γ, E)

    return clip(h)
end


# ## Derivatives

# The easiest way to provide derivatives is via Automatic Differentiation.
# We have imported `Zygote` as `Zyg`.
# For automatic differentiation we need custom adjoints for some `StaticArrays`:
Zyg.@adjoint (T::Type{<:SizedMatrix})(x::AbstractMatrix) = T(x), dv -> (nothing, dv)
Zyg.@adjoint (T::Type{<:SizedVector})(x::AbstractVector) = T(x), dv -> (nothing, dv)
Zyg.@adjoint (T::Type{<:SArray})(x::AbstractArray) = T(x), dv -> (nothing, dv)
# This allows us to define the following methods:

"Return the jacobian of `rbf` at `x` (using Zygote)."
function auto_jac( rbf :: RBFModel, x :: AbstractVector{<:Real} )
    Zyg.jacobian( rbf, x )[1]
end

"Evaluate the model and return the jacobian at the same time."
function eval_and_auto_jac( rbf :: RBFModel, x :: AbstractVector{<:Real} )
    y, back = Zyg._pullback( rbf, x )

    T = eltype(y)   # TODO does this make sense?
    n = length(y)
    jac = zeros(T, n, length(x) )
    for i = 1 : length(x)
        e = [ zeros(T, i -1 ); T(1); zeros(T, n - i )  ]
        jac[i, :] .= back(e)[2]
    end

    return y, jac
end

"Return gradient of output `ℓ` of model `rbf` at point `x` (using Zygote)."
function auto_grad( rbf :: RBFModel, x :: AbstractVector{<:Real}, ℓ :: Int = 1)
    Zyg.gradient( χ -> rbf(χ, ℓ), x )[1]
end

"Evaluate output `ℓ` of the model and return the gradient."
function eval_and_auto_grad( rbf :: RBFModel, x :: AbstractVector{<:Real}, ℓ :: Int = 1 )
    y, back = Zyg._pullback( χ -> rbf(χ, ℓ)[end], x)

    grad = back( one(y) )[2]
    return y, grad
end

# !!! note
#     We need at least `ChainRules@v.0.7.64` to have `auto_grad` etc. work for StaticArrays,
#     see [this issue](https://github.com/FluxML/Zygote.jl/issues/860).

# But we don't need `Zygote`, because we can derive the gradients ourselves.
# Assume that ``φ`` is two times continuously differentiable. \
# What is the gradient of a scalar RBF model?
# Using the chain rule and ``ξ = x - x^j`` we get
# ```math
    # \dfrac{∂}{∂ξ_i} \left( φ(\| ξ \|) \right)
    # =
    # φ\prime ( \| ξ \| ) \cdot
    # \dfrac{∂}{∂ξ_i} ( \| ξ \| )
    # =
    # φ\prime ( \| ξ \| ) \cdot
    # \dfrac{ξ_i}{\|ξ\|}.
# ```
# The right term is always bounded, but not well defined for ``ξ = 0``
# (see [^wild_diss] for details). \
# **That is why we require ``φ'(0) \stackrel{!}= 0``.** \
# We have ``\dfrac{∂}{∂x_i} ξ(x) = 1`` and thus
# ```math
    # ∇r(x) = \sum_{i=1}^N \frac{w_i φ\prime( \| x - x^i \| )}{\| x - x^i \|} (x - x^i) + ∇p(x)
# ```

# We can then implement the formula from above.
# For a fixed center ``x^i`` let ``o`` be the distance vector ``x - x^i``
# and let ``ρ`` be the norm ``ρ = \|o\| = \| x- x^i \|``.
# Then, the gradient of a single kernel is:
function grad( k :: ShiftedKernel, o :: AbstractVector{<:Real}, ρ :: Real )
    ρ == 0 ? zero(k.c) : (df( k.φ, ρ )/ρ) .* o
end

# In terms of `x`:
function grad( k :: ShiftedKernel, x :: AbstractVector{<:Real} )
    o = x - k.c     # offset vector
    ρ = norm2( o )  # distance
    return grad( k, o, ρ )
end

# The jacobian of a vector of kernels follows suit:
function jacT( K :: AbstractVector{<:ShiftedKernel}, x :: AbstractVector{<:Real})
    hcat( ( grad(k,x) for k ∈ K )... )
end
## precalculated offsets and distances, 1 per kernel
function jacT( K :: AbstractVector{<:ShiftedKernel}, offsets :: AbstractVector{<:AbstractVector}, dists :: AbstractVector{<:Real} )
    hcat( ( grad(k,o,ρ) for (k,o,ρ) ∈ zip(K,offsets,dists) )... )
end
jac( K :: AbstractVector{<:ShiftedKernel}, args... ) = transpose( jacT(K, args...) )

# Hence, the gradients of an RBFSum are easy:
function grad( rbf :: RBFSum, x :: AbstractVector{<:Real}, ℓ :: Int = 1 )
    #vec( jacT( rbf.kernels, x) * rbf.weights[:,ℓ] )
    vec( rbf.weights[ℓ,:]'jac( rbf.kernels, x ) )
end

function grad( rbf :: RBFSum, offsets :: AbstractVector{<:AbstractVector}, dists :: AbstractVector{<:Real}, ℓ :: Int)
    return vec( rbf.weights[ℓ,:]'jac( rbf.kernels, offsets, dists ) )
end

# The `grad` method looks very similar for the `PolySum`.
# We obtain the jacobian of the polynomial basis system via
# `PolynomialSystem.jacobian`.
function grad( psum :: PolySum, x :: AbstractVector{<:Real} , ℓ :: Int = 1)
    return vec( psum.weights[ℓ,:]'jacobian( psum.polys, x ))
end

# For the `RBFModel` we simply combine both methods:
function _grad( mod :: RBFModel, x :: AbstractVector{<:Real}, ℓ :: Int = 1 )
    return grad(mod.rbf, x, ℓ) + grad( mod.psum, x, ℓ )
end

function grad( mod :: RBFModel, x :: AbstractVector{<:Real}, ℓ :: Int = 1 )
    return _grad(mod, x, ℓ)
end

grad( mod :: RBFModel, x :: Vector{<:Real}, ℓ :: Int = 1 ) = _type_guard( _grad(mod, x, ℓ), Vector, mod.num_vars )
function grad( mod :: RBFModel, x :: T, ℓ :: Int = 1 ) where T <: Union{SVector, MVector, SizedVector}
    return _type_guard( _grad(mod, x, ℓ), T, mod.num_vars )
end

# We can exploit our custom evaluation methods for "distances":
function _offsets_and_dists( rbf :: RBFSum, x :: AbstractVector{<:Real} )
    offsets = [ x - k.c for k ∈ rbf.kernels ]
    dists = norm2.(offsets)
    return offsets, dists
end

function eval_and_grad( rbf :: RBFSum, offsets :: AbstractVector{<:AbstractVector}, dists :: AbstractVector{<:Real}, ℓ :: Int)
    return eval_at_dist( rbf, dists, ℓ ), grad( rbf, offsets, dists, ℓ)
end

function eval_and_grad( rbf :: RBFSum, x :: AbstractVector{<:Real}, ℓ :: Int = 1)
    offsets, dists = _offsets_and_dists(rbf, x)
    return eval_and_grad( rbf, offsets, dists, ℓ)
end

# For the `PolySum` we use `evaluate_and_jacobian`.
function eval_and_grad( psum :: PolySum, x :: AbstractVector{<:Real}, ℓ :: Int = 1)
    res_p, J_p = evaluate_and_jacobian( psum.polys, x )
    return (psum.weights[ℓ,:]'res_p)[1], vec(psum.weights[ℓ,:]'J_p)
end

# Combine for `RBFModel`:
function eval_and_grad( mod :: RBFModel, x :: AbstractVector{<:Real}, ℓ :: Int = 1 )
    res_rbf, g_rbf = eval_and_grad( mod.rbf, x, ℓ )
    res_polys, g_polys = eval_and_grad( mod.psum, x, ℓ )
    return res_rbf .+ res_polys, g_rbf .+ g_polys
end

# For the jacobian, we use the same trick to save evaluations.
function jac( rbf :: RBFSum, x :: AbstractVector{<:Real} )
    offsets, dists = _offsets_and_dists(rbf, x)
    rbf.weights * jac( rbf.kernels, offsets, dists )
end
jacT(rbf :: RBFSum, args... ) = transpose( jac(rbf, args...) )

function jac( psum :: PolySum, x :: AbstractVector{<:Real} )
    psum.weights * jacobian( psum.polys, x )
end

function _jac( mod :: RBFModel, x :: AbstractVector{<:Real} )
    jac( mod.rbf, x ) + jac( mod.psum, x)
end

jac( mod :: RBFModel, x :: AbstractMatrix{<:Real} ) = _jac(mod,x)
jac( mod :: RBFModel, x :: Vector{<:Real}) = convert(Matrix, _jac(mod,x))
jac( mod :: RBFModel, x :: SVector{<:Real}) = convert( SMatrix{mod.num_outputs, mod_nmu_vars}, _jac(mod,x) )
jac( mod :: RBFModel, x :: MVector{<:Real}) = convert( MMatrix{mod.num_outputs, mod_nmu_vars}, _jac(mod,x) )
jac( mod :: RBFModel, x :: SizedVector{<:Real}) = convert( SizedMatrix{mod.num_outputs, mod_nmu_vars}, _jac(mod,x) )

# As before, define an "evaluate-and-jacobian" function that saves evaluations:
function eval_and_jac( rbf :: RBFSum, x :: AbstractVector{<:Real} )
    offsets, dists = _offsets_and_dists(rbf, x)
    res = eval_at_dist( rbf, dists )
    J = rbf.weights * jac( rbf.kernels, offsets, dists )
    return res, J
end

function eval_and_jac( psum :: PolySum, x :: AbstractVector{<:Real} )
    res_p, J_p = evaluate_and_jacobian( psum.polys, x )
    return vec( psum.weights * res_p ), psum.weights * J_p
end

function eval_and_jac( mod :: RBFModel, x :: AbstractVector{<:Real} )
    res_rbf, J_rbf = eval_and_jac( mod.rbf, x )
    res_polys, J_polys = eval_and_jac( mod.psum, x)
    return res_rbf + res_polys, J_rbf + J_polys
end

## TODO type stable eval_and_grad and eval_and_jac ?

# !!! note
#     Hessians are not yet implemented.

# For the Hessian ``Hr \colon ℝ^n \to ℝ^{n\times n}`` we need the gradients of the
# component functions
# ```math
#     ψ_j(ξ) = \frac{ φ'( \left\| ξ \right\| )}{\|ξ\|} ξ_j
# ```
# Suppose ``ξ ≠ 0``.
# First, using the product rule, we have
# ```math
#    \dfrac{∂}{∂ξ_i}
#    \left(
#    \frac{ φ'( \left\| ξ \right\| )}{\|ξ\|} ξ_j
#    \right) =
#    ξ_j
#    \dfrac{∂}{∂ξ_i}
#    \left(
#    \frac{ φ'( \left\| ξ \right\| )}{\|ξ\|}
#    \right)
#    +
#    \frac{ φ'( \left\| ξ \right\| )}{\|ξ\|}
#    \dfrac{∂}{∂ξ_i}
#    ξ_j
# ```
# The last term is easy because of
# ```math
# \frac{∂}{∂ξ_i} ξ_j
# =
# \begin{cases}
#     1 & \text{if }i = j,\\
#     0 & \text{else.}
# \end{cases}
# ```
# For the first term we find
# ```math
#    \dfrac{∂}{∂ξ_i}
#    \left(
#      \frac{ φ'( \left\| ξ \right\| )}
#       {\|ξ\|}
#    \right)
#    =
#    \frac{
#        φ'\left(\left\| ξ \right\|\right) ∂_i \|ξ\|
#        - \|ξ\| ∂_i φ'\left( \left\| ξ \right\|\right)
#     }{
#         \|ξ\|^2
#     }
#     =
#     \frac{
#         \dfrac{φ'(\|ξ\|)}{\|ξ\|} ξ_i - \|ξ\|φ''(\|ξ\|)\dfrac{ξ_i}{\|ξ\|}
#     }{\|ξ\|^2}
# ```
# Hence, the gradient of ``ψ_j`` is
# ```math
#     ∇ψ_j(ξ)
#     =
#     \left( \frac{φ'(\|ξ\|)}{\|ξ\|^3}
#     -
#     \frac{φ''(\|ξ\|)}{\|ξ\|^2} \right) \cdot ξ
#     -\frac{φ'(\|ξ\|)}{\|ξ\|} e^j,
# ```
# where ``e^j ∈ ℝ^n`` is all zeros, except ``e^j_j = 1``.
# For ``ξ = 0`` the first term vanishes due to L'Hôpital's rule:
# ```math
# ∇ψ_j(0) = φ''(0) e^j.
# ```


# Question to answer, what if you orthogonalize the residual vectors against all
# historical residual vectors???

# ------------------------------------------------------------------------------
# A original flavor of the Conjugate Gradient Algorithm.
# * It performs the Conjugate Gradient and record all the residuals vectors for
#   computing the lanczos iterations.

using LinearAlgebra
using Logging
using Plots

mutable struct CG
    r
    rnew
    d
    A::Function
    x
    b
    itr

    residuals
    reorthogonalize::Bool
    function CG(A::Function, b, x0=nothing)
        this = new()
        this.A = A
        this.x = x0 === nothing ? b .+ 0.1  : x0  # just to handle matrix A that has eigenvalue of exactly 1.
        this.r = b - A(this.x)
        this.rnew = similar(this.r)
        this.d = this.r
        this.itr = 0
        this.residuals = Vector{typeof(this.r)}()
        push!(this.residuals, this.r)
        this.reorthogonalize = true
        return this
    end

    function CG(A::AbstractArray, b::AbstractArray)
        return CG((x)->A*x, b)
    end

end

function (this::CG)()
    r = this.r
    if r == 0
        return 0 # The problem is solved already.
    end
    A = this.A
    d = this.d
    Ad = A(d)

    a = dot(r, r)/dot(d, Ad)
    if a < 0
        error("CG got a non-definite matrix")
    end

    this.x += a*d
    this.rnew = r - a*Ad                # update rnew
    if this.reorthogonalize
        for residual in this.residuals
            this.rnew -= (dot(residual, this.rnew)/dot(residual, residual))*residual
        end
        push!(this.residuals, this.rnew)
    end
    b = dot(this.rnew, this.rnew)/dot(r, r)
    # @assert abs(dot(rnew + β*d, Ad)) < 1e-8 "Not conjugate"
    this.d = this.rnew + b*d
    this.r = this.rnew                      # Override
    this.itr += 1
    return convert(Float64, norm(this.rnew))
end

function GetCurrentResidualNorm(this::CG)
    return norm(this.r)
end

"""
    Turn on the reorthogonalizations using the residual vectors,
    and add current residual to the list of residuals.
"""
function TurnOnReorthgonalize(this::CG)
    this.reorthogonalize = true
    push!(this.residuals, this.r)
    return
end

"""
    Turn off the reorthogonalization on the residual vectors. And then
    clear all the stored residual vectors.
"""
function TurnOffReorthgonalize(this::CG)
    this.reorthogonalize = false
    empty!(this.residuals)
    return
end

### -----------------------Testing Utility -------------------------------------

"""
    Accept a linear system, and a list of guesses from the Lanczos Algorithm,
    it will return a list energy norm of the
    error vector for the linear system.

    Parameters:
        A: The matrix
        b: The vector on the RHS
        Xs: All the guesses vector from the CG Algorithm, including the initial guess!
"""
function EnergyErrorNorm(A, b, Xs)
    XStar = A\b
    InitialError = dot(Xs[1] - XStar, A, Xs[1] - XStar)
    err = (x) -> dot(x - XStar, A, x - XStar)/InitialError

    return err.(Xs)
end

"""
    Bad eigen value distribution, they are distributed from 0.001 to 1 like a
    geometric series.

    * A lot of small eigenvalues are clustered close to zero, few larger ones are
    far away from the other Eigenvalues.

    Parameters:
        rho: Number:
            An number between 0 and 1 that paramaterize the distribution of the
            eigenvalues for the PSD matrix.
        N=20:
            The size of the PSD matrix.

    returns:
        a diagonal matrix.
"""
function GetNastyPSDMatrix(rho::Number, N=20, inverted=false)
    @assert rho <= 1 && rho >= 0
    EigenValues = zeros(N)
    EigenMin, EigenMax = 0.001, 1    # Min Max Eigenvalues.
    EigenValues[1] = EigenMin
    for IdexI in 2:N
        EigenValues[IdexI] = EigenMin +
            ((IdexI - 1)/(N - 1))*(EigenMax - EigenMin)*rho^(N - IdexI)  # formulas
    end
    if inverted
        return I - diagm(EigenValues)
    end
    return diagm(EigenValues)
end

N = 100
A = SymTridiagonal(randn(N), randn(N - 1))
A = A'*A
b = rand(N)
cg1 = CG(A, b)
cg2 = CG(A, b)
cg1Soln = Vector(); push!(cg1Soln, cg1.x)
cg2Soln = Vector(); push!(cg2Soln, cg2.x)

TurnOffReorthgonalize(cg2)
for itr in 1: 1.5*N
    Res2Norm1 = cg1()
    push!(cg1Soln, cg1.x)
    Res2Norm2 = cg2()
    push!(cg2Soln, cg2.x)
    println("With Reorthogonalization: $Res2Norm1, Without: $Res2Norm2")
end

relError1 = EnergyErrorNorm(A, b, cg1Soln)
relError2 = EnergyErrorNorm(A, b, cg2Soln)

fig1 = plot(1:length(relError1), log10.(relError1), label="With Re-Orthogonalization", title="CG Relative Error")
plot!(fig1, 1:length(relError2), log10.(relError2), label="Without Re-Orthogonalization")


#References: Sandoval, Steven, and Phillip L. De Leon. "The Instantaneous Spectrum: A General Framework for Time-Frequency Analysis." IEEE Transactions on Signal Processing 66.21 (2018): 5679-5693.

using QuadGK #for numerical integration

"""
    C = AMFMtriplet(a, ω, φ)
    C = AMFMtriplet(a, ω)

Create a *canonical triplet* 'AMFMtriplet' consisting of an *instantenouse amplitude function* `a`, an *instantaneous frequency function* `ω`, and a *phase reference* `φ`.

Called with two inputs `a, ω`, this is equivalent to `AMFMtriplet(a, ω, 0.0)`.

# Examples
```@example
using ISA
a₀(t) = exp(-t^2)
ω₀(t) = 2.0
φ₀ = 0.0
𝐶₀ = AMFMtriplet(a₀,ω₀,φ₀)
```
"""
struct AMFMtriplet
  a::Function #real-valued function of time
  ω::Function #real-valued function of time
  φ::Real     #real-value
  function AMFMtriplet(a, ω, φ)
    if !(all(Base.return_types(a, (Int64,)).<:Real)) ; error("IA must be a real-valued function of one real variable"); end
    if !(all(Base.return_types(ω, (Int64,)).<:Real)) ; error("IF must be a real-valued function of one real variable"); end
    return new(a, ω, φ)
  end
end

# CONSTRUCTIONS
AMFMtriplet(a, ω) = AMFMtriplet(a, ω, zero(Float64))

# DISPLAY
Base.show(io::IO, x::AMFMtriplet) = print(io, "cannonical triplet")

"""
    ψ = AMFMcomp(C)
    ψ = AMFMcomp(a, ω, φ)
    ψ = AMFMcomp(a, ω)

Create a 'AMFMcomp' parameterized by a single 'AMFMtriplet'.

# Examples
```@example
 using ISA
 𝐶₀ = AMFMtriplet(t->exp(-t^2),t->2.0,0.0)
 ψ₀ = AMFMcomp(𝐶₀)
```
Another convenient way to create a 'AMFMcomp' is by providing
an *instantenouse amplitude function* `a`,
an *instantaneous frequency function* `ω`, and a *phase reference* `φ`.

Called with two inputs `a, ω`, this is equivalent to `AMFMcomp(a, ω, 0.0)`.

```@example
using ISA
a₀(t) = exp(-t^2)
ω₀(t) = 2.0
φ₀ = 0.0
ψ₀ = AMFMcomp(a₀,ω₀,φ₀)
```
"""
struct AMFMcomp
  C::AMFMtriplet
end

# CONSTRUCTIONS
AMFMcomp(a, ω, φ) = AMFMcomp(AMFMtriplet(a, ω, φ))
AMFMcomp(a, ω) = AMFMcomp(AMFMtriplet(a, ω, 0.0))

# METHODS
function (ψ::AMFMcomp)(t::Vector{<:Real})
  a, ω, φ = ψ.C.a, ψ.C.ω, ψ.C.φ
  temp = quadgk.(ω, 0.0, t)
  θ = [ temp[i][1] for i in 1:length(t) ]
  a.(t) .* exp.( 1im .* ( θ .+ φ ) )
end
function (ψ::AMFMcomp)(a::Function,ω::Function,φ::Real)
   return AMFMcomp(AMFMtriplet(a, ω, φ))
end
function (ψ::AMFMcomp)(t::Real)
  return (ψ::AMFMcomp)([t::Real])[1]
end
function (ψ::AMFMcomp)(t::StepRangeLen)
  return (ψ::AMFMcomp)(collect(t))
end
function (ψ::AMFMcomp)(t::UnitRange)
  return (ψ::AMFMcomp)(collect(t))
end

# DISPLAY
Base.show(io::IO, x::AMFMcomp) = print(io, "AM--FM component")


function elementary_rules(rule::UInt8)
    rule_map = zeros(Bool, 2^3)
    for i = 1:length(rule_map)
        rule_map[i] = rule >> (i - 1) & 1
    end
    rule_map
end
function elementary_reversible_rules(rule::UInt8)
    rule_map = zeros(Bool, 2^4)
    for p = 0:1
        for i = 1:2^3
            rule_map[p * 8 + i] = rule >> (i - 1) & 1
            if p == 1
                rule_map[p * 8 + i] = !rule_map[p * 8 + i]
            end
        end
    end
    rule_map
end

export elementary
function elementary(rule::Int, steps::Int, initial_state::Vector{Bool}=Bool[ true ])
    return elementary(UInt8(rule), Int64(steps), initial_state)
end
function elementary(rule::UInt8, steps::Int64, initial_state::Vector{Bool}=Bool[ true ])
    rule_map = elementary_rules(rule)

    current_state = initial_state
    uns = false
    states = AutomataBinaryHistory()

    push!(states, current_state, uns)

    for i = 2:steps
        next_state = zeros(Bool, length(current_state) + 2)
        for j = 1:length(next_state)
            next_state[j] = rule_map[state_at(current_state, uns, j-2) << 2 | state_at(current_state, uns, j-1) << 1 | state_at(current_state, uns, j) + 1]
        end
        uns = rule_map[uns ? 8 : 1]
        current_state = next_state
        push!(states, current_state, uns)
    end

    return states
end
function elementary_reversible(rule::UInt8, steps::UInt64, initial_state::AbstractArray{Bool, 1}=Bool[ true ], initial_state_above::AbstractArray{Bool, 1}=Bool[ false, false, false ])
    rule_map = elementary_reversible_rules(rule)

    current_state = initial_state
    uns = false
    pre_uns = false
    states = AutomataBinaryHistory()

    push!(states, current_state, uns)

    for i = 2:steps
        next_state = zeros(Bool, length(current_state) + 2)
        for j = 1:length(next_state)
            next_state[j] = rule_map[(i == 2 ? state_at(initial_state_above, pre_uns, j) : state_at(states[Int(i-2)][1], pre_uns, j-2)) << 3 | state_at(current_state, uns, j-2) << 2 | state_at(current_state, uns, j-1) << 1 | state_at(current_state, uns, j) + 1]
        end
        pre_uns = uns
        uns = rule_map[pre_uns << 3 | uns << 2 | uns << 1 | uns + 1]
        current_state = next_state
        push!(states, current_state, uns)
    end

    return states
end


module Qutilities

using LinearAlgebra: Diagonal, eigvals, Hermitian, svdvals, tr

export
    binent,
    concurrence,
    concurrence_lb,
    formation,
    mutinf,
    negativity,
    ptrace,
    ptranspose,
    purity,
    sigma_x,
    sigma_y,
    sigma_z,
    spinflip,
    S_renyi,
    S_vn

# All logarithms are base 2.
const LOG = log2

"""
    hermitize(rho::AbstractMatrix)

Make `rho` Hermitian, but carefully.
"""
function hermitize(rho::AbstractMatrix)
    size(rho, 1) == size(rho, 2) || throw(DomainError(size(rho), "Only square matrices are supported."))

    err = maximum(abs.(rho - rho'))
    if err > 1e-12
        @warn "Matrix is not Hermitian: $(err)"
    end

    # Make the diagonal strictly real.
    rho_H = copy(rho)
    for i in 1:size(rho_H, 1)
        rho_H[i, i] = real(rho_H[i, i])
    end

    Hermitian(rho_H)
end

hermitize(rho::Diagonal) = rho

"""
    nonneg(x::Real)

`x` if `x` is non-negative; zero if `x` is negative.
"""
nonneg(x::Real) = x < 0 ? zero(x) : x

"""
    shannon(xs::AbstractVector)

Shannon entropy of `xs`.
"""
shannon(xs::AbstractVector) = -sum([x * LOG(x) for x in xs if x > 0])

# Single-qubit Pauli matrices.
const sigma_x = [[0.0, 1.0] [1.0, 0.0]]
const sigma_y = [[0.0, im] [-im, 0.0]]
const sigma_z = [[1.0, 0.0] [0.0, -1.0]]

"""
    ptrace(rho::AbstractMatrix{T}, dims, which::Int)

Partial trace of `rho` along dimension `which` from `dims`.
"""
function ptrace(rho::AbstractMatrix{T}, dims, which::Int) where {T}
    size(rho) == (prod(dims), prod(dims)) || throw(DomainError(size(rho), "Only square matrices are supported."))

    size_before = prod(dims[1:(which-1)])
    size_at = dims[which]
    size_after = prod(dims[(which+1):end])

    result = zeros(T, size_before*size_after, size_before*size_after)
    for i1 in 1:size_before
        for j1 in 1:size_before
            for k in 1:size_at
                for i2 in 1:size_after
                    for j2 in 1:size_after
                        row1 = size_after*(i1-1) + i2
                        col1 = size_after*(j1-1) + j2
                        row2 = size_at*size_after*(i1-1) + size_after*(k-1) + i2
                        col2 = size_at*size_after*(j1-1) + size_after*(k-1) + j2
                        result[row1, col1] += rho[row2, col2]
                    end
                end
            end
        end
    end
    result
end

"""
    ptrace(rho::AbstractMatrix, which::Int=2)

Partial trace of `rho` along dimension `which`.

`rho` is split into halves and the trace is over the second half by default.
"""
function ptrace(rho::AbstractMatrix, which::Int=2)
    size(rho, 1) % 2 == 0 || throw(DomainError(size(rho, 1), "Matrix size must be even."))

    s = div(size(rho, 1), 2)
    ptrace(rho, (s, s), which)
end

"""
    ptranspose(rho::AbstractMatrix, dims, which::Int)

Partial transpose of `rho` along dimension `which` from `dims`.
"""
function ptranspose(rho::AbstractMatrix, dims, which::Int)
    size(rho) == (prod(dims), prod(dims)) || throw(DomainError(size(rho), "Only square matrices are supported."))

    size_before = prod(dims[1:(which-1)])
    size_at = dims[which]
    size_after = prod(dims[(which+1):end])

    result = similar(rho)
    for i1 in 1:size_before
        for j1 in 1:size_before
            for i2 in 1:size_at
                for j2 in 1:size_at
                    for i3 in 1:size_after
                        for j3 in 1:size_after
                            row1 = size_at*size_after*(i1-1) + size_after*(i2-1) + i3
                            col1 = size_at*size_after*(j1-1) + size_after*(j2-1) + j3
                            row2 = size_at*size_after*(i1-1) + size_after*(j2-1) + i3
                            col2 = size_at*size_after*(j1-1) + size_after*(i2-1) + j3
                            result[row1, col1] = rho[row2, col2]
                        end
                    end
                end
            end
        end
    end
    result
end

"""
    ptranspose(rho::AbstractMatrix, which::Int=2)

Partial transpose of `rho` along dimension `which`.

`rho` is split into halves and the transpose is over the second half by
default.
"""
function ptranspose(rho::AbstractMatrix, which::Int=2)
    size(rho, 1) % 2 == 0 || throw(DomainError(size(rho, 1), "Matrix size must be even."))

    s = div(size(rho, 1), 2)
    ptranspose(rho, (s, s), which)
end

"""
    binent(x::Real)

Binary entropy of `x`.
"""
binent(x::Real) = shannon([x, one(x) - x])

"""
    purity(rho::AbstractMatrix)

Purity of `rho`.
"""
purity(rho::AbstractMatrix) = rho^2 |> tr |> real

"""
    S_vn(rho::AbstractMatrix)

Von Neumann entropy of `rho`.
"""
S_vn(rho::AbstractMatrix) = rho |> hermitize |> eigvals |> shannon

"""
    S_renyi(rho::AbstractMatrix, alpha::Real=2)

Order `alpha` Rényi entropy of `rho`.

The `alpha` parameter may have any value on the interval `[0, Inf]` except 1.
It defaults to 2.
"""
function S_renyi(rho::AbstractMatrix, alpha::Real=2)
    E = rho |> hermitize |> eigvals
    alpha == Inf && return E |> maximum |> LOG |> -
    LOG(sum(E.^alpha)) / (1 - alpha)
end

"""
    mutinf(rho::AbstractMatrix, S::Function=S_vn)

Mutual information of `rho` using the entropy function `S`.
"""
function mutinf(rho::AbstractMatrix, S::Function=S_vn)
    S(ptrace(rho, 1)) + S(ptrace(rho, 2)) - S(rho)
end

"""
    spinflip(rho::AbstractMatrix)

Wootters spin-flip operation for two qubits in the mixed state `rho` in the
standard basis.

Ref: Wootters, W. K. (1998). Entanglement of formation of an arbitrary state of
two qubits. Physical Review Letters, 80(10), 2245.
"""
function spinflip(rho::AbstractMatrix)
    size(rho) == (4, 4) || throw(DomainError(size(rho), "Matrix must be 4 by 4."))

    Y = kron(sigma_y, sigma_y)
    Y * conj(rho) * Y
end

"""
    concurrence(rho::AbstractMatrix)

Concurrence of a mixed state `rho` for two qubits in the standard basis.

Ref: Wootters, W. K. (1998). Entanglement of formation of an arbitrary state of
two qubits. Physical Review Letters, 80(10), 2245.
"""
function concurrence(rho::AbstractMatrix)
    size(rho) == (4, 4) || throw(DomainError(size(rho), "Matrix must be 4 by 4."))

    E = rho * spinflip(rho) |> eigvals
    if any(imag(E) .> 1e-15)
        @warn "Complex eigenvalues: $(maximum(imag(E)))"
    end
    if any(real(E) .< -1e-12)
        @warn "Negative eigenvalues: $(minimum(real(E)))"
    end
    F = sort(sqrt.(nonneg.(real(E))))
    nonneg(F[end] - sum(F[1:(end-1)]))
end

"""
    concurrence_lb(rho::AbstractMatrix)

Lower bound on the concurrence for two qubits in the mixed state `rho` in the
standard basis.

Ref: Mintert, F., & Buchleitner, A. (2007). Observable entanglement measure for
mixed quantum states. Physical Review Letters, 98(14), 140505.
"""
function concurrence_lb(rho::AbstractMatrix)
    size(rho) == (4, 4) || throw(DomainError(size(rho), "Matrix must be 4 by 4."))

    2.0*(purity(rho) - purity(ptrace(rho))) |> nonneg |> sqrt
end

"""
    formation(C::Real)

Entanglement of formation for two qubits with concurrence `C`.

Ref: Wootters, W. K. (1998). Entanglement of formation of an arbitrary state of
two qubits. Physical Review Letters, 80(10), 2245.
"""
formation(C::Real) = binent(0.5 * (1.0 + sqrt(1.0 - C^2)))

"""
    formation(rho::AbstractMatrix)

Entanglement of formation for two qubits in the mixed state `rho` in the
standard basis.

Ref: Wootters, W. K. (1998). Entanglement of formation of an arbitrary state of
two qubits. Physical Review Letters, 80(10), 2245.
"""
formation(rho::AbstractMatrix) = rho |> concurrence |> formation

"""
    negativity(rho::AbstractMatrix)

Logarithmic negativity for a symmetric bipartition of `rho`.

Ref: Plenio, M. B. (2005). Logarithmic negativity: A full entanglement monotone
that is not convex. Physical Review Letters, 95(9), 090503.
"""
negativity(rho::AbstractMatrix) = rho |> ptranspose |> svdvals |> sum |> LOG

end


# Estimate derivatives numerically
# These use centered-differencing for higher accuracy
function derivative_numer{T<:Number}(func::Function, x::T, h::T)
    xp = x + h
    vp = func(xp)
    xm = x - h
    vm = func(xm)
    return (vp-vm)/(xp-xm)
end
derivative_numer{T<:Number}(func::Function, x::T, index::Int, h::T) = derivative_numer(func, x, h)  # compatibility with partial derivatives
function derivative_numer{T<:Number}(func::Function, x::Array{T}, index::Int, h::T)
    xsave = x[index]
    xp = xsave + h
    xm = xsave - h
    x[index] = xp
    vp = func(x)
    x[index] = xm
    vm = func(x)
    x[index] = xsave
    return (vp-vm)/(xp-xm)
end
function derivative_numer{T<:Number}(func::Function, c::(T...,), index::Int, h::T)
    x = [c...]
    xsave = x[index]
    xp = xsave + h
    xm = xsave - h
    x[index] = xp
    vp = func(x...)
    x[index] = xm
    vm = func(x...)
    x[index] = xsave
    return (vp-vm)/(xp-xm)
end
function derivative_numer{T<:Number}(func::Function, x::T, h::Vector{T})
    d = zeros(T, length(h))
    for i = 1:length(h)
        d[i] = derivative_numer(func, x, h[i])
    end
    return d
end
function derivative_numer{T<:Number}(func::Function, x, index::Int, h::Vector{T})
    d = zeros(T, length(h))
    for i = 1:length(h)
        d[i] = derivative_numer(func, x, index, h[i])
    end
    return d
end
derivative_numer{T<:Number}(func::Function, x::T) = derivative_numer(func, x, (eps(max(abs(x),one(T))))^convert(T, 1/3))
derivative_numer{T<:Number}(func::Function, x::Array{T}, index::Int) = derivative_numer(func, x, index, (eps(max(abs(x[index]),one(T))))^convert(T, 1/3))
derivative_numer{T<:Number}(func::Function, c::(T...,), index::Int) = derivative_numer(func, c, index, (eps(max(abs(c[index]),one(T))))^convert(T, 1/3))


@doc raw"""
```
Pentland(slant::Real = Inf, tilt::Real = Inf)
```
Creates a `Pentland` fucntor which attempts to produce a heightmap from a grayscale
image using Pentland's algorithm. Under the assumptions of Lambertian surface,
orthographic projections, the surface being illuminated by distant light sources,
the surface is not self-shadowing and the surface has constant albedo, hence it
can be ignored. The algorithm employs Tayler series expansion and Fourier transforms
to compute a non-iterative solution.
# Output
Returns a `Pentland` functor which can be used to run the Pentland algorithm to
reconstruct a surface form a grayscale image.
# Details
In Pentlands algorithm the image irradiance is defined as:
```math
E(x,y)=R(p,q)=\dfrac{\rho(i_xp+i_yq-i_z)}{\sqrt{1+p^2+q^2}}=\dfrac{p\sin\sigma\cos
\tau+q\sin\sigma\sin\tau+\cos\sigma}{\sqrt{1+p^2+q^2}}
```
This can be reduced using the Taylor series expansion about ``p=p_0`` and ``p=p_0``
and ignoring the higher order terms becomes:
```math
E(x,y)=R(p,q)\approx R(p_0,q_0)+(p-p_0)\dfrac{\delta R}{\delta p}(p_0,q_0)+(q-q_0)
\dfrac{\delta R}{\delta q}(p_0,q_0)
```
which for ``p_0=q_0=0`` further reduces to:
```math
E(x,y)\approx\cos\sigma+p\cos\tau\sin\sigma+q\sin\tau\sin\sigma
```
This gives the following transform identities:
```math
\begin{gathered}
p=\dfrac{\delta}{\delta x}Z(x,y)\xleftrightarrow{\Im}(-j\omega_x)F_z(
\omega_x,\omega_y)\\q=\dfrac{\delta}{\delta y}Z(x,y)\xleftrightarrow{\Im}(-j
\omega_y)F_z(\omega_x,\omega_y)
\end{gathered}
```
By taking the Fourier transform of both sides if ``E(x,y)`` yields the following:
```math
F_E=(-j\omega_x)F_z(\omega_x,\omega_y)\cos\tau\sin\sigma+(-j\omega_y)F_z(\omega_x
,\omega_y)\sin\tau\sin\sigma
```
where ``F_z`` is the Fourier transform of ``Z(x,y)``.

These can be rearranged, and the Inverse Fourier transform used to recover the
surface ``Z(x,y)`` as per the following:
```math
\begin{gathered}
F_E=F_z(\omega_x,\omega_y)[-j\omega_x\cos\tau\sin\sigma-j\omega_y\sin\tau\sin
\sigma]\\\Rightarrow F_z(\omega_x,\omega_y)=\dfrac{F_E}{-j\omega_x\cos\tau\sin
\sigma-j\omega_y\sin\tau\sin\sigma}\\Z(x,y)=\Im^{-1}\{F_z(\omega_x,\omega_y)\}
\end{gathered}
```
# Arguments
The function arguments are described in more detail below.
## `slant`
A `Real` that specifies the slant value to be used by the algorithm. The `slant`
should be a value in the range [0,π/2]. If `slant` is specified to must the `tilt`.
Will defult to `Inf` which will trigger `estimate_img_properties` to be run.
## `tilt`
A `Real` that specifies the tilt value to be used by the algorithm. The `tilt`
should be a value in the range [0,2π]. If `tilt` is specified to must
the `slant`. Will defult to `Inf` which will trigger `estimate_img_properties` to
be run.
!!! note
    If `slant` and `tilt` are not defined (i.e. have defulted to `Inf`) they
    will be calculated at runtime using `estimate_img_properties`.
# Example
Compute the heightmap for a synthetic image generated by `generate_surface`.
```julia
using Images, Makie, ShapeFromShading

#generate synthetic image
img = generate_surface(SynthSphere(50), 1.0, [0.1,0,0.9])

#calculate the heightmap
pentland = Pentland(slant = 0.1106572211739, tilt = 0.0)
Z = pentland(img)

#normalize to maximum of 1 (not necessary but makes displaying easier)
Z = Z./maximum(Z)

#display using Makie (Note: Makie can often take several minutes first time)
r = 0.0:0.1:2
surface(r, r, Z)
```
# Reference
1. A. Pentland, "Shape Information From Shading: A Theory About Human Perception," [1988 Proceedings] Second International Conference on Computer Vision, Tampa, FL, USA, 1988, pp. 404-413. [doi: 10.1109/CCV.1988.590017](https://doi.org/10.1109/ccv.1988.590017 )
"""
function (algorithm::Pentland)(img::AbstractArray)
    #find illumination and albedo
    σ = algorithm.slant
    τ = algorithm.tilt
    if σ == Inf || τ == Inf
        ρ, I, σ, τ = estimate_img_properties(img)
    end

    E = Complex{Float64}.(reinterpret(Float64, img))

    #take Fourier transform
    fft!(E)
    M, N = size(E)

    #setup wx and wy
    wx, wy = setup_transform_values_pentland(M, N)

    #using the illumination direction calculate the transformed Z
    Zₜ = zeros(Complex{Float64}, size(E))
    for i in CartesianIndices(Zₜ)
        Zₜ[i] = E[i] / (-1im * wx[i] * cos(τ) * sin(σ) - 1im * wy[i] * sin(τ)
            * sin(σ))
    end

    #recover Z
    ifft!(Zₜ)
    Z = zeros(Float64, size(E))
    for i in CartesianIndices(Z)
        Z[i] = abs(Zₜ[i])
    end
    return Z
end


using MathOptFormat
using MathOptInterface
const MOI = MathOptInterface
const MOIU = MathOptInterface.Utilities

DIR_PATH = dirname(@__FILE__)

push!(Base.LOAD_PATH, joinpath(DIR_PATH, "..",".."))

using ModelCompare

cd(DIR_PATH)

model = MathOptFormat.MOF.Model()
x = MOI.add_variable(model)
MOI.set(model, MOI.VariableName(), x, "x")
c = MOI.add_constraint(model,
    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([1],[x]), 0.0),
    MOI.GreaterThan(1.0)
)
MOI.set(model, MOI.ConstraintName(), c, "c")
c2 = MOI.add_constraint(model,
    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.0),
    MOI.Interval(-1.0, 1.0)
)
MOI.set(model, MOI.ConstraintName(), c2, "c2")
MOI.write_to_file(model, "orig_A.mps")

remove_intervals(model)
MOI.write_to_file(model, "orig_A_no_interval.mps")

# ------------------------------------------------------------------

model_ = MathOptFormat.MOF.Model()
z = MOI.add_variable(model_)
MOI.set(model_, MOI.VariableName(), z, "z")
x = MOI.add_variable(model_)
MOI.set(model_, MOI.VariableName(), x, "x")
c = MOI.add_constraint(model_,
    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([1,2],[z,x]), 0.0),
    MOI.GreaterThan(1.0)
)
MOI.set(model_, MOI.ConstraintName(), c, "c")
c2 = MOI.add_constraint(model_,
    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}[], 0.0),
    MOI.Interval(-1.0, 1.0)
)
MOI.set(model_, MOI.ConstraintName(), c2, "c2")
MOI.write_to_file(model_, "orig_B.mps")
remove_intervals(model_)

compare_models(model, model_)


### A Pluto.jl notebook ###
# v0.16.4

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

# ╔═╡ 940b1996-fe9d-11ea-2fa4-8b72bee62b76
md"""
# Grid creation and visualization in PDELib.jl

This notebook shows how to perform grid creation and visualization with the assistance of the packages [ExtendableGrids.jl](https://github.com/j-fu/ExtendableGrids.jl) and [SimplexGridFactory.jl](https://github.com/j-fu/SimplexGridFactory.jl) which are part of the  [PDELib.jl](https://github.com/WIAS-BERLIN/PDElib.jl) meta package.

Visualization in this notebook is done using the [GridVisualize.jl](https://github.com/j-fu/GridVisualize.jl) package.
"""

# ╔═╡ d432ad64-f91f-11ea-2e48-4bc7472ac64c
begin
	using SimplexGridFactory
	using ExtendableGrids
	using Triangulate
	using TetGen
	using GridVisualize
        using PlutoVista
	using PlutoUI
        isdefined(Main,:PlutoRunner) ? 	default_plotter!(PlutoVista) : default_plotter!(nothing)
end

# ╔═╡ 47103e8f-a4ff-46ed-a632-572a2e194a50
md"""
## 1D grids

1D grids are created just from arrays of montonically increasing  coordinates
using the [simplexgrid](https://j-fu.github.io/ExtendableGrids.jl/stable/simplexgrid/#ExtendableGrids.simplexgrid-Tuple{AbstractVector{T}%20where%20T}) method.
"""

# ╔═╡ 93a0c45e-d6a3-415a-a82c-e4f7e2a09d22
X1=range(0,1;length=11)

# ╔═╡ 4622a1fc-fda7-4211-9cc0-4eb1a1584aa6
g1=simplexgrid(X1); @show g1

# ╔═╡ 3212b930-194d-422e-9d06-65885b25cc6d
md"""
We can plot a grid with a method from `GridVisualize.jl`
"""

# ╔═╡ 5520b8c0-0874-4790-a956-224e6c43d9cf
gridplot(g1; resolution=(500,150),legend=:rt)

# ╔═╡ 13aef2a1-5745-4fe5-9659-6b7c0e7267fc
md"""
We see some  additional information:

- `cellregion`: each grid cell (interval, triangle, tetrahedron) as an integer region marker attached
- `bfaceregion`: boundary faces (points, lines, triangles) have an interger boundary region marker attached

We can also have a look into the grid structure:
"""

# ╔═╡ f19d20c8-4d69-442d-99c8-10874fa0a6d3
g1.components

# ╔═╡ 28e2e3b0-c168-481b-b467-29e6a5407431
md"""
Components can be accessed via `[ ]`. In fact the keys in the dictionary of components are [types](https://j-fu.github.io/ExtendableGrids.jl/stable/tdict/).
"""

# ╔═╡ f04017d7-1c55-4118-8467-1e134259e35d
g1[Coordinates]

# ╔═╡ 98016b49-8786-46b9-aca6-01d15c253b3f
g1[CellNodes]

# ╔═╡ 2d392a98-bf32-4145-ae93-a8e218367277
md"""
### Modifying region markers

The `simplexgrid` method provides a default distribution of markers, but we would like to be able to change them. This can be done by putting masks on cells or faces (points in 1D):
"""

# ╔═╡ 88247350-aa6c-4876-82c3-3534036d5702
g2=deepcopy(g1)

# ╔═╡ 42f8d91d-14c7-488f-bdb9-b3d22705186f
cellmask!(g2, [0.0], [0.5], 2);

# ╔═╡ db7e0233-4e08-41b8-9ebe-5570e6d32264
bfacemask!(g2, [0.5],[0.5], 3);

# ╔═╡ 74641f73-2efe-4df8-bebb-ed97e77d869e
gridplot(g2; resolution=(500, 150),legend=:rt)

# ╔═╡ 14fb7977-93cf-4f74-a8ec-b6ee25dbdf86
md"""
### Creating locally refined grids

For this purpose, we just need to create arrays with the corresponding coordinate values. This can be done programmatically.

Two support metods are provided for this purpose.
"""

# ╔═╡ 2d5cb9e1-2d14-415e-b792-c3124901011d
hmin=0.01 ; hmax=0.1

# ╔═╡ b1f903b3-29d7-4909-b7e2-8ef3528c9965
md"""
The `geomspace` method creates an array such that the smallest interval size is `hmin` and the largest interval size is not larger but close to `hmax`, and the interval sizes constitute a geometric sequence.
"""

# ╔═╡ 19125aed-5c46-4968-bcfc-0b469628b68e
X2L=geomspace(0,0.5,hmax,hmin)

# ╔═╡ f9debaf8-efe8-44d1-9671-4aa5bffa9bb8
DX2=X2L[2:end].-X2L[1:end-1]

# ╔═╡ 4f7f9eaf-61c5-4543-ae39-e58ca14fb89a
DX2[1:end-1]./DX2[2:end]

# ╔═╡ f873cb89-da6e-4e11-b3f6-2bf0b766ce5f
X2R=geomspace(0.5,1,hmin,hmax)

# ╔═╡ a2e6c397-4a7e-47eb-ad84-df6e9d3fdd43
md"""
We can glue these arrays together and create a grid from them:
"""

# ╔═╡ 805c2a1e-a6c6-47fc-b719-31c2a671a8d0
X2=glue(X2L,X2R)

# ╔═╡ dd7d8e17-f825-4047-9386-d9e2bfd0a48d
gridplot(simplexgrid(X2); resolution=(500,150),legend=:rt)

# ╔═╡ 3d7db57f-2864-4984-a979-609e1d838a9f
md"""
### Plotting functions

We assume that functions can be represented by their node values an plotted via their piecewise linear interpolants. E.g. they could come from some simulation.
"""

# ╔═╡ 4cf840e6-86c5-4af1-a780-6fc78b60716b
g1d2=simplexgrid(range(-10,10,length=201))

# ╔═╡ 151cc4b8-c5ed-4f5e-8d5f-2f708c9c7fae
fsin=map(sin,g1d2)

# ╔═╡ 605581c4-3c6f-4c31-bd90-7645d3f70315
fcos=map(cos,g1d2)

# ╔═╡ bd40e614-00ef-426d-aa98-eca2ef48320e
fsinh=map(x->sinh(0.2*x), g1d2)

# ╔═╡ 71af99ab-612a-4821-8e9c-efc8766e2e3e
let
	vis=GridVisualizer(;resolution=(600,300),legend=:lt)

	scalarplot!(vis, g1d2, fsinh, label="sinh", markershape=:dtriangle, color=:red,markevery=5,clear=false)

	scalarplot!(vis, g1d2, fcos, label="cos", markershape=:xcross, color=:green, linestyle=:dash, clear=false,markevery=20)

	scalarplot!(vis, g1d2, fsin, label="sin", markershape=:none, color=:blue, linestyle=:dot, clear=false, markevery=20)

	reveal(vis)
end

# ╔═╡ 6dfa1d73-8baa-4589-b2e6-547834c9e444
md"""
## 2D grids
### Tensor product grids

For 2D tensor product grids, we can again use the `simplexgrid` method
and apply the mask methods for modifying cell and boundary region markers.
"""

# ╔═╡ f9599246-8238-432c-a315-300d74abfa2c
begin
	g2d1=simplexgrid(X1,X2)
	cellmask!(g2d1, [0.0,0.0], [0.5, 0.5], 2)
	cellmask!(g2d1, [0.5,0.5], [1.0, 1.0], 3)
	bfacemask!(g2d1, [0.0, 0.0], [0.0, 0.5],5)
end

# ╔═╡ ba5af291-fb16-4f21-8b74-664284bf7bd9
gridplot(g2d1,resolution=(600,400),linewidth=0.5,legend=:lt)

# ╔═╡ dfbaacea-4cb4-4147-9f1b-4424d7a7e89b
md"""
To interact with the plot, you can use the mouse wheel or double toch to zoom,
"shift-mouse-left" to pan, and "alt-mouse-left" or "ctrl-mouse-left" to reset.
"""

# ╔═╡ 7d1698dd-3bb7-4b38-9c6d-a88652749eee
md"""
We can also have a look into the components of a 2D grid:
"""

# ╔═╡ 81249f7c-abdf-43cc-b57f-2915b09da009
g2d1.components

# ╔═╡ 0765641a-8ed9-4579-bd9b-90bb02a55792
md"""
### Unstructured grids

For the triangulation of unstructured grids, we use the mesh generator Triangle via the [Triangulate.jl](https://github.com/JuliaGeometry/Triangulate.jl)  and [SimplexGridFactory.jl](https://github.com/j-fu/SimplexGridFactory.jl) packages.

The later package exports the `SimplexGridBuilder` which shall help to simplify the creation of the input for `Triangulate`.
"""

# ╔═╡ 884a11a2-15cf-40fc-a1ca-66ea23c6094e
builder2=let
	b=SimplexGridBuilder(Generator=Triangulate)
	p1=point!(b,0,0)
	p2=point!(b,1,0)
	p3=point!(b,1,1)

	# Specify outer boundary
	facetregion!(b,1)
	facet!(b,p1,p2)
	facetregion!(b,2)
	facet!(b,p2,p3)
	facetregion!(b,3)
	facet!(b,p3,p1)

	cellregion!(b,1)
	regionpoint!(b,0.75,0.25)

	options!(b,maxvolume=0.01)
	b
end

# ╔═╡ 8fbd238c-723e-4bce-af69-9cabfc03f8d9
md"""
We can plot the current state of the builder:
"""

# ╔═╡ 342788d4-e5d0-4239-be87-7658bb67c999
grid2d2=simplexgrid(builder2;maxvolume=0.001)

# ╔═╡ 8f7d958e-5dc5-4324-af21-ad829d7d77eb
gridplot(grid2d2, resolution=(400,300),linewidth=0.5)

# ╔═╡ fd27b44a-f923-11ea-2afb-d79f7e62e214
md"""
### More complicated grids

More complicated grids include:

- local refinement
- interior boundaries
- different region markers
- holes

The particular way to describe these things is due to Jonathan Shewchuk and his mesh generator [Triangle](https://www.cs.cmu.edu/~quake/triangle.html) via its Julia wrapper package  [Triangulate.jl](https://github.com/JuliaGeometry/Triangulate.jl).
"""

# ╔═╡ 4a289b23-46b9-495d-b19c-42b3da71b242
md"""
#### Local refinement
"""

# ╔═╡ b12838f0-fe9c-11ea-2939-155ed907322d
refinement_center=[0.8,0.2]

# ╔═╡ d5d8a1d6-fe9d-11ea-0fd8-df6e81492cb5
md"""
For local refimenent, we define  a function, which is able to
tell if a triangle is to be refined ("unsuitable") or can be kept as it is.

The function measures the distance between the refinement center and the  triangle barycenter. We require that the area increases with the distance from the refinement center.
"""

# ╔═╡ aae2e82a-fe9c-11ea-0427-593f8d2c7746
function unsuitable(x1,y1,x2,y2,x3,y3,area)
        bary_x=(x1+x2+x3)/3.0
        bary_y=(y1+y2+y3)/3.0
        dx=bary_x-refinement_center[1]
        dy=bary_y-refinement_center[2]
        qdist=dx^2+dy^2
        area>0.1*max(1.0e-2,qdist)
end;

# ╔═╡ 1ae86964-fe9e-11ea-303b-65bb128384a5
md"""
#### Interior boundaries

Interior boundaries are described in a similar as exterior ones - just by facets connecting points.
"""

# ╔═╡ 3b8cd906-bc4e-44af-bcf4-8836d597ed4c
md"""
#### Subregions

Subregions are defined as regions surrounded by interior boundaries.
By placing a "region point" into such a region and specifying a "region number",
we can set the cell region marker for all triangles created in the subregion.
"""

# ╔═╡ 9d240ef9-6639-4bde-a463-ea78480a970d
md"""
#### Holes
Holes are defined in a similar way as subregions, but a "hole point" is places into the place which shall become the hole.
"""

# ╔═╡ 511b26c6-f920-11ea-1228-51c3750f495c
builder3=let
	b=SimplexGridBuilder(Generator=Triangulate;tol=1.0e-10)

	#  Specify points
	p1=point!(b,0,0)
	p2=point!(b,1,0)
	p3=point!(b,1,1)
	p4=point!(b,0,0.7)

	# Specify outer boundary
	facetregion!(b,1)
	facet!(b,p1,p2)
	facetregion!(b,2)
	facet!(b,p2,p3)
	facetregion!(b,3)
	facet!(b,p3,p4)
	facetregion!(b,4)
	facet!(b,p1,p4)

	# Activate unsuitable callback
	options!(b,unsuitable=unsuitable)

	# Specify interior boundary
	facetregion!(b,5)
	facet!(b,p1,p3)

	# Coarse elements in upper left region #1
	cellregion!(b,1)
	maxvolume!(b,0.1)
	regionpoint!(b,0.1,0.5)

	# Fine elements in lower right region #2
	cellregion!(b,2)
	maxvolume!(b,0.01)
	regionpoint!(b,0.9,0.5)

	# Hole
	hp1=point!(b,0.4,0.1)
	hp2=point!(b,0.6,0.1)
	hp3=point!(b,0.5,0.3)
	holepoint!(b,0.5,0.2)
	facetregion!(b,6)
	facet!(b,hp1,hp2)
	facet!(b,hp2,hp3)
	facet!(b,hp3,hp1)

	b
end;

# ╔═╡ d2129483-285b-49a2-a11d-886956146b85
md"""
__Create a simplex grid from the builder__
"""

# ╔═╡ ac93589b-6315-4677-9542-c0a2333f1755
grid2d3=simplexgrid(builder3)

# ╔═╡ 59a6c8b5-25aa-47aa-9489-a803672013df
gridplot(grid2d3,legend=:lt, resolution=(400,400))

# ╔═╡ 4c99c40f-cf93-4cba-bef1-0c4ffcbf6833
md"""
### Plotting of functions

Functions defined on the nodes of a triangular grid can be seen as piecewise linear functions from the P1 finite element space defined by the triangulation.
"""

# ╔═╡ bad736d7-875c-4bc0-9ec4-494a90a508f7
fsin2=map((x,y)-> sin(x)*y, grid2d2)

# ╔═╡ a375c23f-6b8c-4b2c-a8b5-d38e6b5a8f6d
fsin3=map((x,y)-> sin(y)*x, grid2d3)

# ╔═╡ c3ef9067-3cdb-4bfd-9406-6ded64539978
scalarplot(grid2d2, fsin2, label="grid2d2")

# ╔═╡ 4dfb2e0f-3e3a-4053-8a76-765546e96992
	scalarplot(grid2d3, fsin3, label="grid2d3",colormap=:spring,isolines=10)

# ╔═╡ 2682df92-5955-4b17-ae4f-8e99c5b17980
md"""
## 3D Grids

### Tensor product grids

Please note that "masking" is not yet implemented.
Furthermore, PyPlot visualization is slow, with GLMakie it is way faster.
"""

# ╔═╡ 265fe6c7-d1cc-48a6-8295-f8f55acf677c
X3=range(0.,10.1,length=11)

# ╔═╡ b357395f-2a6e-476f-b008-02802c85a541
grid3d1=simplexgrid(X3,X3,X3)

# ╔═╡ af449be7-aab6-4de5-a059-3f8508502676
func3=map((x,y,z)-> sin(x/2)*cos(y/2)*z/10,grid3d1)

# ╔═╡ 38e2b4a8-2480-40e7-bde3-6d1775201aae
p3dg=GridVisualizer(dim=3,resolution=(200,200))

# ╔═╡ f97d085c-e7bf-4561-8183-673912bdeab6
gridplot!(p3dg,grid3d1,zplanes=[zplane],yplanes=[yplane], xplanes=[xplane], resolution=(200,200),show=true)

# ╔═╡ ef1fde48-fe90-4714-ac86-614ae3451aa7
p3ds=GridVisualizer(dim=3,resolution=(400,400))

# ╔═╡ d73d18e7-bcf9-4cc1-9154-b70dc1ff5524

	scalarplot!(p3ds,grid3d1, func3, zplanes=[zplane], yplanes=[yplane],xplanes=[xplane],levels=[flevel],colormap=:spring,resolution=(200,200),show=true,levelalpha=0.5,outlinealpha=0.1)

# ╔═╡ a3844fda-5725-4d95-894b-051a5f6c2faa
md"""
f=$(@bind flevel Slider(range(extrema(func3)...,length=20),default=mean(func3),show_value=true))

x=$(@bind xplane Slider(X3[1]:0.1:X3[end],default=X3[end],show_value=true))

y=$(@bind yplane Slider(X3[1]:0.1:X3[end],default=X3[end],show_value=true))

z=$(@bind zplane Slider(X3[1]:0.1:X3[end],default=X3[end],show_value=true))

"""

# ╔═╡ 04041481-0f03-41e1-a7de-1b3fd033c952
mean(x)=sum(x)/length(x)

# ╔═╡ 6cad87eb-1c59-4000-b688-a6f6d41f9413
md"""
### Unstructured grids

The SimplexGridBuilder API supports creation of three-dimensional grids in way very similar to the 2D case. Just define points with three coordinates and planar (!) facets with at least three points to describe the geometry.

The backend for mesh generation in this case is the [TetGen](http://tetgen.org) mesh generator by Hang Si from WIAS Berlin and its Julia wrapper [TetGen.jl](https://github.com/JuliaGeometry/TetGen.jl).
"""

# ╔═╡ fefc7587-8e25-4080-b934-90c0e1afc56a
builder3d=let

    b=SimplexGridBuilder(Generator=TetGen)

    p1=point!(b,0,0,0)
    p2=point!(b,1,0,0)
    p3=point!(b,1,1,0)
    p4=point!(b,0,1,0)
    p5=point!(b,0,0,1)
    p6=point!(b,1,0,1)
    p7=point!(b,1,1,1)
    p8=point!(b,0,1,1)

    facetregion!(b,1)
    facet!(b,p1 ,p2 ,p3 ,p4)
    facetregion!(b,2)
    facet!(b,p5 ,p6 ,p7 ,p8)
    facetregion!(b,3)
    facet!(b,p1 ,p2 ,p6 ,p5)
    facetregion!(b,4)
    facet!(b,p2 ,p3 ,p7 ,p6)
    facetregion!(b,5)
    facet!(b,p3 ,p4 ,p8 ,p7)
    facetregion!(b,6)
    facet!(b,p4 ,p1 ,p5 ,p8)

	hp1=point!(b,0.4,0.4,0.4)
    hp2=point!(b,0.6,0.4,0.4)
    hp3=point!(b,0.6,0.6,0.4)
    hp4=point!(b,0.4,0.6,0.4)
    hp5=point!(b,0.4,0.4,0.6)
    hp6=point!(b,0.6,0.4,0.6)
    hp7=point!(b,0.6,0.6,0.6)
    hp8=point!(b,0.4,0.6,0.6)

    facetregion!(b,7)
    facet!(b,hp1 ,hp2 ,hp3 ,hp4)
    facet!(b,hp5 ,hp6 ,hp7 ,hp8)
    facet!(b,hp1 ,hp2 ,hp6 ,hp5)
    facet!(b,hp2 ,hp3 ,hp7 ,hp6)
    facet!(b,hp3 ,hp4 ,hp8 ,hp7)
    facet!(b,hp4 ,hp1 ,hp5 ,hp8)
	holepoint!(b, 0.5,0.5,0.5)

	b

end;

# ╔═╡ 065735f7-c799-4284-bd59-fe6383bb987c
grid3d2=simplexgrid(builder3d,maxvolume=0.0001)

# ╔═╡ 329992a0-e352-468b-af8b-0b190315fc61
gridplot(grid3d2,zplane=0.1,azim=20,elev=20,linewidth=0.5,outlinealpha=0.3)

# ╔═╡ a7965a6e-2e83-47eb-aee2-d366246a8637
html"""<hr>"""

# ╔═╡ 7ad541b1-f40f-4cdd-b7b5-b792a8e63d71
TableOfContents(depth=4)

# ╔═╡ 071b8834-d3d1-4d08-979f-56b05bc1e0d3
md"""
    begin
       using Pkg
       Pkg.activate(mktempdir())
       Pkg.add(["PlutoUI","Revise","Triangulate","TetGen"])
       using Revise
	   Pkg.develop(["ExtendableGrids","SimplexGridFactory",
           "GridVisualize","PlutoVista"])
    end
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ExtendableGrids = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
GridVisualize = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
PlutoVista = "646e1f28-b900-46d7-9d87-d554eb38a413"
SimplexGridFactory = "57bfcd06-606e-45d6-baf4-4ba06da0efd5"
TetGen = "c5d3f3f7-f850-59f6-8a2e-ffc6dc1317ea"
Triangulate = "f7e6ffb2-c36d-4f8f-a77e-16e897189344"

[compat]
ExtendableGrids = "~0.8.7"
GridVisualize = "~0.3.9"
PlutoUI = "~0.7.16"
PlutoVista = "~0.8.6"
SimplexGridFactory = "~0.5.9"
TetGen = "~1.3.0"
Triangulate = "~2.1.0"
"""

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

[[AbstractTrees]]
git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.3.4"

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

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

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

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

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "a851fec56cb73cfdf43762999ec72eff5b86882a"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.15.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"

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

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

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

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

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

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

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[ElasticArrays]]
deps = ["Adapt"]
git-tree-sha1 = "a0fcc1bb3c9ceaf07e1d0529c9806ce94be6adf9"
uuid = "fdbdab4c-e67f-52f5-8c3f-e7b388dad3d4"
version = "1.2.9"

[[ExtendableGrids]]
deps = ["AbstractTrees", "Dates", "DocStringExtensions", "ElasticArrays", "InteractiveUtils", "LinearAlgebra", "Printf", "Random", "SparseArrays", "Test"]
git-tree-sha1 = "1e8e50f054057f23e908fbd6935766dca6293cc2"
uuid = "cfc395e8-590f-11e8-1f13-43a2532b2fa8"
version = "0.8.7"

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

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

[[GridVisualize]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "ElasticArrays", "ExtendableGrids", "GeometryBasics", "LinearAlgebra", "Observables", "OrderedCollections", "PkgVersion", "Printf", "Requires", "StaticArrays"]
git-tree-sha1 = "925ba2f11df005d894b113292d32fca9afe3f8c8"
uuid = "5eed8a63-0fb0-45eb-886d-8d5a387d12b8"
version = "0.3.9"

[[Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[HypertextLiteral]]
git-tree-sha1 = "5efcf53d798efede8fee5b2c8b09284be359bf24"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.2"

[[IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

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

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

[[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 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[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"

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

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

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

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

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

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

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

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

[[Observables]]
git-tree-sha1 = "fe29afdef3d0c4a8286128d4e45cc50621b1e43d"
uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
version = "0.4.0"

[[OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

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

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "f19e978f81eca5fd7620650d7dbea58f825802ee"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.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"

[[PkgVersion]]
deps = ["Pkg"]
git-tree-sha1 = "a7a7e1a88853564e551e4eba8650f8c38df79b37"
uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
version = "0.1.1"

[[PlutoUI]]
deps = ["Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "4c8a7d080daca18545c56f1cac28710c362478f3"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.16"

[[PlutoVista]]
deps = ["ColorSchemes", "Colors", "DocStringExtensions", "GridVisualize", "UUIDs"]
git-tree-sha1 = "34fc7e41e6eefa58fef0786ab62a20262df88764"
uuid = "646e1f28-b900-46d7-9d87-d554eb38a413"
version = "0.8.6"

[[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"

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

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

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

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

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

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

[[SimplexGridFactory]]
deps = ["DocStringExtensions", "ElasticArrays", "ExtendableGrids", "GridVisualize", "LinearAlgebra", "Printf", "Test"]
git-tree-sha1 = "af52ec74a4b6cfcc5b6d60d259099fa0596de2c1"
uuid = "57bfcd06-606e-45d6-baf4-4ba06da0efd5"
version = "0.5.9"

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

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

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3c76dde64d03699e074ac02eb2e8ba8254d428da"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.13"

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

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

[[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 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

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

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

[[TetGen]]
deps = ["DocStringExtensions", "GeometryBasics", "LinearAlgebra", "Printf", "StaticArrays", "TetGen_jll"]
git-tree-sha1 = "2f1d87ccacd2a7faf9e0bade918946ec4d90bfdf"
uuid = "c5d3f3f7-f850-59f6-8a2e-ffc6dc1317ea"
version = "1.3.0"

[[TetGen_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "bc67a0d0b799fe248b1f199a5c893ccf316f0e60"
uuid = "b47fdcd6-d2c1-58e9-bbba-c1cee8d8c179"
version = "1.5.2+0"

[[Triangle_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "bfdd9ef1004eb9d407af935a6f36a4e0af711369"
uuid = "5639c1d2-226c-5e70-8d55-b3095415a16a"
version = "1.6.1+0"

[[Triangulate]]
deps = ["DocStringExtensions", "Libdl", "Printf", "Test", "Triangle_jll"]
git-tree-sha1 = "2b4f716b192c0c615d96d541ee029e85666388cb"
uuid = "f7e6ffb2-c36d-4f8f-a77e-16e897189344"
version = "2.1.0"

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

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

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

[[libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

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

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

# ╔═╡ Cell order:
# ╟─940b1996-fe9d-11ea-2fa4-8b72bee62b76
# ╠═d432ad64-f91f-11ea-2e48-4bc7472ac64c
# ╟─47103e8f-a4ff-46ed-a632-572a2e194a50
# ╠═93a0c45e-d6a3-415a-a82c-e4f7e2a09d22
# ╠═4622a1fc-fda7-4211-9cc0-4eb1a1584aa6
# ╟─3212b930-194d-422e-9d06-65885b25cc6d
# ╠═5520b8c0-0874-4790-a956-224e6c43d9cf
# ╟─13aef2a1-5745-4fe5-9659-6b7c0e7267fc
# ╠═f19d20c8-4d69-442d-99c8-10874fa0a6d3
# ╟─28e2e3b0-c168-481b-b467-29e6a5407431
# ╠═f04017d7-1c55-4118-8467-1e134259e35d
# ╠═98016b49-8786-46b9-aca6-01d15c253b3f
# ╟─2d392a98-bf32-4145-ae93-a8e218367277
# ╠═88247350-aa6c-4876-82c3-3534036d5702
# ╠═42f8d91d-14c7-488f-bdb9-b3d22705186f
# ╠═db7e0233-4e08-41b8-9ebe-5570e6d32264
# ╠═74641f73-2efe-4df8-bebb-ed97e77d869e
# ╟─14fb7977-93cf-4f74-a8ec-b6ee25dbdf86
# ╠═2d5cb9e1-2d14-415e-b792-c3124901011d
# ╟─b1f903b3-29d7-4909-b7e2-8ef3528c9965
# ╠═19125aed-5c46-4968-bcfc-0b469628b68e
# ╠═f9debaf8-efe8-44d1-9671-4aa5bffa9bb8
# ╠═4f7f9eaf-61c5-4543-ae39-e58ca14fb89a
# ╠═f873cb89-da6e-4e11-b3f6-2bf0b766ce5f
# ╟─a2e6c397-4a7e-47eb-ad84-df6e9d3fdd43
# ╠═805c2a1e-a6c6-47fc-b719-31c2a671a8d0
# ╠═dd7d8e17-f825-4047-9386-d9e2bfd0a48d
# ╟─3d7db57f-2864-4984-a979-609e1d838a9f
# ╠═4cf840e6-86c5-4af1-a780-6fc78b60716b
# ╠═151cc4b8-c5ed-4f5e-8d5f-2f708c9c7fae
# ╠═605581c4-3c6f-4c31-bd90-7645d3f70315
# ╠═bd40e614-00ef-426d-aa98-eca2ef48320e
# ╠═71af99ab-612a-4821-8e9c-efc8766e2e3e
# ╟─6dfa1d73-8baa-4589-b2e6-547834c9e444
# ╠═f9599246-8238-432c-a315-300d74abfa2c
# ╠═ba5af291-fb16-4f21-8b74-664284bf7bd9
# ╟─dfbaacea-4cb4-4147-9f1b-4424d7a7e89b
# ╟─7d1698dd-3bb7-4b38-9c6d-a88652749eee
# ╠═81249f7c-abdf-43cc-b57f-2915b09da009
# ╟─0765641a-8ed9-4579-bd9b-90bb02a55792
# ╠═884a11a2-15cf-40fc-a1ca-66ea23c6094e
# ╟─8fbd238c-723e-4bce-af69-9cabfc03f8d9
# ╠═342788d4-e5d0-4239-be87-7658bb67c999
# ╠═8f7d958e-5dc5-4324-af21-ad829d7d77eb
# ╟─fd27b44a-f923-11ea-2afb-d79f7e62e214
# ╟─4a289b23-46b9-495d-b19c-42b3da71b242
# ╠═b12838f0-fe9c-11ea-2939-155ed907322d
# ╟─d5d8a1d6-fe9d-11ea-0fd8-df6e81492cb5
# ╠═aae2e82a-fe9c-11ea-0427-593f8d2c7746
# ╟─1ae86964-fe9e-11ea-303b-65bb128384a5
# ╟─3b8cd906-bc4e-44af-bcf4-8836d597ed4c
# ╟─9d240ef9-6639-4bde-a463-ea78480a970d
# ╠═511b26c6-f920-11ea-1228-51c3750f495c
# ╟─d2129483-285b-49a2-a11d-886956146b85
# ╠═ac93589b-6315-4677-9542-c0a2333f1755
# ╠═59a6c8b5-25aa-47aa-9489-a803672013df
# ╟─4c99c40f-cf93-4cba-bef1-0c4ffcbf6833
# ╠═bad736d7-875c-4bc0-9ec4-494a90a508f7
# ╠═a375c23f-6b8c-4b2c-a8b5-d38e6b5a8f6d
# ╠═c3ef9067-3cdb-4bfd-9406-6ded64539978
# ╠═4dfb2e0f-3e3a-4053-8a76-765546e96992
# ╟─2682df92-5955-4b17-ae4f-8e99c5b17980
# ╠═265fe6c7-d1cc-48a6-8295-f8f55acf677c
# ╠═b357395f-2a6e-476f-b008-02802c85a541
# ╠═af449be7-aab6-4de5-a059-3f8508502676
# ╠═38e2b4a8-2480-40e7-bde3-6d1775201aae
# ╠═f97d085c-e7bf-4561-8183-673912bdeab6
# ╠═ef1fde48-fe90-4714-ac86-614ae3451aa7
# ╠═d73d18e7-bcf9-4cc1-9154-b70dc1ff5524
# ╠═a3844fda-5725-4d95-894b-051a5f6c2faa
# ╠═04041481-0f03-41e1-a7de-1b3fd033c952
# ╟─6cad87eb-1c59-4000-b688-a6f6d41f9413
# ╠═fefc7587-8e25-4080-b934-90c0e1afc56a
# ╠═065735f7-c799-4284-bd59-fe6383bb987c
# ╠═329992a0-e352-468b-af8b-0b190315fc61
# ╟─a7965a6e-2e83-47eb-aee2-d366246a8637
# ╠═7ad541b1-f40f-4cdd-b7b5-b792a8e63d71
# ╠═071b8834-d3d1-4d08-979f-56b05bc1e0d3
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# Mahalanobis distances

"""
    Mahalanobis(Q; skipchecks=false) <: Metric

Create a Mahalanobis distance (i.e., a bilinear form) with covariance matrix `Q`.
Upon construction, both symmetry/self-adjointness and positive semidefiniteness are checked,
where the latter check can be skipped by passing the keyword argument `skipchecks = true`.

# Example:
```julia
julia> A = collect(reshape(1:9, 3, 3)); Q = A'A;

julia> dist = Mahalanobis(Q)
Mahalanobis{Matrix{Int64}}([14 32 50; 32 77 122; 50 122 194])

julia> dist = Mahalanobis(A, skipchecks=true)
┌ Warning: matrix is not symmetric/Hermitian
└ @ Distances ...
Mahalanobis{Matrix{Int64}}([1 4 7; 2 5 8; 3 6 9])
"""
struct Mahalanobis{M<:AbstractMatrix} <: Metric
    qmat::M
    function Mahalanobis(Q::AbstractMatrix; skipchecks::Bool=false)
        # TODO: turn the warnings into errors in next breaking release
        ishermitian(Q) || @warn "matrix is not symmetric/Hermitian"
        if !skipchecks
            eigmin(Q) ≥ 0 || @warn "matrix is not positive semidefinite"
        end
        return new{typeof(Q)}(Q)
    end
end

"""
    SqMahalanobis(Q; skipchecks=false) <: Metric

Create a squared Mahalanobis distance (i.e., a bilinear form) with covariance matrix `Q`.
Upon construction, both symmetry/self-adjointness and positive semidefiniteness are checked,
where the latter check can be skipped by passing the keyword argument `skipchecks = true`.

# Example:
```julia
julia> A = collect(reshape(1:9, 3, 3)); Q = A'A;

julia> dist = SqMahalanobis(Q)
SqMahalanobis{Matrix{Int64}}([14 32 50; 32 77 122; 50 122 194])

julia> dist = SqMahalanobis(A, skipchecks=true)
┌ Warning: matrix is not symmetric/Hermitian
└ @ Distances ...
SqMahalanobis{Matrix{Int64}}([1 4 7; 2 5 8; 3 6 9])
"""
struct SqMahalanobis{M<:AbstractMatrix} <: SemiMetric
    qmat::M
    function SqMahalanobis(Q::AbstractMatrix; skipchecks::Bool=false)
        # TODO: turn the warnings into errors in next breaking release
        ishermitian(Q) || @warn "matrix is not symmetric/Hermitian"
        if !skipchecks
            eigmin(Q) ≥ 0 || @warn "matrix is not positive semidefinite"
        end
        return new{typeof(Q)}(Q)
    end
end

function result_type(d::Mahalanobis, ::Type{T1}, ::Type{T2}) where {T1,T2}
    z = zero(T1) - zero(T2)
    return typeof(sqrt(z * zero(eltype(d.qmat)) * z))
end

function result_type(d::SqMahalanobis, ::Type{T1}, ::Type{T2}) where {T1,T2}
    z = zero(T1) - zero(T2)
    return typeof(z * zero(eltype(d.qmat)) * z)
end

# SqMahalanobis

function (dist::SqMahalanobis)(a::AbstractVector, b::AbstractVector)
    if length(a) != length(b)
        throw(DimensionMismatch("first array has length $(length(a)) which does not match the length of the second, $(length(b))."))
    end

    Q = dist.qmat
    z = a - b
    return dot(z, Q * z)
end

sqmahalanobis(a::AbstractVector, b::AbstractVector, Q::AbstractMatrix) = SqMahalanobis(Q)(a, b)

function colwise!(r::AbstractArray, dist::SqMahalanobis, a::AbstractMatrix, b::AbstractMatrix)
    Q = dist.qmat
    get_colwise_dims(size(Q, 1), r, a, b)
    z = a - b
    dot_percol!(r, Q * z, z)
end

function colwise!(r::AbstractArray, dist::SqMahalanobis, a::AbstractVector, b::AbstractMatrix)
    Q = dist.qmat
    get_colwise_dims(size(Q, 1), r, a, b)
    z = a .- b
    dot_percol!(r, Q * z, z)
end

function _pairwise!(r::AbstractMatrix, dist::SqMahalanobis, a::AbstractMatrix, b::AbstractMatrix)
    Q = dist.qmat
    m, na, nb = get_pairwise_dims(size(Q, 1), r, a, b)

    Qa = Q * a
    Qb = Q * b
    sa2 = dot_percol(a, Qa)
    sb2 = dot_percol(b, Qb)
    mul!(r, a', Qb)

    for j = 1:nb
        @simd for i = 1:na
            @inbounds r[i, j] = max(sa2[i] + sb2[j] - 2 * r[i, j], 0)
        end
    end
    r
end

function _pairwise!(r::AbstractMatrix, dist::SqMahalanobis, a::AbstractMatrix)
    Q = dist.qmat
    m, n = get_pairwise_dims(size(Q, 1), r, a)

    Qa = Q * a
    sa2 = dot_percol(a, Qa)
    mul!(r, a', Qa)

    for j = 1:n
        for i = 1:(j - 1)
            @inbounds r[i, j] = r[j, i]
        end
        r[j, j] = 0
        for i = (j + 1):n
            @inbounds r[i, j] = max(sa2[i] + sa2[j] - 2 * r[i, j], 0)
        end
    end
    r
end

# Mahalanobis

function (dist::Mahalanobis)(a::AbstractVector, b::AbstractVector)
    sqrt(SqMahalanobis(dist.qmat, skipchecks = true)(a, b))
end

mahalanobis(a::AbstractVector, b::AbstractVector, Q::AbstractMatrix) = Mahalanobis(Q)(a, b)

function colwise!(r::AbstractArray, dist::Mahalanobis, a::AbstractMatrix, b::AbstractMatrix)
    sqrt!(colwise!(r, SqMahalanobis(dist.qmat, skipchecks = true), a, b))
end

function colwise!(r::AbstractArray, dist::Mahalanobis, a::AbstractVector, b::AbstractMatrix)
    sqrt!(colwise!(r, SqMahalanobis(dist.qmat, skipchecks = true), a, b))
end

function _pairwise!(r::AbstractMatrix, dist::Mahalanobis, a::AbstractMatrix, b::AbstractMatrix)
    sqrt!(_pairwise!(r, SqMahalanobis(dist.qmat, skipchecks = true), a, b))
end

function _pairwise!(r::AbstractMatrix, dist::Mahalanobis, a::AbstractMatrix)
    sqrt!(_pairwise!(r, SqMahalanobis(dist.qmat, skipchecks = true), a))
end


# ---
# title: 72. Edit Distance
# id: problem72
# author: Qling
# date: 2021-03-17
# difficulty: Hard
# categories: String, Dynamic Programming
# link: <https://leetcode.com/problems/edit-distance/description/>
# hidden: true
# ---
#
# Given two strings `word1` and `word2`, return _the minimum number of
# operations required to convert`word1` to `word2`_.
#
# You have the following three operations permitted on a word:
#
#   * Insert a character
#   * Delete a character
#   * Replace a character
#
#
#
# **Example 1:**
#
#
#
#     Input: word1 = "horse", word2 = "ros"
#     Output: 3
#     Explanation:
#     horse -> rorse (replace 'h' with 'r')
#     rorse -> rose (remove 'r')
#     rose -> ros (remove 'e')
#
#
# **Example 2:**
#
#
#
#     Input: word1 = "intention", word2 = "execution"
#     Output: 5
#     Explanation:
#     intention -> inention (remove 't')
#     inention -> enention (replace 'i' with 'e')
#     enention -> exention (replace 'n' with 'x')
#     exention -> exection (replace 'n' with 'c')
#     exection -> execution (insert 'u')
#
#
#
#
# **Constraints:**
#
#   * `0 <= word1.length, word2.length <= 500`
#   * `word1` and `word2` consist of lowercase English letters.
#
#
## @lc code=start
using LeetCode

function min_distance(word1::String, word2::String)::Int32
    m, n = length(word1) + 1, length(word2) + 1
    dp = zeros(Int32, m, n)
    dp[:, 1] .= (1:m) .- 1
    dp[1, :] .= (1:n) .- 1

    for i in 2:m, j in 2:n
        dp[i, j] =
            1 + min(
                dp[i - 1, j - 1] - Int(word1[i - 1] == word2[j - 1]),
                dp[i, j - 1],
                dp[i - 1, j],
            )
    end
    return dp[end, end]
end

## @lc code=end


using CmdStan
using Test

ProjDir = dirname(@__FILE__)
cd(ProjDir) do

bernoulli = "
data {
  int<lower=0> N;
  int<lower=0,upper=1> y[N];
}
parameters {
  real<lower=0,upper=1> theta;
}
model {
  theta ~ beta(1,1);
    y ~ bernoulli(theta);
}
"

data = [
  Dict("N" => 10, "y" => [0, 1, 0, 1, 0, 0, 0, 0, 0, 1]),
  Dict("N" => 10, "y" => [0, 1, 0, 0, 0, 0, 1, 0, 0, 1]),
  Dict("N" => 10, "y" => [0, 0, 0, 0, 0, 0, 1, 0, 1, 1]),
  Dict("N" => 10, "y" => [0, 0, 0, 1, 0, 0, 0, 1, 0, 1])
]

m = Stanmodel(Optimize(), name="bernoulli", model=bernoulli);
m.command[1] = CmdStan.cmdline(m)

println()
show(IOContext(stdout, :compact => true), m)
println()
show(m)

@assert m.output.refresh == 100

s1 = Sample()

println()
show(IOContext(stdout, :compact => true), s1)
println()
show(s1)

n1 = CmdStan.Lbfgs()
n2 = CmdStan.Lbfgs(history_size=15)
@assert 3*n1.history_size == n2.history_size

b1 = CmdStan.Bfgs()
b2 = CmdStan.Bfgs(tol_obj=1//100000000)
@assert b1.tol_obj == b2.tol_obj
o1 = Optimize()
o2 = Optimize(CmdStan.Newton())
o3 = Optimize(CmdStan.Lbfgs(history_size=10))
o4 = Optimize(CmdStan.Bfgs(tol_obj=1e-9))
o5 = Optimize(method=CmdStan.Bfgs(tol_obj=1e-9), save_iterations=true)
@assert o5.method.tol_obj == o1.method.tol_obj/10

println()
show(IOContext(stdout, :compact => true), o5)
println()
show(o5)

d1 = Diagnose()
@assert d1.diagnostic.error == 1e-6
d2 = Diagnose(CmdStan.Gradient(error=1e-7))
@assert d2.diagnostic.error == 1e-7

println()
show(IOContext(stdout, :compact => true), d2)
println()
show(d2)

println()
m.command

cd(ProjDir)
isdir("tmp") &&
  rm("tmp", recursive=true);

end # cd


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

Flux.testmode!(p::UCBExplorer, mode = true) = p.is_training = !mode

"""
    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, seed = nothing, is_training = true) =
    UCBExplorer(c, fill(ϵ, na), 1, MersenneTwister(seed), is_training)

@doc raw"""
    (ucb::UCBExplorer)(values::AbstractArray)
Unlike [`EpsilonGreedyExplorer`](@ref), uncertaintyies are considered in UCB.

!!! note
    If multiple values with the same maximum value are found.
    Then a random one will be returned!

```math
A_t = \underset{a}{\arg \max} \left[ Q_t(a) + c \sqrt{\frac{\ln t}{N_t(a)}} \right]
```

See more details at Section (2.7) on Page 35 of the book *Sutton, Richard S., and Andrew G. Barto. Reinforcement learning: An introduction. MIT press, 2018.*
""" 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


# # Chapter 5

WORD_SIZE

bitstring(3)

bitstring(-3)

isbitstype(Int64)

isbitstype(String)

myadd(x, y) = x + y
@code_native myadd(1,2)

typemax(Int64)

bitstring(typemax(Int32))

typemin(Int64)

bitstring(typemin(Int32))

9223372036854775806 + 1

9223372036854775806 + 1 + 1

2^64

2^65

# ##BigInt

big(9223372036854775806) + 1 + 1

big(2)^64

# ## Floating Point

bitstring(2.5)
bitstring(-2.5)

function floatbits(x::Float64)
   b = bitstring(x)
   b[1:1]*"|"*b[2:12]*"|"*b[13:end]
end

floatbits(2.5)

floatbits(-2.5)

0.1 > 1//10

Rational(0.1)

float(big(Rational(0.1)))

bitstring(0.10000000000000001) == bitstring(0.1)

eps(0.1)

nextfloat(0.1)

floatbits(0.1)

floatbits(nextfloat(0.1))

# ##Unchecked conversions

UInt64(1)
UInt32(4294967297)
4294967297 % UInt32
@btime UInt32(1)
@btime 1 % UInt32

# ## FastMath

function sum_diff(x)
    n = length(x); d = 1/(n-1)
    s = zero(eltype(x))
    s = s +  (x[2] - x[1]) / d
    for i = 2:length(x)-1
        s =  s + (x[i+1] - x[i+1]) / (2*d)
    end
    s = s + (x[n] - x[n-1])/d
end

function sum_diff_fast(x)
    n=length(x); d = 1/(n-1)
    s = zero(eltype(x))
    @fastmath s = s +  (x[2] - x[1]) / d
    @fastmath for i = 2:n-1
        s =  s + (x[i+1] - x[i+1]) / (2*d)
    end
    @fastmath s = s + (x[n] - x[n-1])/d
end

t=rand(2000);
@btime sum_diff($t)
@btime sum_diff_fast($t)

macroexpand(Main, :(@fastmath a + b / c))

# ## KBN Summation

sum([1 1e-100 -1])

using KahanSummation

sum_kbn([1 1e-100 -1])

t=[1, -1, 1e-100];

@btime sum($t)

@btime sum_kbn($t)

# ## Subnormal numbers

issubnormal(1.0)

issubnormal(1.0e-308)

3e-308 - 3.001e-308

issubnormal(3e-308 - 3.001e-308)

set_zero_subnormals(true)

3e-308 - 3.001e-308

3e-308 == 3.001e-308

get_zero_subnormals()

function timestep( b, a, dt )
    n = length(b)
    b[1] = 1
    two = eltype(b)(2)
    for i=2:n-1
        b[i] = a[i] + (a[i-1] - two*a[i] + a[i+1]) * dt
    end
    b[n] = 0
end

function heatflow( a, nstep )
    b = similar(a)
    o = eltype(a)(0.1)
    for t=1:div(nstep,2)
        timestep(b,a,o)
        timestep(a,b,o)
    end
end

set_zero_subnormals(false)

t=rand(1000);

@btime heatflow($t, 1000)

set_zero_subnormals(true)

@btime heatflow($t, 1000)