function bulk_modulus(l, m)
    return l + 2m / 3
end

function lame_lambda(k, m)
    return k - 2m / 3
end

function displacement(alpha, x)
    u1 = alpha * x[2] * sin(pi * x[1])
    u2 = alpha * (x[1]^3 + cos(pi * x[2]))
    return [u1, u2]
end

function body_force(lambda, mu, alpha, x)
    b1 = alpha * (lambda + 2mu) * pi^2 * x[2] * sin(pi * x[1])
    b2 =
        -alpha * (6mu * x[1] + (lambda + mu) * pi * cos(pi * x[1])) +
        alpha * (lambda + 2mu) * pi^2 * cos(pi * x[2])
    return [b1, b2]
end

function stress_field(lambda, mu, alpha, x)
    s11 =
        (lambda + 2mu) * alpha * pi * x[2] * cos(pi * x[1]) -
        lambda * alpha * pi * sin(pi * x[2])
    s22 =
        -(lambda + 2mu) * alpha * pi * sin(pi * x[2]) +
        lambda * alpha * pi * x[2] * cos(pi * x[1])
    s12 = alpha * mu * (3x[1]^2 + sin(pi * x[1]))
    return [s11, s22, s12]
end

function onboundary(x, L, W)
    return x[2] ≈ 0.0 || x[1] ≈ L || x[2] ≈ W || x[1] ≈ 0.0
end

function assemble_linear_system(
    mesh,
    basis,
    cellquads,
    facequads,
    interfacequads,
    stiffness,
    penalty,
    eta,
    displacementscale,
)

    L, W = CutCellDG.widths(mesh)
    lambda1, mu1 = CutCellDG.lame_coefficients(stiffness, +1)

    sysmatrix = CutCellDG.SystemMatrix()
    sysrhs = CutCellDG.SystemRHS()

    CutCellDG.assemble_displacement_bilinear_forms!(
        sysmatrix,
        basis,
        cellquads,
        stiffness,
        mesh,
    )
    CutCellDG.assemble_interelement_condition!(
        sysmatrix,
        basis,
        facequads,
        stiffness,
        mesh,
        penalty,
        eta,
    )
    CutCellDG.assemble_coherent_interface_condition!(
        sysmatrix,
        basis,
        interfacequads,
        stiffness,
        mesh,
        penalty,
        eta,
    )
    CutCellDG.assemble_penalty_displacement_bc!(
        sysmatrix,
        sysrhs,
        x -> displacement(displacementscale, x),
        basis,
        facequads,
        stiffness,
        mesh,
        x -> onboundary(x, L, W),
        penalty,
    )
    CutCellDG.assemble_body_force!(
        sysrhs,
        x -> body_force(lambda1, mu1, displacementscale, x),
        basis,
        cellquads,
        mesh,
    )

    matrix = CutCellDG.sparse_displacement_operator(sysmatrix, mesh)
    rhs = CutCellDG.displacement_rhs_vector(sysrhs, mesh)

    return matrix, rhs
end

function nodal_displacement(
    distancefunc,
    stiffness,
    nelmts,
    basis,
    numqp,
    penaltyfactor,
    eta,
)
    L, W = 1.0, 1.0
    lambda1, mu1 = CutCellDG.lame_coefficients(stiffness, +1)
    lambda2, mu2 = CutCellDG.lame_coefficients(stiffness, -1)

    dx = 1.0 / nelmts
    penalty = penaltyfactor / dx * 0.5 * (lambda1 + mu1 + lambda2 + mu2)
    displacementscale = 0.1

    mesh = CutCellDG.DGMesh([0.0, 0.0], [L, W], [nelmts, nelmts], basis)
    levelset = InterpolatingPolynomial(1, basis)
    levelsetcoeffs = CutCellDG.levelset_coefficients(distancefunc, mesh)

    cutmesh = CutCellDG.CutMesh(mesh, levelset, levelsetcoeffs)
    cellquads =
        CutCellDG.CellQuadratures(cutmesh, levelset, levelsetcoeffs, numqp)
    interfacequads =
        CutCellDG.InterfaceQuadratures(cutmesh, levelset, levelsetcoeffs, numqp)
    facequads =
        CutCellDG.FaceQuadratures(cutmesh, levelset, levelsetcoeffs, numqp)

    mergedwithcell, hasmergedcells = CutCellDG.merge_tiny_cells_in_mesh!(
        cutmesh,
        cellquads,
        facequads,
        interfacequads,
        tinyratio=0.2
    )
    mergedmesh = CutCellDG.MergedMesh(cutmesh, mergedwithcell)

    matrix, rhs = assemble_linear_system(
        mergedmesh,
        basis,
        cellquads,
        facequads,
        interfacequads,
        stiffness,
        penalty,
        eta,
        displacementscale,
    )

    nodaldisplacement = matrix \ rhs

    return nodaldisplacement, mergedmesh, cellquads, facequads, interfacequads
end


module ModelsTest

using Test
using LightGraphs
#using Random
using fastmaxent

@testset "Models" begin

#rng = MersenneTwister(1)
@testset "UBCM" begin
    @testset "UBCM Model" begin
    @testset "UBCM - Error catching" begin
        G = LightGraphs.static_scale_free(100,1000,2.1)
        add_vertex!(G)
        # unconnected nodes
        @test_throws DomainError UBCM(G)
        rem_vertex!(G, nv(G))
        # self loops
        add_edge!(G,1,1)
        @test_throws DomainError UBCM(G)
    end
    @testset "UBCM - Instance generation" begin
        G = LightGraphs.static_scale_free(100,1000,2.3)
        model = UBCM(G)
        @test isa(model.x0, Vector)
        @test isa(model.x, Vector)
        @test isa(model.F, Vector)
        @test isa(model.f!, Function)
        @test isa(model, UBCM)
    end
    @testset "UBCM - Solving" begin
        G = LightGraphs.static_scale_free(100,2000,2.4)
        model = UBCM(G)
        @info model
        res = solve!(model)
        @test all(res.zero .> 0) # non-negative solution
        @test all(model.x .> 0) # non-negative solution
        @test isapprox(degree(G), degree(model)) # check constraints match
    end
    end

    @testset "UBCM Compact Model" begin
    @testset "UBCM Compact - Error catching" begin
        G = LightGraphs.static_scale_free(100,1000,2.1)
        add_vertex!(G)
        # unconnected nodes
        @test_throws DomainError UBCM(G)
        rem_vertex!(G, nv(G))
        # self loops
        add_edge!(G,1,1)
        @test_throws DomainError UBCM(G)
    end
    @testset "UBCM - Instance generation" begin
        G = LightGraphs.static_scale_free(100,1000,2.3)
        d = degree(G)
        model = UBCMCompact(G)
        @test isa(model.x0, Vector)
        @test isa(model.xs, Vector)
        @test isa(model.F, Vector)
        @test isa(model.f!, Function)
        @test isa(model, UBCMCompact)
        @test length(model.x0) == length(unique(d))
        @test length(model.F) == length(unique(d))
        @test length(model.xs) == length(unique(d))
        @test isnothing(model.x)
    end
    @testset "UBCM - Solving" begin
        G = LightGraphs.static_scale_free(100,2000,2.4)
        model = UBCMCompact(G)
        @info model
        res = solve!(model)
        @test all(res.zero .> 0) # non-negative solution
        @test all(model.xs .> 0) # non-negative solution
        @test isapprox(degree(G), degree(model)) # check constraints match
    end
    end

    @testset "UBCM Compare" begin
        G = LightGraphs.static_scale_free(300,10000,2.4)
        large_model = UBCM(G)
        compact_model = UBCMCompact(G)
        res_large = solve!(large_model)
        res_compact = solve!(compact_model)
        @test all(isapprox(large_model.x, compact_model.x))
    end
end

@testset "DBCM" begin

end

end
end


#module AutoPhase

#using NMR.DataSet
import Optim

export entropy, AutoPhaseCorrect, AutoPhaseCorrectChen

ent(x) = x*log(x)

"""
`entropy(spect::Data1D)` computes the entropy in the real part of an
NMR spectrum as defined by Chen et al. in
*Journal of Magnetic Resonance* **158** (2002) 164–168.
This quantity can be optimised with respect to zero- and first-order
phase correction for automatic (unsupervised) phase correction.
"""
function entropy(spect::Data1D)
    h=abs.(real.(val(spect)))
    h/=sum(h)
    return -sum(ent.(h))
end

# penalty(x) computes the sum of squares of all negative points in x
function penalty(x)
    x /= sum(abs.(x))
    return sum([k<0.0 ? k*k : 0.0 for k in x])
end

# this is the minimisation target for automatic phase correction
function goalfun(x,spect,γ=0.0e-5)
    piv=0.5*(spect.istop+spect.istart)
    c=PhaseCorrect(spect,Ph0=x[1]*10,Ph1=x[2],Pivot=piv);
    return entropy(c)+γ*penalty(real.(val(c)));
end

"""
`AutoPhaseCorrectChen(spect::Data1D;verbose=false)`
performs zero- and first-order phase correction of `spect`
using a minimum entropy algorithm  by Chen et al. in
*Journal of Magnetic Resonance* **158** (2002) 164–168.
It uses the Nelder-Mead algorithm, as implemented in the `Optim.jl`
package.
"""
function AutoPhaseCorrectChen(spect::Data1D;verbose=false,γ=0.0)
  piv=0.5*(spect.istop+spect.istart)
  result=Optim.optimize(x->goalfun(x,spect,γ),[0.1,0.2],Optim.NelderMead(),Optim.Options(show_trace=false,g_tol=1.0e-9));
  if verbose print(result) end;
  pc=Optim.minimizer(result);
  scorr = PhaseCorrect(spect,Ph0=pc[1]*10,Ph1=pc[2],Pivot=piv);
  if (real(integral(scorr))<0.0) scorr = -1.0*scorr end;
  return scorr ;
end

function unwrap(a;p=π,tol=0.25)
    l=length(a)
    w=zeros(Float64,l)
    offset=0
    for k=1:(l-1)
        if( (p-a[k])<p*tol && (a[k+1]+p)<p*tol)
            offset+=2*p;
        elseif( (a[k]+p)<p*tol && (p-a[k+1])<p*tol)
            offset-=2*p;
        end
        w[k+1]=offset;
    end

    return a+w
end

"""
`AutoPhaseCorrect(spect::Data1D)`
performs zero- and first-order phase correction of `spect`
using a the algorithm by van der Waals and Geerens,
*Journal of Magnetic Resonance* **86** (1990) 127-154.
It works by recognising the peaks in the magnitude mode spectrum
and then representing the phase at each peak location
through a linear regression.
"""
function AutoPhaseCorrect(s::NMR.Data1D;exclude=false,threshold=10)
    # --- Step 1: basic phase adjustment
    phase0=angle(sum(s.dat))
    s1=NMR.PhaseCorrect(s,Ph0=-phase0)

    # --- Step 2: recognise peaks in the absolute mode spectrum
    s2=abs(s1);
    pks=NMR.peaks(s2,threshold=threshold);

    # --- Step 3: obtain list of peak phases
    ppos=[NMR.ind2pos(s1,x) for x in pks.positions]

    if exclude!=false
        i=NMR.ind2pos(s,exclude[1]);
        j=NMR.ind2pos(s,exclude[2]);
        ppos=filter(x->(x<i||x>j),ppos)
    end
    pind = [NMR.pos2ind(s,k) for k in ppos]
    pphase=[angle(s1.dat[p]) for p in ppos]
    α=NMR.polyfit(pind,unwrap(pphase),1)
    # print(α,"\n")
    s3=NMR.PhaseCorrect(s1,Ph0=-α[1],Ph1=-α[2])
    return s3
end

#end


using Test
using ModifiedHankelFunctionsOfOrderOneThird
using SpecialFunctions

# SCL1945:
# "Tables of the modified Hankel function of order one-third and of their derivatives"
# Harvard University Press, 1945.

@testset "Power series: zeros of h₂" begin
    h1, h2, h1p, h2p = modifiedhankel(complex(-1.16905371, 2.02486041))
    @test h1 ≈ complex(-0.34342750, 0.59483387)
    @test h1p ≈ complex(-0.06957489, -1.06099210)
    @test h2p ≈ complex(-1.83769223, 1.06099210)

    h1, h2, h1p, h2p = modifiedhankel(complex(-2.04397472, 3.54026807))
    @test h1 ≈ complex(0.29985265, -0.51936003)
    @test h1p ≈ complex(0.03621447, 1.21517637)
    @test h2p ≈ complex(2.10474721, -1.21517637)

    h1, h2, h1p, h2p = modifiedhankel(complex(-2.76027991, 4.78094505))
    @test h1 ≈ complex(-0.27833328, 0.48208739) atol=1e-7
    @test h1p ≈ complex(-0.02507370, -1.30912788)
    @test h2p ≈ complex(-2.26747600, 1.30912788)
end

@testset "Power series: zeros of h₂′" begin
    h1, h2, h1p, h2p = modifiedhankel(complex(-0.50939649, 0.88230059))
    @test h1 ≈ complex(-0.63166599, -0.52691131)
    @test h2 ≈ complex(0, 1.62098891)
    @test h1p ≈ complex(0.89913968, 0)

    h1, h2, h1p, h2p = modifiedhankel(complex(-1.62409879, 2.81302162))
    @test h1 ≈ complex(0.53589957, 0.33980741)
    @test h2 ≈ complex(0, -1.26801270)
    @test h1p ≈ complex(-1.14943284, 0)

    h1, h2, h1p, h2p = modifiedhankel(complex(-2.41004961, 4.17432837))
    @test h1 ≈ complex(-0.49173751, -0.29946086) atol=1e-7
    @test h2 ≈ complex(0, 1.15117521)
    @test h1p ≈ complex(1.26609349, 0)
end

@testset "Power series: spot checks" begin
    h1, h2, h1p, h2p = modifiedhankel(complex(2., 0.))
    @test h1 ≈ complex(0.60991262, 0.36822576)
    @test h1p ≈ complex(-0.59922801, 0.83306447)
    @test h2 ≈ complex(0.60991262, -0.36822576)
    @test h2p ≈ complex(-0.59922801, -0.83306447)

    h1, h2, h1p, h2p = modifiedhankel(complex(4.5, 2.2))
    @test h1 ≈ complex(-0.00170177, -0.00480825) atol=1e-7
    @test h1p ≈ complex(0.01152459, -0.00111977) atol=1e-7
    @test h2 ≈ complex(-7.26047008, 63.40667850)
    @test h2p ≈ complex(133.64890371, 44.66625637)

    h1, h2, h1p, h2p = modifiedhankel(complex(1.1, 4.1))
    @test h1 ≈ complex(-0.00202396, 0.00177285) atol=1e-7
    @test h1p ≈ complex(-0.00042308, -0.00566719) atol=1e-7
    @test h2 ≈ complex(-131.37854842, -8.34958396)
    @test h2p ≈ complex(-176.03846956, 196.31112124)
end

@testset "Comparison with Airy and Bessel functions" begin
    z = complex(1.345,-0.245)
    T = 2/3*z^(3/2)
    k = (3/2)^(2/3)*(1 - im*sqrt(3)/3)
    β = 2^(4/3)*3^(1/6)

    h1, h2, h1p, h2p = modifiedhankel(z)
    h1m, h2m, h1mp, h2mp = modifiedhankel(-z)
    h1c, h2c, h1cp, h2cp = modifiedhankel(conj(z))

    ai, bi = airyai(z), airybi(z)
    aim, bim = airyai(-z), airybi(-z)
    aip, bip = airyaiprime(z), airybiprime(z)

    # See SCL1945 (22), (25)
    @test h2 ≈ conj(h1c)
    @test h2p ≈ conj(h1cp)

    # SCL1945 (18), (19)
    @test h1 ≈ k*(aim - im*bim)
    @test h2 ≈ conj(k)*(aim + im*bim)

    # SCL1945 (20), (21)
    @test ai ≈ 1/2k*h1m + 1/(2*conj(k))*h2m
    @test bi ≈ im/2k*h1m - im/(2*conj(k))*h2m

    # SCL1945 (23), (24)
    @test aip ≈ -1/(2k)*h1mp - 1/(2*conj(k))*h2mp
    @test bip ≈ -im/(2k)*h1mp + im/(2*conj(k))*h2mp

    # SCL1945 (26)
    h1t = modifiedhankel(cis(-2π/3)*z)[1]
    h2t = modifiedhankel(cis(2π/3)*z)[2]
    @test -im*β*aim ≈ h1t
    @test -im*β*aim ≈ -h2t
    @test -im*β*aim ≈ -cis(π/3)*(cis(π/3)*h1 + h2)

    # SCL1945 (27)
    h1t = modifiedhankel(conj(z)*exp(2π*im/3))[1]
    @test h1t ≈ -h2c
    @test h1t ≈ -conj(h1)

    # SCL1945 (28)
    h1tp = modifiedhankel(z*exp(2π*im/3))[3]
    @test h1tp ≈ cis(π/3)*h2p
    @test h1tp ≈ cis(π/3)*conj(h1cp)

    # SCL1945
    @test besselj(1/3,T) ≈ 1/2*T^(-1/3)*(h1 + h2)  # (36)
    @test bessely(1/3,T) ≈ 1/2im*T^(-1/3)*(h1 - h2)  # (37)
    @test hankelh1(1/3,T) ≈ T^(-1/3)*h1  # (38)
    @test hankelh2(1/3,T) ≈ T^(-1/3)*h2  # (39)
    @test besselj(-1/3,T) ≈ 1/2*T^(-1/3)*(cis(π/3)*h1 + cis(-π/3)*h2)  # (40)
    @test bessely(-1/3,T) ≈ 1/2im*T^(-1/3)*(cis(π/3)*h1 - cis(-π/3)*h2)  # (41)
    @test hankelh1(-1/3,T) ≈ cis(π/3)*T^(-1/3)*h1  # (42)
    @test hankelh2(-1/3,T) ≈ cis(-π/3)*T^(-1/3)*h2  # (43)
    @test besselj(2/3,T) ≈ 1/2*(2/3)^(1/3)*T^(-2/3)*(cis(-2π/3)*h1p + cis(2π/3)*h2p)  # (44)
    @test bessely(2/3,T) ≈ 1/2im*(2/3)^(1/3)*T^(-2/3)*(cis(-2π/3)*h1p - cis(2π/3)*h2p)  # (45)
    @test hankelh1(2/3,T) ≈ (2/3)^(1/3)*cis(-2π/3)*T^(-2/3)*h1p  # (46)
    @test hankelh2(2/3,T) ≈ (2/3)^(1/3)*cis(2π/3)*T^(-2/3)*h2p  # (47)
    @test besselj(-2/3,T) ≈ 1/2*(2/3)^(1/3)*T^(-2/3)*(h1p + h2p)  # (48)
    @test bessely(-2/3,T) ≈ 1/2im*(2/3)^(1/3)*T^(-2/3)*(h1p - h2p)  # (49)
    @test hankelh1(-2/3,T) ≈ (2/3)^(1/3)*T^(-2/3)*h1p  # (50)
    @test hankelh2(-2/3,T) ≈ (2/3)^(1/3)*T^(-2/3)*h2p  # (51)
end

@testset "Asymptotic expansion" begin
    # From Table 2, SCL1945

    @test ModifiedHankelFunctionsOfOrderOneThird.C(1) == 0.10416666666666667
    @test ModifiedHankelFunctionsOfOrderOneThird.C(2) == 0.08355034722222222
    @test ModifiedHankelFunctionsOfOrderOneThird.C(3) == 0.12822657455632716
    @test ModifiedHankelFunctionsOfOrderOneThird.C(4) == 0.29184902646414046
    @test ModifiedHankelFunctionsOfOrderOneThird.C(5) == 0.88162726744375765

    @test ModifiedHankelFunctionsOfOrderOneThird.C(6) ≈ 3.321408281862768 atol=1e-14
    @test ModifiedHankelFunctionsOfOrderOneThird.C(7) ≈ 14.9957629868626 atol=1e-12
    @test ModifiedHankelFunctionsOfOrderOneThird.C(BigInt(8)) ≈ 78.923013011587 atol=1e-11
    @test ModifiedHankelFunctionsOfOrderOneThird.C(BigInt(9)) ≈ 474.451538868 atol=1e-8
    @test ModifiedHankelFunctionsOfOrderOneThird.C(BigInt(10)) ≈ 3207.490091 atol=1e-5

    @test ModifiedHankelFunctionsOfOrderOneThird.C(BigInt(11)) ≈ 24086.5496 atol=1e-3
    @test ModifiedHankelFunctionsOfOrderOneThird.C(BigInt(12)) ≈ 198923.12 atol=1e-1
    @test ModifiedHankelFunctionsOfOrderOneThird.C(BigInt(13)) ≈ 1791902.0 atol=1
    @test ModifiedHankelFunctionsOfOrderOneThird.C(BigInt(14)) ≈ 17484377. atol=1

    @test ModifiedHankelFunctionsOfOrderOneThird.C(BigInt(25)) ≈ 7.3900049415704853993e+19
end


# This implements an efficient sampler for the univariate truncated
# standard normal distribution from:

# Y. Li and S. Ghosh (2015)
# "Efficient Sampling Methods for Truncated Multivariate Normal and Student-t
#  Distributions Subject to Linear Inequality Constraints"
# Journal of Statistical Theory & Practice
# doi: 10.1080/15598608.2014.996690
# http://dx.doi.org/10.1080/15598608.2014.996690

immutable TruncatedNormalSampler <: Sampleable{Univariate, Continuous}
  a::Float64       # Lower bound
  b::Float64       # Upper bound
  m::ASCIIString   # Sampling method to use:
                   #   NR: Normal rejection sampling
                   #   HR: Half-Normal rejection sampling
                   #   UR: Uniform rejection sampling
                   #   ER: Translated exponential rejection sampling
  sgn::Int64       # Change sign of result to be returned?
end

function TruncatedNormalSampler(a::Float64, b::Float64)
  m::ASCIIString
  sgn = 1
  a0 = 0.2570

  # Needs to work for untruncated regions
  if a == -Inf && b == Inf
    return TruncatedNormalSampler(a, b, "NR", sgn)
  end

  # Symmetric case, return negative of result
  if b <= 0  || a == -Inf
    sgn = -1
    olda = a
    a = -b
    b = -olda
  end

  b1 = a + sqrt(π/2) * exp(a*a/2)
  b2 = a + 2 / (a + sqrt(a*a + 4)) * exp((a*a - a*sqrt(a*a +4))/4 + .5)

  # Determine method to use
  a < 0 ?
    b == Inf ? m = "NR" :
      b - a <= sqrt(2π) ? m = "UR" : m = "NR" :
    b == Inf ?
      a < a0 ? m = "HR" : m = "ER" :
      a < a0 ?
        b <= b1 ? m = "UR" : m = "HR" :
        b <= b2 ? m = "UR" : m = "ER"

  return TruncatedNormalSampler(a, b, m, sgn)
end

function rand(s::TruncatedNormalSampler)
#   a = s.a
#   b = s.b
#   sgn = s.sgn
  if s.m == "NR"
    while true
      x = randn()
      x >= s.a && x <= s.b && return x*s.sgn
    end
  elseif s.m == "HR"
    while true
      x = abs(randn())
      if x >= s.a && x <= s.b
        return x*s.sgn
      end
    end
  elseif s.m == "UR"
    Zd = Normal(0, 1)
    Uab = Uniform(s.a, s.b)
    pc = (cdf(Zd, s.b) - cdf(Zd, s.a))
    if s.a >= 0
      pc *= exp(s.a*s.a/2)
    elseif s.b <= 0
      pc *= exp(s.b*s.b/2)
    end
    u = 0.0
    x = 0.0
    while true
      u = rand()
      x = rand(Uab)
      if u < exp(-x*x/2) * pc
        return x*s.sgn
      end
    end
  elseif s.m == "ER"
    λ = (s.a + sqrt(s.a*s.a + 4)) / 2
    u = 0.0
    x = 0.0
    p = 0.0
    while true
      x = randexp() / λ + s.a
      u = rand()
      p = exp(-(x-λ)^2 / 2)
      u < p && x <= s.b && return x*s.sgn
    end
  end
end


module Utils
    function dtot32(d)
        return map(x->UInt32(mod(round(Int64,mod(x,1.0)*2.0^32),Int64(2)^32)),d)
    end
    function gaussian32(μ,α)
        return dtot32(α*randn(length(μ)) .+ μ)
    end
end


module Conversions

using ..WhiskerStructs

function hydro2topo(hydro::HydroWhisker)::TopoWhisker
    c₁ = 0.5 * hydro.T * (hydro.γ + hydro.Ac)
    c2 = 0.5 * hydro.T * (hydro.γ - hydro.Ac);
    M = hydro.T * hydro.λ * 0.5;
    ΔL₁ = 0.5 * hydro.ϵ * M;
    ΔL2 = M * (hydro.Phi - 1) + ΔL₁;
    a = sqrt(sqr(c₁) + sqr(ΔL₁));
    k = sqrt(sqr(c2) + sqr(ΔL2));
    α = asin(ΔL₁ / a);
    β = asin(ΔL2 / k);
    b = 0.5 * hydro.T * (1 - hydro.At);
    l = 0.5 * hydro.T * (1 + hydro.At);

    return TopoWhisker(a, b, k, l, α, β, M)
end

function topo2hyrdro(topo::TopoWhisker)::HydroWhisker

end

export hydro2topo

end


module CYCLOPS_v6_2a_PreNPostprocessModule
#export Size_Transform_Seed_Data

export GetEigenGenes
export GetNEigenGenes
export PCA_Transform_Seed_Data
export Repeat_PCA_Transform_Data
export Row_Shuffle
export Bonferroni_Adjust
export Corrected_Cosinor_Statistics
export Corrected_Cosinor_Statistics_Faster,MultiCore_Cosinor_Statistics
export Compile_MultiCore_Cosinor_Statistics
export best_shift_cos, best_shift_cos2
using StatsBase
using MultivariateStats
using Distributions

###################################################
###################################################
function GetEigenGenes(numeric_data::Array{Float64,2},fraction_var=.75, dfrac_var=0.05,maxeig=50)
    u, w, v = svd(numeric_data)
    expvar=cumsum(w.^2)/sum(w.^2)
    ReductionDim1=1+length(expvar[expvar .<= fraction_var]);
    vardif=diff(expvar)
    ReductionDim2=1+length(vardif[vardif .>= dfrac_var]);
    ReductionDim=minimum([ReductionDim1,ReductionDim2])
    ReductionDim=minimum([ReductionDim,maxeig])
    Transform=v[:,1:ReductionDim]'
    ReductionDim, 10*Transform
end

###################################################
###################################################
###################################################
function GetNEigenGenes(numeric_data::Array{Float64,2},nkeep::Integer)
    u, w, v = svd(numeric_data)
    Transform=v[:,1:nkeep]'
    10*Transform
end

###################################################
###################################################
function PCA_Transform_Seed_Data(numeric_data::Array{Float64,2},fraction_var=.75,maxeig=50)
    Mod=fit(PCA,numeric_data;pratio=fraction_var,maxoutdim=maxeig)
    ReductionDim=outdim(Mod)
    Transform=transform(Mod,numeric_data)
    Transform=10*Transform/maximum(Transform)
    ReductionDim, Transform
end

###################################################
###################################################
function Repeat_PCA_Transform_Data(numeric_data::Array{Float64,2},outsize::Integer)
    z_data=zscore(numeric_data,2)
    Mod=fit(PCA,z_data;pratio=1.,maxoutdim=outsize)
    Transform=transform(Mod,z_data)
    Transform
end
##########################################################
###########################################################
function Row_Shuffle(data_matrix::Array{Float64,2})
#ROW SHUFFLE independantly permutes the elements in each row of a matrix
    (nrows,ncols)=size(data_matrix);
    shuffled_matrix=zeros(nrows,ncols)
    for r=1:nrows
        shuffled_matrix[r,:]=data_matrix[r,sample(1:ncols,ncols;replace=false)];
    end
    shuffled_matrix
end

##########################################################
###########################################################
function Bonferroni_Adjust(pvalues::Array{Float64,1})
    n=size(pvalues)[1]
    adjusted=zeros(n)
    for cnt=1:n
        adjusted[cnt]=minimum([n*pvalues[cnt],1.])
    end
    adjusted
end

##########################################################
##########################################################
#####################################################################################################################
function Corrected_Cosinor_Statistics_Faster(expression::Array{Float64,1},o_PREDICTED_PHASELIST::Array{Float64,1},n_shifts=20)

    len=size(o_PREDICTED_PHASELIST)[1]
    shift_list=linspace(2*pi/n_shifts,2*pi,n_shifts)

    min_error=var(expression)*10E20
    best_shift="error"
    for shift in shift_list
        l_PREDICTED_PHASELIST       =mod.((o_PREDICTED_PHASELIST + shift),(2*pi))

        XLINEAR =reshape(l_PREDICTED_PHASELIST,len,1);

        mod_linear          =llsq(XLINEAR,expression);
        m_linear, b_linear  = mod_linear[1:end-1], mod_linear[end];
        predict_linear      =XLINEAR*m_linear + b_linear;

        sse_linear          =sum(abs2.(expression-predict_linear));
        if sse_linear < min_error
            min_error=sse_linear
            best_shift=shift
        end
    end

    l_PREDICTED_PHASELIST=mod.((o_PREDICTED_PHASELIST + best_shift),(2*pi))
    SIN_l_PREDICTED_PHASELIST   =sin.(l_PREDICTED_PHASELIST);
    COS_l_PREDICTED_PHASELIST   =cos.(l_PREDICTED_PHASELIST);

    XLINEAR =reshape(l_PREDICTED_PHASELIST,len,1);
    XFULL   =hcat(l_PREDICTED_PHASELIST,SIN_l_PREDICTED_PHASELIST,COS_l_PREDICTED_PHASELIST);

    mod_linear          =llsq(XLINEAR,expression);
    m_linear, b_linear  = mod_linear[1:end-1], mod_linear[end];

    mod_full            =llsq(XFULL,expression);
    m_full, b_full      = mod_full[1:end-1], mod_full[end];

    predict_linear      =XLINEAR*m_linear + b_linear;
    predict_full        =XFULL*m_full + b_full;

    sse_linear          =sum(abs2.(expression-predict_linear));
    sse_full            =sum(abs2.(expression-predict_full));

    f_metric            =((sse_linear-sse_full)/2)/((sse_full)/(len-5)); #p2=4+1 (+1 =shift) ,p1=2+1  (+1 =shift) ,n=len
    pval= 1-cdf(FDist(2,len-5),f_metric) #Note - null hypothesis this satisifes F distribution, with (p2−p1, n−p2) degrees of freedom.

    ###################
    #Get Phase,Amp,Avg
    ####################
    SIN_o_PREDICTED_PHASELIST   =sin.(o_PREDICTED_PHASELIST);
    COS_o_PREDICTED_PHASELIST   =cos.(o_PREDICTED_PHASELIST);
    XCOSINOR                    =hcat(SIN_o_PREDICTED_PHASELIST,COS_o_PREDICTED_PHASELIST);
    mod_cosinor                 =llsq(XCOSINOR,expression);

    m_cosinor, b_cosinor        = mod_cosinor[1:end-1], mod_cosinor[end];

    predict_cosinor             = XCOSINOR*m_cosinor + b_cosinor;

    sse_cosinor                 =sum(abs2.(expression-predict_cosinor));
    mean_expression             =mean(expression);
    sse_base                    =sum(abs2.(expression-mean_expression));

    r2                          =1-(sse_cosinor/sse_base)

    sinterm, costerm  = mod_cosinor[1], mod_cosinor[2];
    acrophase=atan2(sinterm,costerm);
    amp=sqrt(sinterm^2+costerm^2);
    fitavg=b_cosinor[1]
    avg=mean(expression)
    pval, acrophase, amp, fitavg, avg, r2
end

##########################################################
#####################################################################################################################
#####################################################################################################################
function MultiCore_Cosinor_Statistics(data::Array{Float64,2},PREDICTED_PHASELIST::Array{Float64,1},n_shifts=20)
    ngenes,nsamples=size(data)
    PrbPval=zeros(ngenes);
    PrbPhase=zeros(ngenes);
    PrbAmp=zeros(ngenes);
    PrbFitMean=zeros(ngenes);
    PrbMean=zeros(ngenes);
    PrbR2=zeros(ngenes);

    for row=1:ngenes
        PrbPval[row],PrbPhase[row],PrbAmp[row],PrbFitMean[row],PrbMean[row],PrbR2[row]=Corrected_Cosinor_Statistics_Faster(data[row,:],PREDICTED_PHASELIST,n_shifts)
    end
    PrbPval,PrbPhase,PrbAmp,PrbFitMean,PrbMean,PrbR2
end

##############################################
#####################################################################################################################
function best_shift_cos2(acrophaselist1,acrophaselist2,samplephase1,conversion_flag)

    if conversion_flag=="hours"
        llist2=mod.(acrophaselist2,24)*(pi/12)
    elseif conversion_flag=="radians"
        llist2=mod.(acrophaselist2,2*pi)
    else
        println("FLAG ERROR")
    end

    bestdist=10;
    bestacrophaselist1=acrophaselist1
    bestsamplephase1=samplephase1

    for a in linspace(-pi,pi,192)
        llist1=mod.(acrophaselist1+a,2*pi)
        adist=mean(1 - cos.(float(llist1-llist2)))
        if bestdist>adist
            bestdist=adist
            bestacrophaselist1=llist1
            bestsamplephase1=mod.(samplephase1+a,2*pi)
        end
      end

    for a in linspace(-pi,pi,192)
        llist1=mod.(-(acrophaselist1+a),2*pi)
        adist=mean(1 - cos.(float(llist1-llist2)))
        if bestdist>adist
            bestdist=adist
            bestacrophaselist1=llist1
            bestsamplephase1=mod.(-(samplephase1+a),2*pi)

        end

    end

    bestacrophaselist1,bestsamplephase1
end

##########################################################
function best_shift_cos(list1,list2,conversion_flag)
    nlist=length(list1)

    if conversion_flag=="hours"
        llist2=mod.(list2,24)*(pi/12)
    elseif conversion_flag=="radians"
        llist2=mod.(list2,2*pi)
    else
        println("FLAG ERROR")
    end

    bestdist=10;
    bestlist=zeros(nlist);
    n=0;
    for a in linspace(-pi,pi,192)
        n=n+1
        llist1=mod.(list1+a,2*pi)
        runningscore=0
        for i in 1:nlist
            cosd=(1-cos(llist1[i]-llist2[i]))/2
            runningscore=runningscore+cosd
        end
        adist=runningscore/nlist
        if bestdist>adist
            bestdist=adist
            bestlist=llist1
        end
      end

    for a in linspace(-pi,pi,192)
        n=n+1
        llist1=mod.(-(list1+a),2*pi)
        runningscore=0
        for i in 1:nlist
            cosd=(1-cos(llist1[i]-llist2[i]))/2
            runningscore=runningscore+cosd
        end
        adist=runningscore/nlist

        if bestdist>adist
            bestdist=adist
            bestlist=llist1
        end

    end

    bestlist
end
#####################################################################################################################
function Compile_MultiCore_Cosinor_Statistics(annotated_data::Array{Any,2},PREDICTED_PHASELIST::Array{Float64,1},NumericStartCol=4,n_shifts=20)

alldata_annot=annotated_data[1:end ,1:(NumericStartCol-1)];
alldata_data=Array{Float64}(annotated_data[2:end , NumericStartCol:end]);

ngenes=size(alldata_data)[1]
rowbin=Int(floor(ngenes/5));
tic()
estimated_phaselist=vec(PREDICTED_PHASELIST)
c1=@spawn MultiCore_Cosinor_Statistics(alldata_data[(1:(1*rowbin)),:],estimated_phaselist) ;
c2=@spawn MultiCore_Cosinor_Statistics(alldata_data[((1+1*rowbin):(2*rowbin)),:],estimated_phaselist,n_shifts);
c3=@spawn MultiCore_Cosinor_Statistics(alldata_data[((1+2*rowbin):(3*rowbin)),:],estimated_phaselist,n_shifts);
c4=@spawn MultiCore_Cosinor_Statistics(alldata_data[((1+3*rowbin):(4*rowbin)),:],estimated_phaselist,n_shifts);
c5=@spawn MultiCore_Cosinor_Statistics(alldata_data[((1+4*rowbin):ngenes),:],estimated_phaselist,n_shifts);

PrbPval1,PrbPhase1,PrbAmp1,PrbFitMean1,PrbMean1,PrbRsq1=fetch(c1);
PrbPval2,PrbPhase2,PrbAmp2,PrbFitMean2,PrbMean2,PrbRsq2=fetch(c2);
PrbPval3,PrbPhase3,PrbAmp3,PrbFitMean3,PrbMean3,PrbRsq3=fetch(c3);
PrbPval4,PrbPhase4,PrbAmp4,PrbFitMean4,PrbMean4,PrbRsq4=fetch(c4);
PrbPval5,PrbPhase5,PrbAmp5,PrbFitMean5,PrbMean5,PrbRsq5=fetch(c5);
toc()

PrbPval=vcat(PrbPval1,PrbPval2,PrbPval3,PrbPval4,PrbPval5);
PrbPhase=vcat(PrbPhase1,PrbPhase2,PrbPhase3,PrbPhase4,PrbPhase5);
PrbAmp=vcat(PrbAmp1,PrbAmp2,PrbAmp3,PrbAmp4,PrbAmp5);
PrbMean=vcat(PrbMean1,PrbMean2,PrbMean3,PrbMean4,PrbMean5);
PrbFitMean=vcat(PrbFitMean1,PrbFitMean2,PrbFitMean3,PrbFitMean4,PrbFitMean5);
PrbRsq=vcat(PrbRsq1,PrbRsq2,PrbRsq3,PrbRsq4,PrbRsq5);

PrbPtr= (PrbAmp + PrbFitMean) ./  (PrbFitMean - PrbAmp)
PrbBon=Bonferroni_Adjust(PrbPval);

cosinor_output=hcat(PrbPval,PrbBon,PrbPhase,PrbAmp,PrbFitMean,PrbMean,PrbRsq,PrbPtr)
headers=["pval","bon_pval","phase","amp","fitmean","mean","rsq","ptr"]
headers=reshape(headers,1,length(headers))

cosinor_output=vcat(headers,cosinor_output)
cosinor_output=hcat(alldata_annot,cosinor_output)
cosinor_output
end

end


export binhist

"""
    _non0hist(points, binning_scheme::RectangularBinning, dims)

Determine which bins are visited by `points` given the rectangular `binning_scheme`,
considering only the marginal along dimensions `dims`. Bins are referenced
relative to the axis minima.

Returns the unordered (sum-normalized, if `normalize==true`) histogram (visitation
frequency) over the array of bin visits.

# Example
```julia
using using Entropies, DelayEmbeddings
pts = Dataset([rand(5) for i = 1:100]);

# Histograms directly from points given a rectangular binning scheme
h1 = _non0hist(pts, RectangularBinning(0.2), 1:3)
h2 = _non0hist(pts, RectangularBinning(0.2), [1, 2])

# Test that we're actually getting normalised histograms
sum(h1) ≈ 1.0, sum(h2) ≈ 1.0
```
"""
function _non0hist(points, binning_scheme::RectangularBinning, dims)
    bin_visits = marginal_visits(points, binning_scheme, dims)
    _non0hist(bin_visits)
end

function _non0hist(points::AbstractDataset{N, T}, binning_scheme::RectangularBinning) where {N, T}
    _non0hist(points, binning_scheme, 1:N)
end

# TODO: ϵ::RectangularBinning(Float64) allocates slightly more memory than the method below
# because of the call to `minima_edgelengths`. Can be optimized, but keep both versions for
# now and merge docstrings.
probabilities(data::AbstractDataset, ϵ::RectangularBinning) = _non0hist(data, ϵ)[1]
function _non0hist(data::AbstractDataset{D, T}, ϵ::RectangularBinning) where {D, T<:Real}

    # TODO: this allocates a lot, but is not performance critical
    mini, edgelengths = minima_edgelengths(data, ϵ)

    # Map each datapoint to its bin edge and sort the resulting list:
    bins = map(point -> floor.(Int, (point .- mini) ./ edgelengths), data)
    sort!(bins, alg=QuickSort)

    # Reserve enough space for histogram:
    L = length(data)
    hist = Vector{Float64}()
    sizehint!(hist, L)

    # Fill the histogram by counting consecutive equal bins:
    prev_bin, count = bins[1], 0
    for bin in bins
        if bin == prev_bin
            count += 1
        else
            push!(hist, count)
            prev_bin = bin
            count = 1
        end
    end
    push!(hist, count)

    # Shrink histogram capacity to fit its size:
    sizehint!(hist, length(hist))
    return Probabilities(hist ./ L), bins, mini, edgelengths
end

function binhist(x::AbstractDataset{D, T}, ϵ::RectangularBinning) where {D, T<:Real}
    hist, bins, mini, edgelengths = _non0hist(x, ϵ)
    unique!(bins)
    b = [β .* edgelengths .+ mini for β in bins]
    return hist, b
end

# Proper dispatch for when an integer is given.
probabilities(data, n::Integer) =
probabilities(Dataset(data), VisitationFrequency(RectangularBinning(n)))

# The following is originally from ChaosTools.jl
probabilities(data::AbstractDataset, ε::AbstractFloat) = _non0hist(data, ε)[1]
function _non0hist(data::AbstractDataset{D, T}, ε::AbstractFloat) where {D, T<:Real}
    mini = minima(data)
    L = length(data)
    hist = Vector{Float64}()
    # Reserve enough space for histogram:
    sizehint!(hist, L)

    # Map each datapoint to its bin edge and sort the resulting list:
    bins = map(point -> floor.(Int, (point - mini)/ε), data)
    sort!(bins, alg=QuickSort)

    # Fill the histogram by counting consecutive equal bins:
    prev_bin, count = bins[1], 0
    for bin in bins
        if bin == prev_bin
            count += 1
        else
            push!(hist, count)
            prev_bin = bin
            count = 1
        end
    end
    push!(hist, count)

    # Shrink histogram capacity to fit its size:
    sizehint!(hist, length(hist))
    return Probabilities(hist ./ L), bins, mini
end

# TODO: This needs to be expanded to allow `est::RectangularBinning(vector)`
"""
    binhist(x::AbstractDataset, ε::Real) → p, bins
    binhist(x::AbstractDataset, ε::RectangularBinning) → p, bins

Hyper-optimized histogram calculation for `x` with rectangular binning `ε`.
Returns the probabilities `p` of each bin of the histogram as well as the bins.
Notice that `bins` are the starting corners of each bin. If `ε isa Real`, then the actual
bin size is `ε` across each dimension. If `ε isa RectangularBinning`, then the bin
size for each dimension will depend on the binning scheme.

See also: [`RectangularBinning`](@ref).
"""
function binhist(data, ε)
    hist, bins, mini = _non0hist(data, ε)
    unique!(bins)
    b = [β .* ε .+ mini for β in bins]
    return hist, b
end


#=

Implements the empirical cumulative distribution function given an array
of observations.

@author : Spencer Lyon <spencer.lyon@nyu.edu>

@date: 2014-08-25

=#

"""
One-dimensional empirical distribution function given a vector of
observations.

##### Fields

- `observations::Vector`: The vector of observations
"""
mutable struct ECDF
    observations::Vector
end

"""
Evaluate the empirical cdf at one or more points

##### Arguments

- `x::Union{Real, Array}`: The point(s) at which to evaluate the ECDF
"""
(e::ECDF)(x::Real) = mean(e.observations .<= x)
(e::ECDF)(x::AbstractArray) = e.(x)

@deprecate ecdf(e::ECDF, x) e(x)


using DrWatson
@quickactivate "NonlinearDynamicsTextbook"
using Random, OrdinaryDiffEq

# Run this file to integrate PDE Brusselator and save snapshots.
# Configure parameters HERE, including the `saveat` parameter
# (when to save the output fields). You can choose to call this file with a
# global existing variable `u0` (the 2xNxN array of the initial condition).
# if this is the case, change the `ic` parameter below to `:given`
a = 9.0
b = 10.2
L = 50.0 # size of domain
N = 250  # no. of grid points
d = 1.7
ic = :random
saveat = 1000:0.25:1025

# Remaining file (DO NOT ALTER!)
u0 = isdefined(Main, :u0) ? u0 : nothing
config = @strdict(a, b, d, L, N, ic, saveat, u0)

function Brusselator_ODE(du,u,p,t)
    a, b, d, hsq = p

    uu = @view u[1,:,:]
    vv = @view u[2,:,:]

    uE = circshift(uu, (0,1))   # u[:,[2:N 1]]
    uW = circshift(uu, (0,-1))  # u[:,[N 1:N-1]]
    uN = circshift(uu, (1,0))   # u[[N 1:N-1],:]
    uS = circshift(uu, (-1,0))  # u[[2:N 1],:]
    vE = circshift(vv, (0,1))
    vW = circshift(vv, (0,-1))
    vN = circshift(vv, (1,0))
    vS = circshift(vv, (-1,0))

    # finite difference formula
    au2v = @. a*uu*uu*vv
    du[1,:,:] = @. 1 - (b+1)*uu + au2v + (uE+uW+uN+uS - 4uu)/hsq
    du[2,:,:] = @. b*uu - au2v + d*(vE+vW+vN+vS - 4vv)/hsq
    return
end

# This is for progress tracking of DiffEq
using Logging: global_logger
using TerminalLoggers: TerminalLogger
global_logger(TerminalLogger())

function produce_brusselator(config)
    @unpack a, b, d, saveat, N, L,ic = config
    h = L/N  # spatial step size
    hsq = h*h

    # initial condition
    if ic == :random
        us = 1  #  u steady state
        vs = b/a   # v steady state
        uu = fill(us, N, N)
        vv = fill(vs, N, N)
        Random.seed!(13371)
        uu = uu .+ 0.01*randn(N,N)    # random perturbation
        u0 = zeros(2,N,N)
        u0[1,:,:] .= uu
        u0[2,:,:] .= vv
    else
        u0 = config["u0"]
        @assert size(u0) == (2, N, N)
    end

    # Solve ODE
    p = (a, b, d, hsq)
    tspan = (0.0, saveat[end])
    @info "Initializing"
    prob = ODEProblem(Brusselator_ODE, u0, tspan, p)
    @info "Starting solve..."
    sol = solve(prob, Tsit5();
        reltol=1e-6, abstol=1e-6, maxiters = Inf, saveat,
        progress = true, progress_steps = 100,
    )
    t = sol.t
    u = sol.u
    output = @strdict u t a b d L N u0
    return output
end

prefix = length(saveat) > 10 ? "brusselator2D_dense" : "brusselator2D"

produce_or_load(datadir("Brusselator"), config, produce_brusselator;
    prefix, suffix = "jld2", tag = false, loadfile = false,
    force = true,
)


# ---
# title: 36. Valid Sudoku
# id: problem36
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Hash Table
# link: <https://leetcode.com/problems/valid-sudoku/description/>
# hidden: true
# ---
#
# Determine if a `9 x 9` Sudoku board is valid. Only the filled cells need to be
# validated  **according to the following rules** :
#
#   1. Each row must contain the digits `1-9` without repetition.
#   2. Each column must contain the digits `1-9` without repetition.
#   3. Each of the nine `3 x 3` sub-boxes of the grid must contain the digits `1-9` without repetition.
#
# **Note:**
#
#   * A Sudoku board (partially filled) could be valid but is not necessarily solvable.
#   * Only the filled cells need to be validated according to the mentioned rules.
#
#
#
# **Example 1:**
#
# ![](https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-
# by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png)
#
#
#
#     Input: board =
#     [["5","3",".",".","7",".",".",".","."]
#     ,["6",".",".","1","9","5",".",".","."]
#     ,[".","9","8",".",".",".",".","6","."]
#     ,["8",".",".",".","6",".",".",".","3"]
#     ,["4",".",".","8",".","3",".",".","1"]
#     ,["7",".",".",".","2",".",".",".","6"]
#     ,[".","6",".",".",".",".","2","8","."]
#     ,[".",".",".","4","1","9",".",".","5"]
#     ,[".",".",".",".","8",".",".","7","9"]]
#     Output: true
#
#
# **Example 2:**
#
#
#
#     Input: board =
#     [["8","3",".",".","7",".",".",".","."]
#     ,["6",".",".","1","9","5",".",".","."]
#     ,[".","9","8",".",".",".",".","6","."]
#     ,["8",".",".",".","6",".",".",".","3"]
#     ,["4",".",".","8",".","3",".",".","1"]
#     ,["7",".",".",".","2",".",".",".","6"]
#     ,[".","6",".",".",".",".","2","8","."]
#     ,[".",".",".","4","1","9",".",".","5"]
#     ,[".",".",".",".","8",".",".","7","9"]]
#     Output: false
#     Explanation: Same as Example 1, except with the **5** in the top left corner being modified to **8**. Since there are two 8's in the top left 3x3 sub-box, it is invalid.
#
#
#
#
# **Constraints:**
#
#   * `board.length == 9`
#   * `board[i].length == 9`
#   * `board[i][j]` is a digit or `'.'`.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


## compute estimated area below a (discrete) function curve
function est_area(inds, vals)
    if length(inds)!=length(vals)
        error("The size of indices does not match the size of values!!!")
    end
    area = inds[1]*vals[1]
    for i = 2:length(inds)
        area+=(inds[i]-inds[i-1])*vals[i]
    end
    area
    return area
end

using JLD

"""
"get_bettis_mean_bettis" loads and computes the indices and values of the betti and mean betti
Inputs:
(1) bettis_path: a .jld file, recording the betti results
(2) (Optional) d_up: an integer, deciding the upper limit for the mean betti computation

Outputs:
a tuple (filt_inds, bettis, mean_bettis), where
(1) filt_inds: the filtration indices (in [0,1])
(2) bettis: a matrix; each row is a betti curve; [b0;b1;b2;...]
(3) mean_bettis: a vector, recording mean (b1, b2, b3, ...)
"""

function get_bettis_mean_bettis(bettis_path::String, d_up = 5::Int; d_start = 1)
    D_bettis = load(bettis_path)
    bettis = D_bettis["bettis"]
    println("size of bettis:", size(bettis))
    true_birth_dic = D_bettis["true_birth_dic"]
    denom = D_bettis["denominator"]

    ## compute the (normalized) filtration index
    filt_inds = Float64[true_birth_dic[k]/denom for k = 1:size(bettis,2)]
    mean_bettis = [est_area(filt_inds, bettis[k+1,:]) for k = d_start:d_up-1]
    return (filt_inds, bettis, mean_bettis)
end

using Plots
pyplot()

##################################################################################
## functions for plotting the betti curves
##################################################################################
using ColorSchemes
colors = ColorSchemes.jet
color_inds = [1,3,5,7,9]

trans = 0.2

"""

"""
function plot_betti_curves_real(path_base, name_tail,
        base_data_name::String, d_up = 5::Int, d_start = 1::Int)
    global colors
    global color_inds
    ## load the bettis result of the real data
    bettis_path = "$(path_base)_$(name_tail).jld"
    filt_inds, bettis_real, mean_bettis = get_bettis_mean_bettis(bettis_path, d_up, d_start = d_start)
    ind_color = 1
    println(size(bettis_real))
    for k = d_start:d_up-1
        bettis_real[k+1,:]
        plot!(filt_inds, bettis_real[k+1,:],
            label = "$(base_data_name): betti $(k)", linewidth = 3, line = :dash,
            color = colors[color_inds[ind_color]])
        ind_color+=1
    end
    plot!()
end

"""

"""
function plot_betti_curves_ctrl(base_path::String, name_tail::String,
        d::Int, seed::Int,
        dir_type::String, pt_type::String,
        base_data_name::String,
        d_up = 5::Int, d_start = 1::Int)
    global colors
    global color_inds
    trans = 0.2
    ## load the bettis result of the controlled data
    name_ctrl = "dir_$(dir_type)_pt_$(pt_type)_d_$(d)_seed_$(seed)"
    bettis_path = "$(base_path)_$(name_ctrl)_$(name_tail).jld"
#     try
        filt_inds, bettis, mean_bettis = get_bettis_mean_bettis(bettis_path, d_up)
        ind_color = 1
        for k = d_start:d_up-1
            if seed==1
                plot!(filt_inds, bettis[k+1,:], label = "betti $k",
                        color = colors[color_inds[ind_color]], alpha = trans, linewidth = 1)
            else
                plot!(filt_inds, bettis[k+1,:], label = "",
                        color = colors[color_inds[ind_color]], alpha = trans, linewidth = 1)
            end
            ind_color+=1
        end
#     catch
#     end
    plt_title = "$(base_data_name) vs controlled
    d = $d, dir: $(dir_type), pt: $(pt_type)"
    try
        if dir_type[1:5]=="angle"
            angle_ratio = round(parse(Int, dir_type[7:end])/pi)/100
            plt_title = "$(base_data_name) vs controlled
            d = $d, dir: $(angle_ratio) pi, pt: $(pt_type)"
        end
    catch
    end
    plot!(title = plt_title)
end

"""

"""
function plot_betti_layout(dpi = 300, ylim = (0,100), legend = :topleft)
    Plots.plot!(dpi = dpi, grid = false, ylim = ylim, legend = legend)
end

#####################################################################################
## functions for scattering the mean betti vectors
#####################################################################################
using Plots
pyplot()

using ColorSchemes
colors_scatter = ColorSchemes.jet
color_scatter_inds = [1,2,3,4,5,6,7,8,9]

"""

"""
function scatter_mean_betti_real_2D(path_base, name_tail,
        label_real, betti_inds = [1,2],
        real_markersize = 8::Int, d_up = 5::Int, trans = 0.7,
        color = "black"; d_start = 1)
    ind1 = betti_inds[1]
    ind2 = betti_inds[2]
    bettis_path = "$(path_base)_$(name_tail).jld"
    filt_inds, bettis, mean_bettis = get_bettis_mean_bettis(bettis_path, d_up)
    scatter!([mean_bettis[ind1]], [mean_bettis[ind2]],
        label = label_real, color = color, alpha = trans,
        markersize = real_markersize, markerstrokewidth=0)
end

"""

"""
function scatter_mean_betti_real_3D(path_base, name_tail,
        label_real, betti_inds = [1,2,3],
        real_markersize = 8::Int, d_up = 5::Int, trans = 0.7,
        color = "black"; d_start = 1)
    ind1 = betti_inds[1]
    ind2 = betti_inds[2]
    ind3 = betti_inds[3]
    bettis_path = "$(path_base)_$(name_tail).jld"
    filt_inds, bettis, mean_bettis = get_bettis_mean_bettis(bettis_path, d_up)
    scatter!([mean_bettis[ind1]],[mean_bettis[ind2]], [mean_bettis[ind3]],
        label = label_real, color = color, alpha = trans,
        markersize = real_markersize, markerstrokewidth=0)
end

"""

"""
function scatter_mean_betti_ctrl_2D(base_path::String, name_tail::String,
        dir_type::String, pt_type::String, num_seed::Int,
        betti_inds = [1,2], d_up = 5::Int, ctrl_markersize = 1::Int,
        trans = 0.7::Float64; d_start = 1)
    global colors_scatter
    global color_inds
    ind1 = betti_inds[1]
    ind2 = betti_inds[2]
    for d = 2:10
        mean_bettis_vecs = zeros(d_up-1,0)
        for seed = 1:num_seed
            try
                ## load the bettis result of the controlled data
                name_ctrl = "dup_$(d_up)_dir_$(dir_type)_pt_$(pt_type)_d_$(d)_seed_$(seed)"
                bettis_path = "$(base_path)_$(name_ctrl)_$(name_tail).jld"
                filt_inds, bettis, mean_bettis = get_bettis_mean_bettis(bettis_path, d_up)

                mean_bettis_vecs = [mean_bettis_vecs mean_bettis]
            catch
                continue
            end
        end
        scatter!(mean_bettis_vecs[ind1,:], mean_bettis_vecs[ind2,:],
            label = "d: $d", color = colors_scatter[color_scatter_inds[d-1]],
            alpha = trans,
            markersize = ctrl_markersize, markerstrokewidth=0)
    end
end

"""

"""
function scatter_mean_betti_ctrl_3D(base_path::String, name_tail::String,
        dir_type::String, pt_type::String, num_seed::Int,
        betti_inds = [1,2,3], d_up = 5::Int, ctrl_markersize = 1::Int,
        trans = 0.7::Float64; d_start = 1)
    global colors_scatter
    global color_inds
    ind1 = betti_inds[1]
    ind2 = betti_inds[2]
    ind3 = betti_inds[3]
    for d = 2:10
        mean_bettis_vecs = zeros(d_up-1,0)
        for seed = 1:num_seed
            try
                ## load the bettis result of the controlled data
                name_ctrl = "dup_$(d_up)_dir_$(dir_type)_pt_$(pt_type)_d_$(d)_seed_$(seed)"
                bettis_path = "$(base_path)_$(name_ctrl)_$(name_tail).jld"
                filt_inds, bettis, mean_bettis = get_bettis_mean_bettis(bettis_path, d_up)

                mean_bettis_vecs = [mean_bettis_vecs mean_bettis]
            catch
                continue
            end
        end
        scatter!(mean_bettis_vecs[ind1,:],mean_bettis_vecs[ind2,:], mean_bettis_vecs[ind3,:],
            label = "d: $d", color = colors_scatter[color_scatter_inds[d-1]],
            alpha = trans,
            markersize = ctrl_markersize, markerstrokewidth=0)
    end
end

"""

"""
function scatter_mean_betti_layout(dpi = 300, legend = :topleft)
    plt_title = "fly vs controlled
    dir: $(dir_type), pt: $(pt_type)"

    Plots.plot!(title = plt_title,
        dpi = dpi, grid = false, legend = legend)
end

################################################################################################
##
################################################################################################
using MultivariateStats

"""

"""
function scatter_mean_betti_PCA(d_PCA::Int,
        base_path_real::String, name_tail_real::String,
        base_path_ctrl::String, name_tail_ctrl::String,
        dir_type::String, pt_type::String,
        num_seed::Int, base_title::String, label_real::String,
        d_up = 5::Int,
        ctrl_markersize = 1::Int, real_markersize = 5::Int,
        ctrl_trans = 0.7::Float64, real_trans = 0.7::Float64; d_start = 1)

    colors = ColorSchemes.jet
    color_inds = [1,3,5,7,9]

    ## plotting dimension should be less than or equal to 3
    ## if not, show error information
    if d_PCA>3
        error("PCA dimension for plotting should be less than or equal to 3.")
    end

    ## get the betti-vector of real data
    betti_vec_real = get_bettis_mean_bettis("$(base_path_real)_$(name_tail_real).jld", d_up, d_start=d_start)[3]
    ctrl_d_to_betti_pts = Dict()

    ## get the betti-vectors of controlled data
    Pts_ctrl = zeros(d_up-d_start, 0)
    for d = 2:10
        Pts_ctrl_d = zeros(d_up-d_start, 0)
        for seed = 1:num_seed
            name_ctrl = "dir_$(dir_type)_pt_$(pt_type)_d_$(d)_seed_$(seed)"
            bettis_path = "$(base_path_ctrl)_$(name_ctrl)_$(name_tail_ctrl).jld"

            betti_vec_ctrl = get_bettis_mean_bettis(bettis_path, d_up, d_start=d_start)[3]
            Pts_ctrl_d = [Pts_ctrl_d betti_vec_ctrl]
        end
        ctrl_d_to_betti_pts[(dir_type, pt_type, d)] = Pts_ctrl_d
        Pts_ctrl = [Pts_ctrl Pts_ctrl_d]
    end
    Pts_ctrl
    ctrl_d_to_betti_pts

    ################################################################################
    # train a PCA model in 2D
    ################################################################################
    M_2D = fit(PCA, [Pts_ctrl betti_vec_real]; maxoutdim=2, pratio = 1.0)
    P_2D = projection(M_2D);

    ################################################################################
    # train a PCA model in 3D
    ################################################################################
    M_3D = fit(PCA, [Pts_ctrl betti_vec_real]; maxoutdim=3)
    P_3D = projection(M_3D);

    ########################################################
    ## 2D scatter plot using PCA
    ########################################################
    if d_PCA==2
        ##################################################################
        ## (static) use pyplot backend to generate and save the plot as a .png file
        ##################################################################
        pyplot()
        pt_real_2D = P_2D'*betti_vec_real
        println("P_2D", P_2D)
        scatter(dpi = 300)
        scatter!([pt_real_2D[1]], [pt_real_2D[2]],
            label = label_real,
            markersize = 15, markerstrokewidth=0, color = :black, alpha = 0.8)
        for d = 2:10
            Pts_ctrl_2D = P_2D'*ctrl_d_to_betti_pts[(dir_type, pt_type, d)]
            scatter!(Pts_ctrl_2D[1,:], Pts_ctrl_2D[2,:], label = "d: $d",
                markerstrokewidth=0, alpha = 0.8, markersize = 5, color = colors[d-1])
        end

        try
            if dir_type[1:5]=="angle"
                angle_ratio = round(parse(Int, dir_type[7:end])/pi)/100
                scatter!(title = "$(base_title) (PCA: 2D)
                    (dir: $(angle_ratio) pi, pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_2D))/100)",
                    legend=:topright)
            else
                scatter!(title = "$(base_title) (PCA: 2D)
                    (dir: $(dir_type), pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_2D))/100)",
                    legend=:topright)
            end
        catch
            scatter!(title = "$(base_title) (PCA: 2D)
                    (dir: $(dir_type), pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_2D))/100)",
                    legend=:topright)
        end

        ## save the plot
        Plots.savefig("scatter_$(label_real)_dir_$(dir_type)_pt_$(pt_type)_PCA_2D.png")

        ##################################################################
        ## (interactive) use plotly backend to generate and save the plot as a .png file
        ##################################################################
        plotly()
        pt_real_2D = P_2D'*betti_vec_real
        scatter(dpi = 300)
        scatter!([pt_real_2D[1]], [pt_real_2D[2]],
            label = label_real,
            markersize = 10, markerstrokewidth=0, color = :black, alpha = 0.8)
        for d = 2:10
            Pts_ctrl_2D = P_2D'*ctrl_d_to_betti_pts[(dir_type, pt_type, d)]
            scatter!(Pts_ctrl_2D[1,:], Pts_ctrl_2D[2,:], label = "d: $d",
                markerstrokewidth=0, alpha = 0.8, markersize = 4, color = colors[d-1])
        end

        try
            if dir_type[1:5]=="angle"
                angle_ratio = round(parse(Int, dir_type[7:end])/pi)/100
                scatter!(title = "$(base_title) (PCA: 2D)
                    (dir: $(angle_ratio) pi, pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_2D))/100)",
                    legend=:topright)
            else
                scatter!(title = "$(base_title) (PCA: 2D)
                    (dir: $(dir_type), pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_2D))/100)",
                    legend=:topright)
            end
        catch
            scatter!(title = "$(base_title) (PCA: 2D)
                    (dir: $(dir_type), pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_2D))/100)",
                    legend=:topright)
        end

        ## save the plot
        Plots.savefig("scatter_$(label_real)_dir_$(dir_type)_pt_$(pt_type)_PCA_2D.html")
    end

    ########################################################
    ## 3D scatter plot using PCA
    ########################################################
    if d_PCA==3
        plotly()
        pt_real_3D = P_3D'*betti_vec_real
        scatter(dpi = 300)
        scatter!([pt_real_3D[1]], [pt_real_3D[2]], [pt_real_3D[3]],
            label = label_real,
            markersize = 5, markerstrokewidth=0, color = :black, alpha = 0.8)
        for d = 2:10
            Pts_ctrl_3D = P_3D'*ctrl_d_to_betti_pts[(dir_type, pt_type, d)]
            scatter!(Pts_ctrl_3D[1,:], Pts_ctrl_3D[2,:], Pts_ctrl_3D[3,:],
                label = "d: $d",
                markerstrokewidth=0, alpha = 0.8, markersize = 2, color = colors[d-1])
        end

        try
            if dir_type[1:5]=="angle"
                angle_ratio = round(parse(Int, dir_type[7:end])/pi)/100
                scatter!(title = "$(base_title) (PCA: 3D)
                    (dir: $(angle_ratio) pi, pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_3D))/100)",
                    legend=:topright)
            else
                scatter!(title = "$(base_title) (PCA: 3D)
                    (dir: $(dir_type), pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_3D))/100)",
                    legend=:topright)
            end
        catch
            scatter!(title = "$(base_title) (PCA: 3D)
                    (dir: $(dir_type), pt: $(pt_type))
                    principal ratio: $(round(100*principalratio(M_3D))/100)",
                    legend=:topright)
        end

        ## save the plot
        Plots.savefig("scatter_$(label_real)_dir_$(dir_type)_pt_$(pt_type)_PCA_3D.html")
    end
    return (ctrl_d_to_betti_pts, betti_vec_real)
end


function DrawTri!(T)
        plot!([T[1,1],T[2,1]],[T[1,2],T[2,2]],label="")
        plot!([T[2,1],T[3,1]],[T[2,2],T[3,2]],label="")
        plot!([T[3,1],T[1,1]],[T[3,2],T[1,2]],label="")
end

function Ellipse(n;a=1,b=1,max=2π,θ=0,center=[0.0,0.0])
    x = [a*cos(t)*cos(θ) - b*sin(t)*sin(θ) for t in 0:(2π/n):2π] .+ center[1]
    y = [a*cos(t)*sin(θ) + b*sin(t)*cos(θ) for t in 0:(2π/n):2π] .+ center[2]
    return cat(x,y,dims=2)
end

function PackingFraction(X,ParticleArea)
    return (size(X,2)*ParticleArea) ./ Alphashapes.AlphaShapeVolume(AlphaShape(X,FindAlpha(X)))
end


"""    nintegrate(f, xmin::NTuple{N,Any}, xmax::NTuple{N,Any};
               reltol = 1e-6, abstol = 1e-12,
               maxevals = 1000000)

Approximate numerical integration routine based on an adaptive algorithm
[^BEG1991].

- `f` is the integrand.
- `xmin` and `xmax` are tuples specifiying the boundaries of the integration
  domain.
- `reltol`, `abstol` and `maxevals` are optional keyword arguments that
  determine the termination criteria.

Returns a tuple of the approximated integral, error estimate, number of
functions evaluations and an object of type `Regions` (a set of subregions in
which the integration domain was subdivided).

[^BEG1991]: J. Berntsen, T. O. Espelid, A. Genz, "An Adaptive Algorithm for the
Approximate Calculation of Multiple Integrals," ACM Trans. Math. Soft., 17 (4),
437-451 (1991).
"""
function nintegrate(
    f::F, xmin::NTuple{N,Any}, xmax::NTuple{N,Any};
    reltol::Float64 = 1e-6, abstol::Float64 = eps(), maxevals::Int = 1000000
) where {F,N}
    # find the center and half-widths of the whole integration domain
    tx = map((a, b) -> (a + b) / 2, xmin, xmax)
    th = map((a, b) -> (b - a) / 2, xmin, xmax)
    T = Base.promote_typeof(tx..., th...)
    x, h = map(T, tx), map(T, th)
    r = Region(integral_type(f, x), x, h)

    # apply the integration rule to the whole domain
    # and initiate the regions heap
    _p = zeros(MMatrix{2,N,T})
    I, E = apply_rule!(f, r, _p)
    regions = BinaryMaxHeap{typeof(r)}()
    push!(regions, r)
    evals = L = L₁₁

    # adaptively subdivide regions until the termination criteria is met
    while evals < maxevals && E > abs(I) * reltol && E > abstol
        r = pop!(regions)
        r₁, r₂ = divide(r)
        I₁, E₁ = apply_rule!(f, r₁, _p)
        I₂, E₂ = apply_rule!(f, r₂, _p)
        ΔI, ΔE = error_estimates!(r₁, r₂, r, I₁, I₂, E₁, E₂)

        push!(regions, r₁)
        push!(regions, r₂)

        evals += 2L
        I += ΔI
        E += ΔE
    end

    return I, E, evals, Regions(regions.valtree)
end

"""    nintegrate(f, regions::Regions)

Approximate numerical integration routine that takes the integrand `f` and a
`Regions` object which is a subdivision of the integration domain.
"""
nintegrate(f::F, regions::Regions) where {F} = nintegrate(f, regions.v)
function nintegrate(f::F, regions::Vector{Region{N,T,R}}) where {F,N,R,T}
    _p = zeros(MMatrix{2,N,T})
    I  = zero(R)
    for r in regions
        I += apply_rule(f, r, _p)
    end
    return I
end

"""    nintegrate{T<:WPoint}(f, W::Vector{T})

Approximate numerical integration routine that takes the integrand `f` and a
`WPoints` object (set of  weights and nodes on integration domain). A `WPoints`
can be obtained from a `Regions` object `R` by calling `weightedpoints(R)`.
"""
function nintegrate(f::F, wp::WPoints{N,T}) where {F,N,T}
    w = weights(wp)
    p = points(wp)

    isempty(w) && throw(ArgumentError("W must contain at least one element"))

    I = w[1] * evaluate(f, p[1])
    @inbounds @simd for i = 2:length(w)
        I += w[i] * evaluate(f, p[i])
    end

    return I
end

"""    weightedpoints(regions::Regions)

Build a `WPoints` (`W`) from a `Regions` object `regions`.  The resulting
object can be used to approximate de integral of a function `f` by calling
`nintegrate(f, W)`
"""
weightedpoints(regions::Regions) = weightedpoints(idem, regions.v)

"""    weightedpoints(f, regions::Regions)

It works the same as `weightedpoints(::Regions)`, but allows a transformation
`f` that takes a weight and a tuple `x` of point coordinates f(w, p) and
returns a `WPoints`.
"""
weightedpoints(f::F, regions::Regions) where {F} = weightedpoints(f, regions.v)
function weightedpoints(f::F, regions::Vector{Region{N,T,R}}) where {F,N,R,T}
    _p = zeros(MMatrix{2,N,T})
    w = Vector{R}()
    p = Vector{SVector{N,T}}()
    wp = WPoints(w, p)
    for r in regions
        weightedpoints!(f, wp, r, _p)
    end
    return wp
end

function divide(r::Region)
    i = r.axis.x
    r₁ = deepcopy(r)
    r₂ = deepcopy(r)
    r₁.x[i] -= r.h[i] / 2
    r₂.x[i] += r.h[i] / 2
    r₁.h[i] = r₂.h[i] = r.h[i] / 2
    return r₁, r₂
end

function apply_rule!(f::F, r::Region, P) where {F}
    I, N₁, N₂, N₃, N₄, fu, fuα₁, fuα₂ = _apply_rule(f, r, P)
    r.axis.x = choose_axis(r.h, fu, fuα₁, fuα₂)
    E = compute_error(N₁, N₂, N₃, N₄)
    V = prod(r.h)
    I = r.I.x = V * I
    E = r.E.x = 8V * E
    return I, E
end

function choose_axis(h, fu, fuα₁, fuα₂)
    r = (α₁ / α₂)^2
    fc = 2fu * (1 - r)
    Df₁ = chop(abs(fuα₁[1] - r * fuα₂[1] - fc))
    Df₂ = chop(abs(fuα₁[2] - r * fuα₂[2] - fc))
    Df₃ = chop(abs(fuα₁[3] - r * fuα₂[3] - fc))

    if Df₁ == Df₂ == Df₃
        return argmax(h)
    else
        return argmax((Df₁, Df₂, Df₃))
    end
end

function compute_error(N₁, N₂, N₃, N₄)
    N₁⃰ = N₂⃰ = N₃⃰ = zero(N₁)

    @inbounds for i = 1:length(s₁)
        N₁⃰ = max(N₁⃰, abs(μ₁[i] * N₁ + N₂) * s₁[i])
        N₂⃰ = max(N₂⃰, abs(μ₂[i] * N₂ + N₃) * s₂[i])
        N₃⃰ = max(N₃⃰, abs(μ₃[i] * N₃ + N₄) * s₃[i])
    end
    @inbounds for i = length(s₁)+1:length(s₃)
        N₂⃰ = max(N₂⃰, abs(μ₂[i] * N₂ + N₃) * s₂[i])
        N₃⃰ = max(N₃⃰, abs(μ₃[i] * N₃ + N₄) * s₃[i])
    end
    @inbounds for i = length(s₃)+1:length(s₂)
        N₂⃰ = max(N₂⃰, abs(μ₂[i] * N₂ + N₃) * s₂[i])
    end

    if c[1] * N₁⃰ ≤ N₂⃰ && c[2] * N₂⃰ ≤ N₃⃰
        return c[3] * N₁⃰
    else
        return c[4] * max(N₁⃰, N₂⃰, N₃⃰)
    end
end

function error_estimates!(r₁, r₂, r, I₁, I₂, E₁, E₂)
    ΔI = I₁ + I₂ - r.I.x

    E₂⃰ = abs(ΔI)
    if (E₁⃰ = E₁ + E₂) > 0
        E₁ += c[5] * E₁ * E₂⃰ / E₁⃰
        E₂ += c[5] * E₂ * E₂⃰ / E₁⃰
    end
    E₁ += c[6] * E₂⃰
    E₂ += c[6] * E₂⃰

    ΔE = E₁ + E₂ - r.E.x
    r₁.E.x = E₁
    r₂.E.x = E₂

    return ΔI, ΔE
end


using Plots

include("../SonarEquations.jl")

## ROC: Gaussian
PFA = 10.0.^range(-4, 1, length = 101)
d_dB = [0 3 6 9 10 12 15 20]
d = 10.0.^(d_dB/10)
POD = 100SonarEquations.probability_of_detection_gaussian.(d, PFA/100)

pt = plot(PFA, POD, label = d_dB,
	xaxis = ("PFA (%)", :log10),
	yaxis = "POD (%)",
	title = "ROC: Gaussian")
display(pt)

savefig(pt, "img/DetectionIndex_Gaussian.png")

## ROC: Exponential
PFA = 10.0.^range(-4, 1, length = 101)
d_dB = [0 3 6 9 10 12 15 20 30]
d = 10.0.^(d_dB/10)
POD = 100SonarEquations.probability_of_detection_exponential.(d, PFA/100)

pt = plot(PFA, POD, label = d_dB,
	xaxis = ("PFA (%)", :log10),
	yaxis = "POD (%)",
	title = "ROC: Exponential")
display(pt)

savefig(pt, "img/DetectionIndex_Exponential.png")

## ROC: Rayleigh
PFA = 10.0.^range(-4, 1, length = 101)
d_dB = [0 3 6 9 10 12 15 20 30]
d = 10.0.^(d_dB/10)
POD = 100SonarEquations.probability_of_detection_rayleigh.(d, PFA/100)

pt = plot(PFA, POD, label = d_dB,
	xaxis = ("PFA (%)", :log10),
	yaxis = "POD (%)",
	title = "ROC: Rayleigh")
display(pt)

savefig(pt, "img/DetectionIndex_Rayleigh.png")


# SteinHierarchericalPoisson
#
# We assume we have the following hierarcherical model:
#
# n_i | T_i, lambda_i ~ Pois(T_i lambda_i)
# lambda_i | beta ~ Gamma(alpha, beta)
# beta ~ Gamma(gamma, delta)
#

using Distributions: quantile, Gamma, Poisson

type SteinHierarchericalPoisson <: SteinDistribution
    # the counts from each poisson
    n::Array{Int}
    # the times from each poisson
    T::Array{Float64,1}
    # the shape parameter for the poisson priors
    alpha::Float64
    # the shape parameter for the beta prior
    gamma::Float64
    # the scale parameter for the beta prior
    delta::Float64
end

# Values used in Seth Tribble's thesis:
defaultalpha = 1.802
defaultgamma = 0.1
defaultdelta = 1.0

SteinHierarchericalPoisson(n, T) =
    SteinHierarchericalPoisson(n, T, defaultalpha, defaultgamma, defaultdelta)

# Returns lower bound of support
function supportlowerbound(d::SteinHierarchericalPoisson, i::Int)
    0.0
end

# Returns upper bound of support
function supportupperbound(d::SteinHierarchericalPoisson, i::Int)
    Inf
end

function numpoissons(d::SteinHierarchericalPoisson)
    @assert length(d.n) == length(d.T)
    length(d.n)
end

function rungibbs(d::SteinHierarchericalPoisson,
                  n::Int;
                  qmcvariates=[])
    p = numpoissons(d)
    lambdas = Array(Float64, n+1, p)
    betas = Array(Float64, n+1)
    # work with qmc variates or iid ones
    if size(qmcvariates,1) != 0
        @assert size(qmcvariates) == (n, p+1)
        randvariates = qmcvariates
    else
        randvariates = rand(n, p+1)
    end
    # initialize the samples
    lambdas[1,:] = [1.0 for _ in 1:p]
    betas[1] = 1.0
    # do the gibbs!
    for i in 1:n
        # sample the lambdas
        for j in 1:p
            g = Gamma(d.alpha + d.n[j], (betas[i] + d.T[j])^-1)
            lambdas[i+1,j] = quantile(g, randvariates[i,j])
        end
        # sample the beta
        g = Gamma(d.gamma + 10*d.alpha, (d.delta + sum(lambdas[i+1,:]))^-1)
        betas[i+1] = quantile(g, randvariates[i,p+1])
    end
    # discard the first point
    lambdas[2:end,:], betas[2:end]
end

function gradlogdensity(d::SteinHierarchericalPoisson,
                        point::Array{Float64,1})
    lambdas, beta = point[1:end-1], point[end]
    gradlambda = beta + d.T + ((d.n + d.alpha) ./ lambdas)
    gradbeta = -sum(lambdas) + d.gamma / beta - d.delta
    [gradlambda; gradbeta]
end


using Pkg
Pkg.add("MultivariateStats")
using Optim
using GLM
using LinearAlgebra
using Random
using Statistics
using DataFrames
using DataFramesMeta
using CSV
using MultivariateStats
using HTTP
#:::::::::::::::::::::::::::::::::::::::::::::::::::
# Question 1
#:::::::::::::::::::::::::::::::::::::::::::::::::::
url = "https://github.com/OU-PhD-Econometrics/fall-2021/blob/master/ProblemSets/PS8-factor/nlsy.csv"

using DataFrames;
df = CSV.read(HTTP.get(url).body, DataFrame)
ols = glm(@formula(logwage ~ black + hispanic + female + schoolt + gradHS + grad4yr), df)
print(ols)
#I tried to make this code run but the variables are string and I read we need to use Dict to make the actual variable names appear so it wouldn't give an error but I don't know how to apply Dict to a code#

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# Question 2
#:::::::::::::::::::::::::::::::::::::::::::::::::::
#I think we would make a matrix of the asvab variables and then run a correlation of the new matrix which would be a submatrix. I had a bit of difficulty writing the code here#

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# Question 3
#:::::::::::::::::::::::::::::::::::::::::::::::::::
olsasvab= glm(@formula(logwage ~ black + hispanic + female + schoolt + gradHS + grad4yr + asvabAR + asvabCS + asvabMK + asvabNO + asvabPC + asvabWK), df)
print(olsasvab)

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# Question 4
#:::::::::::::::::::::::::::::::::::::::::::::::::::
M=fit(PCA, asvabMat; maxoutdim=1)
asvabPCA=MultivariateStats.transform(M,asvabMat)

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# Question 5
#:::::::::::::::::::::::::::::::::::::::::::::::::::
M=fit(FactorAnalysis, asvabMat; maxoutdim=1)
asvabFactorAnalysis=MultivariateStats.transform(M,asvabMat)

#:::::::::::::::::::::::::::::::::::::::::::::::::::
# Question 6
#:::::::::::::::::::::::::::::::::::::::::::::::::::


"""
    fluence_DA_inf_TD(t, β::Array{Float64,1}, ρ::Float64, nmed::Float64)

Compute the time-domain fluence in an infinite medium with Eqn. 3 of Patterson. et al. 1989.

# Arguments
- `t`: the time vector (ns).
- `β::Array{Float64,1}`: the optical properties [μa, μs'] (cm⁻¹)
- `ρ::Float64`: the source detector separation (cm⁻¹)
- `nmed::Float64`: the sample medium's index of refraction

# Examples
julia> fluence_DA_inf_TD(0:1:5, [0.1,10.0], 1.0, 1.0)
"""
function fluence_DA_inf_TD(t, β::Array{Float64,1}, ρ::Float64, nmed::Float64 = 1.0)
    μa::Float64 = β[1]
    μsp::Float64 = β[2]
    D::Float64 = 1/3μsp
    ν::Float64 = 29.9792458/nmed

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

    Threads.@threads for n in eachindex(t)

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

        if isnan(ϕ[n])
            ϕ[n] = 0
        end

    end

    return ϕ
end

"""
    fluence_DA_inf_CW(ρ::Float64, β::Array{Float64,1})

Compute the fluence for a steady-state source in an infinite medium.

# Arguments
- `ρ::Float64`: the source detector separation (cm⁻¹)
- `β::Array{Float64,1}`: the optical properties [μa, μs'] (cm⁻¹)

# Examples
julia> fluence_DA_inf_CW(1.0, [0.1,10.0])
"""
function fluence_DA_inf_CW(ρ::Float64, β::Array{Float64,1})
    μa::Float64 = β[1]
    μsp::Float64 = β[2]
    D::Float64 = 1/3μsp

    ϕ = exp(-sqrt(3*μsp*μa)*ρ)/(4*π*ρ*D)

    if isnan(ϕ)
        ϕ = 0
    end

    return ϕ
end

"""
    fluence_DA_inf_FD(ρ::Float64, β::Array{Float64,1})

Compute the fluence for a steady-state source in an infinite medium.

# Arguments
- `ρ::Float64`: the source detector separation (cm⁻¹)
- `β::Array{Float64,1}`: the optical properties [μa, μs'] (cm⁻¹)

# Examples
julia> fluence_DA_inf_FD(1.0, [0.1,10.0])
"""
function fluence_DA_inf_FD(ρ, β, ω, nmed)
    μa = β[1]
    μsp = β[2]
    D = 1/3μsp
    ν = 29.9792458/nmed

    ϕ = exp(-ρ*sqrt(μa/D + im*ω/(ν*D)))/(4*π*ρ*D)

    if isnan(ϕ)
        ϕ = 0
    end

    return ϕ
end

N = 512
T = 10e-12
ω = 2*π*Vector(0:N-1)/(N*T)

ω = 0.0977*Vector(1:512)

map((ω) -> fluence_DA_inf_FD(1.0, [0.1,10.0], ω, 1.0), ω)
quadgk((ω) -> exp(im*ω*1)*fluence_DA_inf_FD(1.0, [0.1,10.0], ω, 1.0), -Inf, Inf)

function TD_test(t, N, ub)

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

    x, w = gauss(N, 0, ub)

    map!(t -> sum(FD.(x, t) .* w), Rt, t)

return Rt./pi
end

FD(ω, t) = real.(exp(im*ω*t)*fluence_DA_inf_FD(1.0, [0.1,10.0], ω, 1.0))
x, w = gauss(1000, 0, 500)
sum(FD.(x, 1.0) .* w)

a = map(ω -> fluence_DA_inf_FD(1.0, [0.1,10.0], ω, 1.0)*exp(im*ω*t), [0,1,2, 3, 4, -3, -2, -1])

r = map(t -> (quadgk((ω) -> exp(im*ω*t)*fluence_DA_inf_FD(1.0, [0.1,10.0], ω, 1.0), -Inf, Inf))[1], 0.01:0.05:3)

map(t -> (quadgk((ω) -> 1/2pi*exp(im*ω*t)*fluence_DA_inf_FD(1.0, [0.1,10.0], ω, 1.0), 0, Inf))[1], 0.01:0.05:3)

map(t -> (quadgk((ω) -> exp(im*ω*t)*fluence_FD(1.0, 0., [0.1,0.1], [10.,10.], 1.0, ω, 1.0), 0, Inf))[1], 0.01:0.05:3)
r1 = map(t -> (quadgk((ω) -> exp(im*ω*t)*fluence_FD(1.0, 0., [0.1,0.1], [10.,10.], 1.0, ω, 1.0), -1000, 1000))[1], 1:1:3)

map(t -> (quadgk((ω) -> exp(im*ω*t)*fluence_DA_inf_FD(1.0, [0.1,10.0], ω, 1.0), 0, 1024))[1], 0.01:0.05:3)

function FD(ω)
    D = 1/30
    ν = 29.9792458 #speed of light in cm/ns

    ϕ = exp(-sqrt(0.1/D + im*ω/(ν*D)))
end

using QuadGK

tind = 0.1:0.1:3
r = map(t -> (quadgk((ω) -> exp(im*ω*t)*FD(ω), -Inf, Inf))[1], tind)


module Day13

import ..data_dir # from parent module
input = read(joinpath(data_dir, "day13"), String)

export part1, part2

struct Paper
    coordinates::Vector{Tuple{Int64,Int64}}
    folds::Vector{Tuple{AbstractString,Int64}}
end

function Paper(input::AbstractString)
    raw_coordinates, raw_folds = split(input, "\n\n")
    coordinates = [Tuple(parse.(Int64, x)) for x in split.(split(raw_coordinates, "\n"), ",")]

    fold_equations = last.(split.(split(raw_folds, "\n"), " "))
    folds = [Tuple([string(axis), parse(Int64, amount)]) for (axis, amount) in split.(fold_equations, "=")]
    Paper(coordinates, folds)
end

function horizontal_fold(coordinates::Vector{Tuple{Int64,Int64}}, fold_line::Int64)
    # fold up
    below_fold = [coordinate for coordinate in coordinates if coordinate[2] > fold_line]
    above_fold = coordinates[(!in).(coordinates, Ref(below_fold))]

    below_fold_x = [coordinate[1] for coordinate in below_fold]
    below_fold_y = [coordinate[2] for coordinate in below_fold]
    mirrored_y_coordinates = [fold_line - (y - fold_line) for y in below_fold_y]
    mirrored = collect(zip(below_fold_x, mirrored_y_coordinates))
    unique([above_fold; mirrored])
end

function vertical_fold(coordinates::Vector{Tuple{Int64,Int64}}, fold_line::Int64)
    # fold left
    right_of_fold = [coordinate for coordinate in coordinates if coordinate[1] > fold_line]
    left_of_fold = coordinates[(!in).(coordinates, Ref(right_of_fold))]

    right_of_fold_x = [coordinate[1] for coordinate in right_of_fold]
    right_of_fold_y = [coordinate[2] for coordinate in right_of_fold]
    mirrored_x_coordinates = [fold_line - (x - fold_line) for x in right_of_fold_x]
    mirrored = collect(zip(mirrored_x_coordinates, right_of_fold_y))
    unique([left_of_fold; mirrored])
end

function fold(coordinates::Vector{Tuple{Int64,Int64}}, fold::Tuple{AbstractString,Int64})
    axis, amount = fold
    axis == "x" ? vertical_fold(coordinates, amount) : horizontal_fold(coordinates, amount)
end

function part1(input = input)
    paper = Paper(input)
    first_fold = first(paper.folds)
    new_coordinates = fold(paper.coordinates, first_fold)
    length(new_coordinates)
end

function print_coordinates(coordinates::Vector{Tuple{Int64,Int64}})
    max_x = maximum(x for (x, y) in coordinates)
    max_y = maximum(y for (x, y) in coordinates)
    display = ""
    for j = 0:max_y
        for i = 0:max_x
            if (i, j) in coordinates
                display = display * "█"
            else
                display = display * " "
            end
        end
        display = display * "\n"
    end
    print(display)
end

function part2(input = input)
    paper = Paper(input)
    coordinates = paper.coordinates
    folds = paper.folds
    while length(folds) > 0
        first_fold = popfirst!(folds)
        coordinates = fold(coordinates, first_fold)
    end
    print_coordinates(coordinates)
end

end #module


using Random, SnpArrays, DataFrames, GLM
using Suppressor
using LinearAlgebra
using BenchmarkTools
beta = [1.0, 5.0]

n = 10
p = 2
d = 2
m = 2

function generateSPDmatrix(n)
	A = rand(n)
	m = 0.5 * (A * A')
	PDmat = m + (n * Diagonal(ones(n)))
end

function generateRandomVCM(n::Int64, p::Int64, d::Int64, m::Int64)
	# n-by-p design matrix
	X = randn(n, p)

	# p-by-d mean component regression coefficient for each trait
	B = hcat(ones(p, 1), rand(p))

	V = ntuple(x -> zeros(n, n), m)
	for i = 1:m-1
	  copy!(V[i], generateSPDmatrix(n))
	end
	copy!(V[end], Diagonal(ones(n))) # last covarianec matrix is identity

	# a tuple of m d-by-d variance component parameters
	Σ = ntuple(x -> zeros(d, d), m)
	for i in 1:m
	  copy!(Σ[i], generateSPDmatrix(d))
	end
	return(X, B, Σ, V)
end

X, B, Σ, V = generateRandomVCM(n, p, d, m)
dist = Normal()
link = IdentityLink()
glmtraitobject = GLMTrait(X, beta, dist, link)
y_normal  = simulate(glmtraitobject)
@test size(y_normal) == size(X*beta)

@test_throws ErrorException("function not supported by $(typeof(glmtraitobject))") nvc(glmtraitobject)

@test_throws ErrorException("function not supported by $(typeof(glmtraitobject))") ntraits(glmtraitobject)

@test_throws ErrorException("function not supported by $(typeof(glmtraitobject))") noutcomecategories(glmtraitobject)

number_independent_simulations  = 5
@test length(simulate(glmtraitobject, number_independent_simulations)) == number_independent_simulations

@test glmtraitobject isa TraitSimulation.AbstractTraitModel

# make sure that ordered multinomial models works
link = LogitLink()
θ = [1.0, 1.2, 1.4]
Ordinal_Model_Test = OrderedMultinomialTrait(X, beta, θ, link)

@test Ordinal_Model_Test isa TraitSimulation.AbstractTraitModel
@test eltype(simulate(Ordinal_Model_Test)) == Int64
@test length(simulate(Ordinal_Model_Test, number_independent_simulations))  == number_independent_simulations

@test noutcomecategories(Ordinal_Model_Test) == 4

# make sure GLM simulation works
dist = Poisson()
link = IdentityLink()
beta = [21.0, 5.0]
glmtraitobject2 = GLMTrait(abs.(X), beta, dist, link)

# check if clamper worked
@test sum(glmtraitobject2.η .> 20.0) == 0

@test  eltype(simulate(glmtraitobject2)) == Int64

dist = Bernoulli()
link = LogitLink()
glmtraitobject3 = GLMTrait(X, beta, dist, link)

@test eltype(simulate(glmtraitobject3)) == Bool

@test length(simulate(glmtraitobject3, number_independent_simulations))  == number_independent_simulations

dist = NegativeBinomial()
link = LogLink()
glmtraitobject4 = GLMTrait(X, beta, dist, link)

@test eltype(simulate(glmtraitobject4)) == Int64

@test length(simulate(glmtraitobject4, number_independent_simulations)) == number_independent_simulations

dist = Gamma()
link = LogLink()
glmtraitobject5 = GLMTrait(X, beta, dist, link)

@test eltype(simulate(glmtraitobject5)) == Float64

@test length(simulate(glmtraitobject5, number_independent_simulations)) == number_independent_simulations

varcomp = @vc Σ[1] ⊗ V[1] + Σ[2] ⊗ V[2]
# make sure GLMM works
glmtraitobject6 = GLMMTrait(X, B, varcomp, dist, link)
@test glmtraitobject6 isa TraitSimulation.AbstractTraitModel

@test nsamplesize(glmtraitobject6) == n
@test neffects(glmtraitobject6) == p
@test nvc(glmtraitobject6) == m
@test ntraits(glmtraitobject6) == d

y_glmm = simulate(glmtraitobject6)
@test size(y_glmm) == (size(X,  1),  size(B, 2))

@test length(simulate(glmtraitobject6, number_independent_simulations)) == number_independent_simulations

# simulate the SnpArray
G = snparray_simulation([0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5], n)
γ = rand(7)

glmtraitobject7 =  GLMTrait(X, beta, G, γ, dist, link)

@test glmtraitobject7.dist  == Gamma
@test eltype(simulate(glmtraitobject7)) == eltype(dist)

@test length(simulate(glmtraitobject7, number_independent_simulations)) == number_independent_simulations

@test GLMTrait(X, beta, G, γ, dist, link) != empty

@test nsamplesize(glmtraitobject7) == n
@test neffects(glmtraitobject7) == p

model_info_ordinal = @capture_out show(Ordinal_Model_Test)
model_info_glmm = @capture_out show(glmtraitobject6)
model_info_glm = @capture_out show(glmtraitobject7)

@test model_info_ordinal != nothing
@test model_info_glmm != nothing
@test model_info_glm != nothing


module TestDegreeFunctions
  using Test
  using EcologicalNetworks

  # Generate some data

  N = UnipartiteProbabilisticNetwork([0.22 0.4; 0.3 0.1], [:a, :b])

  d_in = vec([0.52 0.5])
  d_out = vec([0.62 0.4])
  d_tot = vec([1.14 0.9])

  Q = BipartiteQuantitativeNetwork(rand(Float64, (3, 5)))
  D = convert(BinaryNetwork, Q)
  @test degree(Q) == degree(D)
  @test degree(Q; dims=1) == degree(D; dims=1)
  @test degree(Q; dims=2) == degree(D; dims=2)

  Dov = degree_var(N; dims=1)
  @test Dov[:b] ≈ 0.3

  Dv = degree_var(N; dims=2)
  @test Dv[:b] ≈ 0.33

  @test_throws ArgumentError degree(N; dims=3)
  @test_throws ArgumentError degree(N; dims=0)

  # Variance

  N = UnipartiteProbabilisticNetwork([0.22 0.4; 0.3 0.1])

  Din = degree(N; dims=2)
  Dout = degree(N; dims=1)
  Dtot = degree(N)

  Dov = degree_var(N; dims=1)
  @test Dov["s2"] ≈ 0.3 atol=0.001
  @test Dov["s1"] ≈ 0.41159 atol=0.001

  Div = degree_var(N; dims=2)
  @test Div["s1"] ≈ 0.3816 atol=0.01

  Dv = degree_var(N)
  @test Dv["s2"] ≈ 0.63 atol=0.001

end


import SpecialFunctions

function flux_quad_GxI(G, nx, ny, u1, u2, rho, pr)
    # G = Array{Float64,1}(undef, 0)
    tx = ny
    ty = -nx
    ut = u1*tx + u2*ty
    un = u1*nx + u2*ny
    beta = 0.5*rho/pr
    S1 = ut*sqrt(beta)
    S2 = un*sqrt(beta)
    B1 = 0.5*exp(-S1*S1)/sqrt(Float64(pi)*beta)
    B2 = 0.5*exp(-S2*S2)/sqrt(Float64(pi)*beta)
    A1neg = 0.5*(1.0 - SpecialFunctions.erf(S1))
    A2neg = 0.5*(1.0 - SpecialFunctions.erf(S2))
    pr_by_rho = pr/rho
    u_sqr = ut*ut + un*un
    G[1] = (rho*A2neg*(ut*A1neg - B1))

    temp1 = pr_by_rho + ut*ut
    temp2 = temp1*A1neg-ut*B1
    G[2] = (rho*A2neg*temp2)

    temp1 = ut*A1neg - B1
    temp2 = un*A2neg - B2
    G[3] = (rho*temp1*temp2)

    temp1 = (7.0 *pr_by_rho) + u_sqr
    temp2 = 0.5*ut*temp1*A1neg

    temp1 = (6.0 *pr_by_rho) + u_sqr
    temp3 = 0.5*B1*temp1

    temp1 = ut*A1neg - B1
    temp4 = 0.5*rho*un*B2*temp1

    G[4] = (rho*A2neg*(temp2 - temp3) - temp4)
    return nothing
end

function flux_quad_GxII(G, nx, ny, u1, u2, rho, pr)
    tx = ny
    ty = -nx
    ut = u1*tx + u2*ty
    un = u1*nx + u2*ny

    beta = 0.5*rho/pr
    # if flag == 0
    #     println(IOContext(stdout, :compact => false), rho)
    #     println(IOContext(stdout, :compact => false), pr)
    # end
    S1 = ut*sqrt(beta)
    S2 = un*sqrt(beta)
    B1 = 0.5*exp(-S1*S1)/sqrt(pi*beta)
    B2 = 0.5*exp(-S2*S2)/sqrt(pi*beta)
    A1pos = 0.5*(1.0 + SpecialFunctions.erf(S1))
    A2neg = 0.5*(1.0 - SpecialFunctions.erf(S2))

    pr_by_rho = pr/rho
    u_sqr = ut^2 + un^2
    G[1] = rho * A2neg* (ut*A1pos + B1)

    temp1 = pr_by_rho + ut*ut
    temp2 = temp1*A1pos + ut*B1
    G[2] = rho*A2neg*temp2

    temp1 = ut*A1pos + B1
    temp2 = un*A2neg - B2
    G[3] = rho*temp1*temp2

    # if flag == 0
    #     println("===%%%%===")
    #     println(IOContext(stdout, :compact => false), beta)
    #     println(IOContext(stdout, :compact => false), ut)
    #     println(IOContext(stdout, :compact => false), S1)
    #     println(IOContext(stdout, :compact => false), B1)
    #     println(IOContext(stdout, :compact => false), "####")
    #     println(IOContext(stdout, :compact => false), 0.5* exp(-S1*S1))
    #     println(IOContext(stdout, :compact => false), sqrt(pi*beta))
    #     println(IOContext(stdout, :compact => false), "####")
    #     # println(IOContext(stdout, :compact => false), B2)
    #     println(IOContext(stdout, :compact => false), A1pos)
    #     # println(IOContext(stdout, :compact => false), A2neg)
    #     # println(IOContext(stdout, :compact => false), u_sqr)
    #     println(IOContext(stdout, :compact => false), temp1)
    #     println(IOContext(stdout, :compact => false), temp2)
    #     # println(IOContext(stdout, :compact => false), temp3)
    #     # println(IOContext(stdout, :compact => false), temp4)
    #     println(IOContext(stdout, :compact => false), rho)
    #     println(IOContext(stdout, :compact => false), rho*temp1*temp2)
    #     println()
    # end

    temp1 = (7 *pr_by_rho) + u_sqr
    temp2 = 0.5*ut*temp1*A1pos

    temp1 = (6 *pr_by_rho) + u_sqr
    temp3 = 0.5*B1*temp1

    temp1 = ut*A1pos + B1
    temp4 = 0.5*rho*un*B2*temp1
    G[4] = rho*A2neg*(temp2 + temp3) - temp4

    return nothing
end

function flux_quad_GxIII(G, nx, ny, u1, u2, rho, pr)
    tx = ny
    ty = -nx

    ut = u1*tx + u2*ty
    un = u1*nx + u2*ny

    beta = 0.5*rho/pr
    S1 = ut*sqrt(beta)
    S2 = un*sqrt(beta)
    B1 = 0.5*exp(-S1*S1)/sqrt(pi*beta)
    B2 = 0.5*exp(-S2*S2)/sqrt(pi*beta)
    A1pos = 0.5*(1.0 + SpecialFunctions.erf(S1))
    A2pos = 0.5*(1.0 + SpecialFunctions.erf(S2))

    pr_by_rho = pr/rho
    u_sqr = ut*ut + un*un
    G[1] = (rho*A2pos*(ut*A1pos + B1))

    temp1 = pr_by_rho + ut*ut
    temp2 = temp1*A1pos + ut*B1
    G[2] = (rho*A2pos*temp2)

    temp1 = ut*A1pos + B1
    temp2 = un*A2pos + B2
    G[3] = (rho*temp1*temp2)

    temp1 = (7*pr_by_rho) + u_sqr
    temp2 = 0.5*ut*temp1*A1pos

    temp1 = (6*pr_by_rho) + u_sqr
    temp3 = 0.5*B1*temp1

    temp1 = ut*A1pos + B1
    temp4 = 0.5*rho*un*B2*temp1

    G[4] = (rho*A2pos*(temp2 + temp3) + temp4)

    return nothing
end

function flux_quad_GxIV(G, nx, ny, u1, u2, rho, pr)
    # G = Array{Float64,1}(undef, 0)

    tx = ny
    ty = -nx

    ut = u1*tx + u2*ty
    un = u1*nx + u2*ny

    beta = 0.5*rho/pr
    S1 = ut*sqrt(beta)
    S2 = un*sqrt(beta)
    B1 = 0.5*exp(-S1*S1)/sqrt(pi*beta)
    B2 = 0.5*exp(-S2*S2)/sqrt(pi*beta)
    A1neg = 0.5*(1.0 - SpecialFunctions.erf(S1))
    A2pos = 0.5*(1.0 + SpecialFunctions.erf(S2))

    pr_by_rho = pr/rho
    u_sqr = ut*ut + un*un

    G[1]  = (rho*A2pos*(ut*A1neg - B1))

    temp1 = pr_by_rho + ut*ut
    temp2 = temp1*A1neg - ut*B1
    G[2] =  (rho*A2pos*temp2)

    temp1 = ut*A1neg - B1
    temp2 = un*A2pos + B2
    G[3] =  (rho*temp1*temp2)

    temp1 = (7.0*pr_by_rho) + u_sqr
    temp2 = 0.5*ut*temp1*A1neg

    temp1 = (6.0 *pr_by_rho) + u_sqr
    temp3 = 0.5*B1*temp1

    temp1 = ut*A1neg - B1
    temp4 = 0.5*rho*un*B2*temp1
    G[4] = (rho*A2pos*(temp2 - temp3) + temp4)
    return nothing
end

# print(flux_quad_GxI(1,2,3,4,5,5))


using LinearAlgebra

function run_test_bench(model::C, pixel_accuracy::Float64 = 1e-5, ray_accuracy::Float64 = 1e-4) where C <: CameraModel

    @testset "Check basics and interface implementation for $(C)." begin

        forward = lookdirection(model)
        up = updirection(model)
        right = cross(forward, up)

        @testset "Test model axes" begin
            @test norm(forward) ≈ 1.0
            @test norm(up) ≈ 1.0
            @test norm(right)  ≈ 1.0
        end

        pixelsize = sensorsize(model)

        # Get a pixel location somewhere near the image centre.
        image_centre = (pixelsize .+ 1) ./ 2
        slight_offset = 0.042 .* pixelsize
        some_pixel_location = image_centre + slight_offset

        # Get the ray that belongs to that pixel.
        ray = pixel2ray(model, some_pixel_location)

        # Generate a 3D point along that ray.
        point = origin(ray) + 4.2 .* direction(ray)

        # Some models might not implement point2pixel.
        if canreproject(model)
            reprojection = point2pixel(model, point)
            @test some_pixel_location ≈ reprojection atol = pixel_accuracy
        else
            @info "point2pixel not implemented for $(C)."
        end

    end

end


using Einsum

function get_Kσs(sp::StiffnessTopOptProblem{xdim,TT}, u_dofs, cellvalues) where {xdim,TT}
    E = getE(sp)
    ν = getν(sp)
    dh = sp.ch.dh
    # usually ndof_pc = xdim * n_basefuncs
    # ? number of nodes per cell == n_basefuncs per cell
    ndof_pc = ndofs_per_cell(dh)
    n_basefuncs = getnbasefunctions(cellvalues)
    global_dofs = zeros(Int, ndof_pc)
    Kσs = [zeros(TT, ndof_pc, ndof_pc) for i in 1:getncells(dh.grid)]
    Kσ_e = zeros(TT, ndof_pc, ndof_pc)
    # block-diagonal - block σ_e = σ_ij, i,j in xdim
    # ! shouldn't this be xdim*xdim by xdim*xdim?
    # ? ψ_e = zeros(TT, xdim*ndof_pc, xdim*ndof_pc)
    ψ_e = zeros(TT, xdim * xdim, xdim * xdim)
    # ? G = zeros(TT, xdim*ndof_pc, ndof_pc)
    G = zeros(TT, xdim * xdim, xdim * n_basefuncs)
    δ = Matrix(TT(1.0)I, xdim, xdim)
    ϵ = zeros(TT, xdim, xdim)
    σ = zeros(TT, xdim, xdim)
    # u_i,j: partial derivative
    u_p = zeros(TT, xdim, xdim)
    for (cellidx, cell) in enumerate(CellIterator(dh))
        Kσ_e .= 0
        reinit!(cellvalues, cell)
        # get cell's dof's global dof indices, i.e. CC_a^e
        celldofs!(global_dofs, dh, cellidx)
        for q_point in 1:getnquadpoints(cellvalues)
            dΩ = getdetJdV(cellvalues, q_point)
            for d in 1:xdim
                ψ_e[((d - 1) * xdim + 1):(d * xdim), ((d - 1) * xdim + 1):(d * xdim)] .= 0
            end
            for a in 1:n_basefuncs
                ∇ϕ = shape_gradient(cellvalues, q_point, a)
                _u = @view u_dofs[(@view global_dofs[xdim * (a - 1) .+ (1:xdim)])]
                # u_i,j, i for spatial xdim, j for partial derivative
                @einsum u_p[i, j] = _u[i] * ∇ϕ[j]
                # effect of the quadratic term in the strain formula have on the stress field is ignored
                @einsum ϵ[i, j] = 1 / 2 * (u_p[i, j] + u_p[j, i])
                # isotropic solid
                @einsum σ[i, j] =
                    E * ν / (1 - ν^2) * δ[i, j] * ϵ[k, k] + E * ν * (1 + ν) * ϵ[i, j]
                for d in 1:xdim
                    # block diagonal
                    ψ_e[
                        ((d - 1) * xdim .+ 1):(d * xdim), ((d - 1) * xdim .+ 1):(d * xdim)
                    ] .+= σ
                    G[(xdim * (d - 1) + 1):(xdim * d), (a - 1) * xdim + d] .= ∇ϕ
                end
            end
            Kσ_e .+= G' * ψ_e * G * dΩ
        end
        Kσs[cellidx] .= Kσ_e
    end

    return Kσs
end

function buckling(problem::StiffnessTopOptProblem{xdim,T}, ginfo, einfo) where {xdim,T}
    dh = problem.ch.dh

    u = ginfo.K \ ginfo.f
    Kσs = get_Kσs(problem, u, einfo.cellvalues)
    Kσ = deepcopy(ginfo.K)

    if Kσ isa Symmetric
        Kσ.data.nzval .= 0
        assembler = Ferrite.AssemblerSparsityPattern(Kσ.data, T[], Int[], Int[])
    else
        Kσ.nzval .= 0
        assembler = Ferrite.AssemblerSparsityPattern(Kσ, T[], Int[], Int[])
    end

    # * assemble global geometric stiffness matrix
    global_dofs = zeros(Int, ndofs_per_cell(dh))
    Kσ_e = zeros(T, size(Kσs[1]))
    celliteratortype = CellIterator{typeof(dh).parameters...}
    _celliterator::celliteratortype = CellIterator(dh)
    TK = eltype(Kσs)
    for (i, cell) in enumerate(_celliterator)
        celldofs!(global_dofs, dh, i)
        if TK <: Symmetric
            Ferrite.assemble!(assembler, global_dofs, Kσs[i].data)
        else
            Ferrite.assemble!(assembler, global_dofs, Kσs[i])
        end
    end

    return ginfo.K, Kσ
end


using Test
using DynamicPolynomials
using MultivariatePolynomials
using LazySets: Hyperrectangle, low, high
using CarlemanLinearization: generate_monomials, build_matrix, quadratic_matrix_form, kron_pow, lift_vector

@testset "Kronecker power (symbolic)" begin
    @polyvar x[1:2]
    y = kron_pow(x, 2)
    @test findall(==(x[1]*x[2]), y) == [2, 3]
    @test findall(==(x[1]^2), y) == [1]
    @test findall(==(x[2]^2), y) == [4]
    @test findall(==(x[2]^3), y) == Int[]
end

@testset "Conversion from polynomial to matrix representation" begin
    vars = @polyvar x y
    dx = 3x + y^2
    dy = x - y - 2.2 * x * y + x^2
    f = [dx, dy]
    F1, F2 = quadratic_matrix_form(f, vars)
    @test F1 == [3.0 0; 1 -1]
    @test F2 == [0.0 0 0 1; 1 -2.2 0 0]
end

@testset "Generating all commutative monomials" begin
    for (n, D) in [(3, 0), (3, 1), (3, 2), (5, 3), (5, 10)]
        monomials = generate_monomials(n, D)
        # No reprtitions
        @test length(monomials) == length(Set(monomials))
        # Correct size
        @test length(monomials) == binomial(n + D, n)
        # Nonnegativity
        @test all([sum(m) == sum(map(abs, m)) for m in monomials])
        # ordering
        @test all([sum(monomials[i - 1]) <= sum(monomials[i]) for i in 2:length(monomials)])
        # not exceeding D
        @test sum(monomials[end]) == D
    end
end

@testset "Compressed matrices" begin
    F1 = [3.0 0; 1 -1]
    F2 = [0.0 0 0 1; 1 -2.2 0 0]
    result = build_matrix(F1, F2, 1; compress = true)
    @test result == F1

    result = build_matrix(F1, F2, 2; compress = true)
    @test result == [
        3. 0. 0. 0. 1.;
        1. -1. 1. -2.2 0;
        0 0 6. 0 0;
        0 0 1. -1. 0;
        0 0 0 2. -2.
    ]
end

@testset "Lifting vectors" begin
    x0 = Hyperrectangle(low=[0, 1, -1], high=[1, 2, 3])
    N = 2
    lifted = lift_vector(x0, N)
    println(zip(low(lifted), high(lifted)))
    sides = Set(zip(low(lifted), high(lifted)))
    @test sides == Set{Tuple{Rational, Rational}}([(0, 1), (1, 2), (-1, 3), (0, 1), (1, 4), (0, 9), (0, 2), (-1, 3), (-2, 6)])
end


MathKernel.area(a::BB)   where {BB <: BoundingBox} = 8 * dot(a.e, SVector{3,Float64}(a.e[2], a.e[3], a.e[1]))
MathKernel.volume(a::BB) where {BB <: BoundingBox} = 8 * prod(a.e)


using CyclicArrays
using Plots
using Statistics
using LaTeXStrings

# 1d array example

faces=zeros(1,1,2,4);
faces[1,1,1,:]=[1,1,2,0];
faces[1,1,2,:]=[1,1,1,0];

grid=CyclicArray(faces);
x=[-3:0.1:3;];
rho0=exp.(-(x).^2)
rho0=10*rho0/sum(rho0);
DT=0.5;
DX=1;
NT=400;
rho1=zeros(length(rho0));

crho0=CyclicArray(rho0,grid);
crho1=CyclicArray(rho0*0,grid);
plot_array = Any[]
anim = @animate for i ∈ 1:NT
    crho1=crho0+(shiftc(crho0,dims=1,shift=1) + shiftc(crho0,dims=1,shift=-1) - (2 * crho0))*DT/DX^2
    println(i," = ",(maximum(crho1)))
    global crho0=crho1;
    plt=plot(x,crho1.data,ylims=(0,0.6),
                          title=string(i/2," seconds"),
                          ylabel=L"\rho~~[kg~m^{-3}]",
                          xlabel="x [m]",
                          leg=false)
    push!(plot_array,plt)
    println(mean(crho1))
end
gif(anim, "Diffusion1D.gif", fps = 30)


using BasisFunctions, BasisFunctions.Test, DomainSets, GridArrays

using Test, LinearAlgebra, Plots

delimit("Plots")
@testset begin
    e = random_expansion(Fourier(4))
    plot(e; plot_complex=false)
    plot(e; plot_complex=true)
    plot(e, exp)

    plot(random_expansion(Fourier(4)⊗Fourier(4)))

    plot(Fourier(4))

    plot(Fourier(4)[1])

    plot(Fourier(4) → -3..5)

    plot(MultiDict((Fourier(4),Fourier(4))))
end


using Formulas
using Base.Test

## Associative property
@test Term(:(a+(b+c))) == Term(:(a+b+c))
@test Term(:((a+b)+c)) == Term(:(a+b+c))
@test Term(:(a&(b&c))) == Term(:(a&b&c))
@test Term(:((a&b)&c)) == Term(:(a&b&c))

## Distributive property
@test Term(:(a & (b+c))) == Term(:(a&b + a&c))
@test Term(:((a+b) & c)) == Term(:(a&c + b&c))
@test Term(:((a+b) & (c+d))) == Term(:(a&c + a&d + b&c + b&d))
@test Term(:(a & (b+c) & d)) == Term(:(a&b&d + a&c&d))

## Expand * to main effects + interactions
@test Term{:+}(Term(:(a*b))) == Term(:(a+b+a&b))
@test sort!(Term{:+}(Term(:(a*b*c)))) == Term(:(a+b+c+a&b+a&c+b&c+a&b&c))

## printing Terms:
@test string(Term(:a)) == "a"
@test string(Term(:(a+b))) == "+(a, b)"
@test string(Term(:(a + a&b))) == "+(a, &(a, b))"
@test string(Term(:(a+b | c))) == "(+(a, b) | c)"


# Type Definitions and Conversions

#Abstract Type Definitions

abstract type AbstractGeometry{T <: Real, N} end
abstract type AbstractPoint{T, N} <: AbstractGeometry{T, N} end
abstract type AbstractLine{T, N} <: AbstractGeometry{T, N} end
abstract type AbstractPlane{T, N} <: AbstractGeometry{T, N} end

# Specific Types
"""
    Point{T, N}
A point in `N` dimensional space stored with the datatype `T`. Has a single field
coordinates which is stored as an `NTuple{N, T}`.
"""
struct Point{T, N} <: AbstractPoint{T, N}
    coordinates::Array{T}
end

Point{T}(args...) where T = Point{T, length(args)}(Array{T}([args...]))
Point(args...) = Point{typeof(args[1])}(args...)

"""
    Line{T, N}
A line which passes through the point `p` and extended using a basis vector `v`
"""
struct Line{T, N} <: AbstractLine{T, N}
    p::Point{T, N}
    v::Array{T}
end

Line(p1::Point{T, N}, p2::Point{T, N}) where N where T = Line{T, N}(p1, p1.coordinates .- p2.coordinates)

"""
    Plane{T, N}
A plane which passes through point `p` and is perpendicular to the normal vector `n`.
"""
struct Plane{T, N} <: AbstractPlane{T, N}
    p::Point{T, N}
    n::Vector{T}
end


struct Residue{T, M}
    value::T
    Residue{T,M}(value) where {T,M} = new(value % M)
    Base. +(a::Residue{T,M}, b::Residue{T,M}) where{T,M} = (a.value + b.value) % M
    Base. *(a::Residue{T,M}, b::Residue{T,M}) where{T,M} = (a.value * b.value) % M
    function Base.inv(a::Residue{T,M})::Union{Nothing, Residue{T,M}} where{T,M}
        a,b,c = gcdx(a.value, M)
        if a != 1
            return nothing
        else
            if b>0
                return b
            else
                return M+b
            end
        end
    end
    Base. /(a::Residue{T,M}, b::Residue{T,M}) where{T,M} = a * inv(b)
    Base. -(a::Residue{T,M}) where{T,M} = Residue{T,M}(M - a.value)
    Base. -(a::Residue{T,M}, b::Residue{T,M}) where{T,M} = a + (-b)
end

struct Polynomial{T}
    coeff::Vector{T}
    function Polynomial{T}(coeff) where {T}
        i, n = lastindex(coeff), 0
        while i > 0 && coeff[i] == 0
            n += 1
            i -= 1
        end
        # n = число нулей в конце массива coeff, соответсвующих старшим степеням
        resize!(coeff, length(coeff)-n)
        new(coeff[1:end-n])
    end
    deg(p::Polynomial{T}) where{T} = length(p.coeff) - 1
    function Base. +(p::Polynomial{T}, q::Polynomial{T})::Polynomial{T} where {T}
        np, nq = length(p.coeff), length(q.coeff)
        if  np >= nq
            coeff = similar(p.coeff)
            coeff[1:nq] .= (@view(p.coeff[1:nq]) .+ q.coeff)
        else
            coeff = similar(q.coeff)
            coeff[1:np] .= (p.coeff .+ @view(q.coeff[1:np]))
        end
        # При сложении некоторые старшие коэфициенты могли обратиться в 0
        i, n = lastindex(coeff), 0
        while i > 0 && coeff[i] == 0
            n += 1
            i -= 1
        end
        # n = число нулей в конце массива coeff, соответсвующих старшим степеням
        resize!(coeff, length(coeff)-n)
        return Polynomial{T}(coeff)
    end
    function Base. -(p::Polynomial{T}, q::Polynomial{T})::Polynomial{T} where {T}
        np, nq = length(p.coeff), length(q.coeff)
        if  np >= nq
            coeff = similar(p.coeff)
            coeff[1:nq] .= (@view(p.coeff[1:nq]) .- q.coeff)
        else
            coeff = similar(q.coeff)
            coeff[1:np] .= (p.coeff .- @view(q.coeff[1:np]))
        end
        # При вычитании некоторые старшие коэфициенты могли обратиться в 0
        i, n = lastindex(coeff), 0
        while i > 0 && coeff[i] == 0
            n += 1
            i -= 1
        end
        # n = число нулей в конце массива coeff, соответсвующих старшим степеням
        resize!(coeff, length(coeff)-n)
        return Polynomial{T}(coeff)
    end
    function Base. *(p::Polynomial{T}, q::Polynomial{T})::Polynomial{T} where {T}
        coeff = zeros(T, deg(p) + deg(q)+1)
        for i in eachindex(p.coeff), j in eachindex(q.coeff)
                coeff[i+j - 1] += p.coeff[i]*q.coeff[j]
        end
        i, n = lastindex(coeff), 0
        while i > 0 && coeff[i] == 0
            n += 1
            i -= 1
        end
        # n = число нулей в конце массива coeff, соответсвующих старшим степеням
        resize!(coeff, length(coeff)-n)
        return Polynomial{T}(coeff)
    end
    Base. +(p::Polynomial{T}, c::T) where {T} = +(p, Polynomial{T}([c]))
    Base. +(c::T, p::Polynomial{T}) where {T} = +(Polynomial{T}([c]), p)

    Base. -(p::Polynomial{T}, c::T) where {T} = -(p, Polynomial{T}([c]))
    Base. -(c::T, p::Polynomial{T}) where {T} = -(Polynomial{T}([c]), p)

    Base. *(p::Polynomial{T}, c::T) where {T} = *(p, Polynomial{T}([c]))
    Base. *(c::T, p::Polynomial{T}) where {T} = *(Polynomial{T}([c]), p)

    Base.div(p::Polynomial{T}, q::Polynomial{T}) where {T} = divrem(p,q)[1]
    Base.rem(p::Polynomial{T}, q::Polynomial{T}) where {T} = divrem(p,q)[2]

    (p::Polynomial)(x) = polyval(reverse(p.coeff), x)
    function Base.display(p::Polynomial)
        if isempty(p.coeff)
            return ""
        end
        n = length(p.coeff)
        if p.coeff[1]!=0
            n-=1
        end
        str = "$(p.coeff[1])" # $(...) - означает "интерполяцию стоки", т.е. вставку в строку некоторого вычисляемого значения
        for i in 2:length(p.coeff)
            if p.coeff[i]!=0
                n-=1
                if i > 2
                    s = " + $(p.coeff[i])x^$(i-1)"
                else
                    s = " + $(p.coeff[i])x"
                end
                str *= s
            end
        end
        if n==length(p.coeff)
            println(0)
        else
            println(str)
        end
    end
end