@safetestset "PaduaTransforms" begin include("test_PaduaTransforms.jl") end

using PoincareInvariants.SecondChebyshevPlans

@safetestset "Differentiation" begin
    using ..SecondChebyshevPlans: DiffPlan, differentiate!

    @test DiffPlan{Float64}(5).D == [0  1  0  3  0   5;
                                     0  0  4  0  8   0;
                                     0  0  0  6  0  10;
                                     0  0  0  0  8   0;
                                     0  0  0  0  0  10;
                                     0  0  0  0  0   0]

    @test DiffPlan{Float64}(4).D == [0  1  0  3  0;
                                     0  0  4  0  8;
                                     0  0  0  6  0;
                                     0  0  0  0  8;
                                     0  0  0  0  0]

    coeffs = [1  2  3  0;
              4  5  0  0;
              6  0  0  0;
              0  0  0  0]

    P = DiffPlan{Float64}(3)

    ∂x = zeros(4, 4)
    ∂y = zeros(4, 4)

    differentiate!(∂x, ∂y, P, coeffs)

    @test ∂x == [2  12  0  0;
                 5   0  0  0;
                 0   0  0  0;
                 0   0  0  0]

    @test ∂y == [ 4  5  0  0;
                 24  0  0  0;
                  0  0  0  0;
                  0  0  0  0]

    ndcoeffs = ntuple(_ -> rand(4, 4), 6)
    nd∂x = ntuple(_ -> zeros(4, 4), 6)
    nd∂y = ntuple(_ -> zeros(4, 4), 6)

    differentiate!(nd∂x, nd∂y, P, ndcoeffs)

    for i in 1:6
        differentiate!(∂x, ∂y, P, ndcoeffs[i])
        @test nd∂x[i] == ∂x
        @test nd∂y[i] == ∂y
    end
end

@safetestset "Integration" begin
    using ..SecondChebyshevPlans: getintweights, integrate

    # integrating odd polynomials over symmetric boundary conditions gives 0
    @test all(getintweights(100)[2:2:end] .== 0)
    @test all(getintweights(999)[2:2:end] .== 0)

    coeffs = [1 4 7 0;
              2 5 8 0;
              3 6 9 0;
		      0 0 0 0]

    @test integrate(coeffs, getintweights(3)) ≈ 1 * 4 + 3 * -4/3 + 7 * -4/3 + 9 * 4/9 atol=5eps()
end

@safetestset "getintegrand! with CallIntPlan" begin
    using ..SecondChebyshevPlans.PaduaTransforms
    using ..SecondChebyshevPlans: DiffPlan, differentiate!, CallIntPlan, getintegrand!
    using PoincareInvariants

    @testset "6 Points in 2 Dimensions" begin
        degree = 2
        D = 2
        phasecoeffs = (
            Float64[1 2 3;
                    4 5 0;
                    6 0 0],
            Float64[1 4 6;
                    2 5 0;
                    3 0 0]
        )

        ∂xcoeffs = ntuple(_ -> zeros(degree+1, degree+1), D)
        ∂ycoeffs = ntuple(_ -> zeros(degree+1, degree+1), D)
        differentiate!(∂xcoeffs, ∂ycoeffs, DiffPlan{Float64}(degree), phasecoeffs)

        phasepoints = Matrix{Float64}(undef, getpaduanum(degree), D)
        iplan = InvPaduaTransformPlan{Float64}(degree)
        invpaduatransform!(phasepoints, iplan, phasecoeffs)

        ω(v, t, p) = [0 -1; 1  0]

        plan = CallIntPlan{Float64, D}(degree)

        intcoeffs = zeros(degree+1, degree+1)

        getintegrand!(intcoeffs, plan, ω, phasepoints, 0, nothing, ∂xcoeffs, ∂ycoeffs)

        @test intcoeffs ≈ [-72   -82  -30;
                           -82  -432    0;
                           -30     0    0] atol=5eps()
    end

    @testset "50 Points in 6 Dimensions" begin
        D = 6
        N = 50
        pointnum = nextpaduanum(N)
        degree = getdegree(pointnum)

        phasecoeffs = ntuple(_ -> zeros(degree+1, degree+1), D)
        phasecoeffs[1][1, 1] = 0.5  # 0.5
        phasecoeffs[1][1, 2] = 0.5  # 0.5 x
        phasecoeffs[D ÷ 2 + 1][1, 1] = 0.5  # 0.5
        phasecoeffs[D ÷ 2 + 1][2, 1] = 0.5  # 0.5 y

        ∂xcoeffs = ntuple(_ -> zeros(degree+1, degree+1), D)
        ∂ycoeffs = ntuple(_ -> zeros(degree+1, degree+1), D)
        differentiate!(∂xcoeffs, ∂ycoeffs, DiffPlan{Float64}(degree), phasecoeffs)

        phasepoints = Matrix{Float64}(undef, getpaduanum(degree), D)
        iplan = InvPaduaTransformPlan{Float64}(degree)
        invpaduatransform!(phasepoints, iplan, phasecoeffs)

        ω = canonical_two_form

        plan = CallIntPlan{Float64, D}(degree)

        intcoeffs = zeros(degree+1, degree+1)

        getintegrand!(intcoeffs, plan, ω, phasepoints, 0, nothing, ∂xcoeffs, ∂ycoeffs)

        testcoeffs = zeros(degree+1, degree+1)
        testcoeffs[1, 1] = 0.25

        @test testcoeffs ≈ intcoeffs atol=1eps()
    end
end

@safetestset "compute! with SecondChebyshevPlan (OOP)" begin
    using ..SecondChebyshevPlans.PaduaTransforms
    using ..SecondChebyshevPlans: SecondChebyshevPlan, compute!, DiffPlan
    using PoincareInvariants

    D = 12
    N = 200
    ω = canonical_two_form
    pointnum = nextpaduanum(N)
    degree = getdegree(pointnum)
    plan = SecondChebyshevPlan{Float64, D}(ω, pointnum)

    testphasecoeffs = [zeros(degree+1, degree+1) for _ in 1:D]
    test∂x = [zeros(degree+1, degree+1) for _ in 1:D]
    test∂y = [zeros(degree+1, degree+1) for _ in 1:D]
    testintcoeffs = zeros(degree+1, degree+1)

    phasepoints = getpaduapoints(degree) do x, y
        ntuple(D) do i
            i == 1 && return x
            i == D ÷ 2 + 1 && return y
            return 0
        end
    end

    pinv = SecondPoincareInvariant{Float64, D}(ω, pointnum, plan)
    @test compute!(pinv, phasepoints, 0, nothing) ≈ 4 atol=20eps()

    testphasecoeffs[1][1, 2] = 1  # 1 x
    testphasecoeffs[D ÷ 2 + 1][2, 1] = 1 # 1 y

    test∂x[1][1, 1] = 1
    test∂y[D ÷ 2 + 1][1, 1] = 1

    for d in 1:D
        @test maximum(abs, plan.phasecoeffs[d] .- testphasecoeffs[d]) / eps() < 10
        @test maximum(abs, plan.∂x[d] .- test∂x[d]) / eps() < 50
        @test maximum(abs, plan.∂y[d] .- test∂y[d]) / eps() < 50
    end

    testintcoeffs[1, 1] = 1
    @test maximum(abs, plan.intcoeffs .- testintcoeffs) / eps() < 50
end

@safetestset "getpoints, getpointspec and getpointnum" begin
    using ..SecondChebyshevPlans.PaduaTransforms
    using ..SecondChebyshevPlans: SecondChebyshevPlan, getpointspec, getpoints

    for N in [10, 321, 2178], T in [Float32, Float64]
        @test getpointspec(N, SecondChebyshevPlan) == nextpaduanum(N)

        @inferred Vector{T} getpoints((x, y) -> x, T, N, SecondChebyshevPlan)
        @inferred Matrix{T} getpoints((x, y) -> (x, y), T, N, SecondChebyshevPlan)

        f(x, y) = 5 * x, cos(x*y), x+y
        rescale(x, y) = ((x, y) .+ 1) ./ 2

        pnts = getpoints(f, T, N, SecondChebyshevPlan)
        testpnts = getpaduapoints(T, nextdegree(N)) do x, y
            px, py = rescale(x, y)
            return f(px, py)
        end

        @test pnts isa Matrix{T}
        @test size(pnts) == (nextpaduanum(N), 3)
        @test pnts ≈ testpnts
    end
end


using Gaugefields
using LinearAlgebra

function MDtest!(gauge_action,U,Dim)
    p = initialize_TA_Gaugefields(U) #This is a traceless-antihermitian gauge fields. This has NC^2-1 real coefficients.
    Uold = similar(U)
    substitute_U!(Uold,U)
    MDsteps = 100
    temp1 = similar(U[1])
    temp2 = similar(U[1])
    comb = 6
    factor = 1/(comb*U[1].NV*U[1].NC)
    numaccepted = 0

    Random.seed!(123)

    numtrj = 100
    for itrj = 1:numtrj
        accepted = MDstep!(gauge_action,U,p,MDsteps,Dim,Uold)
        numaccepted += ifelse(accepted,1,0)

        plaq_t = calculate_Plaquette(U,temp1,temp2)*factor
        println("$itrj plaq_t = $plaq_t")
        println("acceptance ratio ",numaccepted/itrj)
    end
end

function calc_action(gauge_action,U,p)
    NC = U[1].NC
    Sg = -evaluate_GaugeAction(gauge_action,U)/NC #evaluate_GaugeAction(gauge_action,U) = tr(evaluate_GaugeAction_untraced(gauge_action,U))
    Sp = p*p/2
    S = Sp + Sg
    return real(S)
end

function MDstep!(gauge_action,U,p,MDsteps,Dim,Uold)
    Δτ = 1/MDsteps
    NC,_,NN... = size(U[1])
    for μ=1:Dim
        pwork = gauss_distribution(prod(NN)*(NC^2-1))
        substitute_U!(p[μ],pwork)
    end
    println(p[1][1,1,1,1,1,1])

    #gauss_distribution!(p)
    Sold = calc_action(gauge_action,U,p)
    substitute_U!(Uold,U)

    for itrj=1:MDsteps
        U_update!(U,p,0.5,Δτ,Dim,gauge_action)

        P_update!(U,p,1.0,Δτ,Dim,gauge_action)

        U_update!(U,p,0.5,Δτ,Dim,gauge_action)
    end
    Snew = calc_action(gauge_action,U,p)
    println("Sold = $Sold, Snew = $Snew")
    println("Snew - Sold = $(Snew-Sold)")
    ratio = min(1,exp(Snew-Sold))
    if rand() > ratio
        substitute_U!(U,Uold)
        return false
    else
        return true
    end
end

function U_update!(U,p,ϵ,Δτ,Dim,gauge_action)
    temps = get_temporary_gaugefields(gauge_action)
    temp1 = temps[1]
    temp2 = temps[2]
    expU = temps[3]
    W = temps[4]

    for μ=1:Dim
        exptU!(expU,ϵ*Δτ,p[μ],[temp1,temp2])
        mul!(W,expU,U[μ])
        substitute_U!(U[μ],W)

    end
end

function P_update!(U,p,ϵ,Δτ,Dim,gauge_action) # p -> p +factor*U*dSdUμ
    NC = U[1].NC
    temps = get_temporary_gaugefields(gauge_action)
    dSdUμ = temps[end]
    factor =  -ϵ*Δτ/(NC)

    for μ=1:Dim
        calc_dSdUμ!(dSdUμ,gauge_action,μ,U)
        mul!(temps[1],U[μ],dSdUμ) # U*dSdUμ
        Traceless_antihermitian_add!(p[μ],factor,temps[1])
    end
end

function test1()
    NX = 4
    NY = 4
    NZ = 4
    NT = 4
    Nwing = 1
    Dim = 4
    NC = 3

    U  =Initialize_Gaugefields(NC,Nwing,NX,NY,NZ,NT,condition = "cold")

    gauge_action = GaugeAction(U)
    plaqloop = make_loops_fromname("plaquette")
    append!(plaqloop,plaqloop')
    β = 5.7/2
    push!(gauge_action,β,plaqloop)

    show(gauge_action)

    MDtest!(gauge_action,U,Dim)

end

function gauss_distribution(nv)
    variance = 1
    nvh = div(nv,2)
    granf = zeros(Float64,nv)
    for i=1:nvh
        rho = sqrt(-2*log(rand())*variance)
        theta = 2pi*rand()
        granf[i] = rho*cos(theta)
        granf[i+nvh] = rho*sin(theta)
    end
    if 2*nvh == nv
        return granf
    end

    granf[nv] = sqrt(-2*log(rand())*variance) * cos(2pi*rand())
    return granf
end

test1()


function mpneuron(X,W)
    ψ = sum(X .* W)
    if ψ >0
        return 1
    else
        return 0
    end
end


using LinearAlgebra

@testset "lsa" begin
    crps = Corpus([StringDocument("this is a string document"), TokenDocument("this is a token document")])
    F1 = lsa(crps)

    update_lexicon!(crps)
    m = DocumentTermMatrix(crps)

    # lsa on crps
    @test typeof(F1) <: LinearAlgebra.SVD
    @test F1.U * LinearAlgebra.Diagonal(F1.S) * F1.Vt == Matrix(tf_idf(m))

    # lsa on dtm
    F2 = lsa(m)
    @test typeof(F2) <: LinearAlgebra.SVD
    @test F2.U * LinearAlgebra.Diagonal(F2.S) * F2.Vt == Matrix(tf_idf(m))

    @test F1 == F2
end


function Coeffgamm2Simple_dq0(k, T, m)
    (
        k * (coth(Epi(k, m) / (2 * T)) / Epi(k, m)^2)
    ) / (16 * pi^2)
end

function propImSimple_dq0(p0, ps, T, IRScale, UVScale, Npi, m, lamda)
    -hquadrature(
        x ->
            2*Coeffgamm2Simple_dq0(x, T, m) *
            central_fdm(5,1)(z->VImintqsSimple(p0, ps,z, k, T, Npi, m, lamda,UVScale),Epi(k,m)),
        IRScale,
        UVScale,
        atol = 1e-3,
        rtol = 1e-3,initdiv=10,maxevals=1600,
    )[1]
end


# quick script i use during development …
using StaticArrays
using RadialBasisFunctionModels
import Zygote as Zyg

f = x -> [ 1 + x[1]; sum(x.^2) ]
X = [ -3 .+ 6 * rand(2) for i = 1 : 5]
Y = f.(X)

#%%
# Initialize the `RadialFunction` to use for the RBF model:
φ = Gaussian()

# Construct an interpolating model with linear polynomial tail:
rbf = RBFModel( X, Y, φ, 1; centers = C, interpolation_indices = I )

#=
using StaticArrays
ξ = @SVector(rand(2))
jac( rbf, ξ )
auto_jac( rbf, ξ)
=#
#%%

l = function( x )
    global X,Y,φ
    rbf_m = RBFModel( X, Y, φ, 1; centers = C, interpolation_indices = I )
    rbf_m(x, 1)
end

Zyg.gradient( l, rand(2))


include("common.jl")

Δt = 0.01
mmesh = Mesh(30, 30, 1/30)
bdnode = bcnode(mmesh)
bdnode = [bdnode; bdnode .+ mmesh.ndof]
nu = Variable(0.001) * ones(get_ngauss(mmesh))
nodes = fem_nodes(mmesh)
u = @. sin(2π * nodes[:,1])
v = @. sin(2π * nodes[:,2])
u0 = [u;v]
u0[bdnode] .= 0.0
us = solve_burgers(u0, 10, nu)

U = matread("fenics/fwd1.mat")["U"]
loss = sum((us - U)^2)
sess = Session(); init(sess)
# @info run(sess, loss)
BFGS!(sess, loss)


    # matricization of a tensor with indices ind on the lr indicating left or right
    function unfold(tensor::Array{Float64},ind::Vector{Int64},lr::String)
        order    = ndims(tensor);
        newsizes = collect(size(tensor));
        if size([1,2])[1]>1
        deleteat!(newsizes,sort(ind));
        else
            deleteat!(newsizes,ind);
        end
        newperm  = collect(1:order);
        deleteat!(newperm,sort(ind))
        if lr == "left"
            newperm = vcat(ind, newperm);
            tensor  = permutedims(tensor, newperm);
            size_r  = prod(newsizes);
            size_l  = Int(length(tensor)/size_r);
            matrix  = reshape(tensor, (size_l, size_r));
        elseif lr == "right"
            newperm = vcat(newperm, ind);
            tensor  = permutedims(tensor, newperm);
            size_l  = prod(newsizes);
            size_r  = Int(length(tensor)/size_l);
            matrix  = reshape(tensor, (size_l, size_r));
        end
    end

    # combining middlesizes from mpo to get an mps
    function mpo2mps(mpo::MPT{4})
        cores = Vector{Array{Float64,3}}(undef,order(mpo));
        rnks  = rank(mpo);
        sizes = size(mpo);
        for i = 1:order(mpo)
            cores[i] = reshape(mpo[i],(rnks[i][1], sizes[i][1]*sizes[i][2], rnks[i][2]));
        end
        MPT(cores,mpo.normcore);
    end

    # dividing middlesizes from mps to get an mpo
    function mps2mpo(mps::MPS,middlesizes::Matrix{Int64})
        cores = Vector{Array{Float64,4}}(undef,order(mps));
        rnks  = rank(mps);
        for i = 1:order(mps)
            cores[i] = reshape(mps[i],(rnks[i][1], middlesizes[1,i], middlesizes[2,i], rnks[i][2]));
        end
        MPT(cores,mps.normcore)
    end

    function mps2mpo(mps::MPS,middlesizes::Vector{Tuple{Int64, Int64}})
        cores = Vector{Array{Float64,4}}(undef,order(mps));
        rnks  = rank(mps);
        for i = 1:order(mps)
            cores[i] = reshape(mps[i],(rnks[i][1], middlesizes[i][1], middlesizes[i][2], rnks[i][2]));
        end
        MPT(cores,mps.normcore)
    end


import LinearAlgebra: ldiv!, rdiv!

ldiv!(α::Number, B::BlasArray) = lmul!(1 / α, B)
rdiv!(B::BlasArray, α::Number) = rmul!(B, 1 /α )

ldiv!(U::UniformScaling, B::BlasArray) = lmul!(1 / U.λ, B)
rdiv!(B::BlasArray, U::UniformScaling) = rmul!(B, 1 / U.λ)

# unwrap to-be-updated triangular numerators by sending ldiv! to !rdiv and vice-versa
# for (LoTr,UpTr) in ( (LowerTriangular, UpperTriangular),
#                      (UpperTriangular, LowerTriangular),
#                      (UnitLowerTriangular, UnitUpperTriangular),
#                      (UnitUpperTriangular, UnitLowerTriangular)
#                    )
#     for (tra,Tra) in ( (transpose,Transpose), (adjoint,Adjoint) )
#         @eval begin
#             ldiv!(L::$LoTr{T}, R::$Tra{T,<:$UpTr{T}}) where T =
#                 $tra(rdiv!($tra(R),$tra(L)))
#             #
#             rdiv!(L::$Tra{T,<:$LoTr{T}}, R::$UpTr{T}) where T =
#                 $tra(ldiv!($tra(R),$tra(L)))
#             #
#         end
#     end
# end


# Generalized Bayes: https://arxiv.org/pdf/2006.05451.pdf

using Distributions

struct State
  cluster::Vector{Int}
  Z::Matrix{Bool}
  v::Vector{Float64}
  alpha::Float64
end

function loss(yi::Vector{Bool}, zk::Vector{bool})
  return sum(abs.(yi - zk))
end

function loss(y::Matrix{Bool}, i::Integer, s::State)
  return loss(y[i, :], s.Z[s.cluster[i]])
end

function loss(y::Matrix{Bool}, s::State)
  N = length(s.cluster)
  return sum([loss(y, i, s) for i in 1:N])
end

function log_prior_Z(s::State, phi::Float64)
  # TODO
end

function update_c!(s::State, i::Integer, y::Matrix{Bool}, lam::Float64)
  K = size(s.Z, 2)
  log_prob = [-lam * loss(y[i], s.Z[:, k]) for k in 1:K]
  unnormalized_prob = exp.(log_prob - max(log_prob))
  prob = unnormalized_prob ./ sum(unnormalized_prob)
  s.cluster[i] = wsample(prob)
  return
end

function update_c!(s::State, y::Matrix{Bool}, lam::Float64)
  N = length(s.cluster)
  for i in 1:N
    update_c!(s, i, y, lam)
  end
end

function update_Z!(s::State, j:Integer, k::Integer, y::Matrix{Bool}, lam::Float64)
  # TODO
end

function update_Z!(s::State, y::Matrix{Bool}, lam::Float64)
  J, K = size(s.Z)
  for j in 1:J
    for k in 1:K
      update_Z(s, j, k, y, lam)
    end
  end
end


module Issue722

using Test
using Gridap
using Gridap.Fields
using Gridap.CellData

model = simplexify(CartesianDiscreteModel((0,1,0,1),(2,2)))
Ω=Triangulation(model)
Ωo=Gridap.Geometry.TriangulationView(Ω,[1,2,3])
glue = get_glue(Ωo,Val(2))
@test glue.tface_to_mface == [1, 2, 3]
@test glue.mface_to_tface == [1, 2, 3, -1, -2, -3, -4, -5]
no=GenericCellField(fill(GenericField(x->x[2]),num_cells(Ωo)),Ωo,ReferenceDomain())
x_Ω = get_cell_points(Ω)
nox = no(x_Ω)
collect(nox)

end # module


module SymbolicCurves
using CurveProximityQueries
using IntervalArithmetic
using ForceImport
using StaticArrays
@force using Reduce.Algebra

export SymCurve

struct SymCurve{D, T} <: Curve{D, T}
    f
    s
    limits::Interval{T}
end

function SymCurve(xexpr, yexpr, limits::Interval{T}, θ, xcenter, ycenter) where {T}
    dx = df(xexpr, :t)
    dy = df(yexpr, :t)
    sexpr = int(dx*dx + dy*dy, :t)
    s = eval(:(t->$sexpr))
    x = eval(:(t->$xexpr))
    y = eval(:(t->$yexpr))
    rx(u) =  cos(θ)*x(u) + sin(θ)*y(u) + xcenter
    ry(u) = -sin(θ)*x(u) + cos(θ)*y(u) + ycenter
    F = SymCurveBase(rx, ry)
    S = SymArclengthCurve(s)
    SymCurve{2, T}(F, S, limits)
end

struct SymCurveBase
    x
    y
end

struct SymArclengthCurve
    g
end

function (b::SymCurveBase)(t::Real)
    SVector{2, Float64}(b.x(t), b.y(t))
end

(b::SymArclengthCurve)(t) = b.g(t)

(b::SymCurve)(t) = b.f(t)

import CurveProximityQueries: arclength
function arclength(b::SymCurve, θ::Interval)
    s = b.s(θ.hi) - b.s(θ.lo)
    s > 0 ? sqrt(diam(θ)*s) : 0.0
end

#  function ranunculoid(a::Real, θ, xcenter, ycenter)
#      @vars t
#      x(t) = a*0.05*(6*cos(t) - cos(6*t))
#      y(t) = a*0.05*(6*sin(t) - sin(6*t))
#      SymCurve(x, y, Interval(0, 2π), θ, xcenter, ycenter)
#  end
#
#  function fishcurve(a::Real, θ, xcenter, ycenter)
#      @vars t
#      x(t) = a*0.3*(cos(t) - sin(t)^2/√2)
#      y(t) = a*0.3*sin(t)*cos(t)
#      SymCurve(x, y, Interval(0, 2π), θ, xcenter, ycenter)
#  end
#
#  function triplesine(a::Real, b::Real, c::Real, θ, xcenter, ycenter)
#      @vars t
#      x(t) = 0.2*(cos(a*t) + cos(b*t)/2 + sin(c*t)/3)
#      y(t) = 0.2*(sin(a*t) + sin(b*t)/2 + cos(c*t)/3)
#      SymCurve(x, y, Interval(0, 2π), θ, xcenter, ycenter)
#  end
#
#  function lissajous(p::Real, q::Real, θ, xcenter, ycenter)
#      @vars t
#      x(t) = 0.3*sin(p*t)
#      y(t) = 0.3*sin(q*t)
#      SymCurve(x, y, Interval(0, 2π), θ, xcenter, ycenter)
#  end
#
#  function clothoid(a::Real, θ, xcenter, ycenter)
#      x(u) = a*(sqrt(2) * sqrt(pi) * fresnelc((sqrt(2) * u) / sqrt(pi)) * gamma(1 / 4)) / (8 * gamma(5 / 4))
#      y(u) = a*(3 * sqrt(2) * sqrt(pi) * fresnels((sqrt(2) * u) / sqrt(pi)) * gamma(3 / 4)) / (8 * gamma(7 / 4))
#      s(u) = a*a*u
#      rx(u) =  cos(θ)*x(u) + sin(θ)*y(u) + xcenter
#      ry(u) = -sin(θ)*x(u) + cos(θ)*y(u) + ycenter
#      F = SymCurveBase(rx, ry)
#      S = SymArclengthCurve(s)
#      SymCurve(F, S, Interval(-2π, 2π))
#  end
end


module CreepLaw

# This implements viscous creep laws and routines to compute with them
#
# Note that various simple creep laws are defined in this file;
# more complex ones (such as DislocationCreep) are in separate files
# in the same directory
#
# In case you want to add new creep laws, have a look at how the ones
# here are implemented. Please add tests as well!

using Base: Float64
using Parameters, LaTeXStrings, Unitful
using ..Units
using GeoParams: AbstractMaterialParam
using BibTeX
using ..MaterialParameters: MaterialParamsInfo
import Base.show, GeoParams.param_info

abstract type AbstractCreepLaw{T} <: AbstractMaterialParam end

export  computeCreepLaw_EpsII, computeCreepLaw_TauII,       # calculation routines
        CreepLawVariables,                                     # holds additional parameters required for calculations
        LinearViscous,
        PowerlawViscous,
        param_info

# NOTE: we will likely have to remove this, in favor of multiple dispatch options
"""
 Struct that holds parameters required for creep law calculations (such as P,T, grain size etc.)

You can assign the struct as
```julia-repl
 p = CreepLawVariables(P=0.0, T=0.0, d=1.0)
```
where you can also pass vectors or arrays as values.

Note that, if needed, this can be extended, w/out interfering with existing calculation
"""

@with_kw struct CreepLawVariables{_T,U1,U2,U3,U4}
    P::GeoUnit{_T,U1}  =   100.0MPa   # pressure
    T::GeoUnit{_T,U2}  =   500.0C     # temperature
    d::GeoUnit{_T,U3}  =   1.0cm      # grainsize
    f::GeoUnit{_T,U4}  =   0.0MPa     # water-fugacity
end
CreepLawVariables(args...) = CreepLawVariables(convert.(GeoUnit,args)...)

include("DislocationCreep.jl")
include("DiffusionCreep.jl")
include("Viscosity.jl")

# Linear viscous rheology ------------------------------------------------
"""
    LinearViscous(η=1e20Pa*s)

Defines a linear viscous creeplaw

The (isotopic) linear viscous rheology is given by
```math
    \\tau_{ij} = 2 \\eta \\dot{\\varepsilon}_{ij}
```
or
```math
    \\dot{\\varepsilon}_{ij}  = {\\tau_{ij}  \\over 2 \\eta }
```

where ``\\eta_0`` is the reference viscosity [Pa*s] at reference strain rate ``\\dot{\\varepsilon}_0``[1/s], and ``n`` the power law exponent [].
"""
@with_kw_noshow struct LinearViscous{T,U} <: AbstractCreepLaw{T}
    η::GeoUnit{T,U}     =   1e20Pa*s                # viscosity
    η_val::T            =   1.0
end
LinearViscous(args...) = LinearViscous(convert(GeoUnit,args[1]), args[2])

function param_info(s::LinearViscous) # info about the struct
    return MaterialParamsInfo(Equation = L"\tau_{ij} = 2 \eta  \dot{\varepsilon}_{ij}")
end

# Calculation routines for linear viscous rheologies
function computeCreepLaw_EpsII(TauII, s::LinearViscous, p::CreepLawVariables)
    @unpack η   = s

    return EpsII = (TauII/η)*0.5;
end

function computeCreepLaw_TauII(EpsII, s::LinearViscous, p::CreepLawVariables)
    @unpack η   = s

    return TauII = 2.0*(η*EpsII);
end

# Print info
function show(io::IO, g::LinearViscous)
    print(io, "Linear viscosity: η=$(g.η.val)")
end
#-------------------------------------------------------------------------

# Powerlaw viscous rheology ----------------------------------------------
"""
    PowerlawViscous(η0=1e18Pa*s, n=2.0NoUnits, ε0=1e-15/s)

Defines a power law viscous creeplaw as:

```math
    \\tau_{ij}^n  = 2 \\eta_0 \\left( \\dot{\\varepsilon}_{ij} \\over \\dot{\\varepsilon}_0 \\right)
```
where ``\\eta`` is the effective viscosity [Pa*s].
"""
@with_kw_noshow struct PowerlawViscous{T,U1,U2,U3} <: AbstractCreepLaw{T}
    η0::GeoUnit{T,U1}     =  1e18Pa*s            # reference viscosity
    n::GeoUnit{T,U2}      =  2.0*NoUnits         # powerlaw exponent
    ε0::GeoUnit{T,U3}     =  1e-15*1/s;          # reference strainrate
end
PowerlawViscous(a...) = PowerlawViscous(convert.(GeoUnit,a)...)

# Print info
function show(io::IO, g::PowerlawViscous)
    print(io, "Powerlaw viscosity: η0=$(g.η0.val), n=$(g.n.val), ε0=$(g.ε0.val) ")
end
#-------------------------------------------------------------------------

for myType in (:DiffusionCreep, :DislocationCreep)
    @eval begin
        computeCreepLaw_EpsII(TauII, a::$(myType), args) = computeCreepLaw_EpsII(TauII, a; args...)
        computeCreepLaw_TauII(EpsII, a::$(myType), args) = computeCreepLaw_TauII(EpsII, a; args...)
    end
end

# Help info for the calculation routines
"""
    computeCreepLaw_EpsII(TauII, s:<AbstractCreepLaw, p::CreepLawVariables)

Returns the strainrate invariant ``\\dot{\\varepsilon}_{II}`` for a given deviatoric stress
invariant ``\\tau_{II}`` for any of the viscous creep laws implemented.
``p`` is a struct that can hold various parameters (such as ``P``, ``T``) that the creep law
may need for the calculations

```math
    \\dot{\\varepsilon}_{II}   = f( \\tau_{II} )
```

"""
computeCreepLaw_EpsII

"""
    computeCreepLaw_TauII(EpsII, s:<AbstractCreepLaw, p::CreepLawVariables)

Returns the deviatoric stress invariant ``\\tau_{II}`` for a given strain rate
invariant ``\\dot{\\varepsilon}_{II}`` for any of the viscous creep laws implemented.
``p`` is a struct that can hold various parameters (such as ``P``, ``T``) that the creep law
may need for the calculations

```math
    \\tau_{II}  = f( \\dot{\\varepsilon}_{II} )
```

"""
computeCreepLaw_TauII

end


using Dates
using ArgParse
using Printf
using Random
using GaussianProcesses

# before_all = Dates.now()

include("../../btg.jl")
path = "../../datasets/"
include(path * "load_bitcoin.jl")

data, label = bitcoin(path)

max_label = maximum(label)
label = label ./ max_label

xs = copy(data)
xs = xs/maximum(data)

###
###  Manually enter parsed args entries so we can run this file as a script
###
##
parsed_args = Dict()
#push!(parsed_args, "randseed" => 1234)
push!(parsed_args, "randseed" => 120)
push!(parsed_args, "lmax" => 0.3)
push!(parsed_args, "lmin" => 0.2)
push!(parsed_args, "percent_train" => 0.1)
push!(parsed_args, "p" => 1)

randseed = parsed_args["randseed"]; rng = MersenneTwister(randseed)
Random.seed!(randseed);

function covariate_fun(x, p)
    n = size(x, 1)
    d = size(x, 2)
    if p == 1
        return ones(n, 1)
    elseif p == 1 + d
        return hcat(ones(n), x)
    else
        throw(ArgumentError("Only support constant or linear convariate."))
    end
end

function zero_fun(x, p)
    n = size(x, 1)
    d = size(x, 2)
    if p == 1
        return zeros(n, 1)
    elseif p == 1 + d
        return hcat(zeros(n), x)
    else
        throw(ArgumentError("Only support constant or linear convariate."))
    end
end

# training set
p_train = parsed_args["percent_train"]
n_train = (Int64)(round(p_train * length(data)))
n_test = length(data) - n_train
"""
Input percent train
"""
function sample(inds, percent)
    num = (Int64)(round(percent * length(data)))
    #train = sort(rand(1:490, (num,)))
    ind_train = sort(randperm(length(data))[1:num])
    ind_test = copy(inds)
    deleteat!(ind_test, ind_train)
    #work with indices up until this point
    x_train = xs[ind_train]
    x_test = xs[ind_test]
    return ind_train, x_train, ind_test, x_test
end

ind_train, x_train, ind_test, x_test  = sample(data, p_train) #training and testing pts
train_label = label[ind_train]
test_label = label[ind_test]
y_train = train_label

x = reshape(x_train, n_train, 1)
#Fx = covariate_fun(x, parsed_args["p"])
Fx = zero_fun(x, parsed_args["p"])
trainingData0 = trainingData(x, Fx, train_label)

d = getDimension(trainingData0); n = getNumPts(trainingData0); p = getCovDimension(trainingData0)

x_test = reshape(x_test, n_test, 1)
#Fx_test = covariate_fun(x_test, parsed_args["p"])
Fx_test = zero_fun(x_test, parsed_args["p"])
y_test = test_label

elapsedmin = 0

#parameter setting
# myquadtype = parsed_args["sparse"] ? ["SparseCarlo", "SparseCarlo"] : ["QuasiMonteCarlo", "QuasiMonteCarlo"]
myquadtype = ["Gaussian", "Gaussian"]
#rangeλ = [-3.0 3]
rangeλ = reshape([-0.2], 1, 1)
#rangeλ = reshape([-0.3 -0.1], 1, 2)
lmin = parsed_args["lmin"]
lmax = parsed_args["lmax"]
#rangeθ = [1/lmax^2 1/lmin^2]
#rangeθ = reshape([300.0 450.0], 1, 2)
rangeθ = reshape([350.0], 1, 1)

#@info "rangeθ, rangel:" rangeθ, [lmin lmax]
# rangeθ = [0.111 25]
# build btg model

function load_bitcoin_btg()
    return btg(trainingData0, rangeθ, rangeλ; priorθ = inverseUniform(rangeθ), quadtype = myquadtype)
end

function getseed()
    return randseed
end


using Flux: Dense

in_channel = 3
out_channel = 5
N = 4
adj = [0. 1. 0. 1.;
       1. 0. 1. 0.;
       0. 1. 0. 1.;
       1. 0. 1. 0.]

@testset "layer" begin
    @testset "GCNConv" begin
        gc = GCNConv(adj, in_channel=>out_channel)
        @test size(gc.weight) == (out_channel, in_channel)
        @test size(gc.bias) == (out_channel,)
        @test graph(gc.graph) === adj

        X = rand(in_channel, N)
        Y = gc(X)
        @test size(Y) == (out_channel, N)

        gc = GCNConv(in_channel=>out_channel)
        @test size(gc.weight) == (out_channel, in_channel)
        @test size(gc.bias) == (out_channel,)
        @test isnothing(graph(gc.graph))

        fg = FeaturedGraph(adj, X)
        fg_ = gc(fg)
        @test size(feature(fg_)) == (out_channel, N)
        @test_throws AssertionError gc(X)
    end

    @testset "ChebConv" begin
        k = 6
        cc = ChebConv(adj, in_channel=>out_channel, k)
        @test size(cc.weight) == (out_channel, in_channel, k)
        @test size(cc.bias) == (out_channel,)
        @test size(cc.L̃) == (N, N)
        @test cc.k == k
        @test cc.in_channel == in_channel
        @test cc.out_channel == out_channel

        X = rand(in_channel, N)
        Y = cc(X)
        @test size(Y) == (out_channel, N)

        # With variable graph
        cc = ChebConv(in_channel=>out_channel, k)
        @test size(cc.weight) == (out_channel, in_channel, k)
        @test size(cc.bias) == (out_channel,)
        @test isnothing(cc.L̃)
        @test cc.k == k
        @test cc.in_channel == in_channel
        @test cc.out_channel == out_channel

        fg = FeaturedGraph(adj, X)
        fg_ = cc(fg)
        @test size(feature(fg_)) == (out_channel, N)
        @test_throws AssertionError cc(X)
    end

    @testset "GraphConv" begin
        gc = GraphConv(adj, in_channel=>out_channel)
        @test gc.adjlist == [[2,4], [1,3], [2,4], [1,3]]
        @test size(gc.weight1) == (out_channel, in_channel)
        @test size(gc.weight2) == (out_channel, in_channel)
        @test size(gc.bias) == (out_channel,)

        X = rand(in_channel, N)
        Y = gc(X)
        @test size(Y) == (out_channel, N)

        # With variable graph
        gc = GraphConv(in_channel=>out_channel)
        @test size(gc.weight1) == (out_channel, in_channel)
        @test size(gc.weight2) == (out_channel, in_channel)
        @test size(gc.bias) == (out_channel,)

        X = rand(in_channel, N)
        fg = FeaturedGraph(adj, X)
        fg_ = gc(fg)
        @test size(feature(fg_)) == (out_channel, N)
        @test_throws MethodError gc(X)
    end

    @testset "GATConv" begin
        for heads = [1, 6]
            for concat = [true, false]
                gat = GATConv(adj, in_channel=>out_channel, heads=heads, concat=concat)
                @test gat.adjlist == [[2,4], [1,3], [2,4], [1,3]]
                @test size(gat.weight) == (out_channel * heads, in_channel)
                @test size(gat.bias) == (out_channel * heads,)
                @test size(gat.a) == (2*out_channel, heads, 1)

                X = rand(in_channel, N)
                Y = gat(X)
                if concat
                    @test size(Y) == (out_channel * heads, N)
                else
                    @test size(Y) == (out_channel * heads, 1)
                end

                # With variable graph
                gat = GATConv(in_channel=>out_channel, heads=heads, concat=concat)
                @test size(gat.weight) == (out_channel * heads, in_channel)
                @test size(gat.bias) == (out_channel * heads,)
                @test size(gat.a) == (2*out_channel, heads, 1)

                X = rand(in_channel, N)
                fg = FeaturedGraph(adj, X)
                fg_ = gat(fg)
                Y = feature(fg_)
                if concat
                    @test size(Y) == (out_channel * heads, N)
                else
                    @test size(Y) == (out_channel * heads, 1)
                end
                @test_throws MethodError gat(X)
            end
        end
    end

    @testset "GatedGraphConv" begin
        num_layers = 3
        ggc = GatedGraphConv(adj, out_channel, num_layers)
        @test ggc.adjlist == [[2,4], [1,3], [2,4], [1,3]]
        @test size(ggc.weight) == (out_channel, out_channel, num_layers)

        X = rand(in_channel, N)
        Y = ggc(X)
        @test size(Y) == (out_channel, N)

        # With variable graph
        ggc = GatedGraphConv(out_channel, num_layers)
        @test size(ggc.weight) == (out_channel, out_channel, num_layers)

        X = rand(in_channel, N)
        fg = FeaturedGraph(adj, X)
        fg_ = ggc(fg)
        @test size(feature(fg_)) == (out_channel, N)
        @test_throws MethodError ggc(X)
    end

    @testset "EdgeConv" begin
        ec = EdgeConv(adj, Dense(2*in_channel, out_channel))
        @test ec.adjlist == [[2,4], [1,3], [2,4], [1,3]]

        X = rand(in_channel, N)
        Y = ec(X)
        @test size(Y) == (out_channel, N)

        # With variable graph
        ec = EdgeConv(Dense(2*in_channel, out_channel))

        X = rand(in_channel, N)
        fg = FeaturedGraph(adj, X)
        fg_ = ec(fg)
        @test size(feature(fg_)) == (out_channel, N)
        @test_throws MethodError ec(X)
    end
end


@testset "Quadratic objective - parameter in the affine part" begin

    ipopt = Ipopt.Optimizer()
    MOI.set(ipopt, MOI.RawParameter("print_level"), 0)
    opt_in = MOIU.CachingOptimizer(MOIU.Model{Float64}(), ipopt)
    optimizer = POI.ParametricOptimizer(opt_in)

    Q = [3.0 2.0; 2.0 1.0]
    q = [1.0, 6.0]
    G = [2.0 3.0; 1.0 1.0]
    h = [4.0; 3.0]

    x = MOI.add_variables(optimizer, 2)
    y, cy = MOI.add_constrained_variable(optimizer, POI.Parameter(0))

    quad_terms = MOI.ScalarQuadraticTerm{Float64}[]
    for i in 1:2
        for j in i:2 # indexes (i,j), (j,i) will be mirrored. specify only one kind
            push!(
                quad_terms,
                MOI.ScalarQuadraticTerm(Q[i,j],x[i],x[j])
            )
        end
    end
    aff_terms = MOI.ScalarAffineTerm.(q, [x[1], y])

    objF = MOI.ScalarQuadraticFunction(
                            aff_terms,
                            quad_terms,
                            2.0
                        )

    for i in 1:2
        MOI.add_constraint(
            optimizer,
            MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(G[i,:], x), 0.0),
            MOI.GreaterThan(h[i])
        )
    end

    for x_i in x
        MOI.add_constraint(optimizer, MOI.SingleVariable(x_i), MOI.GreaterThan(0.0))
    end

    MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), objF)
    MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    MOI.optimize!(optimizer)

    @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 6.5, atol = ATOL)

    @test isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 0.0, atol = ATOL)
    @test isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 3.0, atol = ATOL)

    MOI.get(optimizer, MOI.VariablePrimal(), y)
    MOI.set(optimizer, MOI.ConstraintSet(), cy, POI.Parameter(5))
    MOI.optimize!(optimizer)

    @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 36.5, atol = ATOL)

end

@testset "Quadratic constraints - parameters in the quadratic part" begin

    ipopt = Ipopt.Optimizer()
    MOI.set(ipopt, MOI.RawParameter("print_level"), 0)
    opt_in = MOIU.CachingOptimizer(MOIU.Model{Float64}(), ipopt)
    optimizer = POI.ParametricOptimizer(opt_in)

    Q = [3.0 2.0; 2.0 1.0]
    q = [1.0, 6.0]
    G = [2.0 3.0; 1.0 1.0]
    h = [4.0; 3.0]
    R = [4.0, 2.0, 1.0, 0.5]

    x = MOI.add_variables(optimizer, 2)
    y, cy = MOI.add_constrained_variable(optimizer, POI.Parameter(2))
    w, cw = MOI.add_constrained_variable(optimizer, POI.Parameter(3))

    quad_terms = MOI.ScalarQuadraticTerm{Float64}[]

    push!(quad_terms, MOI.ScalarQuadraticTerm(Q[1,1],y,y))
    push!(quad_terms, MOI.ScalarQuadraticTerm(Q[1,2],w,y))
    push!(quad_terms, MOI.ScalarQuadraticTerm(Q[2,2],w,w))

    aff_terms = MOI.ScalarAffineTerm.(q, x)

    quad_cons = MOI.ScalarQuadraticFunction(
                            aff_terms,
                            quad_terms,
                            2.0
                        )

    for i in 1:2
        MOI.add_constraint(
            optimizer,
            MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(G[i,:], x), 0.0),
            MOI.GreaterThan(h[i])
        )
    end

    for x_i in x
        MOI.add_constraint(optimizer, MOI.SingleVariable(x_i), MOI.GreaterThan(3.0))
    end

    objF = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(R, [x[1], x[2], y, w]), 0.0)

    MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), objF)
    MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    MOI.optimize!(optimizer)

    @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 21.5, atol = ATOL)

    @test isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 3.0, atol = ATOL)
    @test isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 3.0, atol = ATOL)

    MOI.get(optimizer, MOI.VariablePrimal(), y)
    @test MOI.get(optimizer, MOI.VariablePrimal(), w) == 3

    MOI.set(optimizer, MOI.ConstraintSet(), cy, POI.Parameter(5))
    MOI.set(optimizer, MOI.ConstraintSet(), cw, POI.Parameter(10))
    MOI.optimize!(optimizer)

    @test MOI.get(optimizer, MOI.VariablePrimal(), w) == 10

    @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 28, atol = ATOL)
    @test isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 3.0, atol = ATOL)
    @test isapprox.(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 3.0, atol = ATOL)

end

@testset "Affine constraint with parameters in QP" begin

    ipopt = Ipopt.Optimizer()
    MOI.set(ipopt, MOI.RawParameter("print_level"), 0)
    opt_in = MOIU.CachingOptimizer(MOIU.Model{Float64}(), ipopt)
    optimizer = POI.ParametricOptimizer(opt_in)

    Q = [4.0 1.0; 1.0 2.0]
    q = [1.0; 1.0]
    G = [1.0 1.0; 1.0 0.0; 0.0 1.0; -1.0 -1.0; -1.0 0.0; 0.0 -1.0]
    h = [1.0; 0.7; 0.7; -1.0; 0.0; 0.0];

    x = MOI.add_variables(optimizer, 2)
    y, cy = MOI.add_constrained_variable(optimizer, POI.Parameter(0))

    # define objective
    quad_terms = MOI.ScalarQuadraticTerm{Float64}[]
    for i in 1:2
        for j in i:2 # indexes (i,j), (j,i) will be mirrored. specify only one kind
            push!(
                quad_terms,
                MOI.ScalarQuadraticTerm(Q[i,j],x[i],x[j])
            )
        end
    end

    objective_function = MOI.ScalarQuadraticFunction(
                            MOI.ScalarAffineTerm.(q, x),
                            quad_terms,
                            0.0
                        )
    MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), objective_function)
    MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    # add constraints
    for i in 1:2
        MOI.add_constraint(
            optimizer,
            MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(G[i,:], [x[1], y]), 0.0),
            MOI.LessThan(h[i])
        )
    end

    for i in 3:4
        MOI.add_constraint(
            optimizer,
            MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(G[i,:], [x[2], y]), 0.0),
            MOI.LessThan(h[i])
        )
    end

    for i in 5:6
        MOI.add_constraint(
            optimizer,
            MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(G[i,:], x), 0.0),
            MOI.LessThan(h[i])
        )
    end

    MOI.optimize!(optimizer)

    @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 2.0, atol = ATOL)
    @test isapprox(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 0, atol = ATOL)
    @test isapprox(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 1.0, atol = ATOL)

    MOI.set(optimizer, MOI.ConstraintSet(), cy, POI.Parameter(-1))
    MOI.optimize!(optimizer)
    @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 6.0, atol = ATOL)
    @test isapprox(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 2.0, atol = ATOL)

end


function azeotrope_pressure(model::EoSModel, T; v0 =nothing)
    TYPE = promote_type(eltype(T),eltype([0.5,0.5]))
#     lb_v = lb_volume(model,x)
    ts = T_scales(model,[0.5,0.5])
    pmix = p_scale(model,[0.5,0.5])
    if v0 === nothing
        v0 = x0_bubble_pressure(model,T,[0.5,0.5])
    end
    len = length(v0[1:end-1])
    #xcache = zeros(eltype(x0),len)
    Fcache = zeros(eltype(v0[1:end-1]),len)
    f! = (F,z) -> Obj_az_pressure(model, F, T, exp10(z[1]), exp10(z[2]), z[3:end],z[3:end],ts,pmix)
    r  =Solvers.nlsolve(f!,v0[1:end-1],LineSearch(Newton()))
    sol = Solvers.x_sol(r)
    v_l = exp10(sol[1])
    v_v = exp10(sol[2])
    y = FractionVector(sol[3:end])
    P_sat = pressure(model,v_l,T,y)
    return (P_sat, v_l, v_v, y)
end

function Obj_az_pressure(model::EoSModel, F, T, v_l, v_v, x, y,ts,ps)
    x   = FractionVector(x)
    y   = FractionVector(y) #julia magic, check misc.jl
    μ_l = VT_chemical_potential(model,v_l,T,x)
    μ_v = VT_chemical_potential(model,v_v,T,y)
    p_l = pressure(model,v_l,T,x)
    p_v = pressure(model,v_v,T,y)
    for i in 1:length(x)
        F[i] = (μ_l[i]-μ_v[i])/(R̄*ts[i])
    end
    F[end] = (p_l-p_v)/ps
    return F
end


#=
Force calculations according to Pammer OConner 2013
where force magnitude is estimated based on the degree of bending
in the same section of whisker in every frame selected to be high
SNR (Point P)

=#

#=
x, y - whisker coordinates
theta_f - whisker angle (radians)
curv - whisker curvature
ii - index of contact
i_p - index of high SNR point - we should use the second DLC point for this (as long as it isn't past pole)
E - Elastic Modulus of Whisker
I_p - Moment of inertia of whisker at point p I_p = 1.0
=#
function calc_force(x::Array{Float64,1},y::Array{Float64,1},theta_f::Float64,curv::Float64,ii::Int,i_p::Int,curv_0=0.0; I_p = 1.0, E=1.0)

    #curv_0 intrinsic curvature of the whisker
    delta_kappa = curv - curv_0 #Change in curvature

    #x_f, y_f follicle x and y
    x_f = x[end]
    y_f = y[end]

    #x_c, y_c contact x and y
    x_c = x[ii]
    y_c = y[ii]

    #x_p, y_p point_p x and y
    x_p = x[i_p]
    y_p = y[i_p]

    theta_contact = 0.0
    try
        #We will first look outward from the point of contact. This will fail if there is not >30 pixels of length
        #beyond that point
        c_i = get_contact_ind(x,y,ii,30.0,false)
        theta_contact = contact_angle(x[ii],x[c_i],y[ii],y[c_i])
    catch
        #if this fails, we will try making a vector from
        c_i = get_contact_ind(x,y,ii,30.0,true)
        theta_contact = contact_angle(x[c_i],x[ii],y[c_i],y[ii])
    end

    (theta_0,r_0) = calc_r_theta(x_f,x_c,y_f,y_c)
    (theta_p,r_p) = calc_r_theta(x_p,x_c,y_p,y_c)

    (F, M_0) = force_moment(delta_kappa,r_p,r_0,theta_p,theta_0,theta_contact,I_p = I_p, E = E)

    (F_ax, F_lat) = decompose_force(F,theta_f,theta_contact)

    (M_0,F_ax,F_lat,F,theta_contact)
end

#=
Calculate Force and Moment
delta_kappa - change in curvature from intrinsic
r_p - distance from point of contact to point p (pixels)
r_0 - distance from follicle to point of contact (pixels)
theta_p - angle from point_p to point of contact (radians)
theta_0 - angle from follicle to point of contact (radians)
theta_contact - angle that whisker is pointing at point of contact (radians)
=#
function force_moment(delta_kappa::Real,r_p::Real,r_0::Real,theta_p::Real,
    theta_0::Real,theta_contact::Real;I_p = 1.0, E=1.0)

    F = abs(delta_kappa * E * I_p / (r_p * cos(theta_p - theta_contact)))

    M_0 = r_0 * F * cos(theta_0 - theta_contact)

    (F, M_0)
end

#=
F - Total Contact Force
theta_f - whisker angle (radians)
theta_contact - angle of contact (radians)
=#
function decompose_force(F::Real,theta_f::Real,theta_contact::Real)
    F_ax = F * sin(theta_f - theta_contact)
    F_lat = F * cos(theta_f - theta_contact)
    (F_ax,F_lat)
end

function calc_r_theta(x1::Real,x2::Real,y1::Real,y2::Real)

    theta_p = atan((y2 - y1),(x2 - x1))
    r_p = sqrt((x2 - x1)^2 + (y2 - y1)^2)
    (theta_p,r_p)
end

function contact_angle(x1::Real,x2::Real,y1::Real,y2::Real)
    atan(y2-y1,x2-x1)
end

#=
Finds
=#
function get_contact_ind(x::Array{T,1},y::Array{T,1},ii,s_thres = 10.0,forward_dir=true) where T
    s = 0.0

    x_0 = x[ii]
    y_0 = y[ii]

    i = ii
    while(s < s_thres)

        s = sqrt((x[i] - x_0)^2 + (y[i] - y_0)^2)

        if forward_dir
            i = i + 1
        else
            i = i - 1
        end
    end
    i
end

#=
Positive sign = retraction
negative sign = protraction
=#
function get_theta_n(c,v::Array{Float64,1},f_t::Array{Float64,1},t_c::Array{Float64,1},t=trues(length(f_t)))

    c_sign = zeros(Float64,length(v))
    t_n = zeros(Float64,length(v))

    i=1
    while (i < length(c_sign))

        if c[i]
            j = findnext(.!c,i)

            rr = i:(j-1)
            rr = rr[t[i:(j-1)]]

            c_sign[rr] .= sign(mean(v[rr]))

            for ii in rr
                if c_sign[ii] > 0.0 #retraction
                    t_n[ii] = t_c[ii] + pi/2
                else  #protraction
                    t_n[ii] = t_c[ii] - pi/2
                end
            end

            i = j
        else
           i = i + 1
        end
    end

    (c_sign, t_n)
end

#=

=#
function calculate_all_forces(xx,yy,p,c,aa::Array{Float64,1},
    curv::Array{Float64,1},tracked=trues(length(c)); i_p_loc=50.0,E_in=1.0,moment_of_inertia_p=1.0)

    F_ax=zeros(Float64,length(c))
    F_lat=zeros(Float64,length(c))
    M=zeros(Float64,length(c))
    F_t=zeros(Float64,length(c))
    theta_c = zeros(Float64,length(c))
    F_calc=falses(length(c))

    for i=1:length(c)
        if ((c[i])&(length(xx[i])>1))&(tracked[i])

            (new_x, new_y) = interpolate_whisker(xx[i],yy[i])

            #ii - index of contact
            ii=calc_p_dist(new_x,new_y,p[i,1],p[i,2])[2]

            #i_p - index of high SNR point
            i_p=culm_dist(new_x,new_y,i_p_loc)

            try
                (M[i],F_ax[i],F_lat[i],F_t[i],theta_c[i])=calc_force(new_x,new_y,aa[i],curv[i],ii,i_p,E=E_in,I_p=moment_of_inertia_p)
                F_calc[i] = true
                !isfinite(F_t[i]) && (F_t[i] = 0.0; F_calc[i] = false)
                !isfinite(F_lat[i]) && (F_lat[i] = 0.0; F_calc[i] = false)
                !isfinite(F_ax[i]) && (F_ax[i] = 0.0; F_calc[i] = false)
                !isfinite(M[i]) && (M[i] = 0.0; F_calc[i] = false)
            catch
            end
        end
    end

    (F_ax,F_lat,M,F_t,theta_c,F_calc)
end


struct SecantRelaxation <: AbstractRelaxation end

function reformulate_unary_quadratic_term!(
    model::MOI.ModelLike,
    reformulation::Reformulation{SecantRelaxation},
    _x::VI,
    _y::VI,
    l::Float64,
    u::Float64,
)
    x, y = _map_to_interval_if_needed(model, _x, _y, l, u)
    # Secant cut: y <= x
    MOI.add_constraint(model, 1.0SV(x) - SV(y), MOI.GreaterThan(0.0))
    return nothing
end


using RICoverAlg

# Queueing problems
m = 2;  n = 40 ; lmd = 1.0
g = ones(n); g /=sum(g)
U = zeros(m, n)
for i in (1:n)
   U[2,i] = 28 - i
end
Z = exp.(U/lmd)

@test cancel_zero_alternatives(U,g) == (U,g,findall(a->(!isapprox(a,0; atol = 0)), g))
@test do_cover_iterations(Z,g,[.5,.5]) ≈ [0.2901315939203102, 0.7098684060796899]
@test run_cover_algorithm(Z,g,
                  initialIterations  = 10,
                  aTolSetChoiceProbToZero = 10e-6,
                  aTolerance4Stopping = 0.0,
                  maxIterations = 9999)[2] ≈ 8.880930086724845
@test cancel_zero_alternatives(Z,g) == (Z,g,collect(1:1:40))
@test cancel_zero_alternatives(Z,[0.,1.]) == (Z[[2],:],[1.],[2])

# Matejka & McKay 2015, RBT
U = [0 1 0 1;
    0 0 1 1;
    1/2 1/2 1/2 1/2]
lmd = 0.4
g = [1+1; 1+1]./4
Z,shiftterm=  exp.((U[:,[1,4]].-.5)/lmd)   ,  .5
m = size(Z)[1]
p = ones(m)./m
@test do_cover_iterations(Z,g,p) == [0.2522629174954305
 0.2522629174954305
 0.495474165009139]


abstract type OrthogonalPolyBasis end

struct LegendrePolyBasis{T,A} <: OrthogonalPolyBasis
    n::T
    weights::A

    function LegendrePolyBasis(n; wType = Float64)
        weights_helper = zeros(wType, n, n)
        weights_helper[1, 1] = one(wType)
        weights_helper[2, 2] = one(wType)
        for i = 3:n
            real_index = i - 1
            two_below = ((real_index - 1) / real_index) .* weights_helper[i-2, :]
            one_below =
                ((2 * real_index - 1) / real_index) .*
                [[zero(wType)]; weights_helper[i-1, :][Base.OneTo(n - 1)]]
            weights_helper[i, :] .= one_below .- two_below
        end
        weights =
            [Tuple(weights_helper[i, j] for j in Base.OneTo(i)) for i in Base.OneTo(n)]
        return new{typeof(n),typeof(weights)}(n, weights)
    end
end

function (b::LegendrePolyBasis)(x, i::Int64)
    @unpack weights = b
    @evalpoly(x, weights[i+1]...)
end

struct HermitePolyBasis{T,A} <: OrthogonalPolyBasis
    n::T
    weights::A

    function HermitePolyBasis(n; wType = Int64)
        weights_helper = zeros(wType, n, n)
        weights_helper[1, 1] = one(wType)
        weights_helper[2, 2] = 2
        for i = 3:n
            real_index = i - 1
            weights_helper[i, 1] = -weights_helper[i-1, 2]
            for j = 2:i-1
                weights_helper[i, j] =
                    2 * weights_helper[i-1, j-1] - j * weights_helper[i-1, j+1]
            end
            weights_helper[i, i] = 2 * weights_helper[i-1, i-1]
        end
        weights =
            [Tuple(weights_helper[i, j] for j in Base.OneTo(i)) for i in Base.OneTo(n)]
        return new{typeof(n),typeof(weights)}(n, weights)
    end
end

function (b::HermitePolyBasis)(x, i::Int64)
    @unpack weights = b
    @evalpoly(x, weights[i+1]...)
end


module Econlite

using Parameters
using Markdown
using Unrolled
using LinearAlgebra
using Statistics
import Random.seed!

# Single good preferences
export IsoElastic, LogUtility, CARA, RiskNeutral
export util, invutil, dutil, invdutil
include("utility.jl")

# Aggregators
export aggr, invaggr, partial_aggr, invpartial_aggr, gradient_aggr, gradient_aggr!
export Armington, CobbDouglas, AggLinear
include("aggregators.jl")

# Taxes
export net, gross, tax
export GouveiaStrauss, AdValorem, NullTax
include("taxes.jl")

# Maximum
export generalized_argmax!
export SumNormalize, SumRaw
include("maximum.jl")

# Generate grid
export gridpoints
include("gridpoints.jl")

# Iteration
export SolutionObject, IterationSpec, DisplayMod, NoDisplay
export within_tolerance, contract!
include("iteration.jl")

# Markov chains
export IndexedMarkovChain, MarkovChain, MarkovChainExtended
export expect_markov, transition, markov_invariant, draw_next,
    markov_genpath, markov_genpath_seeded, markov_genpath!, mean,
    indeptensor, makemean1!
include("markovchain.jl")

export rouwenhorst, rouwenhorst_mc
include("rouwenhorst.jl")

end


const MACHEP = 1.2e-16
const DWARF = 1.0e-38

function enorm(n::Int, x::Vector{Float64})
  const rdwarf = 3.834e-20
  const rgiant = 1.304e19

  s1 = s2 = s3 = x1max = x3max = 0.0

  floatn = float(n)
  agiant = rgiant / floatn

  for i = 1:n
    @inbounds xabs = abs(x[i])
    if xabs > rdwarf && xabs < agiant
      # sum for intermediate components
      s2 += xabs * xabs
      continue
    end

    if xabs > rdwarf
      # sum for larger components
      if xabs > x1max
        temp = x1max / xabs
        s1 = 1.0 + s1 * temp * temp
        x1max = xabs
      else
        temp = xabs / x1max
        s1 += temp * temp
      end

      continue
    end

    # sum for smaller components
    if xabs > x3max
      temp = x3max / xabs
      s3 = 1.0 + s3 * temp * temp
      x3max = xabs
    else
      if xabs != 0.0
        temp = xabs / x3max
        s3 += temp * temp
      end
    end
  end

  # Calculation of norm
  if s1 != 0.0
    temp = s1 + (s2 / x1max) / x1max
    return x1max * sqrt(temp)
  end

  if s2 != 0.0
    if s2 >= x3max
      temp = s2 * (1.0 + (x3max / s2) * (x3max * s3))
    else
      temp = x3max * ((s2 / x3max) + (x3max * s3))
    end
    ans = sqrt(temp)
  else
    ans = x3max * sqrt(s3)
  end

  return ans
end

# Forward Difference Approximation #
function fdjac2!(m::Int, n::Int, x::Vector{Float64}, fvec::Vector{Float64}, fjac::Matrix{Float64}, ::Int, iflag::Int, epsfcn::Float64, wa::Vector{Float64}, fcn!::Function)
  # returns:
  # fjac :  is an output m by n array which contains the
  #         approximation to the jacobian matrix evaluated at x.
  temp  = max(epsfcn, MACHEP)
  eps_ = sqrt(temp)
  ij = 1
  @simd for j = 1:n
    @inbounds temp = x[j]
    h = eps_ * abs(temp)
    if h == 0.0
      h = eps_
    end

    @inbounds x[j] = temp + h
    fcn!(m, n, x, wa)
    if iflag < 0
      return fjac
    end
    @inbounds x[j] = temp
    for i = 1:m
      @inbounds fjac[ij] = (wa[i] - fvec[i]) / h
      ij += 1
    end
  end

  return fjac
end

function qrfac!(m::Int, n::Int, a::Matrix{Float64}, ::Int, pivot::Int, ipvt::Vector{Int}, ::Int, rdiag::Vector{Float64}, acnorm::Vector{Float64}, wa::Vector{Float64})
  # returns:
  # a :     is an m by n array. on input a contains the matrix for
  #         which the qr factorization is to be computed. on output
  #         the strict upper trapezoidal part of a contains the strict
  #         upper trapezoidal part of r, and the lower trapezoidal
  #         part of a contains a factored form of q (the non-trivial
  #         elements of the u vectors described above).
  # ipvt:   is an integer output array of length lipvt. ipvt
  #         defines the permutation matrix p such that a*p = q*r.
  #         column j of p is column ipvt(j) of the identity matrix.
  #         if pivot is false, ipvt is not referenced.
  # rdiag:  is an output array of length n which contains the
  #         diagonal elements of r.
  # acnorm: is an output array of length n which contains the
  #         norms of the corresponding columns of the input matrix a.
  #         if this information is not needed, then acnorm can coincide
  #         with rdiag.
  # Compute the initial column norms and initialize several arrays
  ij = 1
  @simd for j = 1:n
    @inbounds acnorm[j] = enorm(m, a[:, ij])
    @inbounds rdiag[j] = acnorm[j]
    @inbounds wa[j] = rdiag[j]
    if pivot != 0
      @inbounds ipvt[j] = j
    end
    ij += 1
  end

  # Reduce a to r with householder transformations
  minmn = min(m, n)
  for j = 1:minmn
    if pivot == 0
      @goto L40
    end

    # bring the column of the largest norm into the pivot position
    kmax = j
    @simd for k = j:n
      @inbounds if rdiag[k] > rdiag[kmax]
        kmax = k
      end
    end

    if kmax == j
      @goto L40
    end
    ij = (m * (j - 1)) + 1
    jj = (m * (kmax - 1)) + 1

    @simd for i = 1:m
      @inbounds temp = a[ij]
      @inbounds a[ij] = a[jj]
      @inbounds a[jj] = temp
      ij += 1
      jj += 1
    end

    @inbounds rdiag[kmax] = rdiag[j]
    @inbounds wa[kmax] = wa[j]
    @inbounds k = ipvt[j]
    @inbounds ipvt[j] = ipvt[kmax]
    @inbounds ipvt[kmax] = k

    @label L40
    # Compute the householder transformation to reduce the j-th column of a to
    # a multiple of the j-th unit vector
    jj = j + m * (j - 1)
    # jj_end = jj + (m - j)
    @inbounds ajnorm = enorm(m - (j - 1), a[jj:end])
    if ajnorm == 0.0
      @goto L100
    end
    @inbounds if a[jj] < 0.0
      ajnorm = -ajnorm
    end
    ij = jj

    @simd for i = j:m
      @inbounds a[ij] /= ajnorm
      ij += 1

    end

    @inbounds a[jj] += 1.0

    # Apply the transformation to the remaining columns
    # and update the norms
    jp1 = j + 1
    if jp1 <= n
      @simd for k = jp1:n
        sum_ = 0.0
        ij = j + m * (k - 1)
        jj = j + m * (j - 1)

        for i = j:m
          @inbounds sum_ += a[jj] * a[ij]
          ij += 1
          jj += 1
        end

        @inbounds temp = sum_ / a[j + m * (j - 1)]
        ij = j + m * (k - 1)
        jj = j + m * (j - 1)
        for i = j:m
          @inbounds a[ij] -= temp * a[jj]
          ij += 1
          jj += 1
        end

        if pivot != 0 && rdiag[k] != 0.0
          @inbounds temp = a[j + m * (k - 1)]  / rdiag[k]
          temp = max(0.0, 1.0 - temp * temp)
          @inbounds rdiag[k] *= sqrt(temp)
          @inbounds temp = rdiag[k]/wa[k]

          if 0.05 * temp * temp <= MACHEP
            a_start = jp1 + m * (k - 1)
            @inbounds rdiag[k] = enorm(m - j, a[a_start:end])
            @inbounds wa[k] = rdiag[k]
          end
        end
      end
    end

    @label L100

    @inbounds rdiag[j] = -ajnorm
  end

  return a, ipvt, rdiag, acnorm
end

function qrsolv!(n::Int, r::Matrix{Float64}, ldr::Int, ipvt::Vector{Int}, diag_::Vector{Float64}, qtb::Vector{Float64}, x::Vector{Float64},
                sdiag::Vector{Float64}, wa::Vector{Float64})
  # copy r and (q transpose) * b to preserve input and initialize s, in particular
  # save the diagonal elements of r in x
  kk = 1
  @simd for j = 1:n
    ij = kk
    ik = kk
    for i = j:n
      @inbounds r[ij] = r[ik]
      ij += 1
      ik += ldr
    end
    @inbounds x[j] = r[kk]
    @inbounds wa[j] = qtb[j]
    kk += ldr + 1
  end

  # eliminate the diagonal matrix d using a given rotation
  for j = 1:n
    @inbounds l = ipvt[j]
    @inbounds if diag_[l] == 0.0
      @goto L90
    end
    for k = j:n
      @inbounds sdiag[k] = 0.0
    end
    @inbounds sdiag[j] = diag_[l]

    # transformations to eliminate the row of d, modify only a single element
    # of (q transpose) * b beyond the first n, which is initially zero
    qtbpj = 0.0
    for k = j:n
      # determine a given rotation which elminates the appropriate element
      # in the current row of d
      @inbounds if sdiag[k] == 0.0
        continue
      end
      kk = k + ldr * (k - 1)
      @inbounds if abs(r[kk]) < abs(sdiag[k])
        @inbounds cotan_ = r[kk] / sdiag[k]
        sin_ = 0.5 / sqrt(0.25 + 0.25 * cotan_ * cotan_)
        cos_ = sin_ * cotan_
      else
        @inbounds tan_ = sdiag[k] / r[kk]
        cos_ = 0.5 / sqrt(0.25 + 0.25 * tan_ * tan_)
        sin_ = cos_ * tan_
      end

      # compute the modified diagonal element of r and the modified element of
      # (q transpose) * b, 0
      @inbounds r[kk] = cos_ * r[kk] + sin_* sdiag[k]
      @inbounds temp = cos_ * wa[k] + sin_ * qtbpj
      @inbounds qtbpj = -sin_ * wa[k] + cos_ * qtbpj
      @inbounds wa[k] = temp

      # accumulate the transformation of the row s
      kp1 = k + 1
      if n >= kp1
        ik = kk + 1
        for i = kp1:n
          @inbounds temp = cos_ * r[ik] + sin_ * sdiag[i]
          @inbounds sdiag[i] = -sin_ * r[ik] + cos_ * sdiag[i]
          @inbounds r[ik] = temp
          ik += 1
        end
      end
    end

    @label L90

    # store the diagonal element of s and restore the corresponding diagonal
    # element of r
    kk = j + ldr * (j - 1)
    @inbounds sdiag[j] = r[kk]
    @inbounds r[kk] = x[j]
  end

  # Solve the triangular system for z, if the system is singular, then obtain
  # a least squares solution
  nsing = n
  @simd for j = 1:n
    @inbounds if sdiag[j] == 0.0 && nsing == n
      nsing = j - 1
    end

    if nsing < n
      @inbounds wa[j] = 0.0
    end
  end

  if nsing < 1
    @goto L150
  end

  @simd for k = 1:nsing
    j = nsing - k + 1
    sum_ = 0.0
    jp1 = j + 1
    if nsing >= jp1
      ij = jp1 + ldr * (j - 1)
      for i = jp1:nsing
        @inbounds sum_ += r[ij] * wa[i]
        ij += 1
      end
    end
    @inbounds wa[j] = (wa[j] - sum_) / sdiag[j]
  end

  @label L150
  # permute the components of z back to components of x
  @simd for j = 1:n
    # l = ipvt[j]
    @inbounds x[ipvt[j]] = wa[j]
  end

  return r, x, sdiag
end

function lmpar!(n::Int, r::Matrix{Float64}, ldr::Int, ipvt::Vector{Int}, diag_::Vector{Float64}, qtb::Vector{Float64}, delta::Float64, par::Float64,
              x::Vector{Float64}, sdiag::Vector{Float64}, wa1::Vector{Float64}, wa2::Vector{Float64})

  # Compute and store in x the gauss-newton direction.  If the jacobin is rank-deficient
  # obtain a least-squares solution

  nsing = n
  jj = 1
  @simd for j=1:n
    @inbounds wa1[j] = qtb[j]
    @inbounds if r[jj] == 0.0 && nsing == n
      nsing = j - 1
    end
    if nsing < n
      @inbounds wa1[j] = 0.0
    end

    jj += ldr + 1
  end

  if nsing >= 1
    @simd for k = 1:nsing
      j = nsing - k + 1
      @inbounds wa1[j] = wa1[j] / r[j + ldr * (j - 1)]
      @inbounds temp = wa1[j]
      jm1 = j - 1
      if jm1 > 0
        ij = ldr * (j - 1) + 1
        for i = 1:jm1 + 1
          @inbounds wa1[i] -= r[ij] * temp
          ij += 1
        end
      end
    end
  end

  @simd for j = 1:n
    @inbounds l = ipvt[j]
    @inbounds x[l] = wa1[j]
  end

  # initialize the iteration counter, evaluate the function at the origin
  # and test for acceptance of the gauss-newton direction
  iter = 0
  @simd for j = 1:n
    @inbounds wa2[j] = diag_[j] * x[j]
  end

  dxnorm = enorm(n, wa2)
  fp = dxnorm - delta

  fp <= 0.1 * delta && @goto L220

  # if the jacobin is not rank deficient, the newton step provides a lower bound
  # parl, for the zero of the function.  Otherwise set this bound to zero
  parl = 0.0
  if nsing >= n
    @simd for j = 1:n
      @inbounds l = ipvt[j]
      @inbounds wa1[l] = diag_[l] * (wa2[l] / dxnorm)
    end
    jj = 1
    @simd for j = 1:n
      sum_ = 0.0
      jm1 = j - 1
      if jm1 >= 1
        ij = jj
        for i = 1:jm1 + 1
          @inbounds sum_ += r[ij] * wa1[i]
          ij += 1
        end
      end
      @inbounds wa1[j] = (wa1[j] - sum_) / r[j + ldr * (j - 1)]
      jj += ldr
    end
    temp = enorm(n, wa1)
    parl = ((fp / delta) / temp) / temp
  end

  jj = 1
  @simd for j = 1:n
    sum_ = 0.0
    ij = jj
    for i = 1:j
      @inbounds sum_ += r[ij] * qtb[i]
      ij += 1
    end
    @inbounds l = ipvt[j]
    @inbounds wa1[j] = sum_ / diag_[l]
    jj += ldr
  end

  gnorm = enorm(n, wa1)
  paru = gnorm / delta
  if paru == 0.0
    paru = DWARF / min(delta, 0.1)
  end

  # if the input par lies outside of the interval (parl, paru), set the par to
  # the closer endpoint
  par = max(par, parl)
  par = min(par, paru)

  if par == 0.0
    par = gnorm / dxnorm
  end

  @label L150
  iter += 1

  # eval the function at the current value of par
  if par == 0.0
    par = max(DWARF, 0.001 * paru)
  end

  temp = sqrt(par)
  # for j = 1:n
  #   wa1[j] = temp * diag_[j]
  # end
  wa1 = temp * diag_

  qrsolv!(n, r, ldr, ipvt, wa1, qtb, x, sdiag, wa2)

  @simd for j = 1:n
    @inbounds wa2[j] = diag_[j] * x[j]
  end

  dxnorm = enorm(n, wa2)
  temp = fp
  fp = dxnorm - delta

  # if the function is small enough, accept the current value of par.  also test
  # for the exceptional cases where parl is zero or the number of iterations
  # has reached 10
  (abs(fp) <= 0.1 * delta || (parl == 0.0 && fp <= temp && temp < 0.0) || iter == 10) && @goto L220

  # compute the newton correction
  @simd for j = 1:n
    @inbounds l = ipvt[j]
    @inbounds wa1[j] = diag_[l] * (wa2[l] / dxnorm)
  end

  jj = 1
  @simd for j = 1:n
    @inbounds wa1[j] = wa1[j] / sdiag[j]
    @inbounds temp = wa1[j]
    jp1 = j + 1
    if jp1 <= n
      ij = jp1 + jj
      for i = jp1:n
        @inbounds wa1[i] -= r[ij] * temp
        ij += 1
      end
    end
    jj += ldr
  end
  temp = enorm(n, wa1)
  parc = ((fp / delta) / temp) / temp

  # depending on the sign of the function, update parl or paru
  if fp > 0.0
    parl = max(parl, par)
  end

  if fp < 0.0
    paru = min(paru, par)
  end

  # compute an improved estimate for par
  par = max(parl, par + parc)

  # end of iteration
  @goto L150

  @label L220
  # termination
  if iter == 0
    par = 0.0
  end

  return par
end

function lmdif!(m::Int, n::Int, x::Vector{Float64}, fvec::Vector{Float64}, ftol::Float64, xtol::Float64, gtol::Float64, maxFev::Int, epsfcn::Float64,
                diag_::Vector{Float64}, mode::Int, factor_::Float64, nprint::Int, info_::Int, nfev::Int, fjac::Matrix{Float64}, ldfjac::Int, ipvt::Vector{Int}, qtf::Vector{Float64},
                wa1::Vector{Float64}, wa2::Vector{Float64}, wa3::Vector{Float64}, wa4::Vector{Float64}, fcn!::Function)

  delta = 0.0
  xnorm = 0.0
  info_ = 0
  iflag = 0
  nfev = 0

  # checking for errors
  (n <= 0 || m < n || ldfjac < m || ftol < 0.0 || xtol < 0.0 || gtol < 0.0 || maxFev <= 0 || factor_ <= 0.0) && @goto L300

  if mode == 2
    for j = 1:n
      @inbounds if diag_[j] <= 0.0
        @goto L300
      end
    end
  end

  # Evaluate the function at its starting point and calculate its norm
  iflag = 1
  fcn!(m, n, x, fvec)
  nfev = 1
  if iflag < 0
    @goto L300
  end
  fnorm = enorm(m, fvec)

  # Initialize the levenberg-marquardt param and iteration counter
  par = 0.0
  iter = 1

  @label L30
  # Calcualte the Jacobian matrix
  iflag = 2
  # if useJac
  #   jacFcn!(m, n, x, fjac, iflag)
  # else
  fdjac2!(m, n, x, fvec, fjac, ldfjac, iflag, epsfcn, wa4, fcn!)
  # end
  # fjac = jacFcn!(x)

  nfev += n

  if iflag < 0
    @goto L300
  end

  # if requested, call fcn to enable printing of iterates
  if nprint > 0
    iflag = 0
    if mod(iter - 1, nprint) == 0
      fcn!(m, n, x, fvec)
      if iflag < 0
        @goto L300
      end
    end
  end

  # compute the QR factorization of the Jacobian
  qrfac!(m, n, fjac, ldfjac, 1, ipvt, n, wa1, wa2, wa3)

  # on the first iteration and if mode is 1, scale according to the norms
  # of the columns in the initial jacobin
  if iter == 1
    if mode != 2
      @simd for j = 1:n
        @inbounds diag_[j] = wa2[j]
        if wa2[j] == 0.0
          @inbounds diag_[j] = 1.0
        end
      end
    end

    # on the first iteration, calculate the norm of the scaled x and initialize
    # the step bound delta
    @simd for j = 1:n
      @inbounds wa3[j] = diag_[j] * x[j]
    end

    xnorm = enorm(n, wa3)
    delta = factor_ * xnorm
    if delta == 0.0
      delta = factor_
    end

  end

  # form (q transpose) * fvec and store the first n components in qtf
  @simd for i = 1:m
    @inbounds wa4[i] = fvec[i]
  end

  jj = 1
  @simd for j = 1:n
    @inbounds temp3 = fjac[jj]
    if temp3 != 0.0
      sum_ = 0.0
      ij = jj
      for i = j:m
        @inbounds sum_ += fjac[ij] * wa4[i]
        ij += 1
      end
      temp = -sum_ / temp3
      ij = jj
      for i = j:m
        @inbounds wa4[i] += fjac[ij] * temp
        ij += 1
      end
    end
    @inbounds fjac[jj] = wa1[j]
    jj += m + 1
    @inbounds qtf[j] = wa4[j]
  end

  # println("wa1: ", wa1)
  # println("wa2: ", wa2)
  # println("qtf: ", qtf)
  # println("wa4: ", wa4)
  # println("fjac: ", fjac)
  # error("break")

  # compute the norm of the scaled gradient
  gnorm = 0.0
  if fnorm != 0.0
    jj = 1
    @simd for j = 1:n
      @inbounds l = ipvt[j]
      @inbounds if wa2[l] != 0.0
        sum_ = 0.0
        ij = jj
        for i = 1:j
          @inbounds sum_ += fjac[ij] * (qtf[i] / fnorm)
          ij += 1
        end
        @inbounds gnorm = max(gnorm, abs(sum_ / wa2[l]))
      end
      jj += m
    end
  end

  # test for convergence of the gradient norm
  if gnorm <= gtol
    info_ = 4
  end

  if info_ != 0
    @goto L300
  end

  # rescale if necessary
  if mode != 2
    @simd for j = 1:n
      @inbounds diag_[j] = max(diag_[j], wa2[j])
    end
  end

  # Beginning of inner loop
  @label L200

  # determine the levenberg-marquardt param
  par = lmpar!(n, fjac, ldfjac, ipvt, diag_, qtf, delta, par, wa1, wa2, wa3, wa4)

  # println("AFTER: ")
  # println("fvec: ", fvec)
  # println("fjac: ", fjac)
  # println("wa1: ", wa1)
  # println("wa2: ", wa2)
  # println("wa3: ", wa3)
  # println("wa4: ", wa4)
  # println("ipvt: ", ipvt)
  # println("diag: ", diag_)
  # println("qtf: ", qtf)
  # println("delta: ", delta)
  #
  # error("STOP")

  # store the direction of p and x + p, calculate the norm of p
  @simd for j = 1:n
    @inbounds wa1[j] = -wa1[j]
    @inbounds wa2[j] = x[j] + wa1[j]
    @inbounds wa3[j] = diag_[j] * wa1[j]
  end

  pnorm = enorm(n, wa3)

  # on the initial iteration, adjust the initial step bound
  if iter == 1
    delta = min(delta, pnorm)
  end

  iflag = 1
  fcn!(m, n, wa2, wa4)
  nfev += 1

  if iflag < 0
    @goto L300
  end

  fnorm1 = enorm(m, wa4)
  # println(wa4)

  # Compute the scaled actual reduction
  actred = -1.0

  # println("fnorm: ", fnorm)
  # println("fnorm1: ", fnorm1)

  if (0.1 * fnorm1) < fnorm
    temp = fnorm1 / fnorm
    actred = 1.0 - temp * temp
  end

  # Compute the scaled predicted reduction and the scaled directional derivative
  jj = 1
  @simd for j = 1:n
    @inbounds wa3[j] = 0.0
    @inbounds l = ipvt[j]
    @inbounds temp = wa1[l]
    ij = jj
    for i = 1:j
      @inbounds wa3[i] += fjac[ij] * temp
      ij += 1
    end

    jj += m
  end

  temp1 = enorm(n, wa3) / fnorm
  temp2 = (sqrt(par) * pnorm) / fnorm
  prered = temp1 * temp1 + (temp2 * temp2) / 0.5
  dirder = -(temp1 * temp1 + temp2 * temp2)

  # println("temp1: ", temp1)
  # println("temp2: ", temp2)
  # println("prered: ", prered)
  # println("dirder: ", dirder)

  # Compute the ratio of the actual to the predicted reduction
  ratio = 0.0
  if prered != 0.0
    ratio = actred / prered
  end

  if ratio <= 0.25
    if actred >= 0.0
      temp = 0.5
    else
      temp = 0.5 * dirder / (dirder + 0.5 * actred)
    end

    if ((0.1 * fnorm1) >= fnorm) || temp < 0.1
      temp = 0.1
    end

    delta = temp * min(delta, pnorm/0.1)
    par = par / temp
  else
    if par == 0.0  || ratio >= 0.75
      delta = pnorm / 0.5
      par = 0.5 * par
    end
  end
  # test for successful iteration
  if ratio >= 0.0001
    @simd for j = 1:n
      @inbounds x[j] = wa2[j]
      @inbounds wa2[j] = diag_[j] * x[j]
    end
    @simd for i = 1:m
      @inbounds fvec[i] = wa4[i]
    end

    xnorm = enorm(n, wa2)
    fnorm = fnorm1
    iter += 1
  end

  # Tests for convergence
  if abs(actred) <= ftol && prered <= ftol && 0.5 * ratio <= 1.0
    info_ = 1
  end

  if delta <= xtol * xnorm
    info_ = 2
  end

  if abs(actred) <= ftol && prered <= ftol && 0.5 * ratio <= 1.0 && info_ == 2
    info_ = 3
  end

  info_ != 0 && @goto L300

  # Tests for termination and stringent tolerances
  if nfev >= maxFev
    info_ = 5
  end

  if abs(actred) <= MACHEP && prered <= MACHEP && 0.5 * ratio <= 1.0
    info_ = 6
  end

  if delta <= MACHEP * xnorm
    info_ = 7
  end

  if gnorm <= MACHEP
    info_ = 8
  end

  info_ != 0 && @goto L300

  # end of inner loop, repeat if iteration unsuccessful
  if ratio < 0.0001
    @goto L200
  end

  # end of outer loop
  @goto L30

  @label L300
  # Termination either normal or user imposed
  if iflag < 0
    info_ = iflag
  end
  iflag = 0
  if nprint > 0
    fcn!(m, n, x, fvec)
  end

  return x, fvec, info_, nfev, fjac, ipvt, qtf
end


struct MonteCarlo <: AbstractMonteCarlo
    n::Integer
    MonteCarlo(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

struct SobolSampling <: AbstractQuasiMonteCarlo
    n::Integer
    SobolSampling(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

struct HaltonSampling <: AbstractQuasiMonteCarlo
    n::Integer
    HaltonSampling(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

struct LatinHypercubeSampling <: AbstractQuasiMonteCarlo
    n::Integer
    LatinHypercubeSampling(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

struct LatticeRuleSampling <: AbstractQuasiMonteCarlo
    n::Integer
    LatticeRuleSampling(n) = n > 0 ? new(n) : error("n must be greater than zero")
end

function sample(inputs::Array{<:UQInput}, sim::MonteCarlo)
    return sample(inputs, sim.n)
end

function sample(inputs::Array{<:UQInput}, sim::AbstractQuasiMonteCarlo)
    random_inputs = filter(i -> isa(i, RandomUQInput), inputs)
    deterministic_inputs = filter(i -> isa(i, DeterministicUQInput), inputs)

    n_rv = count_rvs(random_inputs)

    u = qmc_samples(sim, n_rv)

    samples = quantile.(Normal(), u)
    samples = DataFrame(names(random_inputs) .=> eachcol(samples))

    if !isempty(deterministic_inputs)
        samples = hcat(samples, sample(deterministic_inputs, sim.n))
    end

    to_physical_space!(inputs, samples)

    return samples
end

function qmc_samples(sim::SobolSampling, rvs::Integer)
    return transpose(QuasiMonteCarlo.sample(sim.n, zeros(rvs), ones(rvs), SobolSample()))
end

function qmc_samples(sim::HaltonSampling, rvs::Integer)
    coprimes = [prime(i) for i in 1:rvs]
    return transpose(
        QuasiMonteCarlo.sample(sim.n, zeros(rvs), ones(rvs), LowDiscrepancySample(coprimes))
    )
end

function qmc_samples(sim::LatinHypercubeSampling, rvs::Integer)
    return transpose(
        QuasiMonteCarlo.sample(sim.n, zeros(rvs), ones(rvs), LatinHypercubeSample())
    )
end

function qmc_samples(sim::LatticeRuleSampling, rvs::Integer)
    return transpose(
        QuasiMonteCarlo.sample(sim.n, zeros(rvs), ones(rvs), LatticeRuleSample())
    )
end


include("mandelbrot.jl")

img = draw_image(remin=-2.3, remax=0.8, immin=-1.55, immax=1.55, width=4000, height=4000, maxiter=1000, radius=30.)
imwrite(img, "mandelbrot.png")

img = draw_image(remin=-0.79, remax=-0.73, immin=0.06, immax=0.12, width=2500, height=2500, maxiter=800, radius=20.)
imwrite(img, "mandelbrot_seahorse_valley.png")

img = draw_image(remin=-0.090, remax=-0.088, immin=0.654, immax=0.656, width=2500, height=2500, maxiter=800, radius=20.)
imwrite(img, "mandelbrot_triple_spiral.png")


module Bingomatic
    using Plots, StatsBase, TextWrap
    export make_card, sample_words, savefig

    """
    * `word_pool`: a list of words
    * `n`: the dimensions of the sampled words
    * `free_space`: sets center cell to "Free Space" if true
    """
    function sample_words(word_pool, n...=(5,5)...; free_space=true)
        n_cells = prod(n)
        center = Int(ceil(n_cells/2))
        length(word_pool) < n_cells ? error("list must have more elements than cells") : nothing
        iseven(n_cells) ? error("Must have an odd number of cells") : nothing
        cells = sample(word_pool, n, replace=false)
        cells[center] = free_space ? "Free Space" : cells[center]
        return cells
    end

    """
    * `labels`: an array containing bingo words
    * `n_rows`: the number of rows and columns in each bingo card
    * `word_size`: the size of words in points
    * `line_width`: the number of characters per line for each word or phrase
    * `break_words`: break long words when wrapping words
    * `kwargs`: variable keyword arguments to pass to the `plot` function
    """
    function make_card(list; word_size=12, line_width=8, break_words=false, kwargs...)
        min_x = 0
        max_x = 2
        n_rows = size(list, 1)
        n_cells = n_rows^2
        offset = 1/n_rows
        iseven(n_rows) ? error("n_rows must be odd") : nothing
        length(list) < n_cells ? error("labels must have more elements than cells") : nothing
        ticks = range(min_x, max_x, length=n_rows+1)
        scale = range(min_x+offset, max_x-offset, length=n_rows)
        x = [[i j] for i in scale for j in scale] |> x -> vcat(x...)
        w_list = map(x -> TextWrap.wrap(x, width=line_width, break_long_words=break_words), list)
        return scatter(x[:,1], x[:,2], markersize=0, xlims=(min_x,max_x), ylims=(min_x,max_x),
            series_annotations = text.(w_list[:], :center, word_size), label = false, grid=true,
            gridstyle=:solid, gridlinewidth=1.5, gridalpha=1, xticks=ticks, yticks=ticks,
            xaxis=font(0), yaxis=font(0), xlabel="", ylabel="", frame_style=:box, size=(400,400);
            kwargs...)
    end
end


module OptionsPricing

export bs, crr, mc, Option, EuropeanOption, AmericanOption, AsianOption

abstract type Option end

struct EuropeanOption<:Option
    s::Float64
    k::Float64
    r::Float64
    σ::Float64
    t::Float64
    call_or_put::String
end

struct AmericanOption<:Option
    s::Float64
    k::Float64
    r::Float64
    σ::Float64
    t::Float64
    call_or_put::String
end

struct AsianOption<:Option
    s::Float64
    k::Float64
    r::Float64
    σ::Float64
    t::Float64
    call_or_put::String
    dates::Vector{DateTime}
end

include("bs.jl")
include("crr.jl")
include("mc.jl")

end # module


# Test for nep helper creators
using NonlinearEigenproblemsTest
using NonlinearEigenproblems
using Test
using LinearAlgebra

@bench @testset "NEPTypes: creat help" begin
    # Mder_NEP
    function my_Mder(s,der)
        A0=ones(Float64,3,3);
        A1=ones(Float64,3,3)*3; A1[2,3]=0;
        return (3^der)*exp(3*s)*A0+factorial(der)*((-1)^der)*A1/(3.3+s)^(der+1)
    end
    AA0=ones(Float64,3,3);
    AA1=ones(Float64,3,3)*3; AA1[2,3]=0;
    nep_ref=SPMF_NEP([AA0,AA1],[s->exp(3*s), S->inv(3.3*I+S)]);
    nep=Mder_NEP(3,my_Mder)

    λ=3.3;
    norm(compute_Mder(nep,λ,4)-compute_Mder(nep_ref,λ,4))

    λ=-1.2+0.2im;
    X=[ -0.715845   0.865534   0.254796 ; -0.856405  -0.482516   0.0265129;
  1.02593   -0.62892   -2.09615  ]
    @test norm(compute_Mlincomb(nep,λ,X)-compute_Mlincomb(nep_ref,λ,X))<eps()*100

    # Mder_Mlincomb_NEP
    function my_Mlincomb(s,X)
        A0=ones(Float64,3,3);
        A1=ones(Float64,3,3)*3; A1[2,3]=0;
        z=zeros(size(A0,1))
        for k=1:size(X,2)
            der=k-1;
            z+=(3^der)*exp(3*s)*(A0*X[:,k]);
            z+=(factorial(der)*((-1)^der)/(3.3+s)^(der+1))*(A1*X[:,k]);
        end
        return z;
    end
    nep_mlincomb=Mder_Mlincomb_NEP(3,my_Mder, my_Mlincomb)
    @test norm(compute_Mlincomb(nep,λ,X)-compute_Mlincomb(nep_mlincomb,λ,X))<eps()*100
end


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

"""
    intersecttype(b1, b2)

Compute the intersection type of two boxes `b1` and `b2`.

The intersection type can be one of four types:

1. overlap with non-zero measure
2. intersect at one of the boundaries
3. intersect at corner point
4. do not overlap nor intersect
"""
function intersecttype(b1::Box{Dim,T}, b2::Box{Dim,T}) where {Dim,T}
  m1, M1 = coordinates.(extrema(b1))
  m2, M2 = coordinates.(extrema(b2))

  # relevant vertices
  u = Point(max.(m1, m2))
  v = Point(min.(M1, M2))

  # branch on possible configurations
  if u ≺ v
    OverlappingBoxes(Box(u, v))
  elseif u ≻ v
    NoIntersection()
  elseif isapprox(u, v, atol=atol(T))
    CornerTouchingBoxes(u)
  else
    FaceTouchingBoxes(Box(u, v))
  end
end


# Compare the selection accuracy of KS test and BIC for different sample size
@everywhere using GaussianMixtureTest: gmm, kstest
@everywhere using Distributions:Normal, MixtureModel
@everywhere using Random:seed!

@everywhere function compareBIC(Ctrue::Int, n::Int, b::Int)

    C_max = max(5, (2*Ctrue - 1))
    mu_all = log(1/0.779 - 1)
    if Ctrue == 2
        mu_true = [mu_all - 1.0, mu_all + 0.8]
        wi_true =  [.6, .4]
        sigmas_true = [1.2, .8]
    elseif Ctrue == 3
        mu_true = [mu_all - 2.0, mu_all + 1.0, mu_all + 3.5]
        wi_true = [.3, .4, .3]
        sigmas_true = [1.2, .8, .9]
    end
    m = MixtureModel(map((u, v) -> Normal(u, v), mu_true, sigmas_true), wi_true)

    C_max = max(5, (2*Ctrue - 1))
    bic = fill(Inf, C_max)
    pvalues = fill(1.0, C_max)

    seed!(b)
    x = rand(m, n);
    for C in 1:C_max
        wi, mu, sigmas, ml = gmm(x, C)
        T, pvalues[C] = kstest(x, C)
        bic[C] = -2 * ml + (3 * C - 1) * log(n)
    end
    vcat(bic, pvalues)
end


using Test
using LinearAlgebra
using OrdinaryDiffEq: ODEProblem, solve, SSPRK33, Tsit5

import ClimaCore:
    Fields,
    Domains,
    Topologies,
    Meshes,
    DataLayouts,
    Operators,
    Geometry,
    Spaces

convergence_rate(err, Δh) =
    [log(err[i] / err[i - 1]) / log(Δh[i] / Δh[i - 1]) for i in 2:length(Δh)]

@testset "1D ∂T/t = α ∇²T ODE solve" begin

    FT = Float64
    n_elems_seq = 2 .^ (5, 6, 7)
    err, Δh = zeros(length(n_elems_seq)), zeros(length(n_elems_seq))

    for (k, n) in enumerate(n_elems_seq)
        z₀ = FT(0)
        z₁ = FT(10)
        t₀ = FT(0)
        t₁ = FT(10)
        μ = FT(-1 / 2)
        ν = FT(5)
        𝓌 = FT(1)
        δ = FT(1)

        domain = Domains.IntervalDomain(z₀, z₁, x3boundary = (:bottom, :top))
        zp = (z₀ + z₁ / n / 2):(z₁ / n):(z₁ - z₁ / n / 2)

        function gaussian(z, t; μ = -1 // 2, ν = 1, 𝓌 = 1, δ = 1)
            return exp(-(z - μ - 𝓌 * t)^2 / (4 * ν * (t + δ))) / sqrt(1 + t / δ)
        end

        function ∇gaussian(z, t; μ = -1 // 2, ν = 1, 𝓌 = 1, δ = 1)
            return -2 * (z - μ - 𝓌 * t) / (4 * ν * (δ + t)) *
                   exp(-(z - μ - 𝓌 * t)^2 / (4 * ν * (δ + t))) / sqrt(1 + t / δ)
        end

        z₀ = FT(0)
        z₁ = FT(10)

        mesh = Meshes.IntervalMesh(domain, nelems = n)

        cs = Spaces.CenterFiniteDifferenceSpace(mesh)
        fs = Spaces.FaceFiniteDifferenceSpace(cs)
        zc = Fields.coordinate_field(cs)

        T = gaussian.(zc, -0; μ = μ, δ = δ, ν = ν, 𝓌 = 𝓌)
        V = ones(FT, fs)

        function ∑tendencies!(dT, T, z, t)
            bc_vb = Operators.SetValue(
                FT(gaussian(z₀, t; ν = ν, δ = δ, 𝓌 = 𝓌, μ = μ)),
            )
            bc_gt = Operators.SetGradient(
                FT(∇gaussian(z₁, t; ν = ν, δ = δ, 𝓌 = 𝓌, μ = μ)),
            )
            A = Operators.AdvectionC2C(
                bottom = bc_vb,
                top = Operators.Extrapolate(),
            )
            gradc2f = Operators.GradientC2F(bottom = bc_vb, top = bc_gt)
            gradf2c = Operators.GradientF2C()
            return @. dT = gradf2c(ν * gradc2f(T)) - A(V, T)
        end

        # Solve the ODE operator
        Δt = 0.001
        prob = ODEProblem(∑tendencies!, T, (t₀, t₁))
        sol = solve(
            prob,
            Tsit5(),
            reltol = 1e-8,
            abstol = 1e-8,
            #dt = Δt,
        )
        computed_result = sol.u[end]
        analytical_result = gaussian.(zp, t₁; μ = μ, δ = δ, ν = ν, 𝓌 = 𝓌)
        Δh[k] = cs.Δh_c2c[1]
        err[k] =
            norm(parent(computed_result) .- analytical_result) /
            length(analytical_result)
    end
    conv = convergence_rate(err, Δh)
    # conv should be approximately 2 for second order-accurate stencil.
    @test 1.4 ≤ conv[1] ≤ 2.1
    @test 1.4 ≤ conv[2] ≤ 2.1
    @test conv[1] ≤ conv[2]
    @test err[3] ≤ err[2] ≤ err[1] ≤ 1e-2
end


# Mandatory
export polyhedron, loadpolyhedron!
export hrepiscomputed, hrep, vrepiscomputed, vrep
export volume, volume_simplex, unscaled_volume_simplex, surface, center_of_mass

"""
    polyhedron(rep::Representation{T})

Creates a polyhedron from the representation `rep` using the default library included in the Polyhedra package.
"""
polyhedron(rep::Representation{T}) where {T} = polyhedron(rep, default_library(FullDim(rep), T))

"""
    hrepiscomputed(p::Polyhedron)

Returns whether the H-representation of this polyhedron has been computed.
"""
function hrepiscomputed end

"""
    hrep(p::Polyhedron)

Returns an H-representation for the polyhedron `p`.
"""
hrep(p::Polyhedron) = error("`hrep` not implemented for `$(eltype(p))`")

"""
    vrepiscomputed(p::Polyhedron)

Returns whether the V-representation of this polyhedron has been computed.
"""
function vrepiscomputed end

"""
    vrep(p::Polyhedron)

Returns a V-representation for the polyhedron `p`.
"""
vrep(p::Polyhedron) = error("`vrep` not implemented for `$(eltype(p))`")

"""
    volume(p::Polyhedron{T}) where {T}

Returns the `fulldim(p)`-dimensional hyper-volume of the polyhedron `p`.
Returns `Inf` or `-one(T)` if it is infinite depending on whether the type `T` has an infinite value.
"""
function volume(p::Polyhedron{T}) where T
    return reduce(+, unscaled_volume_simplex(Δ) for Δ in triangulation(p);
                  init=zero(T)) / factorial(fulldim(p))
end

function unscaled_volume_simplex(s)
    A = Matrix{coefficient_type(s)}(undef, fulldim(s), npoints(s) - 1)
    v = first(points(s))
    for (i, p) in enumerate(Iterators.drop(points(s), 1))
        A[:, i] = p - v
    end
    return abs(det(A))
end
function volume_simplex(s)
    return unscaled_volume_simplex(s) / factorial(fulldim(s))
end

"""
    center_of_mass(p::Polyhedron{T}) where {T}

Returns the center of mass of `p`, represented as a `Vector{T}` of length `fulldim(p)`.
Throws an error if `p` is degenerate.
"""
function center_of_mass(p::Polyhedron{T}) where T
    # Implementation strategy: For a simplex, the center of mass coincides with
    # the centroid which is easy to compute.  So we triangulate `p` into
    # simplices and compute a volume-weighted average of these simplices'
    # centers of mass.
    simplices = triangulation(p)
    isempty(simplices) && error("Tried to compute center of mass of a degenerate polyhedron")
    unscaled_center = zeros(T, fulldim(p))
    unscaled_vol = zero(T)
    for Δ in simplices
        v = unscaled_volume_simplex(Δ)
        unscaled_center += v * sum(points(Δ))
        unscaled_vol += v
    end
    return unscaled_center / (unscaled_vol * (fulldim(p) + 1))
end

"""
    surface(p::Polyhedron{T}) where {T}

Returns the `fulldim(p)-1`-dimensional hyper-volume of the surface of the polyhedron `p`.
Returns `Inf` or `-one(T)` if it is infinite depending on whether the type `T` has an infinite value.
"""
function surface end

loadpolyhedron!(p::Polyhedron, filename::AbstractString, extension::Type{Val{:ine}}) = error("loadpolyhedron! not implemented for .ine")
loadpolyhedron!(p::Polyhedron, filename::AbstractString, extension::Type{Val{:ext}}) = error("loadpolyhedron! not implemented for .ext") # FIXME ExtFileVRepresentation or just ExtFile

loadpolyhedron!(p::Polyhedron, filename::AbstractString, extension::Symbol) = loadpolyhedron!(p, filename, Val{extension})

function loadpolyhedron!(p::Polyhedron, filename::AbstractString, extension::AbstractString)
    s = something(findfirst(isequal(extension), ["ext", "ine"]), 0)
    if s == 0
        error("Invalid extension $extension, please give 'ext' for V-representation or 'ine' for H-representation")
    end
    loadpolyhedron!(p, filename, [:ext, :ine][s])
end


using DiscreteChoice
using JLD2
using ForwardDiff
using Optim

@load "example/data/homo.jld2" Y X β ξ

model = DCModel(Y,X; hetero_preference=false)

estDCModel!(model; init_guess = vcat(β, ξ))

res = Optim.minimizer(model.optResults) |> copy
res .- vcat(β, ξ) |> maximum


@testset "spherical harmonics expansion" begin
  ɛ = eps(Float64)
  @polyvar x y z

  @test_throws DomainError SphericalHarmonicCoefficients(-2)
  @test_throws DomainError SphericalHarmonicCoefficients([1,2,3,4,5])
  @test_throws DomainError SphericalHarmonicCoefficients(-2,1.0,true)
  @test_throws DomainError SphericalHarmonicCoefficients([1,2,3,4,5],1.0,true)
  @test_throws DomainError SphericalHarmonicCoefficients([[1,2,3,4],[1,2,3,4]],[1.0],BitArray(undef,(1, 0)))
  @test_throws DomainError SphericalHarmonicCoefficients([[1,2,3,4],[1,2,3,4]],[1.0, 1.0],BitArray(undef,(1,)))
  @test_throws DomainError SphericalHarmonicCoefficients([[1,2,3,4],[1,2,3]],[1.0, 1.0],BitArray(undef,(1, 0)))
  @test_throws DomainError SphericalHarmonicCoefficients([[1,2,3,4],[1,2]],1.0,true)

  c1 = SphericalHarmonicCoefficients(2)
  c2 = SphericalHarmonicCoefficients([1,2,3,4])
  c3 = SphericalHarmonicCoefficients([1+ε,2,3,4])
  c4 = SphericalHarmonicCoefficients([3,2,3,4])
  c5 = SphericalHarmonicCoefficients(2,1.0,true)
  c6 = SphericalHarmonicCoefficients([1,2,3,4],2.0,false)
  c7 = SphericalHarmonicCoefficients([[2],[1,2,3,4]],1.0,false)

  @test c2==c2
  @test c2!=c3
  @test isapprox(c2,c3)
  @test !isapprox(c2,c4)
  @test !isapprox(c1,c5)
  @test !isapprox(c2,c6)
  @test isapprox(c2,c7[2])

  @test length(c1.c) == 9
  @test c2.L == 1
  @test c7[1].L != c7[2].L

  @test c2[1] == 1
  @test c2[2] == 2
  @test c2[3] == 3
  @test c2[4] == 4

  @test c2[0,0] == 1
  @test c2[1,-1] == 2
  @test c2[1,0] == 3
  @test c2[1,1] == 4

  c2[1] = 10
  c2[2] = 20
  c2[3] = 30
  c2[4] = 40
  @test c2[1] == 10
  @test c2[2] == 20
  @test c2[3] == 30
  @test c2[4] == 40

  c2[0,0] = 100
  c2[1,-1] = 200
  c2[1,0] = 300
  c2[1,1] = 400
  @test c2[1] == 100
  @test c2[2] == 200
  @test c2[3] == 300
  @test c2[4] == 400

  #Test by using unit vectors
  #l = 10 -> size(C) = l²+2l+1 = 121

  for l in 0:2
    for m in -l:l
      C = SphericalHarmonicCoefficients(2)
      C[l,m] = 1
      @test isapprox(sphericalHarmonicsExpansion(C,x,y,z),sqrt((2*l+1)/(4*pi))*rlm(l,m,x,y,z),atol=ɛ)
    end
  end

  #Testing the addition
  C = SphericalHarmonicCoefficients([1;0.5;10.78;0.87456])
  #@test isapprox(sphericalHarmonicsExpansion(C,x,y,z),1*ylm(0,0,x,y,z)+0.5*ylm(1,-1,x,y,z)+10.78*ylm(1,0,x,y,z)+0.87456*ylm(1,1,x,y,z),atol=ɛ)

  # Test: solid & spherical harmonic expansion
  c = ones(4)
  Csolid = SphericalHarmonicCoefficients(c,1.0,true)
  Cspher = SphericalHarmonicCoefficients(c,1.0,false)

  @test isapprox(sphericalHarmonicsExpansion(Csolid,x,y,z),1+x+y+z,atol=ɛ)
  @test isapprox(sphericalHarmonicsExpansion(Cspher,x,y,z),sqrt(1/(4pi))+sqrt(3/(4pi))*x+sqrt(3/(4pi))*y+sqrt(3/(4pi))*z,atol=ɛ)

end

@testset "coefficients" begin
  ɛ = eps(Float64)

  # Test: solid ↔ spherical
  csolid = zeros(25)
  csolid[1:4] = [sqrt(1/(4pi)),sqrt(3/(4pi)),sqrt(3/(4pi)),sqrt(3/(4pi))]
  cspher = zeros(25)
  cspher[1:4] = [1,1,1,1]

  Csolid = SphericalHarmonicCoefficients(csolid,0.042,true)
  Cspher = SphericalHarmonicCoefficients(cspher,0.042,false)

  @test isapprox(spherical!(deepcopy(Csolid)),Cspher,atol=ε)
  @test isapprox(solid!(deepcopy(Cspher)),Csolid,atol=ε)
  @test isapprox(spherical!(deepcopy(Cspher)),Cspher,atol=ε)
  @test isapprox(solid!(Csolid),Csolid,atol=ε)

  # Test: Normalization
  c1 = ones(9)
  C1 = SphericalHarmonicCoefficients(c1,1.0,true)
  c2 = [1,2,2,2,4,4,4,4,4]
  C2 = SphericalHarmonicCoefficients(c2,2.0,true)

  @test isapprox(normalize!(C1,1/2.0),C2,atol=ε)
  normalize!(C1,2.0)
  @test isapprox(normalize(C2,C2.R),C1,atol=ε)

  # Test: generate an array of SphericalHarmonicCoefficients
  c3 = reshape([ones(9) for i=1:6],2,3)
  R = ones(Float64,2,3)
  sol = BitArray(R)
  C3 = SphericalHarmonicCoefficients(c3,R,sol)

  for (i,co) in enumerate(C3)
      @test isapprox(C1,co,atol=ε)
  end

  # Test: Operations
  c4 = 2*ones(9)
  C4 = SphericalHarmonicCoefficients(c4,1.0,true)
  @test isapprox(C1*2,C4,atol=ε)
  @test isapprox(C4/2,C1,atol=ε)
  @test isapprox(1.0+C1,C4,atol=ε)
  @test isapprox(3.0-C1,C4,atol=ε)
  @test isapprox(C4-1.0,C1,atol=ε)

  C5 = SphericalHarmonicCoefficients(zeros(9),2.0,true)
  @test isapprox(2*C1,C1+C1,atol=ε)
  @test isapprox(C2-C2,C5,atol=ε)

  @test_throws DomainError Csolid+Cspher
  @test_throws DomainError Csolid-Cspher
  @test_throws DomainError C4+C5
  @test_throws DomainError C4-C5

  # Test: Save and read coefficients to/from an HDF5 file
  write("Test1.h5",[C2])
  CTest1 = SphericalHarmonicCoefficients("Test1.h5")
  @test isapprox(CTest1[1],C2,atol=ε)

  write("Test2.h5",C3)
  CTest2 = SphericalHarmonicCoefficients("Test2.h5")
  for i=1:length(C3)
      @test isapprox(CTest2[i],C3[i],atol=ε)
  end

  rm("Test1.h5")
  rm("Test2.h5")

end