# This file was copied from https://github.com/jeanpauphilet/SubsetSelectionCIO.jl
# as at commit a21d34652e0349a6b6f33e9c926ffad659d05e26

struct UnsetSolver <: MathProgBase.AbstractMathProgSolver end
function getsolver(s::Type{S}, tl::Float64) where {S <: MathProgBase.AbstractMathProgSolver}
    UnsetSolver()
end
function getsolver(s::Type{S}, tl::Float64) where {S <: GurobiSolver}
    GurobiSolver(OutputFlag = 0, TimeLimit = tl)
end
function getsolver(s::Type{S}, tl::Float64) where {S <: CplexSolver}
    CplexSolver(CPX_PARAM_SCRIND = 0, CPX_PARAM_MIPDISPLAY = 0,
                CPXPARAM_TimeLimit = tl)
end

###########################
# FUNCTION oa_formulation
###########################
"""Computes the minimum regression error with Ridge regularization subject an explicit
cardinality constraint using cutting-planes.

w^* := arg min  ∑_i ℓ(y_i, x_i^T w) +1/(2γ) ||w||^2
           st.  ||w||_0 = k

INPUTS
  ℓ           - LossFunction to use
  Y           - Vector of outputs. For classification, use ±1 labels
  X           - Array of inputs
  k           - Sparsity parameter
  γ           - ℓ2-regularization parameter
  indices0    - (optional) Initial solution
  ΔT_max      - (optional) Maximum running time in seconds for the MIP solver. Default is 60s
  gap         - (optional) MIP solver accuracy

OUTPUT
  indices     - Indicates which features are used as regressors
  w           - Regression coefficients
  Δt          - Computational time (in seconds)
  status      - Solver status at termination
  Gap         - Optimality gap at termination
  cutCount    - Number of cuts needed in the cutting-plane algorithm
  """
function oa_formulation_bm(ℓ::LossFunction,
          Y::YVector,
          X::Array{Float64,2}, k::Int, γ::Float64,
          solver::MathProgBase.AbstractMathProgSolver;
          indices0=find(x-> x<k/size(X,2), rand(size(X,2))),
          node_heuristics=false)

  if solver == UnsetSolver()
    error("You need to set a solver for the exact primal method. E.g.:
    `ExactPrimalCuttingPlane(0.1, 30.0, GurobiSolver)`")
  end

  n = size(Y, 1)
  p = size(X, 2)

  miop = Model(solver=solver)

  s0 = zeros(p); s0[indices0]=1
  c0, ∇c0 = SubsetSelectionCIO.inner_op(ℓ, Y, X, s0, γ)

  # Optimization variables
  @variable(miop, s[j=1:p], Bin, start=s0[j])
  @variable(miop, t>=0, start=c0)

  # Objective
  @objective(miop, Min, t)

  # Constraints
  @constraint(miop, sum(s)<=k)

  cutCount=1; bestObj=c0; bestSolution=s0[:];
  @constraint(miop, t>= c0 + dot(∇c0, s-s0))

  # Outer approximation method for Convex Integer Optimization (CIO)
  function outer_approximation(cb)
    cutCount += 1
    c, ∇c = SubsetSelectionCIO.inner_op(ℓ, Y, X, getvalue(s), γ)
    if c<bestObj
      bestObj = c; bestSolution=getvalue(s)[:]
    end
    @lazyconstraint(cb, t>=c + dot(∇c, s-getvalue(s)))
  end
  addlazycallback(miop, outer_approximation)
  function myheuristic(cb)
    _, ∇c = SubsetSelectionCIO.inner_op(ℓ, Y, X, getvalue(s), γ)
    order = sortperm(∇c)
    promising_indices = collect(1:p)[order[1:k]]
    # hsolution = zeros(Int, p)
    # hsolution[promising_indices] = 1
    # setsolutionvalue(cb, s, hsolution)
    for i in promising_indices
      setsolutionvalue(cb, s[i], 1)
    end

    addsolution(cb)
  end
  if node_heuristics
    addheuristiccallback(miop, myheuristic)
  end

  status = solve(miop)
  Δt = getsolvetime(miop)

  if status != :Optimal
    Gap = 1 - JuMP.getobjbound(miop) /  getobjectivevalue(miop)
  else
    Gap = 0.0
  end

  if status == :Optimal
    bestSolution = getvalue(s)[:]
  end
  # Find selected regressors and run a standard linear regression with Tikhonov
  # regularization
  indices = find(s->s>0.5, bestSolution)
  w = SubsetSelection.recover_primal(ℓ, Y, X[:, indices], γ)

  return indices, w, Δt, status, Gap, cutCount
end


using jInv.Mesh
using Multigrid
using SparseArrays
using LinearAlgebra
println("************************************************* GMG for Elasticity 2D using Vanka ******************************************************");

domain = [0.0, 1.0, 0.0, 1.0];
n = [128,128];
Mr = getRegularMesh(domain,n)
mu = 1.0*ones(prod(Mr.n));
lambda = 1.0.*mu;

mutable struct ElasticityParam
	Mesh   			:: RegularMesh;
	lambda          :: Array{Float64};
	mu				:: Array{Float64}
	MixedFormulation:: Bool
end

Hparam = ElasticityParam(Mr,lambda,mu,true);

function restrictParam(mesh_fine,mesh_coarse,param_fine,level)
	mu_c 	 	= restrictCellCenteredVariables(param_fine.mu,mesh_fine.n);
	lambda_c 	= restrictCellCenteredVariables(param_fine.lambda,mesh_fine.n);
	return ElasticityParam(mesh_coarse,lambda_c,mu_c,true);
end

# function GetVectorLapOperatorMixedFormulation(M::RegularMesh, mu::Vector,lambda::Vector)
	# vecGRAD,~,Div,nf, = getDifferentialOperators(M,2);
	# Mu     			  = getTensorMassMatrix(M,mu[:],saveAvMat=false)[1];
	# Rho	   			  = getFaceMassMatrix(M,1e-5*mu, saveMat = false, avN2C = avN2C_Nearest);
	# A 				  = vecGRAD'*Mu*vecGRAD + Rho;
	# A 				  = spdiagm(0=>ones(size(Rho,1)));
	# C 				  = -(spdiagm(0=>0.0./(lambda[:])));
	# Div.nzval .*= -1.0;
	# H = [A  Div' ; -Div  -C;];
	# return H;
# end

# function GetVectorLapOperator(M::RegularMesh, mu::Vector,lambda::Vector)
	# vecGRAD,~,Div,nf, = getDifferentialOperators(M,2);
	# Mu     			  = getTensorMassMatrix(M,mu[:],saveAvMat=false)[1];
	# Rho	   			  = getFaceMassMatrix(M,1e-5*mu, saveMat = false, avN2C = avN2C_Nearest);
	# A 				  = vecGRAD'*Mu*vecGRAD + Rho;
	# return A;
# end

function getOperator(Mr,Hparam)
	# Ar = GetVectorLapOperator(Mr, Hparam.mu,Hparam.lambda);
	# Ar = GetVectorLapOperatorMixedFormulation(Mr, Hparam.mu,Hparam.lambda);
	Ar = GetLinearElasticityOperatorMixedFormulation(Mr, Hparam.mu,Hparam.lambda);
	Ar = Ar + 1e-3*opnorm(Ar,1)*sparse(1.0I,size(Ar,2),size(Ar,2));
	return Ar;
end

levels      = 5;
numCores 	= 2;
maxIter     = 5;
relativeTol = 1e-10;
relaxType   = "VankaFaces";
# relaxType   = "Jac";
relaxParam  = 0.5;
relaxPre 	= 1;
relaxPost   = 1;
cycleType   ='V';
coarseSolveType = "Julia";
transferOperatorType = "SystemsFacesMixedLinear";

MG = getMGparam(Float64,Int64,levels,numCores,maxIter,relativeTol,relaxType,relaxParam,relaxPre,relaxPost,cycleType,coarseSolveType,0.5,0.0,transferOperatorType);

MGsetup(getMultilevelOperatorConstructor(Hparam,getOperator,restrictParam),Mr,MG,1,true);
Ar = MG.As[1]; # It is actually transposed, but matrix is symmetric.
# MGsetup(Ar,Mr,MG,1,true);

N = size(Ar,2);
sol = randn(N);
sol = sol/norm(sol);
sol = sol .- sum(sol)./N;
b = Ar*sol; b = b/norm(b);
x = zeros(N);

println("****************************** Stand-alone GMG: ******************************")
x, = solveMG(MG,copy(b),x,true);
@test norm(Ar*x - b) < 0.05;

println("****************************** GMG + CG: ******************************")
x[:].=0.0;
solveCG_MG(Ar,MG,b,x,true)
@test norm(Ar*x - b) < 0.01;

println("TODO: see why the code below doesn't work.")
clear!(MG)

println("************************************************* GMG for Elasticity 3D  using Vanka ******************************************************");

domain = [0.0, 1.0, 0.0, 1.0,0.0,1.0];
n = [8,8,8];
Mr = getRegularMesh(domain,n)
mu = 1.0*ones(prod(Mr.n));
lambda = 0.5.*mu;

Hparam = ElasticityParam(Mr,lambda,mu,true);

MGsetup(getMultilevelOperatorConstructor(Hparam,getOperator,restrictParam),Mr,MG,size(b,1),true);
Ar = MG.As[1]; # It is actually transposed, but matrix is symmetric.
N = size(Ar,2);
b = Ar*rand(N); b = b/norm(b);
x = zeros(N);

println("****************************** Stand-alone 3D GMG: ******************************")
solveMG(MG,b,x,true);
@test norm(Ar*x - b) < 0.1;
println("****************************** 3D GMG + CG: ******************************")
x[:].=0.0;
solveCG_MG(Ar,MG,b,x,true)
@test norm(Ar*x - b) < 0.1;


using Test
using QCMaterial

@testset "hamiltonian.generate_ham_1d_hubbard" begin
    nsite = 2
    n_qubit = 2*nsite
    U = 1.0
    t = -0.01
    μ = 0.0

    ham = generate_ham_1d_hubbard(t, U, nsite, μ)

    ham_ref = FermionOperator()
    up1 = up_index(1)
    down1 = down_index(1)
    up2 = up_index(2)
    down2 = down_index(2)

    #クーロン斥力
    ham_ref += FermionOperator("$(up1)^ $(down1)^ $(up1) $(down1)", -U)
    ham_ref += FermionOperator("$(up2)^ $(down2)^ $(up2) $(down2)", -U)

    #ホッピング積分
    ham_ref += FermionOperator("$(up2)^ $(up1)", t)
    ham_ref += FermionOperator("$(up1)^ $(up2)", t)
    ham_ref += FermionOperator("$(down2)^ $(down1)", t)
    ham_ref += FermionOperator("$(down1)^ $(down2)", t)

    #ケミカルポテンシャルの項
    ham_ref += FermionOperator("$(up1)^  $(up1) ", -μ)
    ham_ref += FermionOperator("$(down1)^ $(down1)", -μ)
    ham_ref += FermionOperator("$(up2)^  $(up2) ", -μ)
    ham_ref += FermionOperator("$(down2)^ $(down2)", -μ)

    @test ham == ham_ref
end


module CGcoefficient

export CG, threeJ, sixJ, nineJ,
       dCG, d3j, d6j, d9j,
       SqrtRational,
       HalfInt,
       iphase,
       is_same_parity,
       check_jm,
       check_couple

include("SqrtRational.jl")
include("util.jl")
include("WignerSymbols.jl")

end # module CGcoefficient


import Statistics

lines = readlines("input.txt")
matrix = parse.(Bool, hcat(collect.(lines)...))

oxygen = co2 = matrix'
for idx = axes(matrix, 1)
    if size(oxygen, 1) > 1
        mean = Statistics.mean(oxygen[:,idx]) .>= 0.5
        global oxygen = oxygen[findall(x -> x[idx] == mean, eachrow(oxygen)), :]
    end

    if size(co2, 1) > 1
        mean = Statistics.mean(co2[:,idx]) .>= 0.5
        global co2 = co2[findall(x -> x[idx] != mean, eachrow(co2)), :]
    end
end

function bitarr_to_int(arr)
    return sum(arr .* (2 .^ collect(length(arr)-1:-1:0)))
end

println(bitarr_to_int(oxygen') * bitarr_to_int(co2'))


using Distributions: MvNormal

struct GaussianDataset{D} <: AbstractDataset{D}
    X
    Xt
end

n_display(::GaussianDataset) = 1_000

function GaussianDataset(
    n_data::Int,
    mean::AbstractVector{T},
    cov::AbstractVecOrMat{T};
    seed::Int=1,
    test_ratio=1/6,
    n_test::Int=ratio2num(n_data, test_ratio),
    rng=MersenneTwister(seed),
) where {T<:AbstractFloat}
    X = rand(rng, MvNormal(mean, cov), n_data)
    Xt = rand(rng, MvNormal(mean, cov), n_test)
    D = length(mean)
    @info "Oh you just get the $(D)D Gaussian dataset" n_data=n_data n_test=n_test mean=mean cov=cov
    return GaussianDataset{D}(X, Xt)
end

GaussianDataset(n_data; kwargs...) = GaussianDataset(n_data, 2f0 * ones(Float32, 2), [1 81f-2; 81f-2 1])


export smooth
using FFTViews

function smooth(a::RFFTSpectrumArray{T}, octave::Real = 1//3) where {T <: Real}
    # algorithm: Hatziantoniou, Panagiotis D., and John N. Mourjopoulos. "Generalized fractional-octave smoothing of audio and acoustic responses." Journal of the Audio Engineering Society 48.4 (2000): 259-280.
    # but based on description in:
    # Tylka, Joseph G., Braxton B. Boren, and Edgar Y. Choueiri. "A generalized method for fractional-octave smoothing of transfer functions that preserves log-frequency symmetry." Journal of the Audio Engineering Society 65.3 (2017): 239-245.
    # Method 1: Symmetric weights
    # rectangular window
    octave ≥ 0 || error("octave must be ≥ 0")
    n = nframes(a)
    as = similar(a)
    X = data(a)
    Xs = data(as)

    X = [X; conj.(X[end-1:-1:2,:])] # we expect spectra created using rfft(), so extend by negative frequencies

    rQ = 2sinh(octave*log(2)/2) # Q reciprocial: 1/Q CHECK use log2?
    for c in 1:nchannels(a)
        XV = FFTView(view(X, :, c)) # make it circular and starting on zero index
        plb, pub = 0, -1 # previous lower and upper bounds
        msum = 0.0 # moving sum
        for k in 0:n-1
            mk = floor(Int, k*rQ/2)
            lb, ub = k-mk, k+mk
            msum = msum - sum(XV[plb:lb-1]) + sum(XV[pub+1:ub])
            mmean = msum / (2mk + 1)
            Xs[k+1, c] = mmean
            plb, pub = lb, ub
        end
    end
    as
end

function smooth_naive(a::RFFTSpectrumArray{T}, octave::Real = 1//3) where {T <: Real}
    # slower version of smooth - always recomputes window means
    octave ≥ 0 || error("octave must be ≥ 0")
    n = nframes(a)
    as = similar(a)
    X = data(a)
    Xs = data(as)

    X = [X; conj.(X[end-1:-1:2,:])] # we expect spectra created using rfft(), so extend by negative frequencies
    X = FFTView(X) # make it circular and starting on zero index

    rQ = 2sinh(octave*log(2)/2) # Q reciprocial: 1/Q CHECK use log2?
    for c in 1:nchannels(a)
        for k in 0:n-1
            mk = floor(Int, k*rQ/2)
            # Xs[k+1, c] = mean(X[k-mk:k+mk, c])
            Xs[k+1, c] = mean(X[max(k-mk, 0):min(k+mk, n-1), c])
        end
    end
    as
end

function smooth(X::RFFTSpectrumArray{T}, octave::Real = 1//3) where {E, T <: SignalElement{E}}
    Y = similar(X)
    # mags = smooth_naive(abs.(X), octave)
    mags = smooth(abs.(X), octave)
    phis = smooth(unwrap(angle.(X), dims=1), octave)
    Y .= (MagPhase(max(m, 0.0), p) for (m, p) in zip(mags, phis)) # max for numerical instability giving negative magnitude
    Y
end


module Jazz
using MIDI, MusicManipulations

"""
```julia
average_swing_ratio(notes::Notes, asr_method::String)
```
Calculate the average swing ratio of given `notes` array. Return the average
swing ratio and the associated standard deviation.
## Methods
### `"swung8s"`
Classify each note with the grid `[0, 2//3, 1]`. Use the notes with
index 2 to calculate the swing ratio, and then average for all.
### `"triplets"`
Classify each note with the grid `[0, 1//3, 2//3, 1]`. Use the notes with
index 3 to calculate the swing ratio, and then average for all.
"""
function average_swing_ratio(notes::Notes, asr_method::String)

    tpq = notes.tpq
    sr = Float64[]

    # Create array of Swing Notes
    if asr_method == "swung8s"
        s = classify(notes, [0, 2//3, 1])
        swingnotes = notes.notes[s .== 2]
    elseif asr_method == "triplets"
        s = classify(notes, [0, 1//3, 2//3, 1])
        swingnotes = notes.notes[s .== 3]
    end

    # Create Swing ratios array
    for i in 1:length(swingnotes)
        pos = Int64(swingnotes[i].position)
        posmod = mod(pos, tpq)
        push!(sr, posmod/(tpq-posmod))
    end

    return mean(sr), std(sr)
end

function inbetween_portion(notes::Notes)
  clas = classify(notes, [0, 1//3, 2//3, 1])
  count(i -> i == 2, clas)/length(notes)
end

end


using MittagLefflerFunctions
using PyPlot
using SpecialFunctions: erfcx

α = 1/2
β = 1.0

#x = range(0, 10, length=201)
x = range(-5, 3, length=300)
y = erfcx.(-x)

Nvals = collect(4:2:10)

style = ("-", "--", ":", "-.")
function draw_errors(contour::Symbol)
    for row = 1:length(Nvals)
        N = Nvals[row]
        E = MLQuad(α, β, N, contour)
        err = abs.( y - E.(x) )
        semilogy(x, err, style[row], label="N = $N")
    end
    legend(loc="upper right")
    grid(true)
    xlabel(L"$x$")
end

figure(1)
draw_errors(:parabola)
xylims = (-5.4, 3.4, 3e-12, 6e-4)
axis(xylims)
savefig("figure07L.pdf")

figure(2)
draw_errors(:hyperbola)
savefig("figure07R.pdf")
axis(xylims)


using Base: Test, LinAlg
using HDF5

using DSGE
include("../util.jl")
path = dirname(@__FILE__)

m = Model990()
Γ0, Γ1, C, Ψ, Π = eqcond(m)
stake = 1 + 1e-6
G1, C, impact, fmat, fwt, ywt, gev, eu, loose = gensys(Γ0, Γ1, C, Ψ, Π, stake)

h5 = h5open("$path/../reference/gensys.h5")
G1_exp = read(h5, "G1_gensys")
C_exp = reshape(read(h5, "C_gensys"), 60, 1)
impact_exp = read(h5, "impact")
eu_exp = read(h5, "eu")
close(h5)

@test_matrix_approx_eq G1_exp G1

@test_matrix_approx_eq C_exp C

@test_matrix_approx_eq impact_exp impact

@test isequal(eu_exp, eu)


"""
    _inceptionblock(inplanes, out_1x1, red_3x3, out_3x3, red_5x5, out_3x3, pool_proj)

Create an inception module for use in GoogLeNet
([reference](https://arxiv.org/abs/1409.4842v1)).

# Arguments
- `inplanes`: the number of input feature maps
- `out_1x1`: the number of output feature maps for the 1x1 convolution (branch 1)
- `red_3x3`: the number of output feature maps for the 3x3 reduction convolution (branch 2)
- `out_3x3`: the number of output feature maps for the 3x3 convolution (branch 2)
- `red_5x5`: the number of output feature maps for the 5x5 reduction convolution (branch 3)
- `out_5x5`: the number of output feature maps for the 5x5 convolution (branch 3)
- `pool_proj`: the number of output feature maps for the pooling projection (branch 4)
"""
function _inceptionblock(inplanes, out_1x1, red_3x3, out_3x3, red_5x5, out_5x5, pool_proj)
    branch1 = Chain(Conv((1, 1), inplanes => out_1x1))

    branch2 = Chain(Conv((1, 1), inplanes => red_3x3),
                    Conv((3, 3), red_3x3 => out_3x3; pad = 1))

    branch3 = Chain(Conv((1, 1), inplanes => red_5x5),
                    Conv((5, 5), red_5x5 => out_5x5; pad = 2))

    branch4 = Chain(MaxPool((3, 3), stride=1, pad = 1),
                    Conv((1, 1), inplanes => pool_proj))

    return Parallel(cat_channels,
                    branch1, branch2, branch3, branch4)
end

"""
    googlenet(; nclasses = 1000)

Create an Inception-v1 model (commonly referred to as GoogLeNet)
([reference](https://arxiv.org/abs/1409.4842v1)).

# Arguments
- `nclasses`: the number of output classes
"""
function googlenet(; nclasses = 1000)
  layers = Chain(Chain(Conv((7, 7), 3 => 64; stride = 2, pad = 3),
                       MaxPool((3, 3), stride = 2, pad = 1),
                       Conv((1, 1), 64 => 64),
                       Conv((3, 3), 64 => 192; pad = 1),
                       MaxPool((3, 3), stride = 2, pad = 1),
                       _inceptionblock(192, 64, 96, 128, 16, 32, 32),
                       _inceptionblock(256, 128, 128, 192, 32, 96, 64),
                       MaxPool((3, 3), stride = 2, pad = 1),
                       _inceptionblock(480, 192, 96, 208, 16, 48, 64),
                       _inceptionblock(512, 160, 112, 224, 24, 64, 64),
                       _inceptionblock(512, 128, 128, 256, 24, 64, 64),
                       _inceptionblock(512, 112, 144, 288, 32, 64, 64),
                       _inceptionblock(528, 256, 160, 320, 32, 128, 128),
                       MaxPool((3, 3), stride = 2, pad = 1),
                       _inceptionblock(832, 256, 160, 320, 32, 128, 128),
                       _inceptionblock(832, 384, 192, 384, 48, 128, 128),
                       AdaptiveMeanPool((1, 1))),
                 Chain(flatten,
                       Dropout(0.4),
                       Dense(1024, nclasses)))

  return layers
end

"""
    GoogLeNet(; pretrain = false,  nclasses = 1000)

Create an Inception-v1 model (commonly referred to as `GoogLeNet`)
([reference](https://arxiv.org/abs/1409.4842v1)).

# Arguments
- `pretrain`: set to `true` to load the model with pre-trained weights for ImageNet
- `nclasses`: the number of output classes

See also [`googlenet`](#).
"""
struct GoogLeNet{T}
  layers::T
end

function GoogLeNet(; pretrain = false, nclasses = 1000)
  layers = googlenet(nclasses = nclasses)
  pretrain && Flux.loadparams!(layers, weights("googlenet"))

  GoogLeNet(layers)
end

@functor GoogLeNet

(m::GoogLeNet)(x) = m.layers(x)


"""
Module này thực hiện tính toán với hàng đợi

# Cách dùng

- `m = TênMôHình(tham_số...)`: Tạo một mô hình, danh sách mô hình xem ở dưới
- `λe(m)`: Tính cường độ đến hiệu quả ``\\lambda_e``.
- `pn(m)`: Tính xác suất ``P\\left[N(t) = n\\right]``.
- `L(m)`: Lượng khách trung bình trong hệ thống
- `Lq(m)`: Lượng khách trung bình trong hàng đợi
- `W(m)`: Thời gian trung bình trong hệ thống
- `Wq(m)`: Thời gian trung bình trong hàng đợi

## Ví dụ
~~~julia
julia> include("$(@__FILE__)")
Main.MMCKModels

julia> using .MMCKModels

julia> m = MM1(6, 8)
MM1(6, 8, 0.75)

julia> L(m)
3.0

julia> Lq(m)
2.25

julia> W(m)
0.5
~~~

Các loại mô hình hàng đợi:
- Hiện có: `MM1`, `MM1K`
- Dự kiến: `MMC`

Viết `?TênMôHình` để xem cách dùng.
"""
module MMCKModels

export MM1, MM1K, MMC,
	L, Lq, W, Wq, λe, pn

abstract type AbstractMMCK end;

"""
	MM1(μ, λ)

Tạo mô hình M/M/1
"""
struct MM1 <: AbstractMMCK
	λ; μ; ρ
	function MM1(λ::Real, μ::Real)
		if λ/μ >= 1
			@error "ρ phải < 1"
		end
		new(λ, μ, λ/μ)
	end
	function MM1(λ, μ)
		new(λ, μ, λ/μ)
	end
end

pn(m::MM1, n::Int) = (1 - m.ρ) * m.ρ^n
L(m::MM1) = m.ρ / (1 - m.ρ)
Lq(m::MM1) = m.ρ ^ 2 / (1 - m.ρ)
W(m::MM1) = L(m) / m.λ
Wq(m::MM1) = Lq(m) / m.λ

"""
	MM1K(λ, μ, k)

Tạo mô hình `M/M/1/K`.
"""
struct MM1K <: AbstractMMCK
	λ; μ; k; ρ
	MM1K(λ, μ, k) = new(λ, μ, k, λ/μ)
end

function pn(m::MM1K, n::Int)
	ρ = m.ρ
	k = m.k
	if ρ == 1
		1 / (k + 1)
	else
		ρ^n * (1 - ρ) / (1 - ρ^(k+1))
	end
end

λe(m::MM1K) = m.λ * (1 - pn(m, m.k))
L(m::MM1K) = let ρ = m.ρ, k = m.k
	if ρ == 1
		k / 2
	else
		l = k + 1
		ρ * (1 + k*ρ^l - l*ρ^k) / (1 - ρ) / (1 - ρ^l)
	end
end
Lq(m::MM1K) = let ρ = m.ρ, k = m.k
	if ρ == 1
		k * (k - 1) / 2 / (k + 1)
	else
		L(m) - ρ * (1 - ρ^k) / (1 - ρ^(k+1))
	end
end
W(m::MM1K) = L(m) / λe(m)
Wq(m::MM1K) = W(m) - 1/μ

"""
	MMC(μ, λ, c)

Tạo mô hình M/M/c
"""
struct MMC <: AbstractMMCK
	μ; λ; c; r; ρ
	function MMC(μ, λ, c)
		new(μ, λ, c, λ/μ, λ/μ/c)
	end
end

function pn(m::MMC, n::Integer)
	μ = m.μ
	λ = m.λ
	c = m.c
	r = m.r
	ρ = m.ρ
	if n == 0
		1 / ((c * r^c) / factorial(c) / (c - r) + sum(r^n/factorial(i) for i = 0:c-1))
	elseif n < c
		pn(m, 0) * r^n / factorial(n)
	else
		pn(m, 0) * r^n / factorial(c) / c^(n-c)
	end
end

end


using StanBase

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

stanmodel = HelpModel( "help", stan_prog;
  method = StanBase.Help(help=:sample))

stan_help(stanmodel;n_chains=1)

run(`cat $(stanmodel.output_base*"_chain_1.log")`)


"""
    solve_objbound_edge_cases(model::MOI.ModelLike, config::TestConfig)

Test a variety of edge cases related to the ObjectiveBound attribute.
"""
function solve_objbound_edge_cases(model::MOI.ModelLike, config::TestConfig)
    @testset "Min IP with constant" begin
        MOI.empty!(model)
        @test MOI.is_empty(model)
        MOIU.loadfromstring!(model, """
            variables: x
            minobjective: 2.0x + -1.0
            c1: x >= 1.5
            c2: x in Integer()
        """)
        x = MOI.get(model, MOI.VariableIndex, "x")
        test_model_solution(model, config;
            objective_value = 3.0,
            variable_primal = [(x, 2.0)]
        )
        if config.solve
            @test MOI.get(model, MOI.ObjectiveBound()) <= 3.0
        end
    end

    @testset "Max IP with constant" begin
        MOI.empty!(model)
        @test MOI.is_empty(model)
        MOIU.loadfromstring!(model, """
            variables: x
            maxobjective: 2.0x + 1.0
            c1: x <= 1.5
            c2: x in Integer()
        """)
        x = MOI.get(model, MOI.VariableIndex, "x")
        test_model_solution(model, config;
            objective_value = 3.0,
            variable_primal = [(x, 1.0)]
        )
        if config.solve
            @test MOI.get(model, MOI.ObjectiveBound()) >= 3.0
        end
    end

    @testset "Min LP with constant" begin
        MOI.empty!(model)
        @test MOI.is_empty(model)
        MOIU.loadfromstring!(model, """
            variables: x
            minobjective: 2.0x + -1.0
            c1: x >= 1.5
        """)
        x = MOI.get(model, MOI.VariableIndex, "x")
        test_model_solution(model, config;
            objective_value = 2.0,
            variable_primal = [(x, 1.5)]
        )
        if config.solve
            @test MOI.get(model, MOI.ObjectiveBound()) <= 2.0
        end
    end

    @testset "Max LP with constant" begin
        MOI.empty!(model)
        @test MOI.is_empty(model)
        MOIU.loadfromstring!(model, """
            variables: x
            maxobjective: 2.0x + 1.0
            c1: x <= 1.5
        """)
        x = MOI.get(model, MOI.VariableIndex, "x")
        test_model_solution(model, config;
            objective_value = 4.0,
            variable_primal = [(x, 1.5)]
        )
        if config.solve
            @test MOI.get(model, MOI.ObjectiveBound()) >= 4.0
        end
    end
end
unittests["solve_objbound_edge_cases"] = solve_objbound_edge_cases


using FEASTSolver
using LinearAlgebra
# using MatrixMarket
using SparseArrays

function mmwrite(filename, matrix :: SparseMatrixCSC)
  open(filename, "w") do file
    elem = eltype(matrix) <: Bool ? "pattern" :
           eltype(matrix) <: Integer ?  "integer" :
           eltype(matrix) <: AbstractFloat ? "real" :
           eltype(matrix) <: Complex ? "complex" :
           error("Invalid matrix type")
      sym = "general"

      # write mm header
      write(file, "%%MatrixMarket matrix coordinate $elem $sym\n")

      # only use lower triangular part of symmetric and Hermitian matrices
      #if issymmetric(matrix) || ishermitian(matrix)
      #    matrix = tril(matrix)
      #end

      # write matrix size and number of nonzeros
      write(file, "$(size(matrix, 1)) $(size(matrix, 2)) $(nnz(matrix))\n")

      rows = rowvals(matrix)
      vals = nonzeros(matrix)
      for i in 1:size(matrix, 2)
          for j in nzrange(matrix, i)
              write(file, "$(rows[j]) $i")
              if elem == "pattern" # omit values on pattern matrices
              elseif elem == "complex"
                  write(file, " $(real(vals[j])) $(imag(vals[j]))")
              else
                  write(file, " $(vals[j])")
              end
              write(file, "\n")
          end
      end
  end
end

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

    mmwrite("data/butterflyM0.mtx", sparse(M0))
    mmwrite("data/butterflyM1.mtx", sparse(M1))
    mmwrite("data/butterflyM2.mtx", sparse(M2))
    mmwrite("data/butterflyM3.mtx", sparse(M3))
    mmwrite("data/butterflyM4.mtx", sparse(M4))


module FEFMM_Tests
    using Test
    using FEFMM
    using UnicodePlots
    using LinearAlgebra: norm

    include("testmediums.jl")

    @testset "FEFMM Tests" begin
        @testset "Solve Piecewise Quadratic" begin
            @test isapprox(FEFMM.solve_piecewise_quadratic(1.0,1.0,1.0), 2.0)
            @test isapprox(FEFMM.solve_piecewise_quadratic(1.0,1.0,1.0,1.0,1.0), 1.0+1/sqrt(2))
            @test isapprox(FEFMM.solve_piecewise_quadratic(1.0,1.0,100.0,1.0,1.0), 2.0)
            @test isapprox(FEFMM.solve_piecewise_quadratic(2.0,1.0,1.0,100.0,1.0), 1.0)
            @test isapprox(FEFMM.solve_piecewise_quadratic(1.0,1.0,1.0,1.0,1.0,1.0,1.0), 1.0+1/sqrt(3))
            @test isapprox(FEFMM.solve_piecewise_quadratic(1.0,2.0,3.0,100.0,10.0,1.0,1.0), 2.0/3.0)
            @test isapprox(FEFMM.solve_piecewise_quadratic(1.0,2.0,3.0,10.0,100.0,1.0,1.0), 2.0/3.0)
            @test isapprox(FEFMM.solve_piecewise_quadratic(1.0,2.0,3.0,10.0,1.0,100.0,1.0), 1.0)
            @test isapprox(FEFMM.solve_piecewise_quadratic(1.0,2.0,3.0,1.0,10.0,100.0,1.0), 2.0)
        end

        @testset "Basic FEFMM Checks" begin
            κ21d = ones(101)
            dx1d = [0.1]
            xs1d = CartesianIndex(1)
            τa1d = 0:0.1:10
            @test all(isapprox.(τa1d, FEFMM.fefmm(κ21d, dx1d, xs1d)[1]))
            κ22d = ones(101,101)
            dx2d = [0.1,0.1]
            xs2d = CartesianIndex(1,1)
            τaxis = 0:0.1:10
            τa2d = sqrt.(τaxis'.^2 .+ τaxis.^2)
            @test all(isapprox.(τa2d, FEFMM.fefmm(κ22d, dx2d, xs2d)[1]))
            κ23d = ones(101,101,101)
            dx3d = [0.1,0.1,0.1]
            xs3d = CartesianIndex(1,1,1)
            τa3d = zeros(101,101,101)
            for k in 1:101
                for j in 1:101
                    for i in 1:101
                        τa3d[i,j,k] = sqrt(τaxis[i]^2+τaxis[j]^2+τaxis[k]^2)
                    end
                end
            end
            @test all(isapprox.(τa3d, FEFMM.fefmm(κ23d, dx3d, xs3d)[1]))
        end

        @testset "Constant Gradient of Squared Slowness 2D" begin
            hlist = [1/20, 1/40, 1/80, 1/160, 1/320]#, 1/640, 1/1280]
            linorm = zeros(length(hlist))
            l2norm = zeros(length(hlist))
            for i = 1:length(hlist)
                (x0, k2, t_exact) = const_k2_2D(hlist[i])
                (t_pred, ordering) = FEFMM.fefmm(k2, [hlist[i], hlist[i]], x0)
                err = t_exact.-t_pred
                linorm[i] = norm(err, Inf)
                l2norm[i] = norm(err, 2)/sqrt(length(err))
            end

            plt = lineplot(log10.(hlist),
                           log10.(linorm),
                           title = "Constant Gradient of Squared Slowness 2D",
                           name = "L_inf norm",
                           xlabel = "log_10(h)",
                           ylabel = "log_10(Error)",
                           xlim = [floor(minimum(log10.(hlist))), ceil(maximum(log10.(hlist)))],
                           ylim = [floor(minimum(log10.([linorm; l2norm]))), ceil(maximum(log10.([linorm; l2norm])))],
                           color = :blue)

            lineplot!(plt, log10.(hlist), log10.(l2norm), name = "mean L2 norm", color = :red)
            println(plt)
            @test reverse(linorm) == sort(linorm) #test for decreasing error with decreasing h
            @test reverse(l2norm) == sort(l2norm)
        end

        @testset "Constant Gradient of Squared Velocity 2D" begin
            hlist = [1/20, 1/40, 1/80, 1/160, 1/320]#, 1/640, 1/1280]
            linorm = zeros(length(hlist))
            l2norm = zeros(length(hlist))
            for i = 1:length(hlist)
                (x0, k2, t_exact) = const_v2_2D(hlist[i])
                (t_pred, ordering) = FEFMM.fefmm(k2, [hlist[i], hlist[i]], x0)
                err = t_exact.-t_pred
                linorm[i] = norm(err, Inf)
                l2norm[i] = norm(err, 2)/sqrt(length(err))
            end

                plt = lineplot(log10.(hlist),
                            log10.(linorm),
                            title = "Constant Gradient of Squared Velocity 2D",
                            name = "L_inf norm",
                            xlabel = "log_10(h)",
                            ylabel = "log_10(Error)",
                            xlim = [floor(minimum(log10.(hlist))), ceil(maximum(log10.(hlist)))],
                            ylim = [floor(minimum(log10.([linorm; l2norm]))), ceil(maximum(log10.([linorm; l2norm])))],
                            color = :blue)

            lineplot!(plt, log10.(hlist), log10.(l2norm), name = "mean L2 norm", color = :red)
            println(plt)
            @test reverse(linorm) == sort(linorm) #test for decreasing error with decreasing h
            @test reverse(l2norm) == sort(l2norm)
        end

        @testset "Gaussian Factor 2D" begin
            hlist = [1/20, 1/40, 1/80, 1/160, 1/320]#, 1/640, 1/1280]
            linorm = zeros(length(hlist))
            l2norm = zeros(length(hlist))
            for i = 1:length(hlist)
                (x0, k2, t_exact) = gaussian_factor_2D(hlist[i])
                (t_pred, ordering) = FEFMM.fefmm(k2, [hlist[i], hlist[i]], x0)
                err = t_exact.-t_pred
                linorm[i] = norm(err, Inf)
                l2norm[i] = norm(err, 2)/sqrt(length(err))
            end

                plt = lineplot(log10.(hlist),
                            log10.(linorm),
                            title = "Gaussian Factor 2D",
                            name = "L_inf norm",
                            xlabel = "log_10(h)",
                            ylabel = "log_10(Error)",
                            xlim = [floor(minimum(log10.(hlist))), ceil(maximum(log10.(hlist)))],
                            ylim = [floor(minimum(log10.([linorm; l2norm]))), ceil(maximum(log10.([linorm; l2norm])))],
                            color = :blue)

            lineplot!(plt, log10.(hlist), log10.(l2norm), name = "mean L2 norm", color = :red)
            println(plt)
            @test reverse(linorm) == sort(linorm) #test for decreasing error with decreasing h
            @test reverse(l2norm) == sort(l2norm)
        end

        @testset "Constant Gradient of Squared Slowness 3D" begin
            hlist = [1/10, 1/20, 1/40, 1/80]#, 1/160, 1/320]
            linorm = zeros(length(hlist))
            l2norm = zeros(length(hlist))
            for i = 1:length(hlist)
                (x0, k2, t_exact) = const_k2_3D(hlist[i])
                (t_pred, ordering) = FEFMM.fefmm(k2, [hlist[i], hlist[i], hlist[i]], x0)
                err = t_exact.-t_pred
                linorm[i] = norm(err, Inf)
                l2norm[i] = norm(err, 2)/sqrt(length(err))
            end

            plt = lineplot(log10.(hlist),
                           log10.(linorm),
                           title = "Constant Gradient of Squared Slowness 3D",
                           name = "L_inf norm",
                           xlabel = "log_10(h)",
                           ylabel = "log_10(Error)",
                           xlim = [floor(minimum(log10.(hlist))), ceil(maximum(log10.(hlist)))],
                           ylim = [floor(minimum(log10.([linorm; l2norm]))), ceil(maximum(log10.([linorm; l2norm])))],
                           color = :blue)

            lineplot!(plt, log10.(hlist), log10.(l2norm), name = "mean L2 norm", color = :red)
            println(plt)
            @test reverse(linorm) == sort(linorm) #test for decreasing error with decreasing h
            @test reverse(l2norm) == sort(l2norm)
        end

        @testset "Constant Gradient of Squared Velocity 3D" begin
            hlist = [1/10, 1/20, 1/40, 1/80]#, 1/160, 1/320]
            linorm = zeros(length(hlist))
            l2norm = zeros(length(hlist))
            for i = 1:length(hlist)
                (x0, k2, t_exact) = const_v2_3D(hlist[i])
                (t_pred, ordering) = FEFMM.fefmm(k2, [hlist[i], hlist[i], hlist[i]], x0)
                err = t_exact.-t_pred
                linorm[i] = norm(err, Inf)
                l2norm[i] = norm(err, 2)/sqrt(length(err))
            end

            plt = lineplot(log10.(hlist),
                           log10.(linorm),
                           title = "Constant Gradient of Squared Velocity 3D",
                           name = "L_inf norm",
                           xlabel = "log_10(h)",
                           ylabel = "log_10(Error)",
                           xlim = [floor(minimum(log10.(hlist))), ceil(maximum(log10.(hlist)))],
                           ylim = [floor(minimum(log10.([linorm; l2norm]))), ceil(maximum(log10.([linorm; l2norm])))],
                           color = :blue)

            lineplot!(plt, log10.(hlist), log10.(l2norm), name = "mean L2 norm", color = :red)
            println(plt)
            @test reverse(linorm) == sort(linorm) #test for decreasing error with decreasing h
            @test reverse(l2norm) == sort(l2norm)
        end

        @testset "Gaussian Factor 3D" begin
            hlist = [1/10, 1/20, 1/40, 1/80]#, 1/160, 1/320]
            linorm = zeros(length(hlist))
            l2norm = zeros(length(hlist))
            for i = 1:length(hlist)
                (x0, k2, t_exact) = gaussian_factor_3D(hlist[i])
                (t_pred, ordering) = FEFMM.fefmm(k2, [hlist[i], hlist[i], hlist[i]], x0)
                err = t_exact.-t_pred
                linorm[i] = norm(err, Inf)
                l2norm[i] = norm(err, 2)/sqrt(length(err))
            end

            plt = lineplot(log10.(hlist),
                           log10.(linorm),
                           title = "Gaussian Factor 3D",
                           name = "L_inf norm",
                           xlabel = "log_10(h)",
                           ylabel = "log_10(Error)",
                           xlim = [floor(minimum(log10.(hlist))), ceil(maximum(log10.(hlist)))],
                           ylim = [floor(minimum(log10.([linorm; l2norm]))), ceil(maximum(log10.([linorm; l2norm])))],
                           color = :blue)

            lineplot!(plt, log10.(hlist), log10.(l2norm), name = "mean L2 norm", color = :red)
            println(plt)
            @test reverse(linorm) == sort(linorm) #test for decreasing error with decreasing h
            @test reverse(l2norm) == sort(l2norm)
        end

    end
end


function particle_in_box(b, n::Int)
    L = get_length(b)
    N = sqrt(2/L)
    a = n * π / L
    #return N .* sin.(a .* (b .+ 0.5*L) )
    return N .* sin.(a .* (b .- b[begin]) )
end

function initial_orbitals(b::AbstractBasis)
    l = length(b)
    ψ = zeros(l,l)
    for j in 1:l-2 # end points are zero so -2
        ψ[:,j] = particle_in_box(b, j)
    end
    ψ[end,:] .= 0  # Improve accuracy at the end
    # Add nonzero end functions
    ψ[1,end-1] = 1
    ψ[end,end] = 2
    w = get_weight(b)
    return gram_schmit(ψ, w)
end

function gram_schmit(orbitals::AbstractMatrix, w::AbstractVector)
    out = similar(orbitals)
    norm = sqrt( sum( w .* orbitals[:,1].^2 ) )
    out[:,1] = orbitals[:,1] ./ norm
    for i in 2:size(out,2)
        tmp = orbitals[:,i]
        for j in 1:i-1
           tmp -= sum( out[:,j] .* w .* orbitals[:,i] ) .* out[:,j]
        end
        norm = sqrt( sum( w .* tmp.^2 ) )
        out[:,i] = tmp ./ norm
    end
    return out
end

"""
    solve_hartree_fock(b::AbstractBasis, Vn; Ve=x->exp(-x^2), rtol=1E-9, max_iter=100)

Solve Hartree Fock equations for two electrons for given nuclear potential `Vn`.

Electron-electron repulsion is fixed to exp(-Δx^2).

# Returns
- `E`         :  Energy of electronic system
- `Eorb`      :  Orbital energy
- `orbitals`  :  Orbitals in collumns
"""
function solve_hartree_fock(b::AbstractBasis, Vn ; Ve=x->exp(-x^2), rtol=1E-9, max_iter=100)
    # Solve FC = SCε for two electrons
    orbitals = initial_orbitals(b)
    F = fock_matrix(b, orbitals; Vn=Vn)
    h₁ = one_electron_operator(b, Vn)

    # We have orthogonal, but not orthonormal basis
    X = Diagonal( 1.0 ./ sqrt.( get_weight(b) ) )

    # Energy is h₁ + F (=2h₁+J) for two electrons
    E₀ = orbitals[:,1]' * ( h₁ + F ) * orbitals[:,1]

    @info "Energy at start = $(E₀)"
    eo, orbitals = eigen( X * F * X )
    orbitals = X * orbitals
    F = fock_matrix(b, orbitals; Vn=Vn)
    for i in 1:max_iter
        E₁ = orbitals[:,1]' * ( h₁ + F ) * orbitals[:,1]
        @info "Energy at iteration $i = $(E₁)"
        if abs(E₁-E₀) < rtol
            break
        else
            E₀ = E₁
            eo, orbitals = eigen( X * F * X )
            orbitals = X * orbitals
            F = fock_matrix(b, orbitals; Vn=Vn)
        end
    end
    E = orbitals[:,1]' * ( h₁ + F ) * orbitals[:,1]
    @info "Final energy = $(E)"
    return E, eo, orbitals
end


module TestLTSA
    using ManifoldLearning
    using Test

    @testset "LTSA" begin

	k = 12
	d = 2
	X, L = swiss_roll()
	Y = transform(LTSA, X; k=k, d=d)

	@test outdim(Y) == d
	@test size(projection(Y), 2) == size(X, 2)
	@test neighbors(Y) == k
	@test length(eigvals(Y)) == d
    end
end


using Flux, FluxTools

# Explicit model definition
m0 = Model((Dense(3, 5), Dense(5, 4), Dense(3, 1))) do x, (l1, l2, l3)
    x = l1(x)
    x = l2(x)
    return l3(x[1:3, :])
end

m0(rand(3, 12))

@show m0.layers

# Model definition via macro. As many layers as there are `$` signs.
m1 = @model x begin
    y = $(Dense(2, 3))(x)
    z = y .* 1.2
    return $(Dense(3, 4))(abs.(z))
end

m1(rand(2, 10))

@show m1.layers

# Nested example

m2 = @model x begin
    y = $m1(x)
    return softmax($(Dense(4, 2))(y))
end

m2(rand(2, 10))

@show length(params(m2)) == length(params(m1)) + length(params(Dense(4, 2)))


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    Sphere(center, radius)

A sphere with `center` and `radius`.
"""
struct Sphere{Dim,T} <: Primitive{Dim,T}
  center::Point{Dim,T}
  radius::T
end

Sphere(center::Tuple, radius) = Sphere(Point(center), radius)

paramdim(::Type{<:Sphere{Dim}}) where {Dim} = Dim - 1

center(s::Sphere) = s.center
radius(s::Sphere) = s.radius

# https://en.wikipedia.org/wiki/N-sphere#Volume_and_surface_area
function measure(s::Sphere{Dim}) where {Dim}
  r, n = s.radius, Dim
  2π^(n/2)*r^(n-1) / gamma(n/2)
end

function Base.in(p::Point, s::Sphere)
  x = coordinates(p)
  c = coordinates(s.center)
  r = s.radius
  sum(abs2, x - c) ≈ r^2
end


function spharm( l, m, Gvec )

  Ng = size(Gvec)[2]

  const SMALL = 1e-9

  cost = zeros(Float64, Ng)
  sint = zeros(Float64, Ng)
  phi  = zeros(Float64, Ng)

  for ig = 1:Ng
    gmod = sqrt( Gvec[1,ig]^2 + Gvec[2,ig]^2 + Gvec[3,ig]^2 )
    if gmod < SMALL
       cost[ig] = 0.0
    else
       cost[ig] = Gvec[3,ig]/gmod
    end
    #
    #  beware the arc tan, it is defined modulo pi
    #
    if Gvec[1,ig] > SMALL
      phi[ig] = atan( Gvec[2,ig]/Gvec[1,ig] )
    elseif Gvec[1,ig] < -SMALL
      phi[ig] = atan( Gvec[2,ig]/g[1,ig] ) + pi
    else
      phi[ig] = if Gvec[2,ig] >= 0 pi/2 else -pi/2 end
    end
    sint[ig] = sqrt( max( 0.0, 1-cost[ig]^2 ) )
  end

  ylm = zeros( Float64, Ng )

  if l == 0
    for ig = 1:Ng
      ylm[ig] = 0.5*sqrt(1.0/pi)
    end
    return ylm

  elseif l == 1
    # py
    if m == -1
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*sint[ig]*sin(phi[ig])
      end
      return ylm
    # pz
    elseif m == 0
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*cost[ig]
      end
      return ylm
    # px
    elseif m == 1
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*sint[ig]*cos(phi[ig])
      end
      return ylm
    end

  elseif l == 2
    # dxy
    if m == -2
      for ig = 1:Ng
        #ylm[ig] = sqrt(oppi*15/16)*sin(phi[ig])^2*sin(2*phi[ig])
        ylm[ig] = sqrt(15/16/pi)*sint[ig]^2*sin(2*phi[ig])
      end
      return ylm
    # dyz
    elseif m == -1
      for ig = 1:Ng
        #ylm[ig] = sqrt(oppi*15/4)*cost[ig]*sint[ig]*sin(phi[ig])
        ylm[ig] = sqrt(15/(4*pi))*cost[ig]*sint[ig]*sin(phi[ig])
      end
      return ylm
    # dz2
    elseif m == 0
      for ig = 1:Ng
        #ylm[ig] = sqrt(oppi*5/8)*( 2*cost[ig]^2 - sin(phi[ig])^2 )
        ylm[ig] = 0.25*sqrt(5/pi)*( 3*cost[ig]^2 - 1 )
      end
      return ylm
    # dxz
    elseif m == 1
      for ig = 1:Ng
        #ylm[ig] = sqrt(oppi*15/4)*cost[ig]*sint[ig]*cos(phi[ig])
        ylm[ig] = sqrt(15/4/pi)*cost[ig]*sint[ig]*cos(phi[ig])
      end
      return ylm
    # dx2-y2
    elseif m == 2
      for ig = 1:Ng
        #ylm[ig] = sqrt(oppi*15/16)*sin(phi[ig])^2*cos(2*phi[ig])
        ylm[ig] = 0.5*sqrt(15/4/pi)*sint[ig]^2*cos(2*phi[ig])
      end
      return ylm

    end

  end

end


using LinearAlgebra
using Random
using Distributions

# plot features require Gadfly, etc
using ApproxManifoldProducts

# more packages required
using TransformUtils

const LinAlg = LinearAlgebra
const KDE = KernelDensityEstimate
const TU = TransformUtils
const AMP = ApproxManifoldProducts

# temporarily required for experimental versions of on-manifold products
KDE.setForceEvalDirect!(true)

## create two densities

# two densities on a cylinder
p = manikde!(randn(2,100), (:Euclid, :Circular) )

pts2a = 3.0*randn(1,100).+5.0
pts2b = TransformUtils.wrapRad.(0.5*randn(1,100).+pi)
q = manikde!([pts2a;pts2b], (:Euclid, :Circular) )

# approximate the product between hybrid manifold densities
pq = manifoldProduct([p;q], (:Euclid, :Circular), Niter=2)
# getBW(pq)[:,1]

## plotting with Makie

using AbstractPlotting, FileIO
using Makie

N = 200
x = LinRange(-7.5, 7.5, N)
y = LinRange(-pi, pi, N)
zp = zeros(N,N)
zq = zeros(N,N)
zpq = zeros(N,N)

pt = zeros(2,1)
for i in 1:length(x), j in 1:length(y)
  pt[1,1] = x[i]
  pt[2,1] = y[j]
  zp[i,j] = p(pt,false, 1e-3, (+, addtheta), (-,difftheta))[1]
  zq[i,j] = q(pt,false, 1e-3, (+, addtheta), (-,difftheta))[1]
  zpq[i,j] = pq(pt,false, 1e-3, (+, addtheta), (-,difftheta))[1]
end

## Compound plot

scene = hbox(
        # contour(x, y, z, levels = 20, linewidth =3),
        contour(x, y, zpq, levels = 0, linewidth = 0, fillrange = true),
        contour(x, y, zq, levels = 0, linewidth = 0, fillrange = true),
        contour(x, y, zp, levels = 0, linewidth = 0, fillrange = true),
)

## simpler plot

scene = contour(x, y, zpq, levels = 0, linewidth = 0, fillrange = true)

# save("/tmp/test.png", scene)

## better wrap around

pts1a = 6.0*rand(1,100).-3.0
pts1b = TransformUtils.wrapRad.(0.3*randn(1,100).-0.6*pi)
p = manikde!([pts1a;pts1b], (:Euclid, :Circular) )

pts2a = 6.0*rand(1,100).-3.0
pts2b = TransformUtils.wrapRad.(0.3*randn(1,100).+0.6*pi)
q = manikde!([pts2a;pts2b], (:Euclid, :Circular) )

# approximate the product between hybrid manifold densities
pq = manifoldProduct([p;q], (:Euclid, :Circular), Niter=3)

N = 200
x = LinRange(-7.5, 7.5, N)
y = LinRange(-pi, pi, N)
zp = zeros(N,N)
zq = zeros(N,N)
zpq = zeros(N,N)

pt = zeros(2,1)
for i in 1:length(x), j in 1:length(y)
  pt[1,1] = x[i]
  pt[2,1] = y[j]
  zp[i,j] = p(pt,false, 1e-3, (+, addtheta), (-,difftheta))[1]
  zq[i,j] = q(pt,false, 1e-3, (+, addtheta), (-,difftheta))[1]
  zpq[i,j] = pq(pt,false, 1e-3, (+, addtheta), (-,difftheta))[1]
end

scene = hbox(
  # contour(x, y, z, levels = 20, linewidth =3),
  contour(x, y, zpq, levels = 0, linewidth = 0, fillrange = true),
  contour(x, y, zq, levels = 0, linewidth = 0, fillrange = true),
  contour(x, y, zp, levels = 0, linewidth = 0, fillrange = true),
)

## Save scene to file

# using AbstractPlotting, CairoMakie, FileIO
# AbstractPlotting.current_backend[] = CairoMakie.CairoBackend("/tmp/test.svg")
# open("/tmp/test.svg","w") do io
#   show(io, MIME"image/svg+xml"(), scene)
# end

#


const Q = [
[16 11 10 16 24 40 51 61];
[12 12 14 19 26 58 60 55];
[14 13 16 24 40 57 69 56];
[14 17 22 29 51 87 80 62];
[18 22 37 56 68 109 103 77];
[24 35 55 64 81 104 113 92];
[49 64 78 87 103 121 120 101];
[72 92 95 98 112 100 103 99];
]

function quantize(x::Matrix{Float32})::Matrix{Int}
    return round.(x ./ Q)
end

function unquantize(x::Matrix{Int})::Matrix{Float32}
    return x .* Q
end

function unquantize(x::Matrix{Float64})::Matrix{Float32}
    return unquantize(Int.(round.(x)))
end


function create_model(connectivity)
  N = size(connectivity, 1) # Number of nodes
  E = count(!iszero, connectivity)# Number of edges
  @parameters α[1:N], β[1:N], γ[1:E], κ[1:E], η[1:E]
  @variables t, a[1:N](t)
  rxs = Reaction[]
  for i=1:size(connectivity, 1)
    push!(rxs, Reaction(β[i]*α[i], nothing, [a[i]]))
    push!(rxs, Reaction(β[i], [a[i]], nothing))
  end
  e = 1 # Edge count
  for (i, row) in enumerate(eachrow(connectivity))
    for (j, val) in enumerate(row)
      if val == -1
        push!(rxs, Reaction(hill(abs(a[j]),γ[e],κ[e],η[e]), [a[i]], nothing))
        e += 1
      elseif val == 1
        push!(rxs, Reaction((1.0 - a[i]) * hill(abs(a[j]),γ[e],κ[e],η[e]), nothing, [a[i]]))
        e += 1
      end
    end
  end
  @named model = ReactionSystem(rxs, t)
  return model
end


# https://wickedengine.net/2020/04/26/capsule-collision-detection/

p1 = [0.0, 0.0, 0.0]
o1 = one(Quaternion)
h1 = 1.0
r1 = 0.1

p2 = [1.0, 0.0, 0.0]
o2 = one(Quaternion)
# o2 = Quaternion(RotZ(0.41) * RotY(-0.3 * π))
h2 = 1.0
r2 = 0.1

function get_tip_base(position, orientation, height, radius)
    k = [0.0, 0.0, 0.5 * height + radius]
    tip = position + Array(vector_rotate(k, orientation))
    base = position + Array(vector_rotate(-k, orientation))
    return tip, base
end

t1, b1 = get_tip_base(p1, o1, h1, r1)
t2, b2 = get_tip_base(p2, o2, h2, r2)

function closest_point_on_line(pa::Vector{T}, pb::Vector{T}, p::Vector{T}) where T
    pab = pb - pa
    t = dot(p - pa, pab) / dot(pab, pab)
    return pa + min(max(t, 0.0), 1.0) * pab
end

function closest_segment_points_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)
    # capsule a
    a_tip, a_base = get_tip_base(xa, qa, ha, ra)
    a_radius = ra

    # capsule b
    b_tip, b_base = get_tip_base(xb, qb, hb, rb)
    b_radius = rb

    # capsule a
    a_normal = a_tip - a_base
    a_normal ./= norm(a_normal)
    a_line_end_offset = a_normal * a_radius
    a_A = a_base + a_line_end_offset
    a_B = a_tip - a_line_end_offset

    # capsule b
    b_normal = b_tip - b_base
    b_normal ./= norm(b_normal)
    b_line_end_offset = b_normal * b_radius
    b_A = b_base + b_line_end_offset
    b_B = b_tip - b_line_end_offset

    # vectors between line endpoints
    v0 = b_A - a_A
    v1 = b_B - a_A
    v2 = b_A - a_B
    v3 = b_B - a_B

    # squared distances
    d0 = dot(v0, v0)
    d1 = dot(v1, v1)
    d2 = dot(v2, v2)
    d3 = dot(v3, v3)

    # best potential endpoint on capsule a
    if (d2 < d0 || d2 < d1 || d3 < d0 || d3 < d1)
        bestA = a_B
    else
        bestA = a_A
    end

    # select point on capsule B line segment nearest best potential endpoint on A capsule
    bestB = closest_point_on_line(b_A, b_B, bestA)

    # same for capsule A segment
    bestA = closest_point_on_line(a_A, a_B, bestB)

    return bestA, bestB
end

function kinematics_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)
    bestA, bestB = closest_segment_points_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)

    kA = vector_rotate(bestA - xa, inv(qa))
    kB = vector_rotate(bestB - xb, inv(qb))

    return kA, kB
end

ka, kb = kinematics_capsule_capsule(p1, o1, h1, r1, p2, o2, h2, r2)

function closest_points_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)
    bestA, bestB = closest_segment_points_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)

    direction = bestB - bestA
    direction_magnitude = norm(direction)
    direction ./= direction_magnitude

    c1 = bestA + direction * ra
    c2 = bestB - direction * rb
    return c1, c2
end

function direction_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)
    ca, cb = closest_segment_points_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)

    direction = cb - ca
    direction_magnitude = norm(direction)

    if direction_magnitude > 0.0
        direction ./= direction_magnitude
    else
        direction = [1.0, 0.0, 0.0]
    end

    return direction
end

function sdf_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)
    ca, cb = closest_points_capsule_capsule(xa, qa, ha, ra, xb, qb, hb, rb)

    direction = cb - ca
    direction_magnitude = norm(direction)

    return direction_magnitude
end

c1, c2 = closest_points_capsule_capsule(p1, o1, h1, r1, p2, o2, h2, r2)
sdf_capsule_capsule(p1, o1, h1, r1, p2, o2, h2, r2)

# visualize
vis = Visualizer()
open(vis)

# capsule a
cyl1 = GeometryBasics.Cylinder(Point(0.0, 0.0, -0.5 * h1), Point(0.0, 0.0, 0.5 * h1), r1)
sph1 = GeometryBasics.Sphere(Point(0.0, 0.0, 0.0), r1)
color1 = Colors.RGBA(0.7, 0.7, 0.7, 0.5);
setobject!(vis[:cap1][:cyl], cyl1, MeshPhongMaterial(color=color1))
setobject!(vis[:cap1][:base], sph1, MeshPhongMaterial(color=color1))
settransform!(vis[:cap1][:base], Translation(0.0, 0.0, -0.5 * h1))
setobject!(vis[:cap1][:tip], sph1, MeshPhongMaterial(color=color1))
settransform!(vis[:cap1][:tip], Translation(0.0, 0.0, 0.5 * h1))

# capsule b
cyl2 = GeometryBasics.Cylinder(Point(0.0, 0.0, -0.5 * h2), Point(0.0, 0.0, 0.5 * h2), r2)
sph2 = GeometryBasics.Sphere(Point(0.0, 0.0, 0.0), r2)
color2 = Colors.RGBA(0.7, 0.7, 0.7, 0.5);
setobject!(vis[:cap2][:cyl], cyl2, MeshPhongMaterial(color=color2))
setobject!(vis[:cap2][:base], sph2, MeshPhongMaterial(color=color2))
settransform!(vis[:cap2][:base], Translation(0.0, 0.0, -0.5 * h2))
setobject!(vis[:cap2][:tip], sph2, MeshPhongMaterial(color=color2))
settransform!(vis[:cap2][:tip], Translation(0.0, 0.0, 0.5 * h2))

# set configuration
settransform!(vis[:cap1], compose(Translation(p1), LinearMap(o1)))
settransform!(vis[:cap2], compose(Translation(p2), LinearMap(o2)))

# closest points
color_cp = Colors.RGBA(0.0, 0.0, 0.0, 1.0);
cs1 = GeometryBasics.Sphere(Point(0.0, 0.0, 0.0), 0.025)
cs2 = GeometryBasics.Sphere(Point(0.0, 0.0, 0.0), 0.025)
setobject!(vis[:cs1], cs1, MeshPhongMaterial(color=color_cp))
setobject!(vis[:cs2], cs2, MeshPhongMaterial(color=color_cp))
settransform!(vis[:cs1], Translation(c1))
settransform!(vis[:cs2], Translation(c2))


using Turing
using Test

obs = [0,1,0,1,1,1,1,1,1,1]

@model constrained_test(obs) = begin
  p ~ Beta(2,2)
  for i = 1:length(obs)
    obs[i] ~ Bernoulli(p)
  end
  p
end

chain = sample(constrained_test(obs), HMC(1000, 1.5, 3)) # using a large step size (1.5)

check_numerical(chain, [:p], [10/14], eps=0.1)


using LazySets, MathematicalPredicates, SparseArrays

function mna1_specification()
    # initial set: xᵢ ∈ [0.001, 0.0015] if i ≤ 2 and xᵢ = 0 otherwise
    X0 = Hyperrectangle(low=[fill(0.001, 2); zeros(576)],
                        high=[fill(0.0015, 2); zeros(576)])

    # safety property: x1 ≤ 0.5
    property = is_contained_in(HalfSpace(sparsevec([1], [1.0], 578), 0.5))

    # time horizon: 20 time units
    time_horizon = 20.0

    # specification
    O = Dict(:T => time_horizon, :property => property)

    return X0, O
end


function inv(m,n)
    a = m
    b = n
    u_a = 1
    v_b = 1
    u_b = 0
    v_a = 0

    if NOD(a,b)!=1
        return Nothing
    end

    while b != 0
        k, r = divrem(a,b)
        a, b = b, r
        u, v = u_a, v_a
        u_a, v_a = u_b, u_a
        u_b, v_b = u-k*u_b, v-k*v_b
    end
    while u_a<0
        u_a+=n
    end
    return u_a
end

function NOD(a,b)
    m = a
    n = b
    while n != 0
        m, n = n, m % n
    end
    return m
end

Base.inv(a::ResidueRing{m}) where m = ResidueRing{m}.inv(a,m)


using ArnoldiMethod
using ArnoldiMethodTransformations
using LinearAlgebra
using SparseArrays
using Statistics
using Test

N = 20
# construct fixed eval matrix in random basis
D = diagm(0=>0:(N-1))

@testset "ordinary eigenvalue problem: " begin
    for i ∈ 1:500
        S = I + .1*randn(N,N) # random basis

        A = S\D*S
        # find eigenpairs closest to 5.001 (cannot be 5 as algorithm is unstable if σ is exactly an eval)
        decomp, hist = partialschur(A,5.001)
        # get evecs
        λ, v = partialeigen(decomp,5.001)
        @test norm(A*v-v*diagm(0=>λ))/prod(size(v)) ≤ sqrt(eps(float(N)))N

        As = sparse(A)
        # find eigenpairs closest to 5.001 (cannot be 5 as algorithm is unstable if σ is exactly an eval)
        decomp, hist = partialschur(As,5.001)
        # get evecs
        λ, v = partialeigen(decomp,5.001)
        @test norm(A*v-v*diagm(0=>λ))/prod(size(v)) ≤ sqrt(eps(float(N)))N
    end
end


"""
predict_links(S::Int64, n::Int64, chain)
    S: number of species
    n: number of links that will be predicted
    chain: Monte-Carlo chain of the flexible links model fitted to empirical data
Returns n values of predicted numbers of links
"""
function predict_links(S::Int64, n::Int64, chain)
    predicted_links = zeros(Int64, n) # create vector for the predicted links
    p = get_params(chain[200:end,:,:]) # get fitted parameters of the flexible links model
    # simulate n number of links
    for i in 1:n
        # select parameter values randomly
        p_i = rand(1:length(p.μ))
        μ, ϕ = p.μ[p_i], p.ϕ[p_i]
        # predict number of links
        predicted_links[i] = rand(BetaBinomial(S^2-(S-1), μ*exp(ϕ), (1-μ)*exp(ϕ))) + (S-1)
    end
    return predicted_links
  end


using Iscalc
using Plots
#plotlyjs()
#gr()
plotly()

#Svensson = JuliaDB.load("C:/Users/António/Desktop/BaseDados/Svensson_2018-05-26.jdb")
Svensson = obter(:NSS, inicio = "01-01-2000", fim = "28-05-2018")
show(IOContext(STDOUT, limit=true), "text/plain", Svensson)

DataReferencia = Date("2018-05-28")

Portfolio = ["PTOTESOE0013"  "2022-10-17"    0.02200  	 107.63;
             "PTOTEKOE0011"	 "2025-10-15"	 0.02875	 111.03;
             "PTOTETOE0012"	 "2026-07-21"	 0.02875	 109.56;
             "PTOTEUOE0019"	 "2027-04-14"	 0.04125	 118.90;
             "PTOTEROE0014"	 "2030-02-15"	 0.03875	 117.60]

# Juros Corridos das Obrigações
JC = JCorridos(Portfolio,DataReferencia)
println(JC)

# Tenores e Respectivos Fluxos de Caixa
TFC = FluxosCaixa(Portfolio,DataReferencia)
println(TFC)

Tenores = column(TFC,:Tenores)
FC = column(TFC,:FluxosCaixa)

# Estrutura Temporal Taxas de Juro: Curva Spot

FS = fspot(Svensson, Tenores)
printm(FS)

# Gráfico da ETTJ para a data mais recente
plot(Tenores,FS[1,2:end], ylims = (-1,1), ylabel="ETTJ Taxas Spot", xlims = (0, 12.5), xlabel = "Tenores", label="$(FS[1,1])")
gui()

# Estrutura Temporal Taxas de Juro: Função Desconto
FD = fdesconto(Svensson, Tenores)
printm(FD)

#VPortfolio = table(FD[:,1], FD[:,2:(length(Tenores)+1)] * FC, names=[:Data,:Vp])

VPortfolio = hcat(FD[:,1], FD[:,2:end] * FC)
printm(VPortfolio)

c = VPortfolio[:,2] .-lag(VPortfolio[:,2],-1)

media = mean(c[1:end-1])
quantil05 = quantile(convert(Array{Float64},c[1:end-1]), 0.05)

c = hcat(FD[:,1],c)
printm(c)

VaR_05 = media - quantil05
println("VaR_5% = ", VaR_05)


module MMS_Example

using EventSimulation
using Random
using Test
using Future: randjump

# Objectives of the example:
# * show how SimQueue and SimResource objects can be used
# * show that SimQueue gives a fine grained control than SimResource
#   but is more expensive (actually SimResource use is a hack)
# * show how MersenneTwister with randjump can be used to control RNG streams

# exact formula for M/M/s queue
function run_mms_exact(ar, sr, s)
    ρ = ar/sr
    P₀= 1 / (ρ^s/(factorial(s)*(1-ρ/s)) +
             sum(ρ^n/factorial(n) for n in 0:(s-1)))
    (ρ+(ρ^(s+1)/(factorial(s-1)*(s-ρ)^2))*P₀)/ar
end

# hardcoded M/M/1 queue
function run_mm1_fast(until, ar, sr, seed)
    start_time = time_ns()
    ma = MersenneTwister(seed)
    ms = randjump(ma, big(10)^20)
    queue = Vector{Float64}()
    nextArrival = randexp(ma) / ar
    nextDeparture = Inf
    totalWait = 0.0
    totalCount = 0
    now = 0.0
    msg = String[]
    while now < until
        if nextArrival < nextDeparture
            now = nextArrival
            if now <= until
                push!(msg, "A $now")
            end
            if isempty(queue)
                nextDeparture = nextArrival + randexp(ms) / sr
            end
            push!(queue, nextArrival)
            nextArrival += randexp(ma) / ar
        else
            now = nextDeparture
            if now <= until
                push!(msg, "D $now")
                totalWait += nextDeparture - popfirst!(queue)
                totalCount += 1
            end
            if isempty(queue)
                nextDeparture = Inf
            else
                nextDeparture += randexp(ms) / sr
            end
        end
    end
    println("MM1 fast time: ", (time_ns()-start_time)/10^9)
    return totalWait/totalCount, totalCount, msg
end

# Implementation of M/M/s queue using SimQueue
mutable struct StateQ <: AbstractState
    ar::Float64
    sr::Float64
    q::SimQueue{Float64}
    tcust::Int
    tin::Float64
    ma::MersenneTwister
    ms::MersenneTwister
    msg::Vector{String}
    function StateQ(ar, sr)
        ma = MersenneTwister(1)
        ms = randjump(ma, big(10)^20)
        new(Float64(ar), Float64(sr), SimQueue{Float64}(),
            0, 0.0, ma, ms, String[])
    end
end

function arrivalQ!(s::Scheduler)
    push!(s.state.msg, "A $(s.now)")
    provide!(s, s.state.q, s.now)
end

function startserviceQ!(s::Scheduler, a::Float64)
    register!(s, x -> endserviceQ!(x, a) , randexp(s.state.ms) / s.state.sr)
end

function endserviceQ!(s::Scheduler, a::Float64)
    s.state.tcust += 1
    s.state.tin += s.now - a
    push!(s.state.msg, "D $(s.now)")
    request!(s, s.state.q, startserviceQ!)
end

function run_mms_queue(until, ar, sr, count)
    start_time = time_ns()
    s = Scheduler(StateQ(ar, sr))
    repeat_register!(s, arrivalQ!, x -> randexp(x.state.ma) / x.state.ar)
    for i in 1:count
        request!(s, s.state.q, startserviceQ!)
    end
    go!(s, until)
    println("MM$count queue time: ", (time_ns() - start_time)/10^9)
    return s.state.tin/s.state.tcust, s.state.tcust, s.state.msg
end

# Implementation of M/M/s queue using SimResource
# Notice that if s>1 we have no control of arrival vs departure order
mutable struct StateR <: AbstractState
    ar::Float64
    sr::Float64
    r::SimResource{Int}
    ars::Vector{Float64}
    tcust::Int
    tin::Float64
    ma::MersenneTwister
    ms::MersenneTwister
    msg::Vector{String}
    function StateR(ar, sr)
        ma = MersenneTwister(1)
        ms = randjump(ma, big(10)^20)
        new(Float64(ar), Float64(sr), SimResource{Int}(),
            Vector{Float64}(), 0, 0.0, ma, ms, String[])
    end
end

function arrivalR!(s::Scheduler)
    register!(s, arrivalR!, randexp(s.state.ma) / s.state.ar)
    push!(s.state.ars, s.now)
    push!(s.state.msg, "A $(s.now)")
    provide!(s, s.state.r, 1)
end

function startserviceR!(s::Scheduler)
    register!(s, endserviceR!, randexp(s.state.ms) / s.state.sr)
end

function endserviceR!(s::Scheduler)
    s.state.tcust += 1
    # use s.state.tin only for count==1, otherwise it is inexact
    # as customer ordering migh have changed during the service
    s.state.tin += s.now - popfirst!(s.state.ars)
    push!(s.state.msg, "D $(s.now)")
    request!(s, s.state.r, 1, startserviceR!)
end

function run_mms_resource(until, ar, sr, count)
    start_time = time_ns()
    s = Scheduler(StateR(ar, sr))
    register!(s, arrivalR!, randexp(s.state.ma) / s.state.ar)
    for i in 1:count
        request!(s, s.state.r, 1, startserviceR!)
    end
    go!(s, until)
    println("MM$count resource time: ", (time_ns()-start_time)/10^9)
    # have to handle shortcomming of resource
    return (count == 1 ? s.state.tin/s.state.tcust : NaN,
            s.state.tcust, s.state.msg)
end

@testset "Test of M/M/1 queue" begin
    t_f = run_mm1_fast(100_000, 0.8, 1.4, 1)
    t_q = run_mms_queue(100_000, 0.8, 1.4, 1)
    t_r = run_mms_resource(100_000, 0.8, 1.4, 1)
    t_x = run_mms_exact(0.8, 1.4, 1)

    @test t_f == t_q == t_r
    @test isapprox(t_x, t_r[1], atol=0.1)
end

@testset "Test of M/M/2 queue" begin
    t2_q = run_mms_queue(100_000, 0.8, 0.7, 2)
    t2_r = run_mms_resource(100_000, 0.8, 0.7, 2)
    t2_x = run_mms_exact(0.8, 0.7, 2)

    @test t2_q[2:3] == t2_r[2:3]
    @test isapprox(t2_x, t2_q[1], atol=0.1)
end

@testset "Test of M/M/5 queue" begin
    t5_q = run_mms_queue(100_000, 0.8, 0.2, 5)
    t5_r = run_mms_resource(100_000, 0.8, 0.2, 5)
    t5_x = run_mms_exact(0.8, 0.2, 5)

    @test t5_q[2:3] == t5_r[2:3]
    @test isapprox(t5_x, t5_q[1], atol=0.2)
end

end # module


function expand(v1,v2)
    v = vec([])
    for i in 1:size(v1)[1]
        for j in 1:size(v2)[1]
            push!(v, v1[i]*v2[j])
        end
    end
    return v
end

function calc(x)
    v = vec([])
    fin = vec([])
    for i in 1:size(x)[1]
        push!(v,join(sort(collect(x[i]))))
    end
    t = Set(v)
    temp = 0
    for j in t
        temp = count(i->(i == j),v)
        if temp != 1
            push!(fin,string(temp)*j)
        else
            push!(fin,j)
        end
    end
    return fin
end

function poly_expand(m,n)
    alpha = "abcdefghijklmnopqrstuvwxyz"
    x = vec([])
    for i in 1:m
        push!(x,alpha[i])
    end
    t = copy(x)
    for i in 1:(n-1)
        t = copy(expand(x,t))
    end
    return calc(t)
end


export LB1, LB2, LB3, LB4, LB5, LB6

"""
    compute_bound(LB, fpath)

Compute bound `LB` for the instance-file fpath
"""
function compute_bound(LB, fpath)
    i = read(fpath, Instance)
    return LB(i)
end

"""
    compute_bounds(boundset, fpath)

Computes the bounds in `boundset` for the instance-file `fpath` and returns a dictionary with `boundname => value` pairs.
"""
function compute_bounds(boundset, fpath)
    i = read(fpath, Instance)
    d = Dict{String, Int}()
    for lb in boundset
        v = lb(i)
        d[string(lb)] = v
    end
    return d
end

"""
    LB1(jobs)

Calculate a lower bound on the truck waiting time:

The sum of the processing times of truck-related jobs
"""
LB1(jobs) = sum(t_processing, Iterators.filter(istruck, jobs))

LB1(i::Instance) = LB1(jobs(i))

"""
    LB2(jobs)

Calculate a lower bound on the makespan:

The maximum of the minimum completion times of all jobs.
"""
LB2(jobs) = maximum(j -> t_arrival(j) + t_processing(j), jobs)

LB2(i::Instance) = LB2(jobs(i))

"""
    LB3(jobs, ncranes)

Calculate a lower bound on the makespan:

Balancing total processing time over cranes
"""
LB3(jobs, ncranes) = Int(ceil(sum(t_processing, jobs) / ncranes ))

LB3(i::Instance) = LB3(jobs(i), ncranes(i))

"""
    LB4(jobs, l0)

Calculate a lower bound on the makespan:

# TODO: Implement bound
"""

LB4(jobs, l0) = LB3 + minimaltravel(jobs, l0)

LB4(i::Instance) = LB4(jobs(i), crane_starting_pos(i))

"""
    LB5(jobs)

Calculate a lower bound on the truck waiting time:

Compute the total truck waiting time _after_ taking into account job precedences. Analogous to LB1, but with precedences.
"""
function LB5(jobs)
    Ecompl = compute_earliest_completion_times(jobs)
    V = Iterators.filter(istruck, jobs)
    mapreduce(j -> Ecompl[id(j)] - t_arrival(j), +,  V)
end

LB5(i::Instance) = LB5(jobs(i))

"""
    LB6(jobs)

Calculate a lower bound on the makespan:

Compute the maximum earliest completion time of the jobs.
"""
function LB6(jobs)
    Ecompl = compute_earliest_completion_times(jobs)
    return maximum(Ecompl)
end

LB6(i::Instance) = LB6(jobs(i))

"""
    interlocdist(jobs)

Returns the sorted distances between unique job-locations
"""
function interlocdist(jobs)
    locsorted = sort!(unique(loc.(jobs)))
    return sort!(map(l -> -(l...), Iterators.zip(locsorted[1:end-1], locsorted[2:end])))
end

"""
    minimaltravel(jobs, ncranes)

    Calculate the minimal travel distance for ncranes to visit all positions
"""
function minimaltravel(jobs, l0)
    Djobs = interlocdist(jobs)

end

function compute_earliest_completion_times(jobs)
    A = t_arrival.(jobs)
    P = t_processing.(jobs)
    C = A .+ P

    pg = precedence_graph(jobs)
    ids = topological_sort_by_dfs(pg)
    for id in ids
        I = map(inn -> C[inn] ,inneighbors(pg, id))
        if !isempty(I)
            C[id] = max(maximum(I), A[id]) + P[id]
        end
    end
    return C
end

makespan_bound_functions = [LB2, LB3, LB4, LB6]
t_truckwaiting_bound_functions = [LB1, LB5]


using PolynomialBasis
using ImplicitDomainQuadrature
using Revise
using CutCell
include("../../test/useful_routines.jl")

function step_interface(levelset, levelsetcoeffs, mesh, levelsetspeed, dt)
    cutmesh = CutCell.CutMesh(levelset, levelsetcoeffs, mesh)
    paddedmesh = CutCell.BoundaryPaddedMesh(cutmesh, 1)
    refseedpoints, spatialseedpoints, seedcellids =
        CutCell.seed_zero_levelset(2, levelset, levelsetcoeffs, cutmesh)
    paddedlevelset = CutCell.BoundaryPaddedLevelset(
        paddedmesh,
        refseedpoints,
        spatialseedpoints,
        seedcellids,
        levelset,
        levelsetcoeffs,
        cutmesh,
        1e-10,
    )
    return CutCell.step_first_order_levelset(paddedlevelset,levelsetspeed,dt)
end

function grid_size(mesh)
    w = CutCell.widths(mesh)
    nn = CutCell.nodes_per_mesh_side(mesh)

    return w ./ (nn .- 1)
end

function time_step_size(levelsetspeed,mesh;CFL=0.5)
    dx = minimum(grid_size(mesh))
    s = maximum(abs.(levelsetspeed))
    return CFL*dx/s
end

function run_time_steps(levelset,initialcondition,mesh,levelsetspeed,dt,nsteps)
    levelsetcoeffs = [copy(initialcondition) for i = 1:nsteps+1]
    for i = 1:nsteps
        levelsetcoeffs[i+1] = step_interface(levelset,levelsetcoeffs[i],mesh,levelsetspeed,dt)
    end
    return levelsetcoeffs
end

x0 = [0.0, 0.0]
L, W = 1.0, 1.0
nelmts = 40
numghostlayers = 1
polyorder = 2

xc = [0.5,0.5]
radius = 0.5
speed = 1.0
stoptime = 0.4

basis = TensorProductBasis(2,polyorder)
mesh = CutCell.Mesh(x0,[L,W],[nelmts,nelmts],basis)

levelset = InterpolatingPolynomial(1, basis)
initiallevelset =
    CutCell.levelset_coefficients(x -> circle_distance_function(x,xc,radius), mesh)
levelsetspeed = speed*ones(length(initiallevelset))

dt = time_step_size(levelsetspeed,mesh)
@assert isinteger(stoptime/dt)
nsteps = ceil(Int,stoptime/dt)

levelsetcoeffs = run_time_steps(levelset,initiallevelset,mesh,levelsetspeed,dt,nsteps)

actualstoptime = dt*nsteps
finalradius = radius - speed*actualstoptime

function grid_range(mesh)
    x0 = CutCell.reference_corner(mesh)
    w = CutCell.widths(mesh)
    nfmside = CutCell.nodes_per_mesh_side(mesh)

    x = range(x0[1],stop=x0[1]+w[1],length=nfmside[1])
    y = range(x0[2],stop=x0[2]+w[2],length=nfmside[2])

    return x,y
end

rectangle(w, h, x, y) = Shape(x .+ [0,w,w,0], y .+ [0,0,h,h])

using Plots
x,y = grid_range(mesh)

# Z1 = reshape(levelsetcoeffs[1],length(y),:)
# Z2 = reshape(levelsetcoeffs[end],length(y),:)
# fig = plot(legend=false,aspect_ratio=:equal)
# plot!(fig,rectangle(L,W,x0[1],x0[2]),opacity=0.2,linewidth=2,strokecolor="black")
# contour!(fig,x,y,Z1,color="black",linewidth=2,levels=[0.0])
# contour!(fig,x,y,Z2,color="red",linewidth=2,levels=[0.0])

anim = @animate for i = 1:length(levelsetcoeffs)
    Z = reshape(levelsetcoeffs[i],length(y),:)
    fig = plot(legend=false,aspect_ratio=:equal)
    plot!(fig,rectangle(L,W,x0[1],x0[2]),opacity=0.2,linewidth=2,fillcolor="blue")
    contour!(fig,x,y,Z,levels=[0.0],color="red",linewidth=2)
end

gif(anim,fps=10)
gif(anim,"examples/levelset-propagate/shrinking-circle.gif",fps=5)


using Statistics, LinearAlgebra, SpatialJackknife, Test

"""
This function generates a set of points with random coordinates in the plane
between values of zero and one. Each point has an associated value drawn from
a standard normal distribution.
"""
function grf_2d(npoints::Int = 10000)::Array{Float64, 2}
    xyval = Array{Float64}(undef, npoints, 3)
    xyval[:, 1:2] = rand(npoints, 2)
    xyval[:, 3] = randn(npoints)
    xyval
end

"""
Here we generate points in known quadrants in 2d space so that we can test
if the correct subvolume indices are assigned by get_subvols()
"""
function fixed_quadrants_pts(quadpts::Int = 2500)::Tuple{Array{Float64, 2},
                                                         Array{Float64, 1},
                                                         Array{Int, 1}}
    npts = quadpts * 4
    pts = zeros(npts, 2)
    vals = zeros(npts)
    quads = ones(Int, npts)

    for i in 1:4
        xyvals = grf_2d(quadpts)

        xth = i > 2 ? 1 : 0
        yth = i % 2 == 0 ? 1 : 0

        pts[((i - 1) * quadpts + 1):(i * quadpts), 1] = xyvals[:, 1] .+ xth
        pts[((i - 1) * quadpts + 1):(i * quadpts), 2] = xyvals[:, 2] .+ yth

        vals[((i - 1) * quadpts + 1):(i * quadpts)] = xyvals[:, 3]
        quads[((i - 1) * quadpts + 1):(i * quadpts)] .= i
    end

    pts ./= 2

    pts, vals, quads
end

"""
This function uses the previous ones to generate points in a unit square and
tests if the random mask subvolume finder can allocate the correct quadrants.
"""
function test_2d_randmask_subvols()::Bool

    quadpts = 30
    nrands = quadpts * 4 * 3000

    datpts, datvals, true_quads = fixed_quadrants_pts(quadpts)
    randpts = rand(nrands, 2)

    subvols = get_subvols(datpts, randpts, 2)

    all(true_quads .== subvols)
end

"""
This function tests if the unit square points can be assigned to the correct
quadrants by the other method of get_subvol, which assumes a regular cube.
"""
function test_2d_cube_subvols()::Bool

    quadpts = 250
    datpts, datvals, true_quads = fixed_quadrants_pts(quadpts)

    subvols = get_subvols(datpts, side_divs = 2, edges = [[0.0, 1.0]])

    all(true_quads .== subvols)
end

"""
This tests whether the jackknife can find the mean and covariance of a set of
observables made from points distributed in a unit square. The points have
vector quantities associated with them distributed according to a mean and
covariance
"""
function test_meancovars(means::Array{Float64, 1},
                         covar::Array{Float64, 2},
                         npts::Int = 1000,
                         side_divs::Int = 3)::Tuple

    # get cholesky decomposition to transform data
    A = cholesky(covar)

    # generate datapoints
    ys = randn(npts, 2)
    zs = A.L * ys'
    zs = collect(zs')
    zs = zs .+ means'

    # get positions on square and compute subvols
    xs = rand(npts, 2)
    subvols = get_subvols(xs, side_divs = side_divs, edges = [[0.0, 1.0]])

    function compute_mean(dat)
        return [mean(dat[:, 1]), mean(dat[:, 2])]
    end

    function compute_covar(dat)
        ndims = size(dat)[2]
        return reshape(cov(dat), ndims^2)
    end

    # and compute estimates for mean and covariance
    outmeans, mean_error = jackknife(compute_mean, zs, subvols)
    outcovar, covar_error = jackknife(compute_covar, zs, subvols, covar = false)

    (outmeans, mean_error), (outcovar, covar_error)
end

@testset "Running tests" begin

    @test test_2d_randmask_subvols()
    @test test_2d_cube_subvols()

    testnpts = 1000
    side_divs = 5
    testmeans = [52.7, 79.3]
    testcovar = [15.1 4.9; 4.9 8.6]

    mean_errs, cov_errs = test_meancovars(testmeans, testcovar, testnpts, side_divs)

    testcovar = reshape(testcovar, 4)

    mean_bias = mean_errs[1] .- testmeans
    covar_bias = cov_errs[1] .- testcovar

    @test mean_errs[1] ≈ testmeans atol = maximum(sqrt.(diag(mean_errs[2]) + mean_bias .^ 2))
    @test cov_errs[1] ≈ testcovar atol = maximum(sqrt.(cov_errs[2] .+ covar_bias .^ 2))
end


using Images, FileIO, Colors, FixedPointNumbers, Statistics
# using ColorBrewer
#
# definition of system:
# x_{n+1} = sin(a y_n) + c cos(a x_n)
# y_{n+1} = sin(b x_n) + d cos(b y_n)

#size of the rendered canvas in pixels. it is always a square canvas
CANVAS_SIZE = 1500
#parameters governing the iterations
A=2.2
B=1.8
C=1.2
D=1.3
#number of points to render.
NUM_POINTS=Int(1e9)
#initial value
X_INIT=0
Y_INIT=0
#proportion of the canvas to be kept as a margin
MARGIN=0.025
#file path to save to
OUT_FILE_NAME="./image_6.jpg"
#occupancy root. smaller values lead to a more even image. higher values
#increase the rendered contrast. smaller values lead to an even image.
OCCUPANCY_ROOT = 1/4
#what percentile of the clifford attractor should not be completely saturated
#by color?
PERCENTILE_CLIP = 0.998
#what color should the attractor be? it will always be on a white background.
#you can invert it to black with the INVERSE parameter below.  grabbing some
#some sample color palettes from the ColorBrewer package which look nice.
#COLORS=((0.4,0.761,0.647),(0.988,0.553,0.384),(0.553,0.627,0.796))
COLOR=(0.553,0.627,0.796)
#the default is the above color (in rgb) on a white background. you can invert
#all colors using this parameter.
INVERSE=true

#these functions might be called a few hundred million times, so an if statement has
#been brought outside the function definitions to try and speed things up.
if D < C
    function create_plot_scalers(c, d, margin)
        x_range = 1 + c
        y_range = 1 + d
        norm = (2 * x_range * (1/(1-2*margin)))
        other_max = 2*y_range / norm
        less_margin = (1-other_max) / 2
        (x_range, y_range, norm, margin, less_margin)
    end
    function scale_point((x, y), (x_range, y_range, norm, margin, less_margin))
        trans_x, trans_y = x + x_range, y + y_range
        trans_x /= norm
        trans_y /= norm
        trans_x += margin
        trans_y += less_margin
        (trans_x, trans_y)
    end
else
    function create_plot_scalers(c, d, margin)
        x_range = 1 + c
        y_range = 1 + d
        norm = (2 * y_range * (1/(1-2*margin)))
        other_max = 2*x_range / norm
        less_margin = (1-other_max) / 2
        (x_range, y_range, norm, margin, less_margin)
    end
    function scale_point((x, y), (x_range, y_range, norm, margin, less_margin))
        trans_x, trans_y = x + x_range, y + y_range
        trans_x /= norm
        trans_y /= norm
        trans_y += margin
        trans_x += less_margin
        (trans_x, trans_y)
    end
end

function increment_canvas!(canvas, state, canvas_size, plot_scale_params)
    plot_x, plot_y = scale_point(state, plot_scale_params)
    plot_x *= canvas_size
    plot_y *= canvas_size
    plot_x = floor(Int, plot_x)
    plot_y = floor(Int, plot_y)
    plot_x = canvas_size - plot_x + 1
    plot_y = canvas_size - plot_y + 1
    canvas[plot_y, plot_x] += 1
end

function update_state(a,b,c,d,(x, y))
    new_x = sin(a*y) + c*cos(a*x)
    new_y = sin(b*x) + d*cos(b*y)
    (new_x, new_y)
end

function clifford_attractor(a, b, c, d, num_points, canvas_size, margin, x_init, y_init, percentile_clip, occupancy_root)
    plot_scale_params = create_plot_scalers(c, d, margin)
    canvas = zeros(canvas_size, canvas_size)
    state = (x_init, y_init)
    increment_canvas!(canvas, state, canvas_size, plot_scale_params)
    for step in 2:num_points
        state = update_state(a,b,c,d,state)
        increment_canvas!(canvas, state, canvas_size, plot_scale_params)
    end
    canvas = canvas.^occupancy_root
    scale = quantile(canvas[canvas.>0], percentile_clip)
    canvas/scale
end

function pixel_color_transform(pixel,(r,g,b))
    if pixel <= 0
        output = RGB{N0f8}(0.,0.,0.)
    elseif pixel <= 1
        output = RGB{N0f8}((1-r)*pixel,(1-g)*pixel,(1-b)*pixel)
    else
        output = RGB{N0f8}((1-r),(1-g),1-b)
    end
    output
end

function render_img(data,color)
    x,y = size(data)
    canvas = zeros(RGB{N0f8},x,y)
    for i in 1:x
        for j in 1:y
            canvas[i,j] = pixel_color_transform(data[i,j], color)
        end
    end
    canvas
end

function main(a=A, b=B, c=C, d=D,
              num_points=NUM_POINTS, x_init=X_INIT, y_init=Y_INIT,
              canvas_size=CANVAS_SIZE, margin=MARGIN,
              out_file_name=OUT_FILE_NAME,
              percentile_clip=PERCENTILE_CLIP, occupancy_root=OCCUPANCY_ROOT, color=COLOR, inverse=INVERSE)
    output = clifford_attractor(a, b, c, d, num_points, canvas_size, margin, x_init, y_init, percentile_clip, occupancy_root)
    img = render_img(output,color)
    if !inverse
        img = RGB{N0f8}(1.,1.,1.) .- img
    end
    save(out_file_name, img)
end

main()


function secret_handshake(code)
    messages = ["wink", "double blink", "close your eyes", "jump"]
    answer = String[]
    for i in 1:4
        q = div(code, 2)
        if 2q != code
            push!(answer, messages[i])
        end
        code = q
    end
    q = div(code, 2)
    return 2q != code ? reverse(answer) : answer
end


using Test
using GlobalSensitivityAnalysis

ATOL_sample = 1e-8
ATOL_sobol = 1e-9 # sobol method indices
ATOL_delta = 1e-3 # delta method indices
ATOL_CI = 1e-2

# see: https://www.nature.com/articles/sdata2018187#Sec5
include(joinpath(@__DIR__, "../src/quantile_matching/WRS.jl"))

@testset "Test Utils" begin
    include("test_utils.jl")
end

@testset "Test Sobol Method" begin

    @testset "Compare to SALib.py" begin
        include("sobol/test_sobol_sobolsequence.jl")
        include("sobol/test_sobol_ishigami.jl")
        include("sobol/test_sobol_borehole.jl")
        include("sobol/test_sobol_ci.jl")
    end

    @testset "Unit Testing" begin
        include("sobol/test_sobol_unit.jl")
    end
end

@testset "Test Delta Method" begin

    @testset "Compare to SALib.py" begin
        include("delta/test_delta_ishigami.jl")
        include("delta/test_delta_borehole.jl")
    end

    @testset "Unit Testing" begin
        include("delta/test_delta_unit.jl")
    end
end


# Frequency analysis of 1D data.
#
#
# Hongyang Zhou, hyzhou@umich.edu 12/10/2019

using MAT, Statistics, PyPlot
using SpaceAnalysis

"""
    B_spectrum(filename, varname, vartype, Fs, Brange)

Return single-side spectrum amplitude for magnetic field read from MAT file
`filename`. `varname` ∈ ["Bobs", "Bsim"]. `vartype` ∈ ["x", "y", "z", "mag"].

Note: MAT package seems not able to read timeseries data. A workaround is by
identifying the starting and ending array index in magnetopause crossings.
"""
function B_spectrum(filename, varname="Bobs", vartype="mag", Fs=1/0.3,
   Brange=1500:6000)

   data = matread(filename)
   B = data[varname]
   if vartype == "mag"
      Btmp = hypot.(B[Brange,1], B[Brange,2], B[Brange,3])
   elseif vartype == "x"
      Btmp = B[Brange,1]
   elseif vartype == "y"
      Btmp = B[Brange,2]
   elseif vartype == "z"
      Btmp = B[Brange,3]
   end

   spectrum(Btmp, Fs)
end

filenames = ["B_G8.mat", "B_G8_PIC.mat", "B_G8_PIC_AMR2.mat", "B_G8_Hall_AMR2.mat"]

fObs, pObs   = B_spectrum(filenames[1], "Bobs", "mag")
fHall, pHall = B_spectrum(filenames[1], "Bsim", "mag", 1.0, 1573:2959)
fHall2, pHall2 = B_spectrum(filenames[4], "Bsim", "mag", 1.0, 1573:2959)
fPIC, pPIC   = B_spectrum(filenames[2], "Bsim", "mag", 1.0, 1573:2959)
fPIC2, pPIC2 = B_spectrum(filenames[3], "Bsim", "mag", 1.0, 1573:2959)

nSmooth = 19
nSHalf = (nSmooth-1) ÷ 2

figure()
plot(fObs[2:end], sma(pObs[2:end].^2, nSmooth),"k")
plot(fHall[2:end], sma(pHall[2:end].^2, nSmooth))
plot(fHall2[2:end], sma(pHall2[2:end].^2, nSmooth))
plot(fPIC[2:end], sma(pPIC[2:end].^2, nSmooth),"#d62728")
plot(fPIC2[2:end], sma(pPIC2[2:end].^2, nSmooth))

plt.yscale("log")
xlim(0.0, 0.3)
grid(true)
legend(["Observation", "Hall MHD fine grid", "Hall MHD coarse grid",
   "PIC fine grid", "PIC coarse grid"])
title("Single-Sided Amplitude Spectrum of B(t)")
xlabel("f (Hz)")
ylabel("|P1(f)|")


# calculate P(z|t)
# P(z|t) = P(z,t) / P(t)

module CondZtBar
    using DataFrames, CSV, Plots, FreqTables
    pyplot()

    function main(is_test=false)
        data_path = joinpath(split(@__FILE__, "src")[1], "data/sensor_data_600.txt")
        df_org = CSV.read(data_path, DataFrame,
                      header=["date", "time", "ir", "lidar"],
                      delim=' ')

        hours = [Int64(floor(e/10000)) for e in df_org.time]

        # add hour array to df
        df_h = DataFrame(hour=hours)
        df_new = hcat(df_org, df_h)

        # pivot
        freqs = freqtable(df_new, :lidar, :hour)
        probs = freqs / length(df_org.lidar)

        # sum
        keys = names(probs, 2)
        probs_t = [sum(probs[begin:end, Name(key)]) for key in keys]

        # P(z|t)
        cond_z_t = probs / probs_t[1] # P(t=0)
        z = names(probs, 1)
        println(cond_z_t)

        # bar plot
        bar(cond_z_t[begin:end, Name(6)], xticks=z, alpha=0.3, color=:blue, label="Hour:6")
        bar!(cond_z_t[begin:end, Name(14)], xticks=z, alpha=0.3, color=:red, label="Hour:14")

        if is_test == false
            save_path = joinpath(split(@__FILE__, "src")[1], "src/prob_stats/complex_dist/cond_z_t_bar/cond_z_t.png")
            savefig(save_path)
        end
    end
end


using BenchmarkTools, SpecialFunctions, FiniteDifferences, ForwardDiff, StaticArrays
include("shared.jl")

const FDFAST(fn, x) = (fn(x+h)-fn(x))/h
const FD2 = central_fdm(2, 1)

dbesselk_fdfast(v, x) = FDFAST(_v->besselk(_v, x), v)
dbesselk_fd2(v, x)    = FD2(_v->besselk(_v, x), v)
dbesselk_ad(v, x)     = ForwardDiff.derivative(_v->_besselk(_v, x), v)

#=
# And note zero allocations for the AD version. Pretty good. And faster by
# significant margins---like a factor of five---than even the most reckless FD.
for (v, x) in Iterators.product((0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.25, 3.0, 4.25),
                                 (1e-4, 0.25, 2.5, 5.0, 7.5, 12.0, 25.0))
  println("(v,x) = ($v, $x):")
  print("FDFAST:")
  @btime dbesselk_fdfast($v, $x)
  print("FD2:   ")
  @btime dbesselk_fd2($v, $x)
  print("AD:    ")
  @btime dbesselk_ad($v, $x)
  print("\n\n")
end
=#

# matern function:
const oo = @SVector ones(2)
const zz = @SVector zeros(2)

@inline pv(v) = @SVector [1.1, 1.1, v]
function matern_d3_ad(v)
  ForwardDiff.derivative(_v->BesselK.matern(oo, zz, pv(_v)), v)
end

function matern_d3_fd(v)
  FDFAST(_v->BesselK.matern(oo, zz, pv(_v)), v)
end


using Base.Test
import ArchGDAL; const AG = ArchGDAL

@testset "Reproject a Geometry" begin
    @testset "Method 1" begin
        source = AG.unsafe_importEPSG(2927); target = AG.unsafe_importEPSG(4326)
            transform = AG.unsafe_createcoordtrans(source, target)
                point = AG.unsafe_fromWKT("POINT (1120351.57 741921.42)")
                    @test AG.toWKT(point) == "POINT (1120351.57 741921.42)"
                    AG.transform!(point, transform)
                    @test AG.toWKT(point) == "POINT (-122.598135130878 47.3488013802885)"
                AG.destroy(point)
            AG.destroy(transform)
        AG.destroy(target); AG.destroy(source)
    end

    @testset "Method 2" begin
        AG.importEPSG(2927) do source; AG.importEPSG(4326) do target
            AG.createcoordtrans(source, target) do transform
                AG.fromWKT("POINT (1120351.57 741921.42)") do point
                    @test AG.toWKT(point) == "POINT (1120351.57 741921.42)"
                    AG.transform!(point, transform)
                    @test AG.toWKT(point) == "POINT (-122.598135130878 47.3488013802885)"
        end end end end
    end
end

@testset "Get Projection" begin
    AG.registerdrivers() do
        AG.read("ospy/data1/sites.shp") do dataset
            layer = AG.getlayer(dataset, 0)
            spatialref = AG.getspatialref(layer)
            @test AG.toWKT(spatialref) == """PROJCS["WGS_1984_UTM_Zone_12N",GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_84",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-111.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0],AUTHORITY["EPSG","32612"]]"""
            @test AG.toWKT(spatialref, false) == """
            PROJCS["WGS_1984_UTM_Zone_12N",
                GEOGCS["GCS_WGS_1984",
                    DATUM["WGS_1984",
                        SPHEROID["WGS_84",6378137.0,298.257223563]],
                    PRIMEM["Greenwich",0.0],
                    UNIT["Degree",0.0174532925199433],
                    AUTHORITY["EPSG","4326"]],
                PROJECTION["Transverse_Mercator"],
                PARAMETER["False_Easting",500000.0],
                PARAMETER["False_Northing",0.0],
                PARAMETER["Central_Meridian",-111.0],
                PARAMETER["Scale_Factor",0.9996],
                PARAMETER["Latitude_Of_Origin",0.0],
                UNIT["Meter",1.0],
                AUTHORITY["EPSG","32612"]]"""
            @test AG.toWKT(spatialref, true) == """
            PROJCS["WGS_1984_UTM_Zone_12N",
                GEOGCS["GCS_WGS_1984",
                    DATUM["WGS_1984",
                        SPHEROID["WGS_84",6378137.0,298.257223563]],
                    PRIMEM["Greenwich",0.0],
                    UNIT["Degree",0.0174532925199433]],
                PROJECTION["Transverse_Mercator"],
                PARAMETER["False_Easting",500000.0],
                PARAMETER["False_Northing",0.0],
                PARAMETER["Central_Meridian",-111.0],
                PARAMETER["Scale_Factor",0.9996],
                PARAMETER["Latitude_Of_Origin",0.0],
                UNIT["Meter",1.0]]"""
            @test AG.toPROJ4(spatialref) == "+proj=utm +zone=12 +datum=WGS84 +units=m +no_defs "
            @test AG.toXML(spatialref)[1:17] == "<gml:ProjectedCRS"
            @test AG.toMICoordSys(spatialref) == "Earth Projection 8, 104, \"m\", -111, 0, 0.9996, 500000, 0"
            AG.nextfeature(layer) do feature
                @test AG.toWKT(AG.getspatialref(AG.getgeom(feature))) == """PROJCS["WGS_1984_UTM_Zone_12N",GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_84",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-111.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0],AUTHORITY["EPSG","32612"]]"""
            end
        end
    end

    AG.importEPSG(26912) do spatialref
        @test AG.toPROJ4(spatialref) == "+proj=utm +zone=12 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs "
        @test AG.toWKT(spatialref) == """PROJCS["NAD83 / UTM zone 12N",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-111],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","26912"]]"""
        AG.morphtoESRI!(spatialref)
        @test AG.toPROJ4(spatialref) == "+proj=utm +zone=12 +ellps=GRS80 +units=m +no_defs "
        AG.toWKT(spatialref) == """PROJCS["NAD_1983_UTM_Zone_12N",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137,298.257222101]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-111],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["Meter",1]]"""
    end
end


#=
  Sum of 2 dice.

  I asked how one should observe a sum of the two dice here:
  https://discourse.julialang.org/t/turing-observing-sum-of-two-random-dice/50486

=#

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

@model function two_dice(ss)
    d1 ~ DiscreteUniform(1, 6)
    d2 ~ DiscreteUniform(1, 6)

    s ~ Dirac(d1 + d2)
    true ~ Dirac(s == ss)

end

# Note: Here we observe c=5 from outside
model = two_dice(5)

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
# chns = sample(model, IS(), 10_000)
chns = sample(model, PG(20), 1_000)
# chns = sample(model, SMC(1000), 1000)

display(chns)
# display(plot(chns))
# display(histogram(chns))
# display(gelmandiag(chns))

show_var_dist_pct(chns, :d1)
show_var_dist_pct(chns, :d2)
show_var_dist_pct(chns, :s)


######################################################################

struct FAbs2{T<:AbstractFunctionWithParameters} <: AbstractFunctionWithParameters
    f::T
end
func(d::FAbs2, x::NumberOrTuple; p=freepars(d)) = abs2(func(d.f,x;p))
pars(d::FAbs2, isfree::Bool) = pars(d.f, isfree)
updatevalueorflag(d::FAbs2, s::Symbol, isfree::Bool, v=getproperty(pars(d),s)) =
    FAbs2(updatevalueorflag(d.f,s,isfree,v))
#
abs2(f::AbstractFunctionWithParameters) = FAbs2(f)

######################################################################

struct FLog{T<:AbstractFunctionWithParameters} <: AbstractFunctionWithParameters
    f::T
end
func(d::FLog, x::NumberOrTuple; p=freepars(d)) = log(func(d.f,x;p))
pars(d::FLog, isfree::Bool) = pars(d.f, isfree)
updatevalueorflag(d::FLog, s::Symbol, isfree::Bool, v=getproperty(pars(d),s)) =
    FLog(updatevalueorflag(d.f,s,isfree,v))
#
log(f::AbstractFunctionWithParameters) = FLog(f)

######################################################################

struct FProd{
        T1<:AbstractFunctionWithParameters,
        T2<:AbstractFunctionWithParameters} <: AbstractFunctionWithParameters
    f1::T1
    f2::T2
end
func(d::FProd, x::NumberOrTuple; p=freepars(d)) = func(d.f1,x;p) * func(d.f2,x;p)
pars(d::FProd, isfree::Bool) = pars(d.f1, isfree) + pars(d.f2, isfree)
updatevalueorflag(d::FProd, s::Symbol, isfree::Bool, v=getproperty(pars(d),s)) =
    FProd(
        ispar(d.f1,s) ? updatevalueorflag(d.f1,s,isfree,v) : d.f1,
        ispar(d.f2,s) ? updatevalueorflag(d.f2,s,isfree,v) : d.f2)
#
*(f1::AbstractFunctionWithParameters, f2::AbstractFunctionWithParameters) = FProd(f1,f2)

######################################################################

struct FSum{T<:AbstractFunctionWithParameters,N,V}  <: AbstractFunctionWithParameters
    fs::StaticVector{N,T}
    αs::V
end
function FSum(fs::Union{Tuple,AbstractVector}, αs)
    N = length(fs)
    FSum(SVector{N}(collect(fs)), αs)
end
#
length(d::FSum{T,N}) where {T,N} = N
pars(d::FSum, isfree::Bool) = sum(pars.(d.fs, isfree)) + pars(d.αs, isfree)
function getindex(d::FSum, i::Number)
    α_sym = keys(d.αs)[i]
    α_val = getproperty(d.αs, α_sym)
    FSum([d.fs[i]], nt(α_sym, α_val))
end
const FSumFunc = AlgebraPDF.FSum{T} where T<:AbstractFunctionWithParameters
const FSumPDF = AlgebraPDF.FSum{T} where T<:AbstractPDF

function func(d::FSumFunc, x::NumberOrTuple; p=freepars(d))
    allp = p+fixedpars(d)
    α_vals = (getproperty(allp,s) for s in keys(d.αs))
    f_vals = func.(d.fs, Ref(x);p)
    return sum(α_vals .* f_vals)
end

#
function func_norm(d::FSumPDF, x; p=freepars(d)) # suppose to work also for all x <: AbstractVector
    allp = p+fixedpars(d)
    α_vals = (getproperty(allp,s) for s in keys(d.αs))
    f_vals = [f(x;p) for f in d.fs]
    return sum(α_vals .* f_vals)
end
func(d::FSumPDF, x::NumberOrTuple; p=freepars(d)) = func_norm(d,x;p)
func(d::FSumPDF, x::AbstractArray; p=freepars(d)) = func_norm(d,x;p)
func(d::FSumPDF, x::AbstractRange; p=freepars(d)) = func_norm(d,x;p)
lims(d::FSumPDF) = lims(d.fs[1])

function updatevalueorflag(d::FSum, s::Symbol, isfree::Bool, v=getproperty(pars(d),s))
    fs = [ispar(f,s) ? updatevalueorflag(f,s,isfree,v) : f for f in d.fs]
    αs = ispar(d.αs,s) ? updatevalueorflag(d.αs,s,isfree,v) : d.αs
    FSum(fs, αs)
end
#
function integral(d::FSumPDF; p=freepars(d.αs))
    allp = p+fixedpars(d.αs)
    sum(getproperty(allp,s) for s in keys(d.αs))
end

function integral(d::FSumPDF, lims; p=freepars(d))
    allp = p+fixedpars(d)
    α_vals = (getproperty(allp,s) for s in keys(d.αs))
    f_vals = integral.(d.fs, Ref(lims); p)
    return sum(α_vals .* f_vals)
end

+(f1::AbstractFunctionWithParameters,
  f2::AbstractFunctionWithParameters; p=(α1=1.0, α2=1.0)) = FSum([f1,f2], p)

-(f1::AbstractFunctionWithParameters,
  f2::AbstractFunctionWithParameters; p=(α1=1.0, α2=-1.0)) = FSum([f1,f2], p)

+(f1::FSum, f2::FSum) = FSum([f1.fs...,f2.fs...], f1.αs+f2.αs)

*(f::AbstractFunctionWithParameters, c::ParTypes) = FSum([f], c)
*(c::ParTypes, f::AbstractFunctionWithParameters) = FSum([f], c)

######################################################################

struct NegativeLogLikelihood{T<:AbstractFunctionWithParameters, D<:AbstractArray} <: AbstractFunctionWithParameters
    f::T
    data::D
    nagativepenatly::Float64
end
func(d::NegativeLogLikelihood, x::NumberOrTuple; p=freepars(d)) = -sum((v>0) ? log(v) : d.nagativepenatly for v in d.f(d.data;p))
pars(d::NegativeLogLikelihood, isfree::Bool) = pars(d.f, isfree)
updatevalueorflag(d::NegativeLogLikelihood, s::Symbol, isfree::Bool, v=getproperty(pars(d),s)) =
    NegativeLogLikelihood(updatevalueorflag(d.f,s,isfree,v), d.data, d.nagativepenatly)
#
NegativeLogLikelihood(d, data::AbstractArray) = NegativeLogLikelihood(d, data, -1e4)
minussum(d::FLog, data::AbstractArray) = NegativeLogLikelihood(d.f, data)

######################################################################

struct Extended{T <: AlgebraPDF.FSumPDF} <: AbstractFunctionWithParameters
    nll::NegativeLogLikelihood{T}
end

pars(d::Extended, isfree::Bool) = pars(d.nll, isfree)
updatevalueorflag(d::Extended, s::Symbol, isfree::Bool, v=getproperty(pars(d),s)) =
    Extended(updatevalueorflag(d.nll,s,isfree,v))
#
function func(d::Extended, x::NumberOrTuple; p=freepars(d))
    nll = func(d.nll, x; p)
    μ = integral(d.nll.f; p)
    penalty = μ
    return nll + penalty
end

# model property
model(nll::NegativeLogLikelihood) = nll.f
model(enll::Extended) = model(enll.nll)