using HarmonicOrthogonalPolynomials, StaticArrays, Test, InfiniteArrays, LinearAlgebra, BlockArrays, ClassicalOrthogonalPolynomials, QuasiArrays
import HarmonicOrthogonalPolynomials: ZSphericalCoordinate, associatedlegendre, grid, SphereTrav, RealSphereTrav, FiniteRealSphericalHarmonic, FiniteSphericalHarmonic

# @testset "associated legendre" begin
#     m = 2
#     θ = 0.1
#     x = cos(θ)
#     @test associatedlegendre(m)[x,1:10] ≈ -(2:11) .* (sin(θ/2)^m * cos(θ/2)^m * Jacobi(m,m)[x,1:10])
# end

@testset "SphereTrav" begin
    A = SphereTrav([1 2 3; 4 0 0])
    @test A == [1, 2, 4, 3]
    @test A[Block(2)] == [2,4,3]
    B = SphereTrav([1 2 3 4 5; 6 7 8 0 0; 9 0 0 0 0 ])
    @test B == [1, 2, 6, 3, 4, 7, 9, 8, 5]
end

@testset "RadialCoordinate" begin
    rθ = RadialCoordinate(0.1,0.2)
    @test rθ ≈ SVector(rθ) ≈ [0.1cos(0.2),0.1sin(0.2)]
    @test RadialCoordinate(1,0.2) isa RadialCoordinate{Float64}
    @test RadialCoordinate(1,1) isa RadialCoordinate{Float64}
    @test_throws BoundsError rθ[3]
end

@testset "SphericalCoordinate" begin
    θφ = SphericalCoordinate(0.2,0.1)
    @test θφ ≈ ZSphericalCoordinate(0.1,cos(0.2))
    @test θφ == SVector(θφ)
    @test SphericalCoordinate(1,1) isa SphericalCoordinate{Float64}
    @test_throws BoundsError θφ[4]

    φz = ZSphericalCoordinate(0.1,cos(0.2))
    @test φz == SVector(φz)

    @test norm(θφ) === norm(φz) === 1.0
    @test θφ in UnitSphere()
    @test ZSphericalCoordinate(0.1,cos(0.2)) in UnitSphere()

    @test convert(SVector{3,Float64}, θφ) ≈ convert(SVector{3}, θφ) ≈
            convert(SVector{3,Float64}, φz) ≈ convert(SVector{3}, φz) ≈ θφ

    @test ZSphericalCoordinate(θφ) ≡ convert(ZSphericalCoordinate, θφ) ≡ φz
    @test SphericalCoordinate(φz) ≡ convert(SphericalCoordinate, φz) ≡ θφ
end

@testset "Evaluation" begin
    S = SphericalHarmonic()
    @test eltype(axes(S,1)) == SphericalCoordinate{Float64}

    θ,φ = 0.1,0.2
    x = SphericalCoordinate(θ,φ)
    @test S[:,Block.(Base.OneTo(10))] isa FiniteSphericalHarmonic
    @test S[x, Block(1)[1]] == S[x,1] == sqrt(1/(4π))
    @test view(S,x, Block(1)).indices[1] isa SphericalCoordinate
    @test S[x, Block(1)] == [sqrt(1/(4π))]

    @test associatedlegendre(0)[0.1,1:2] ≈ [1.0,0.1]
    @test associatedlegendre(1)[0.1,1:2] ≈ [-0.9949874371066201,-0.29849623113198603]
    @test associatedlegendre(2)[0.1,1] ≈ 2.97

    @test S[x,Block(2)] ≈ 0.5sqrt(3/π)*[1/sqrt(2)*sin(θ)exp(-im*φ),cos(θ),1/sqrt(2)*sin(θ)exp(im*φ)]
    @test S[x,Block(3)] ≈ [0.25sqrt(15/2π)sin(θ)^2*exp(-2im*φ),
                            0.5sqrt(15/2π)sin(θ)cos(θ)exp(-im*φ),
                            0.25sqrt(5/π)*(3cos(θ)^2-1),
                            0.5sqrt(15/2π)sin(θ)cos(θ)exp(im*φ),
                            0.25sqrt(15/2π)sin(θ)^2*exp(2im*φ)]
    @test S[x,Block(4)] ≈ [0.125sqrt(35/π)sin(θ)^3*exp(-3im*φ),
                          0.25sqrt(105/2π)sin(θ)^2*cos(θ)*exp(-2im*φ),
                          0.125sqrt(21/π)sin(θ)*(5cos(θ)^2-1)*exp(-im*φ),
                          0.25sqrt(7/π)*(5cos(θ)^3-3cos(θ)),
                          0.125sqrt(21/π)sin(θ)*(5cos(θ)^2-1)*exp(im*φ),
                          0.25sqrt(105/2π)sin(θ)^2*cos(θ)*exp(2im*φ),
                          0.125sqrt(35/π)sin(θ)^3*exp(3im*φ)]

    @test S[x,Block.(1:4)] == [S[x,Block(1)]; S[x,Block(2)]; S[x,Block(3)]; S[x,Block(4)]]
end

@testset "Real Evaluation" begin
    S = SphericalHarmonic()
    R = RealSphericalHarmonic()
    @test eltype(axes(R,1)) == SphericalCoordinate{Float64}
    θ,φ = 0.1,0.2
    x = SphericalCoordinate(θ,φ)
    @test R[x, Block(1)[1]] ≈ R[x,1] ≈ sqrt(1/(4π))
    @test R[x, Block(2)][1] ≈ S[x, Block(2)][2]
    # Careful here with the (-1) conventions?
    @test R[x, Block(2)][3] ≈ 1/sqrt(2)*(S[x, Block(2)][3]+S[x, Block(2)][1])
    @test R[x, Block(2)][2] ≈ im/sqrt(2)*(S[x, Block(2)][1]-S[x, Block(2)][3])
end

@testset "Expansion" begin
    @testset "grid" begin
        N = 2
        S = SphericalHarmonic()[:,Block.(Base.OneTo(N))]

        @test size(S,2) == 4
        g = grid(S)
        @test eltype(g) == SphericalCoordinate{Float64}

        # compare with FastTransforms.jl/examples/sphere.jl
        # The colatitudinal grid (mod $\pi$):
        N = 2
        θ = (0.5:N-0.5)/N
        # The longitudinal grid (mod $\pi$):
        M = 2*N-1
        φ = (0:M-1)*2/M
        X = [sinpi(θ)*cospi(φ) for θ in θ, φ in φ]
        Y = [sinpi(θ)*sinpi(φ) for θ in θ, φ in φ]
        Z = [cospi(θ) for θ in θ, φ in φ]
        @test g ≈ SVector.(X, Y, Z)
    end

    @testset "transform" begin
        N = 2
        S = SphericalHarmonic()[:,Block.(Base.OneTo(N))]
        xyz = axes(S,1)

        P = factorize(S)
        @test eltype(P) == ComplexF64
        c = P \ (xyz -> 1).(xyz)
        @test blocksize(c,1) == blocksize(S,2)
        @test c == S \ (xyz -> 1).(xyz)
        @test (S * c)[SphericalCoordinate(0.1,0.2)] ≈ 1

        f = c -> ((x,y,z) = c; 1 + x + y + z)
        u = S * (S \ f.(xyz))
        p = SphericalCoordinate(0.1,0.2)
        @test u[p] ≈ 1+sum(p)
    end

    @testset "adaptive" begin
        S = SphericalHarmonic()
        xyz = axes(S,1)
        u = S * (S \ (xyz -> 1).(xyz))
        @test u[SphericalCoordinate(0.1,0.2)] ≈ 1

        f = c -> exp(-100*c.θ^2)
        u = S * (S \ f.(xyz))
        r = SphericalCoordinate(0.1,0.2)
        @test u[r] ≈ f(r)

        f = c -> ((x,y,z) = c; 1 + x + y + z)
        u = S * (S \ f.(xyz))
        p = SphericalCoordinate(0.1,0.2)
        @test u[p] ≈ 1+sum(p)

        f = c -> ((x,y,z) = c; exp(x)*cos(y*sin(z)))
        u = S * (S \ f.(xyz))
        @test u[p] ≈ f(p)
    end
end

@testset "Real Expansion" begin
    @testset "grid" begin
        N = 2
        S = RealSphericalHarmonic()[:,Block.(Base.OneTo(N))]
        @test S isa FiniteRealSphericalHarmonic

        @test size(S,2) == 4
        g = grid(S)
        @test eltype(g) == SphericalCoordinate{Float64}

        # compare with FastTransforms.jl/examples/sphere.jl
        # The colatitudinal grid (mod $\pi$):
        N = 2
        θ = (0.5:N-0.5)/N
        # The longitudinal grid (mod $\pi$):
        M = 2*N-1
        φ = (0:M-1)*2/M
        X = [sinpi(θ)*cospi(φ) for θ in θ, φ in φ]
        Y = [sinpi(θ)*sinpi(φ) for θ in θ, φ in φ]
        Z = [cospi(θ) for θ in θ, φ in φ]
        @test g ≈ SVector.(X, Y, Z)
    end

    @testset "transform" begin
        N = 2
        S = RealSphericalHarmonic()[:,Block.(Base.OneTo(N))]
        xyz = axes(S,1)

        P = factorize(S)
        @test eltype(P) == Float64
        c = P \ (xyz -> 1).(xyz)
        @test blocksize(c,1) == blocksize(S,2)
        @test c == S \ (xyz -> 1).(xyz)
        @test (S * c)[SphericalCoordinate(0.1,0.2)] ≈ 1

        f = c -> ((x,y,z) = c; 1 + x + y + z)
        u = S * (S \ f.(xyz))
        p = SphericalCoordinate(0.1,0.2)
        @test u[p] ≈ 1+sum(p)
    end

    @testset "adaptive" begin
        S = RealSphericalHarmonic()
        xyz = axes(S,1)
        u = S * (S \ (xyz -> 1).(xyz))
        @test u[SphericalCoordinate(0.1,0.2)] ≈ 1

        f = c -> exp(-100*c.θ^2)
        u = S * (S \ f.(xyz))
        r = SphericalCoordinate(0.1,0.2)
        @test u[r] ≈ f(r)

        f = c -> ((x,y,z) = c; 1 + x + y + z)
        u = S * (S \ f.(xyz))
        p = SphericalCoordinate(0.1,0.2)
        @test u[p] ≈ 1+sum(p)

        f = c -> ((x,y,z) = c; exp(x)*cos(y*sin(z)))
        u = S * (S \ f.(xyz))
        @test u[p] ≈ f(p)
    end
end

@testset "Laplacian basics" begin
    S = SphericalHarmonic()
    R = RealSphericalHarmonic()
    Sxyz = axes(S,1)
    Rxyz = axes(R,1)
    SΔ = Laplacian(Sxyz)
    RΔ = Laplacian(Rxyz)
    @test SΔ isa Laplacian
    @test RΔ isa Laplacian
    @test *(SΔ,S) isa ApplyQuasiArray
    @test *(RΔ,R) isa ApplyQuasiArray
    @test copy(SΔ) == SΔ == RΔ == copy(RΔ)
    @test axes(SΔ) == axes(RΔ) == (axes(S,1),axes(S,1)) == (axes(R,1),axes(R,1))
    @test axes(SΔ) isa Tuple{Inclusion{SphericalCoordinate{Float64}},Inclusion{SphericalCoordinate{Float64}}}
    @test axes(RΔ) isa Tuple{Inclusion{SphericalCoordinate{Float64}},Inclusion{SphericalCoordinate{Float64}}}
    @test Laplacian{eltype(axes(S,1))}(axes(S,1)) == SΔ
end

@testset "test copy() for SphericalHarmonics" begin
    S = SphericalHarmonic()
    R = RealSphericalHarmonic()
    @test copy(S) == S
    @test copy(R) == R
    S = SphericalHarmonic()[:,Block.(Base.OneTo(10))]
    R = RealSphericalHarmonic()[:,Block.(Base.OneTo(10))]
    @test S isa FiniteSphericalHarmonic
    @test R isa FiniteRealSphericalHarmonic
    @test copy(S) == S
    @test copy(R) == R
end

@testset "Eigenvalues of spherical Laplacian" begin
    S = SphericalHarmonic()
    xyz = axes(S,1)
    Δ = Laplacian(xyz)
    @test Δ isa Laplacian
    # define some explicit spherical harmonics
    Y_20 = c -> 1/4*sqrt(5/π)*(-1+3*cos(c.θ)^2)
    Y_3m3 = c -> 1/8*exp(-3*im*c.φ)*sqrt(35/π)*sin(c.θ)^3
    Y_41 = c -> 3/8*exp(im*c.φ)*sqrt(5/π)*cos(c.θ)*(-3+7*cos(c.θ)^2)*sin(c.θ) # note phase difference in definitions
    # check that the above correctly represents the respective spherical harmonics
    cfsY20 = S \ Y_20.(xyz)
    @test cfsY20[Block(3)[3]] ≈ 1
    cfsY3m3 = S \ Y_3m3.(xyz)
    @test cfsY3m3[Block(4)[1]] ≈ 1
    cfsY41 = S \ Y_41.(xyz)
    @test cfsY41[Block(5)[6]] ≈ 1
    # Laplacian evaluation and correct eigenvalues
    @test (Δ*S*cfsY20)[SphericalCoordinate(0.7,0.2)] ≈ -6*Y_20(SphericalCoordinate(0.7,0.2))
    @test (Δ*S*cfsY3m3)[SphericalCoordinate(0.1,0.36)] ≈ -12*Y_3m3(SphericalCoordinate(0.1,0.36))
    @test (Δ*S*cfsY41)[SphericalCoordinate(1/3,6/7)] ≈ -20*Y_41(SphericalCoordinate(1/3,6/7))
end

@testset "Laplacian of expansions in complex spherical harmonics" begin
    S = SphericalHarmonic()
    xyz = axes(S,1)
    Δ = Laplacian(xyz)
    @test Δ isa Laplacian
    # define some functions along with the action of the Laplace operator on the unit sphere
    f1  = c -> cos(c.θ)^2
    Δf1 = c -> -1-3*cos(2*c.θ)
    f2  = c -> sin(c.θ)^2-3*cos(c.θ)
    Δf2 = c -> 1+6*cos(c.θ)+3*cos(2*c.θ)
    f3  = c -> 3*cos(c.φ)*sin(c.θ)-cos(c.θ)^2*sin(c.θ)^2
    Δf3 = c -> -1/2-cos(2*c.θ)-5/2*cos(4*c.θ)-6*cos(c.φ)*sin(c.θ)
    f4  = c -> cos(c.θ)^3
    Δf4 = c -> -3*(cos(c.θ)+cos(3*c.θ))
    f5  = c -> 3*cos(c.φ)*sin(c.θ)-2*sin(c.θ)^2
    Δf5 = c -> 1-9*cos(c.θ)^2-6*cos(c.φ)*sin(c.θ)+3*sin(c.θ)^2
    # compare with HarmonicOrthogonalPolynomials Laplacian
    @test (Δ*S*(S\f1.(xyz)))[SphericalCoordinate(2.12,1.993)]  ≈ Δf1(SphericalCoordinate(2.12,1.993))
    @test (Δ*S*(S\f2.(xyz)))[SphericalCoordinate(3.108,1.995)] ≈ Δf2(SphericalCoordinate(3.108,1.995))
    @test (Δ*S*(S\f3.(xyz)))[SphericalCoordinate(0.737,0.239)] ≈ Δf3(SphericalCoordinate(0.737,0.239))
    @test (Δ*S*(S\f4.(xyz)))[SphericalCoordinate(0.162,0.162)] ≈ Δf4(SphericalCoordinate(0.162,0.162))
    @test (Δ*S*(S\f5.(xyz)))[SphericalCoordinate(0.1111,0.999)] ≈ Δf5(SphericalCoordinate(0.1111,0.999))
end

@testset "Laplacian of expansions in real spherical harmonics" begin
    R = RealSphericalHarmonic()
    xyz = axes(R,1)
    Δ = Laplacian(xyz)
    @test Δ isa Laplacian
    # define some functions along with the action of the Laplace operator on the unit sphere
    f1  = c -> cos(c.θ)^2
    Δf1 = c -> -1-3*cos(2*c.θ)
    f2  = c -> sin(c.θ)^2-3*cos(c.θ)
    Δf2 = c -> 1+6*cos(c.θ)+3*cos(2*c.θ)
    f3  = c -> 3*cos(c.φ)*sin(c.θ)-cos(c.θ)^2*sin(c.θ)^2
    Δf3 = c -> -1/2-cos(2*c.θ)-5/2*cos(4*c.θ)-6*cos(c.φ)*sin(c.θ)
    f4  = c -> cos(c.θ)^3
    Δf4 = c -> -3*(cos(c.θ)+cos(3*c.θ))
    f5  = c -> 3*cos(c.φ)*sin(c.θ)-2*sin(c.θ)^2
    Δf5 = c -> 1-9*cos(c.θ)^2-6*cos(c.φ)*sin(c.θ)+3*sin(c.θ)^2
    # compare with HarmonicOrthogonalPolynomials Laplacian
    @test (Δ*R*(R\f1.(xyz)))[SphericalCoordinate(2.12,1.993)]  ≈ Δf1(SphericalCoordinate(2.12,1.993))
    @test (Δ*R*(R\f2.(xyz)))[SphericalCoordinate(3.108,1.995)] ≈ Δf2(SphericalCoordinate(3.108,1.995))
    @test (Δ*R*(R\f3.(xyz)))[SphericalCoordinate(0.737,0.239)] ≈ Δf3(SphericalCoordinate(0.737,0.239))
    @test (Δ*R*(R\f4.(xyz)))[SphericalCoordinate(0.162,0.162)] ≈ Δf4(SphericalCoordinate(0.162,0.162))
    @test (Δ*R*(R\f5.(xyz)))[SphericalCoordinate(0.1111,0.999)] ≈ Δf5(SphericalCoordinate(0.1111,0.999))
end

@testset "Laplacian raised to integer power, adaptive" begin
    S = SphericalHarmonic()
    xyz = axes(S,1)
    @test Laplacian(xyz) isa Laplacian
    @test Laplacian(xyz)^2 isa QuasiArrays.ApplyQuasiArray
    @test Laplacian(xyz)^3 isa QuasiArrays.ApplyQuasiArray
    f1  = c -> cos(c.θ)^2
    Δ_f1 = c -> -1-3*cos(2*c.θ)
    Δ2_f1 = c -> 6+18*cos(2*c.θ)
    Δ3_f1 = c -> -36*(1+3*cos(2*c.θ))
    Δ = Laplacian(xyz)
    Δ2 = Laplacian(xyz)^2
    Δ3 = Laplacian(xyz)^3
    t = SphericalCoordinate(0.122,0.993)
    @test (Δ*S*(S\f1.(xyz)))[t] ≈ Δ_f1(t)
    @test (Δ^2*S*(S\f1.(xyz)))[t] ≈ (Δ*Δ*S*(S\f1.(xyz)))[t] ≈ Δ2_f1(t)
    @test (Δ^3*S*(S\f1.(xyz)))[t] ≈ (Δ*Δ*Δ*S*(S\f1.(xyz)))[t] ≈ Δ3_f1(t)
end

@testset "Finite basis Laplacian, complex" begin
    S = SphericalHarmonic()[:,Block.(Base.OneTo(10))]
    @test S isa FiniteSphericalHarmonic
    xyz = axes(S,1)
    @test Laplacian(xyz) isa Laplacian
    @test Laplacian(xyz)^2 isa QuasiArrays.ApplyQuasiArray
    @test Laplacian(xyz)^3 isa QuasiArrays.ApplyQuasiArray
    f1  = c -> cos(c.θ)^2
    Δ_f1 = c -> -1-3*cos(2*c.θ)
    Δ2_f1 = c -> 6+18*cos(2*c.θ)
    Δ3_f1 = c -> -36*(1+3*cos(2*c.θ))
    Δ = Laplacian(xyz)
    Δ2 = Laplacian(xyz)^2
    Δ3 = Laplacian(xyz)^3
    t = SphericalCoordinate(0.122,0.993)
    @test (Δ*S*(S\f1.(xyz)))[t] ≈ Δ_f1(t)
    @test (Δ^2*S*(S\f1.(xyz)))[t] ≈ (Δ*Δ*S*(S\f1.(xyz)))[t] ≈ Δ2_f1(t)
    @test (Δ^3*S*(S\f1.(xyz)))[t] ≈ (Δ*Δ*Δ*S*(S\f1.(xyz)))[t] ≈ Δ3_f1(t)
end

@testset "Finite basis Laplacian, real" begin
    S = RealSphericalHarmonic()[:,Block.(Base.OneTo(10))]
    @test S isa FiniteRealSphericalHarmonic
    xyz = axes(S,1)
    @test Laplacian(xyz) isa Laplacian
    @test Laplacian(xyz)^2 isa QuasiArrays.ApplyQuasiArray
    @test Laplacian(xyz)^3 isa QuasiArrays.ApplyQuasiArray
    f1  = c -> cos(c.θ)^2
    Δ_f1 = c -> -1-3*cos(2*c.θ)
    Δ2_f1 = c -> 6+18*cos(2*c.θ)
    Δ3_f1 = c -> -36*(1+3*cos(2*c.θ))
    Δ = Laplacian(xyz)
    Δ2 = Laplacian(xyz)^2
    Δ3 = Laplacian(xyz)^3
    t = SphericalCoordinate(0.122,0.993)
    @test (Δ*S*(S\f1.(xyz)))[t] ≈ Δ_f1(t)
    @test (Δ^2*S*(S\f1.(xyz)))[t] ≈ (Δ*Δ*S*(S\f1.(xyz)))[t] ≈ Δ2_f1(t)
    @test (Δ^3*S*(S\f1.(xyz)))[t] ≈ (Δ*Δ*Δ*S*(S\f1.(xyz)))[t] ≈ Δ3_f1(t)
end

@testset "abs(Δ)^α - Basics of absolute Laplacian powers" begin
    # Set 1
    α = 1/3
    S = SphericalHarmonic()
    Sxyz = axes(S,1)
    SΔα = AbsLaplacianPower(Sxyz,α)
    Δ = Laplacian(Sxyz)
    @test copy(SΔα) == SΔα
    @test SΔα isa AbsLaplacianPower
    @test SΔα isa QuasiArrays.LazyQuasiMatrix
    @test axes(SΔα) == (axes(S,1),axes(S,1))
    @test abs(Δ) == -Δ == AbsLaplacianPower(axes(Δ,1),1)
    @test abs(Δ)^α == SΔα
    # Set 2
    α = 7/13
    S = SphericalHarmonic()
    Sxyz = axes(S,1)
    SΔα = AbsLaplacianPower(Sxyz,α)
    Δ = Laplacian(Sxyz)
    @test copy(SΔα) == SΔα
    @test SΔα isa AbsLaplacianPower
    @test SΔα isa QuasiArrays.LazyQuasiMatrix
    @test axes(SΔα) == (axes(S,1),axes(S,1))
    @test abs(Δ) == -Δ == AbsLaplacianPower(axes(Δ,1),1)
    @test abs(Δ)^α == SΔα
end

@testset "sum" begin
    S = SphericalHarmonic()
    R = RealSphericalHarmonic()
    @test sum(S; dims=1)[:,1:10] ≈ sum(R; dims=1)[:,1:10] ≈ [sqrt(4π) zeros(1,9)]

    x = axes(S,1)
    @test sum(S * (S \ ones(x))) ≈ sum(R * (R \ ones(x))) ≈ 4π
    f = x -> cos(x[1]*sin(x[2]+x[3]))
    @test sum(S * (S \ f.(x))) ≈ sum(R * (R \ f.(x))) ≈ 11.946489824270322609
end

@testset "Angular momentum" begin
    S = SphericalHarmonic()
    R = RealSphericalHarmonic()
    ∂θ = AngularMomentum(axes(S, 1))
    @test axes(∂θ) == (axes(S, 1), axes(S, 1))
    @test ∂θ == AngularMomentum(axes(R, 1)) == AngularMomentum(axes(S, 1).domain)
    @test copy(∂θ) ≡ ∂θ
    A = S \ (∂θ * S)
    A2 = S \ (∂θ^2 * S)
    @test diag(A[1:9, 1:9]) ≈ [0; 0; -im; im; 0; -im; im; -2im; 2im]
    N = 20
    @test isdiag(A[1:N, 1:N])
    @test A[1:N, 1:N]^2 ≈ A2[1:N, 1:N]
end


include("fit.jl")
include("data.jl")

function example_iso1(epsi=0.5)
    data = [100, 1, 0, 0, 3, 5, 0, 99, 0, 0]
    sorted_data = sort(data)
    noisy_data = DP.laplace_mechanism(sorted_data, sensitivity=1, epsilon=epsi)
    postprocessed1 = sort(noisy_data)
    dpdata = DP.isotonic(noisy_data)
    println("Sorted: ", sorted_data)
    println("Noisy: ", round.(noisy_data, digits=3))
    println("Sorting the noisy data: ", round.(postprocessed1, digits=3))
    println("Isotonic regression: ", round.(dpdata, digits=3))
    # this is not very sorted now
    error_noisy_data = sum(x -> x^2, sorted_data - noisy_data)
    error_sorted = sum(x -> x^2, sorted_data - postprocessed1)
    error_isotonic = sum(x -> x^2, sorted_data - dpdata)
    println("Error of noisy data: ", error_noisy_data)
    println("Error of sorted noisy data: ", error_sorted)
    println("Error of isotonic: ", error_isotonic)

end

mean(x::Array) = sum(x)/length(x)

function example_iso2(epsi=0.5)
    data = getnet()
    sorted_data = sort(data)
    noisy_data = DP.laplace_mechanism(sorted_data, sensitivity=1, epsilon=epsi)
    dpdata = DP.isotonic(noisy_data)
    println("Noisy Data error: ", mean((sorted_data - noisy_data).^2))
    println("Postprocessed error: ", mean((sorted_data - dpdata).^2))
end

function example_marginal(epsi=3.0)
    datashape = (3,4,5)
    data = reshape(1:60, datashape)
    q1 = DP.Marginal((1,))
    q2 = DP.Marginal((2,))
    q3 = DP.Marginal((3,))

    answer1 = DP.answer(q1, data)
    answer2 = DP.answer(q2, data)
    answer3 = DP.answer(q3, data)

    dp1 = DP.laplace_mechanism(answer1, sensitivity=DP.sens(q1), epsilon=epsi/3)
    dp2 = DP.laplace_mechanism(answer2, sensitivity=DP.sens(q2), epsilon=epsi/3)
    dp3 = DP.laplace_mechanism(answer3, sensitivity=DP.sens(q3), epsilon=epsi/3)

    dphist = DP.fit(datashape, [(q1, dp1, 1.0), (q2, dp2, 1.0), (q3, dp3, 1.0)])

    for (i, q) in enumerate([q1, q2, q3])
        println("####################")
        println("Marginal ", i)
        println("####################")
        println("True:")
        show(DP.answer(q, data))
        println()
        println("DP:")
        show(round.(DP.answer(q, dphist), digits=3))
        println()
    end
end

sqerror(x::Number, y::Number) = (x-y)^2
sqerror(x::Array, y::Array) = sum(a -> a^2, x-y)

function evaluate_strategy(workload, strategy, d, data, epsilon)
    noisy_answers = DP.protect(strategy, data, epsilon)
    noisy_data = DP.fit(size(data), [(strategy, noisy_answers, 1.0)])
    mean([sqerror(DP.answer(q, data), DP.answer(q, noisy_data)) for q in workload])
end

function evaluate_mwem(workload, T, data, epsilon)
    noisy_data = DP.mwem(workload, T, data, epsilon)
    mean([sqerror(DP.answer(q, data), DP.answer(q, noisy_data)) for q in workload])
end

function example_range(epsilon=2, T=3, amount=100000)
    data = getfrank()[1:200]
    d = length(data)

    rangequeries = DP.all_range_queries(d)
    idquery = DP.IDQuery()
    error_id = mean([evaluate_strategy(rangequeries, idquery, d, data, epsilon) for _ in amount])

    tree = DP.tree_queries(d, numsplits=100)
    error_tree = mean([evaluate_strategy(rangequeries, tree, d, data, epsilon) for _ in amount])

    #error_mwem = mean([evaluate_mwem(rangequeries, T, data, epsilon) for _ in amount])

    println("#### Results: ####")
    println("ID Query mean squared error: ", error_id)
    println("Range Query mean squared error: ", error_tree)
    #println("MWEM mean squared error: ", error_mwem)
end


module AutodiffTests

using Test

using Gridap.Arrays

function user_cell_energy(cell_u)
  function f(u)
    e = zero(eltype(u))
    for ui in u
      e += ui^2
    end
    e
  end
  lazy_map(f,cell_u)
end

function user_cell_residual(cell_u)
  lazy_map(Broadcasting(i->2*i),cell_u)
end

function user_cell_jacobian(cell_u)
  function f(u)
    n = length(u)
    j = zeros(n,n)
    for i in 1:n
      j[i,i] = 2
    end
    j
  end
  lazy_map(f,cell_u)
end

L = 10
l = 4 # Do not use a number <13 (too easy for ForwardDiff)

cell_u = [rand(l) for i in 1:L]
cell_e = user_cell_energy(cell_u)
cell_r = user_cell_residual(cell_u)
cell_j = user_cell_jacobian(cell_u)
cell_h = cell_j

cell_j_auto = autodiff_array_jacobian(user_cell_residual,cell_u)
cell_r_auto = autodiff_array_gradient(user_cell_energy,cell_u)
cell_h_auto = autodiff_array_hessian(user_cell_energy,cell_u)
test_array(cell_r_auto,cell_r)
test_array(cell_j_auto,cell_j)
test_array(cell_h_auto,cell_h)

ids = [3,4,1,2]

function user_cell_energy_Γ(cell_u)
  cell_u_Γ = lazy_map(Reindex(cell_u),ids)
  user_cell_energy(cell_u_Γ)
end

function user_cell_residual_Γ(cell_u)
  cell_u_Γ = lazy_map(Reindex(cell_u),ids)
  user_cell_residual(cell_u_Γ)
end

function user_cell_jacobian_Γ(cell_u)
  cell_u_Γ = lazy_map(Reindex(cell_u),ids)
  user_cell_jacobian(cell_u_Γ)
end

cell_e_Γ = user_cell_energy_Γ(cell_u)
cell_r_Γ = user_cell_residual_Γ(cell_u)
cell_j_Γ = user_cell_jacobian_Γ(cell_u)
cell_h_Γ = cell_j_Γ

cell_r_Γ_auto = autodiff_array_gradient(user_cell_energy_Γ,cell_u,ids)
cell_j_Γ_auto = autodiff_array_jacobian(user_cell_residual_Γ,cell_u,ids)
cell_h_Γ_auto = autodiff_array_hessian(user_cell_energy_Γ,cell_u,ids)

test_array(cell_r_Γ_auto,cell_r_Γ)
test_array(cell_j_Γ_auto,cell_j_Γ)
test_array(cell_h_Γ_auto,cell_h_Γ)

end # module


n = 300
x = randexp(n)
hist(x, nbins=30, fill="lemonchiffon", ecol="navy", norm="pdf")
plot!(x->exp(-x), 0, maximum(x), lw=0.05)


"""
```
solve(m::AbstractModel)
```

Driver to compute the model solution and augment transition matrices.

### Arguments
- `m`: the model object

### Return values
 - TTT, RRR, and CCC matrices of the state transition equation:
    ```
    S_t = TTT*S_{t-1} + RRR*ϵ_t + CCC
    ```
"""
function solve(m::AbstractModel)

    # Get equilibrium condition matrices
    Γ0, Γ1, C, Ψ, Π  = eqcond(m)

    # Solve model
    TTT_gensys, CCC_gensys, RRR_gensys, fmat, fwt, ywt, gev, eu, loose = gensys(Γ0, Γ1, C, Ψ, Π, 1+1e-6)
    if !((eu[1] == 1) & (eu[2] == 1))
        throw(GensysError("Gensys does not give existence"))
    end
    TTT_gensys = real(TTT_gensys)
    RRR_gensys = real(RRR_gensys)
    CCC_gensys = reshape(CCC_gensys, length(CCC_gensys), 1)

    # Augment states
    TTT, RRR, CCC = augment_states(m, TTT_gensys, RRR_gensys, CCC_gensys)

    return TTT, RRR, CCC
end

"""
```
GensysError <: Exception
```
A `GensysError` is thrown when Gensys does not give a unique solution, or no solution
exists. If a `GensysError`is thrown during Metropolis-Hastings, it is caught by `posterior`.
`posterior` then returns a value of `-Inf`, which Metropolis-Hastings always rejects.
### Fields
* `msg::ASCIIString`: Info message. Default = "Error in gensys."
"""
type GensysError <: Exception
    msg::ASCIIString
end
GensysError() = GensysError("Error in gensys.")
Base.showerror(io::IO, ex::GensysError) = print(io, ex.msg)


using DrWatson
@quickactivate :FractalDimension # uses DynamicalSystems, PyPlot
include(srcdir("style.jl"))

datasets = Vector(undef, 6)

X = Data.roessler_chaotic(; N = 1e4)
εs = MathConstants.e .^ (-8:0.2:5)

Hs = genentropy.(Ref(X), εs)

x = log.(εs)
y = - Hs
figure()
plot(x, y)

lrs, slopes = linear_regions(x, y; tol = 0.25)

plot(x, y)
for i in 1:length(lrs)-1
    plot(
        x[lrs[i]:lrs[i+1]], y[lrs[i]:lrs[i+1]];
        marker = "o", ms = 5
    )
end


function heffmapLapply(A::AbstractTensor{<:Any, N}, L::AbstractTensor{<:Any,3}) where N
    IA = ntuple(i -> ifelse(i == 1, -1, i), N)
    IL = (-1,1,N+1)
    IB = ntuple(identity, N+1)
    B  = tensorcontract(A, IA, L, IL, IB)
    return B
end

function heffmapCapply(A::AbstractTensor{<:Any, N}, C::AbstractTensor{<:Any,4}) where N
    IA = ntuple(i -> ifelse(i == 1, -1, ifelse(i == 2, -2, i-1)), N)
    IC = (-2,-1,1,N)
    IB = ntuple(identity, N)
    B  = tensorcontract(A, IA, C, IC, IB)
    return B
end

function heffmapRapply(A::AbstractTensor{<:Any,N}, R::AbstractTensor{<:Any,3}) where N
    IA = ntuple(i -> ifelse(i == 1, -1, ifelse(i == 2, -2, i-2)), N)
    IR = (-2,-1,N-1)
    IB = ntuple(identity, N-1)
    B  = tensorcontract(A, IA, R, IR, IB)
    return B
end

function heffmapapply(A, heff)
    L, Cs, R = heff[1], heff[2:end-1], heff[end]
    A2 = heffmapLapply(A, L)
    for C in Cs
        A2 = heffmapCapply(A2, C)
    end
    A3 = heffmapRapply(A2, R)
    return A3
end

"""
    heffmap(heff)
returns a function that applies the effective hamiltonian to a tensor.
`heff` is an indexable with entries of type `AbstractTensor` with
`h[1]` being the left-environment or boundary mpo-tensor (rank-3),
`h[end]` being the right-environment or boundary mpo-tensor (rank-3) and
`h[2:end-1]` being the central mpo-tensors (rank-4).
Note that heff needs to have at least 2 elements.
"""
heffmap(heff) = x -> heffmapapply(x, heff)

function heffmapconst(mpo::AbstractMPO{L}, envs, i) where L
    if i == 1
        heff = (mpo[1],envs[1])
    elseif i == L
        heff = (envs[L-1], mpo[L])
    else
        heff = (envs[i-1], mpo[i], envs[i])
    end
    return heffmap(heff)
end


"""
gives the ordering for nedelec2 (divergence)
"""
function relorientation(face::SArray{Tuple{3},T,1,3}, tet::SArray{Tuple{4},T,1,4}) where {T}
    # v = setdiff(tet,face)
    # length(v) == 1 || return 0

    # a = something(findfirst(isequal(v[1]), tet),0)

    a = findfirst(x -> !(x in face), tet)
    a == nothing && return 0

    @assert 1 <= a <= 4
    q = findnext(x ->!(x in face), tet, a+1)
    q == nothing || return 0

    v = tet[a]

    Face = deleteat(tet,a)
    # @show a tet face Face indexin(face,Face)
    p = parity(Vector{Int}(indexin(face,Face)))
    return a * (-1)^a * (-1)^p

    # face = [v[1],face[1],face[2],face[3]]
    # w = sortperm(face)
    # b = parity(w)
    # return -a*(-1)^b
end

"""
gives the ordering for nedelec1 (curl)
if a tetrahedron is given by simplex(a,b,c,d), the order is:
    a-b
    a-c
    a-d
    b-c
    b-d
    c-d
"""

function relorientation(edge::SArray{Tuple{2},Int64,1,2}, tet::SArray{Tuple{4},Int64,1,4})
    # v = setdiff(tet, edge)
    # length(v) == 2 || return 0

    count(x -> !(x in edge), tet) != 2 && return 0

    w1 = findfirst(isequal(edge[1]), tet)
    w2 = findfirst(isequal(edge[2]), tet)
    t = tetschoice(w1,w2)
    s = sign(w2-w1)

    return s*t
end

function tetschoice(w1::Int64,w2::Int64)
    a = min(w1,w2)
    b = max(w1,w2)
    if a == 1
        return b-1
    elseif a == 2
        return b+1
    elseif a == 3
        return b+2
    end
end

function relorientation(node::SArray{Tuple{1},Int64,1,1}, face::SArray{Tuple{N},Int64,1,N} where {N})
    idx = something(findfirst(isequal(node[1]), face), 0)
    sgn = (-1)^(idx-1)
    return sgn * idx
end

function relorientation(face, simplex)

    v = setdiff(simplex, face)
    length(v) == 1 || return 0

    # find the position of the missing vertex
    v = v[1]
    #i = Base.findfirst(simplex, v)
    i = something(findfirst(isequal(v), simplex),0)
    s = (-1)^(i-1)

    # remove that vertex from the simplex
    face2 = Array{Int}(undef,length(simplex)-1)
    for j in 1 : i-1
        face2[j] = simplex[j]
    end
    for j in i : length(simplex)-1
        face2[j] = simplex[j+1]
    end

    # get the permutation that maps face to face2
    #p = indexin(face, face2)
    #p = [ findfirst(face2,v) for v in face ]
    p = [something(findfirst(isequal(v),face2),0) for v in face]

    return s * levicivita(p) * i
end


# Practical Machine learning
# Bayesian learning - Naive Bayes example
# Chapter 9

using StatsBase
using NaiveBayes

function test_multinomial()
    print("testing MultinomialNB... ")
    m = MultinomialNB([:a, :b, :c], 5)
    X = [1 2 5 2;
         5 3 -2 1;
         0 2 1 11;
         6 -1 3 3;
         5 7 7 1]
    y = [:a, :b, :a, :c]

    fit(m, X, y)
    @assert predict(m, X) == y
    println("OK")
end

function test_gaussian()
    print("testing GaussianNB... ")
    n_obs = 100
    m = GaussianNB([:a, :b, :c], 5)
    X = randn(5, n_obs)
    y = sample([:a, :b, :c], n_obs)

    fit(m, X, y)
    accuracy = sum(predict(m, X) .== y) / n_obs
    println(accuracy)
    println("OK")
end


using NiceNumbers
using Test, LinearAlgebra

@testset "NiceNumbers" begin
    @testset "Constructors" begin
        @test NiceNumber(8) === NiceNumber(8, 0, 0)
        @test NiceNumber(2, 3, 4) === NiceNumber(8)
        @test NiceNumber(2, 3, 0) === NiceNumber(2)
        @test NiceNumber(2, 0, 5) === NiceNumber(2)
        @test NiceNumber(3 // 5) === NiceNumber(3 // 5, 0, 0)
        @test NiceNumber(3 // 5, 3 // 5, 27 // 125) === NiceNumber(3 // 5, 9 // 125, 15)
        @test NiceNumber(0, 1, 1 // 5) === NiceNumber(0, 1 // 5, 5)
        @test NiceNumber(0.1) === NiceNumber(1 // 10)
    end

    n = NiceNumber(2, 3, 5)
    m = NiceNumber(5, -3, 5)
    k = NiceNumber(8, 4, 3)
    c = NiceNumber(0, 1, -5)
    z = NiceNumber(2, -3, -5)

    @testset "Basic Arithmetic" begin
        @test n + m === NiceNumber(7)
        @test_broken n + k
        @test n - m === NiceNumber(-3, 6, 5)
        @test_broken n - k
        @test n * m === NiceNumber(-35, 9, 5)
        @test_broken n * k
        @test n / m === NiceNumber(-11 // 4, -21 // 20, 5)
        @test_broken n / k
        @test c * c === NiceNumber(-5)
    end

    @testset "Scalar Arithmetic" begin
        @test +n === n
        @test -n === NiceNumber(-2, -3, 5)
        @test 4 + n === n + 4 === NiceNumber(6, 3, 5)
        @test 4 - n === -(n - 4) === NiceNumber(2, -3, 5)
        @test 3 * n === n * 3 === NiceNumber(6, 9, 5)
        @test n / 3 === inv(3 / n) === NiceNumber(2 // 3, 1, 5)

        @test sqrt(NiceNumber(9)) === NiceNumber(3)
        @test sqrt(NiceNumber(3 // 5)) === NiceNumber(0, 1 // 5, 15)
        @test NiceNumbers.nthroot(NiceNumber(81), 8) === NiceNumber(0, 1, 3)
        @test NiceNumber(4)^(3 // 2) === NiceNumber(8)
        @test 4^NiceNumber(3 // 2) === NiceNumber(8)
    end

    @testset "Rational Arithmetic" begin
        @test n // 3 === n / 3
        @test 3 // n === 3 / n
        @test n // n === n / n === NiceNumber(1)
    end

    @testset "Comparisons" begin
        @test NiceNumber(7) < NiceNumber(42)
        @test NiceNumber(7) < NiceNumber(7, 1, 2)
        @test NiceNumber(7) <= NiceNumber(7, 1, 2)
        @test NiceNumber(7) <= NiceNumber(7)
        @test NiceNumber(5, 1, 2) < 7
        @test NiceNumber(5, 1, 2) > 5
        @test NiceNumber(13, 3, 7) == 13 + 3 * √7
        @test 13 + 3 * √7 == NiceNumber(13, 3, 7)
    end

    @testset "Conversion" begin
        @test NiceNumber(n) == n
        @test float(NiceNumber(13, 3, 7)) == 13 + 3 * √7
        @test Float64(NiceNumber(13, 3, 7)) == 13 + 3 * √7
    end

    @testset "Promotion" begin
        @test n + 18 // 5 === NiceNumber(28 // 5, 3, 5)
        @test n + 3.6 === NiceNumber(28 // 5, 3, 5)
        @test NiceNumber(3,3,-1) + 7im === NiceNumber(3,10,-1) # issue #5
        @test NiceNumber(3,3,-1) * 7im === NiceNumber(-21,21,-1) # issue #5
    end

    @testset "Complex stuff" begin
        @test abs(n) === n
        @test abs(m) === -m
        @test abs(c) === NiceNumber(0,1,5)
        @test conj(z) === NiceNumber(2,3,-5)
        @test abs(z) === NiceNumber(7)
    end

    u = NiceNumber[4, 12, 3] * sqrt(NiceNumber(2))
    @testset "Linear Algebra" begin
        @test norm(n) === n
        @test norm(m) === -m
        @test norm(u) === NiceNumber(0, 13, 2)
    end

    @testset "Misc" begin
        @test isrational(NiceNumber(139 // 7, 0, 17))
        @test isinteger(NiceNumber(12))
        @test isfinite(NiceNumber(12))
        @test !isfinite(NiceNumber(1 // 0, 7, 7))
        @test !isfinite(NiceNumber(7, 1 // 0, 7))
        @test one(n) === one(NiceNumber) === NiceNumber(1)
        @test zero(n) === zero(NiceNumber) === NiceNumber(0)
    end

    @testset "Macro" begin
        @test @nice 4^1.5 === NiceNumber(8)
        @test @nice sqrt(9//4)+7*sqrt(3)*im === NiceNumber(3//2,7,-3)
    end

    @testset "Show Methods" begin
        io = IOBuffer()
        @test sprint(io -> show(io, n)) == "2+3⋅√5"
        @test sprint(io -> show(io, NiceNumber(0))) == "0"
        @test sprint(io -> show(io, NiceNumber(7))) == "7"
        @test sprint(io -> show(io, NiceNumber(7 // 2))) == "7//2"
        @test sprint(io -> show(io, NiceNumber(7, 1, 5))) == "7+√5"
        @test sprint(io -> show(io, NiceNumber(7, -1, 5))) == "7-√5"
        @test sprint(io -> show(io, NiceNumber(7, 5, 5))) == "7+5⋅√5"
        @test sprint(io -> show(io, NiceNumber(7, -5, 5))) == "7-5⋅√5"
        @test sprint(io -> show(io, NiceNumber(7, 1 // 2, 5))) == "7+1//2⋅√5"
        @test sprint(io -> show(io, NiceNumber(7 // 2, 1, 5))) == "7//2+√5"
        @test sprint(io -> show(io, NiceNumber(7 // 2, 5, 5))) == "7//2+5⋅√5"
        @test sprint(io -> show(io, NiceNumber(7 // 2, 1 // 2, 5))) == "7//2+1//2⋅√5"
        @test sprint(io -> show(io, NiceNumber(0, 1, 5))) == "√5"
        @test sprint(io -> show(io, NiceNumber(0, 5, 5))) == "5⋅√5"
        @test sprint(io -> show(io, NiceNumber(0, -5, 5))) == "-5⋅√5"
        @test sprint(io -> show(io, NiceNumber(0, 1 // 2, 5))) == "1//2⋅√5"
    end

    @testset "Factorizations" begin
        @testset "Cholesky" begin
            @nice L = [2 0 0 0;0 1 0 0;4 6*sqrt(-1) 9 0;0 4 0 2]
            @test L == cholesky(L*L').L
        end

        @testset "LU" begin
            @nice L = [1 0 0;im 1 0; 1/2 -3im 1]
            @nice U = [1 1 1;0 1 1; 0 0 1]
            LUL, LUU = lu(L*U, Val(false))
            @test LUL == L
            @test LUU == U

            @nice A = [2 2 0;2 2 1; 2 3 5]
            L, U, p = lu(A, Val(true))
            @test L == @nice [1 0 0;1 1 0;1 0 1]
            @test U == @nice [2 2 0;0 1 5;0 0 1]
            @test p == [1,3,2]
        end

        @testset "QR" begin
            @nice A = [4 2;0 3//5;0 -4//5;0 0]
            Q, R = qr(A)
            @test Q == @nice [-1 0 0 0;0 -3/5 4/5 0;0 4/5 3/5 0;0 0 0 1]
            @test R == @nice [-4 -2;0 -1]
        end
    end
end


@testset "acquisition" begin
model = GPE([1.], [2.], MeanZero(), SEIso(1., 0.))
ac = BO.MaxMean()
opt = BO.nlopt_setup(ac, model, [-5.], [5.],
                     merge(BO.defaultoptions(typeof(model), typeof(ac)),
                           (maxtime = 3., ftol_abs = eps())))
@test opt.maxeval == 2000
@test opt.maxtime == 3.
@test opt.ftol_abs == eps()

maxf, maxx = BO.acquire_max(opt, [-5.], [5.], 10)
@test maxx ≈ [1.]
end


"""
    FVectorvariate

Structure that subtypes `VariateForm`, indicating that a single sample
is a vector from a fiber of a vector bundle.
"""
struct FVectorvariate <: VariateForm end

"""
    FVectorSupport(space::AbstractManifold, VectorBundleFibers)

Value support for vector bundle fiber-valued distributions (values from a fiber of a vector
bundle at a `point` from the given manifold).
For example used for tangent vector-valued distributions.
"""
struct FVectorSupport{TSpace<:VectorBundleFibers,T} <: ValueSupport
    space::TSpace
    point::T
end

"""
    FVectorDistribution{TSpace<:VectorBundleFibers, T}

An abstract distribution for vector bundle fiber-valued distributions (values from a fiber
of a vector bundle at point `x` from the given manifold).
For example used for tangent vector-valued distributions.
"""
abstract type FVectorDistribution{TSpace<:VectorBundleFibers,T} <:
              Distribution{FVectorvariate,FVectorSupport{TSpace,T}} end

"""
    MPointvariate

Structure that subtypes `VariateForm`, indicating that a single sample
is a point on a manifold.
"""
struct MPointvariate <: VariateForm end

"""
    MPointSupport(M::AbstractManifold)

Value support for manifold-valued distributions (values from given
[`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold)  `M`).
"""
struct MPointSupport{TM<:AbstractManifold} <: ValueSupport
    manifold::TM
end

"""
    MPointDistribution{TM<:AbstractManifold}

An abstract distribution for points on manifold of type `TM`.
"""
abstract type MPointDistribution{TM<:AbstractManifold} <:
              Distribution{MPointvariate,MPointSupport{TM}} end

"""
    support(d::FVectorDistribution)

Get the object of type `FVectorSupport` for the distribution `d`.
"""
function Distributions.support(::T) where {T<:FVectorDistribution}
    return error("support not implemented for type $T")
end

function uniform_distribution(M::AbstractManifold)
    return uniform_distribution(M, allocate_result(M, uniform_distribution))
end

"""
    Random.rand(M::AbstractManifold, [d::Integer]; vector_at=nothing)
    Random.rand(rng::AbstractRNG, M::AbstractManifold, [d::Integer]; vector_at=nothing)

Generate a random point on manifold `M` (when `vector_at` is `nothing`) or a tangent
vector at point `vector_at` (when it is not `nothing`).

Optionally a random number generator `rng` to be used can be specified. An optional integer
`d` indicates that a vector of `d` points or tangent vectors is to be generated.

!!! note

    Usually a uniform distribution should be expected for compact manifolds and a
    Gaussian-like distribution for non-compact manifolds and tangent vectors, although it is
    not guaranteed. The distribution may change between releases.

    `rand` methods for specific manifolds may take additional keyword arguments.

"""
Random.rand(M::AbstractManifold)
function Random.rand(M::AbstractManifold, d::Integer; kwargs...)
    return [rand(M; kwargs...) for _ in 1:d]
end
function Random.rand(rng::AbstractRNG, M::AbstractManifold, d::Integer; kwargs...)
    return [rand(rng, M; kwargs...) for _ in 1:d]
end
function Random.rand(M::AbstractManifold; kwargs...)
    p = allocate_result(M, rand)
    rand!(M, p; kwargs...)
    return p
end
function Random.rand(rng::AbstractRNG, M::AbstractManifold; kwargs...)
    p = allocate_result(M, rand)
    rand!(rng, M, p; kwargs...)
    return p
end


using MathOptInterface
using Xpress
using XpressPSR
using Test

const MOI = MathOptInterface

XpressPSR.initialize()

#Example of simple Thermal Generation Dispatch and the derivatives of the generation by the demand (dg/dd)
mutable struct DispachModel
    optimizer::MOI.AbstractOptimizer # Optimizer
    g::Vector{MOI.VariableIndex}     # Generation Variable Indexes
    Df::MOI.VariableIndex            # Decifit Variable Index
    c_limit_lower                    # Constraint of lower limit for variables
    c_limit_upper                    # Constraint of upper limite for variables
    c_demand                         # Constraint of the generation by the demand
end

#Simple Model of Thermal Generation Dispatch by Constraints with MOI.SingleVariable
function GenerateModel_SingleVariable()
    # Parameters
    d = 45.0                   # Demand
    I = 3                      # Number of generators
    g_sup = [10.0, 20.0, 30.0] # Upper limit of generation for each generator
    c_g = [1.0, 3.0, 5.0]      # Cost of generation for each generator
    c_Df = 10.0                # Cost of deficit

    # Creation of the Optimizer
    optimizer = Xpress.Optimizer(PRESOLVE=0, logfile = "outputXpress_SV.log")
    # Variables
    g = MOI.add_variables(optimizer, I) # Generation for each generator
    Df = MOI.add_variable(optimizer)    # Deficit

    # Constraints
    c_limit_inf = Vector{Any}(undef, I + 1) # Lower limit constraints
    c_limit_sup = Vector{Any}(undef, I)     # Upper limit constraints
    for i in 1:I
        c_limit_inf[i] = MOI.add_constraint(optimizer, MOI.SingleVariable(g[i]), MOI.GreaterThan(0.0))
        c_limit_sup[i] = MOI.add_constraint(optimizer, MOI.SingleVariable(g[i]), MOI.LessThan(g_sup[i]))
    end
    c_limit_inf[I+1] = MOI.add_constraint(optimizer, MOI.SingleVariable(Df), MOI.GreaterThan(0.0))

    c_demand = MOI.add_constraint(optimizer,
        MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ones(I+1),[g;Df]), 0.0),
        MOI.EqualTo(d)
    ) # Constraint of the Demand

    # Objectives
    objective_function = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([c_g; c_Df], [g; Df]), 0.0)     # Total cost function
    MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), objective_function)  # Pass the objective function to the optimizer
    MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)                                             # Set the objective sense to MIN

    # Solve the optimizer
    MOI.optimize!(optimizer)

    MOI.write_to_file(optimizer, "LP_SV.lp")

    # Return the solved optimizer with interest variables and constraints
    return DispachModel(optimizer, g, Df, c_limit_inf, c_limit_sup, c_demand)
end

#Simple Model of Thermal Generation Dispatch by Constraints with MOI.ScalarAffineFunction
function GenerateModel_ScalarAffineFunction()
    # Parameters
    d = 45.0                   # Demand
    I = 3                      # Number of generators
    g_sup = [10.0, 20.0, 30.0] # Upper limit of generation for each generator
    c_g = [1.0, 3.0, 5.0]      # Cost of generation for each generator
    c_Df = 10.0                # Cost of deficit

    # Creation of the Optimizer
    optimizer = Xpress.Optimizer(PRESOLVE=0, logfile = "outputXpress_SAF.log")
    # Variables
    g = MOI.add_variables(optimizer, I) # Generation for each generator
    Df = MOI.add_variable(optimizer)    # Deficit

    # Constraints
    c_limit_inf = Vector{Any}(undef, I + 1) # Lower limit constraints
    c_limit_sup = Vector{Any}(undef, I)     # Upper limit constraints
    vectAux = zeros(I + 1)                  # Auxiliar Vector
    for i in 1:I
        vectAux[i] = 1.0
        c_limit_inf[i] = MOI.add_constraint(optimizer,
            MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(vectAux,[g;Df]), 0.0),
            MOI.GreaterThan(0.0)
        )
        c_limit_sup[i] = MOI.add_constraint(optimizer,
            MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(vectAux,[g;Df]), 0.0),
            MOI.LessThan(g_sup[i])
        )
        vectAux[i] = 0.0
    end
    vectAux[I+1] = 1.0
    c_limit_inf[I+1] = MOI.add_constraint(optimizer, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(vectAux,[g;Df]),0.0), MOI.GreaterThan(0.0))
    vectAux[I+1] = 0.0

    c_demand = MOI.add_constraint(optimizer,
        MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(ones(I+1),[g;Df]), 0.0),
        MOI.EqualTo(d)
    ) # Constraint of the Demand

    # Objectives
    objective_function = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([c_g; c_Df], [g; Df]), 0.0)     # Total cost function
    MOI.set(optimizer, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), objective_function)  # Pass the objective function to the optimizer
    MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)                                             # Set the objective sense to MIN

    # Solve the optimizer
    MOI.optimize!(optimizer)

    MOI.write_to_file(optimizer, "LP_SAF.lp")

    # Return the solved optimizer with interest variables and constraints
    return DispachModel(optimizer, g, Df, c_limit_inf, c_limit_sup, c_demand)
end

#It uses a solved (optimized) Dispach Model created by the function GenerateModel
function Forward(model::DispachModel, ϵ::Float64 = 1.0)
    #Initialization of parameters and references to simplify the notation
    optimizer = model.optimizer
    vectRef = [model.g; model.Df]
    I = length(model.g)

    #Get the primal solution of the model
    vect =  MOI.get.(optimizer, MOI.VariablePrimal(), vectRef)

    #Pass the perturbation to the Xpress Framework and set the context to Forward
    MOI.set(optimizer, Xpress.ForwardSensitivityInConstraint(), model.c_demand, ϵ)

    #Get the derivative of the model
    dvect = MOI.get.(optimizer, Xpress.ForwardSensitivityOutVariable(), vectRef)

    #Return the values as a vector
    return [vect;dvect]
end

function Backward(model::DispachModel, ϵ::Float64 = 1.0)
    #Initialization of parameters and references to simplify the notation
    optimizer = model.optimizer
    vectRef = [model.g; model.Df]
    I = length(model.g)

    #Get the primal solution of the model
    vect =  MOI.get.(optimizer, MOI.VariablePrimal(), vectRef)

    #Set variables needed for the Xpress Backward Framework
    dvect = zeros(I + 1)

    #Loop for each primal variable
    for i in 1:I + 1
        #Set the perturbation in the Primal Variables and set the context to Backward
        MOI.set(optimizer, Xpress.BackwardSensitivityInVariable(), vectRef[i], ϵ)

        #Get the value of the derivative of the model
        dvect[i] = MOI.get(optimizer, Xpress.BackwardSensitivityOutConstraint(), model.c_demand)

        #Reset perturbation
        MOI.set(optimizer, Xpress.BackwardSensitivityInVariable(), vectRef[i], 0.0)
    end

    #Return the values as a vector
    return [vect;dvect]
end

#Initialization of Parameters
@testset verbose = true "Results" begin
    @testset verbose = true "Single_Variable" begin
        #Generate model by SingleVariable
        model1 = GenerateModel_SingleVariable()
        #Get the results of models with the DiffOpt Forward context
        @testset "Forward" begin
            resultForward = Forward(model1)
            @test resultForward[1:4] == [10.0;20.0;15.0;0.0]
            @test resultForward[5:8] == [0.0;0.0;1.0;0.0]
        end
        #Get the results of models with the DiffOpt Backward context
        @testset "Backward" begin
           resultBackward = Backward(model1)
           @test resultBackward[1:4] == [10.0;20.0;15.0;0.0]
           @test resultBackward[5:8] == [0.0;0.0;1.0;0.0]
        end
    end
    @testset verbose = true "Scalar_Affine_Function" begin
        #Generate model by ScalarAffineFunction
        model2 = GenerateModel_ScalarAffineFunction()
        #Get the results of models with the DiffOpt Forward context
        @testset verbose = true "Forward" begin
            resultForward = Forward(model2)
            @test resultForward[1:4] == [10.0;20.0;15.0;0.0]
            @test resultForward[5:8] == [0.0;0.0;1.0;0.0]
        end
       #Get the results of models with the DiffOpt Backward context
       @testset verbose = true "Backward" begin
           resultBackward = Backward(model2)
           @test resultBackward[1:4] == [10.0;20.0;15.0;0.0]
           @test resultBackward[5:8] == [0.0;0.0;1.0;0.0]
       end
    end
end

;#END


using NumCME
using Catalyst
using StaticArrays: MVector, @MVector
using Test

𝕊 = [[-1, 1, 0] [1, -1, 0] [0, 0, 1] [0, 0, -1]]

x₀ = [1, 0, 0]
k₀₁ = 0.05
k₁₀ = 0.1
λ = 5.0
γ = 0.5
L = 20.0
θ = [k₀₁, k₁₀, λ, γ, L]
a1 = propensity() do x, p
    p[1] * x[1]
end
a2 = propensity() do x, p
    p[2] * x[2]
end
a3 = propensity() do x, p
    p[3] * x[2]
end
a4 = propensity((t, p) -> max(0.0, 1.0 - sin(π * t / p[5]))) do x, p
    p[4] * x[3]
end

cmemodel1 = CmeModel(𝕊, [a1,a2,a3,a4], θ)

@parameters k01 k10 α γ
rn = @reaction_network begin
    k01, G0 --> G1
    k10, G1 --> G0
    α, G1 --> G1 + RNA
    γ*max(0.0, 1.0 - sin(π * t / L)), RNA --> ∅
end k01 k10 α γ L

cmemodel2 = CmeModel(rn, θ)
@test get_species_count(cmemodel2) == 3
@test get_reaction_count(cmemodel2) == 4
@test get_parameter_count(cmemodel2) == 5
for i in 1:3
    @test !istimevarying(cmemodel2.propensities[i])
end
@test istimevarying(cmemodel2.propensities[4])

test_times = [0.0, 10.0, 20.0, 30.0]
test_space = StateSpaceSparse(𝕊, [1,0,0])
expand!(test_space, 100)
pass = true
for t in test_times
    for state in get_states(test_space)
        pass &= cmemodel1.propensities[1](state,θ) ≈ cmemodel2.propensities[1](state,θ)
        pass &= cmemodel1.propensities[2](state,θ) ≈ cmemodel2.propensities[2](state,θ)
        pass &= cmemodel1.propensities[3](state,θ) ≈ cmemodel2.propensities[3](state,θ)
        pass &= cmemodel1.propensities[4](t,state,θ) ≈ cmemodel2.propensities[4](t,state,θ)
        (!pass) && break
    end
end
@test pass


println("On teste le package Relaxation")

using Relaxation

r=1.0;s=1.0;t=1.0;
Relaxation_success=true

# psi(x::Any,r::Float64,s::Float64,t::Float64)
if norm(Relaxation.psi(s,r,s,t)-s)>precision
 x = linspace(-0.5,10,1000); y = Relaxation.psi(x,r,s,t)
 PyPlot.plot(x, y, color="red", linewidth=2.0, linestyle="-")
 x = linspace(-0.5,10,1000); y = Relaxation.psi(x,r,s,t)
 PyPlot.plot(y, x, color="blue", linewidth=2.0, linestyle="-")
end

#invpsi(x::Any,r::Float64,s::Float64,t::Float64)
if norm(Relaxation.invpsi(s,r,s,t)-s)>precision
 x = linspace(-0.5,10,1000); y = Relaxation.psi(x,r,s,t)
 PyPlot.plot(x, y, color="red", linewidth=2.0, linestyle="-")
 x = linspace(-0.5,10,1000); y = Relaxation.psi(x,r,s,t)
 PyPlot.plot(y, x, color="blue", linewidth=2.0, linestyle="-")
end
#dpsi(x::Any,r::Float64,s::Float64,t::Float64)
#phi(yg::Any,yh::Any,r::Float64,s::Float64,t::Float64)
#phi(x::Any,nb_comp::Int64,r::Float64,s::Float64,t::Float64)
#dphi(yg::Any,yh::Any,r::Float64,s::Float64,t::Float64)
#dphi(x::Any,nb_comp::Int64,r::Float64,s::Float64,t::Float64)

#AlphaThetaMax(x::Float64,dx::Float64,y::Float64,dy::Float64,r::Float64,s::Float64,t::Float64)
#AlphaThetaMax : résoud l'équation en alpha où psi(x,r,s,t)=s+t*theta(x,r)
r=1.0;s=1.0;t=0.0 #résoud l'éq.

x=1.0;y=2.0;dx=1.0;dy=-1.0;
#doit rendre (0,1)
if !isequal(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t),[0.0,1.0])
 println(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))
 Thetafunc_success=false
end

x=0.0;y=2.0;dx=1.0;dy=-2.0;
#solution : (1,0.5)
if !isequal(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t),[1.0,0.5])
 println(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))
 Relaxation_success=false
end

r=1.0;s=1.0;t=1.0
#on tape la contrainte :
x=1.0;y=2.0;dx=1.0;dy=0.0;
if !(minimum(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))>=0.0) || !(maximum(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))==Inf)
 println(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))
 Relaxation_success=false
end
x=2.0;y=1.0;dx=-1.0;dy=2.0;
if !(minimum(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))>=0.0) || !(maximum(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))<Inf)
 println(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))
 Relaxation_success=false
end

#on est sous (s,s) et donc on ne tape pas la contrainte virtuelle
x=0.0;y=0.0;dx=0.0;dy=-2.0;
if !isequal(maximum(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t)),Inf)
 println(Relaxation.AlphaThetaMax(x,dx,y,dy,r,s,t))
 Relaxation_success=false
end

#Bilan : donner une sortie bilan
if Relaxation_success==true
 println("Relaxationmod.jl passes the test !")
else
 println("Relaxationmod.jl contains some error")
end


using CDDLib

@testset "Testing Repeated Game functionality" begin

    pd_payoff = [9.0 1.0
                 10.0 3.0]

    A = Player(pd_payoff)
    B = Player(pd_payoff)
    nfg = NormalFormGame((A, B))

    # Tests construction of repeated game
    rpd = RepeatedGame(nfg, 0.75)
    C, H, Z = Games.initialize_sg_hpl(4, [0.0, 0.0], 1.0)

    #
    # Test various helper functions
    #
    @testset "Testing flow utility computations" begin
        @test abs(flow_u_1(rpd, 1, 1) - 9.0) < 1e-14
        @test abs(flow_u_2(rpd, 2, 2) - 3.0) < 1e-14
        @test maximum(abs, flow_u(rpd, 2, 2) - [3.0, 3.0]) < 1e-14
    end

    @testset "Testing best deviation computations" begin
        @test best_dev_i(rpd, 1, 1) == 2
    end

    @testset "Testing unit circle function" begin
        H = Games.unitcircle(4)
        points = [1.0 0.0
                  0.0 1.0
                  -1.0 0.0
                  0.0 -1.0]

        @test maximum(abs, H - points) < 1e-12
    end

    @testset "Testing subgradient and hyperplane level initialize" begin
        C, H, Z = Games.initialize_sg_hpl(4, [0.0, 0.0], 1.0)

        @test maximum(abs, C - ones(4)) < 1e-12
        @test maximum(abs, H - Z') < 1e-12
    end

    @testset "Testing worst value computation" begin
        @test abs(worst_value_i(rpd, H, C, 1) + 1.0) < 1e-12
    end

    #
    # Test the actual computation
    #
    @testset "Testing outer approximation" begin
        dict = Dict(:nH=>64, :maxiter=>150, :tol=>1e-9)
        plib = CDDLib.Library()
        for kwargs in [dict, merge(dict, Dict(:plib=>plib))]
            vertices = @inferred(outerapproximation(rpd; kwargs...))
            p_in_v = [vertices[i, :] for i in 1:size(vertices, 1)]

            mybools = [all(isapprox.([3.0, 3.0], p)) for p in p_in_v]
            @test any(mybools)
        end
    end

end


function double_expectation(numsamples=1000;seed=1)

    rg = MersenneTwister(seed)

    N = 5
    F = 2
    idx = ceil.(Int, rand(rg, N)*F)

    μf = rand(rg,N)
    Σf = randn(rg,N,N) ; Σf = Σf*Σf'
    qf = MvNormal(μf, Σf)
    f  = rand(rg, qf)

    μα = randn(rg, F); Σα = Diagonal(rand(rg, F))
    qα = MvNormal(μα, Σα)

    S = Diagonal(rand(rg,N) .+ 0.1);

    # first verify that this holds for diagonal matrices
    A = Diagonal(rand(rg,N))
    f'*A'*inv(S)*A*f, sum(f.^2 .* diag(A).^2 ./ diag(S))  # ✅

    # test inner expectation
    innerexpectation = μf'*A'*inv(S)*A*μf + tr(A*inv(S)*A*Σf)

    # numinnerexpectation = @distributed (+) for i=1:numsamples
    #
    #     local f = rand(qf)
    #
    #     f'*A'*inv(S)*A*f / numsamples
    #
    # end
    #
    # innerexpectation, numinnerexpectation # ✅

    # test double expectation
    μa = μα[idx]
    Σa = Diagonal(Σα.diag[idx])

    doubleexpectation  = sum(μf.^2 .* (μa.^2 .+ Σa.diag) ./ S.diag) + sum((μa.^2 .+ Σa.diag).*diag(Σf) ./S.diag)

    shorterexpectation = sum((μf.^2 .+ diag(Σf)) .* (μa.^2 .+ Σa.diag) ./ S.diag)

    numdoubleexpectation = 0#@distributed (+) for i=1:numsamples
    #
    #     local A = Diagonal(rand(qa))
    #
    #     (μf'*A'*(S\A*μf) + tr(A*inv(S)*A*Σf)) / numsamples
    #
    # end

    altnumdoubleexpectation = @distributed (+) for i=1:numsamples

        local A = Diagonal(rand(qα)[idx])
        local f = rand(qf)

        f'*A'*(S\A*f) / numsamples

    end

    shorterexpectation, doubleexpectation, numdoubleexpectation,altnumdoubleexpectation # ✅

end


#!/usr/bin/env julia

function kron_δ(i,j)
    if i == j
        return 1
    else
        return 0
    end
end

function harmean(i,j)
    2 / ((1/i)+(1/j))
end

function inv_degree(g,i)
    1/degree(g,i)
end

function strength(g,i)
    neigh = neighbors(g,i)
    strengthi = sum(map(x-> g.weights[x,i],neigh))
    strengtho = sum(map(x-> g.weights[i,x],neigh))
    strength = strengthi + strengtho
    strength
end

function strengthin(g,i)
    neigh = inneighbors(g,i)
    strength = sum(map(x-> g.weights[x,i],neigh))
    strength
end

function strengthout(g,i)
    neigh = outneighbors(g,i)
    strength = sum(map(x-> g.weights[x,i],neigh))
    strength
end

function inv_degree_ihara(g,i,v)
    try
        1/strength(g,i,v)
    catch
        0
    end
end

function rel_single(g,i)
    if outdegree(g,i) == 1
        1
    else
        0
    end
end

function mglobal_clustering_coefficient(g::AbstractGraph)
    c = 0
    ntriangles = 0
    for v in vertices(g)
        neighs = neighbors(g, v)
        for i in neighs, j in neighs
            i == j && continue
            if has_edge(g, i, j)
                c += 1
            end
        end
        k = degree(g, v)
        ntriangles += k * (k - 1)
    end
    ntriangles == 0 && return 0.
    return c / ntriangles
end

function inv_degree_N(g,i)
    if degree(g,i) != 1
        1/(degree(g,i)-1)
    else
        1/degree(g,i)
    end
end

function nonBM_embedding(g)
  NB = la_NBM(g)
  λ, eigve = eigs(NB, nev=20)
  los_reales = Array{Float64}(0)
  los_indices_reales = Array{Int64}(0)
  for i in 1:length(λ)
        if imag(λ[i]) == 0 && real(λ[i]) != 0 && i*real(λ[i]) >= sqrt(real(λ[1]))
          push!(los_reales,λ[i])
          push!(los_indices_reales,i)
      end
  end
  matrix_embedded = sub(real(eigve),:,los_indices_reales)
  length(los_reales), los_indices_reales
  ϕ = zeros(Float64, nv(g), length(los_reales))
  for n=1:(length(los_reales))
      v= matrix_embedded[:,n]
      ϕ[:,n] = contract(g, v)
  end
  return length(los_reales), ϕ
end

function grados(g)
	grados = Array{Int64}(0)
	gradosdiv = Array{Int64}(0)
	for i in vertices(g)
		push!(grados,degree(g,i))
	end
	grados
end

function avg_grados(g)
    grado = grados(g)
    avg_grad = mean(grado)
    avg_grad
end

function max_grados(g)
    grado = grados(g)
    maxi = maximum(grado)
    maxi
end

function mat_an(g,v,NBM)
	unos = ones(2ne(g))/sqrt(2ne(g))
	unnos = unos*unos'
	R = NBM - unnos
	R
end

function eigens(M,threshold)
	valores, vectores = eigs(M,which=:LR,nev=3)
    ind = findall(x->imag(x)==0.0,valores)
    valores = valores[ind]
    vectores = vectores[:,ind]
    if real(last(valores)) > threshold
		valores, vectores = eigs(M,which=:LR,nev=4)
        ind = findall(x->imag(x)==0.0,valores)
        valores = valores[ind]
        vectores = vectores[:,ind]
		if real(last(valores)) > threshold
		    valores, vectores = eigs(M,which=:LR,nev=5)
            ind = findall(x->imag(x)==0.0,valores)
            valores = valores[ind]
            vectores = vectores[:,ind]
		    if real(last(valores)) > threshold
		        valores, vectores = eigs(M,which=:LR,nev=10)
                ind = findall(x->imag(x)==0.0,valores)
                valores = valores[ind]
                vectores = vectores[:,ind]
		        if real(last(valores)) > threshold
		            valores, vectores = eigs(M,which=:LR,nev=nv(g))
                    ind = findall(x->imag(x)==0.0,valores)
                    valores = valores[ind]
                    vectores = vectores[:,ind]
		        end
		    end
		end
	end
	valores, vectores
end

function num_com(v,threshold)
	cuantos = Array{Float64}(undef,0)
	index = Array{Int64}(undef,0)
	for i in 1:length(v)
		if imag(v[i]) == 0 && real(v[i]) > threshold
		    push!(cuantos,real(v[i]))
		    push!(index,i)
		end
	end
    cuantos,index
end

function embe_nbm(g,ind,vec)
	reales = real(vectores[:,index])
	up = reales[1:nv(g),:]
	down = reales[nv(g)+1:2*nv(g),:]
	matrix_embedded = up + down
	matrix_embedded
end

function contract(g,v,ei)
    y = zeros(Float64, nv(g))
    for i in 1:nv(g)
        for j in inneighbors(g,i)
            u = ei[Edge(j,i)]
            y[i] += v[u]
        end
    end
    y
end

function contraction(g,vec,emb,ei)
	contracted = zeros(Float64, nv(g), length(vec))
	for n in 1:length(vec)
    	contracted[:,n] = contract(g,emb,ei)
	end
	contracted
end


"""
    WeibullExpert(k, θ)

PDF:

```math
f(x; k, \\theta) = \\frac{k}{\\theta} \\left( \\frac{x}{\\theta} \\right)^{k-1} e^{-(x/\\theta)^k},
\\quad x \\geq 0
```

See also: [Weibull Distribution](https://en.wikipedia.org/wiki/Weibull_distribution) (Wikipedia)

"""
struct WeibullExpert{T<:Real} <: NonZIContinuousExpert
    k::T
    θ::T
    WeibullExpert{T}(k::T, θ::T) where {T<:Real} = new{T}(k, θ)
end

function WeibullExpert(k::T, θ::T; check_args=true) where {T <: Real}
    check_args && @check_args(WeibullExpert, k >= one(k) && θ > zero(θ))
    return WeibullExpert{T}(k, θ)
end

## Outer constructors
WeibullExpert(k::Real, θ::Real) = WeibullExpert(promote(k, θ)...)
WeibullExpert(k::Integer, θ::Integer) = WeibullExpert(float(k), float(θ))
WeibullExpert() = WeibullExpert(2.0, 1.0)

## Conversion
function convert(::Type{WeibullExpert{T}}, k::S, θ::S) where {T <: Real, S <: Real}
    WeibullExpert(T(k), T(θ))
end
function convert(::Type{WeibullExpert{T}}, d::WeibullExpert{S}) where {T <: Real, S <: Real}
    WeibullExpert(T(d.k), T(d.θ), check_args=false)
end
copy(d::WeibullExpert) = WeibullExpert(d.k, d.θ, check_args=false)

## Loglikelihood of Expert
logpdf(d::WeibullExpert, x...) = Distributions.logpdf.(Distributions.Weibull(d.k, d.θ), x...)
pdf(d::WeibullExpert, x...) = Distributions.pdf.(Distributions.Weibull(d.k, d.θ), x...)
logcdf(d::WeibullExpert, x...) = Distributions.logcdf.(Distributions.Weibull(d.k, d.θ), x...)
cdf(d::WeibullExpert, x...) = Distributions.cdf.(Distributions.Weibull(d.k, d.θ), x...)

## expert_ll, etc
expert_ll_exact(d::WeibullExpert, x::Real) = LRMoE.logpdf(d, x)
function expert_ll(d::WeibullExpert, tl::Real, yl::Real, yu::Real, tu::Real)
    expert_ll = (yl == yu) ? logpdf.(d, yl) : logcdf.(d, yu) + log1mexp.(logcdf.(d, yl) - logcdf.(d, yu))
    expert_ll = (tu == 0.) ? -Inf : expert_ll
    return expert_ll
end
function expert_tn(d::WeibullExpert, tl::Real, yl::Real, yu::Real, tu::Real)
    expert_tn = (tl == tu) ? logpdf.(d, tl) : logcdf.(d, tu) + log1mexp.(logcdf.(d, tl) - logcdf.(d, tu))
    expert_tn = (tu == 0.) ? -Inf : expert_tn
    return expert_tn
end
function expert_tn_bar(d::WeibullExpert, tl::Real, yl::Real, yu::Real, tu::Real)
    expert_tn_bar = (tl == tu) ? 0.0 : log1mexp.(logcdf.(d, tu) + log1mexp.(logcdf.(d, tl) - logcdf.(d, tu)))
    return expert_tn_bar
end

exposurize_expert(d::WeibullExpert; exposure = 1) = d

## Parameters
params(d::WeibullExpert) = (d.k, d.θ)
function params_init(y, d::WeibullExpert)
    pos_idx = (y .> 0.0)

    function init_obj(logk, y)
        n = length(y)
        k_tmp = exp(logk)
        θ_tmp = ( sum(y.^k_tmp) / n)^(1/k_tmp)
        return -1 * (n*logk - n*log(θ_tmp) + k_tmp*sum(log.(y)) - n*(k_tmp-1)*log(θ_tmp) - 1/(θ_tmp^k_tmp)*sum(y.^k_tmp) )
    end

    logk_init = Optim.minimizer( Optim.optimize(x -> init_obj(x, y[pos_idx]),  0.0, 2.0 ))

    k_init = exp(logk_init)
    θ_init = ( sum(y[pos_idx].^k_init) / length(y[pos_idx]))^(1/k_init)

    try
        return WeibullExpert(k_init, θ_init)
    catch;
        WeibullExpert()
    end
end

## KS stats for parameter initialization
function ks_distance(y, d::WeibullExpert)
    p_zero = sum(y .== 0.0) / sum(y .>= 0.0)
    return max(abs(p_zero-0.0), (1-0.0)*HypothesisTests.ksstats(y[y .> 0.0], Distributions.Weibull(d.k, d.θ))[2])
end

## Simululation
sim_expert(d::WeibullExpert) = Distributions.rand(Distributions.Weibull(d.k, d.θ), 1)[1]

## penalty
penalty_init(d::WeibullExpert) = [2.0 10.0 1.0 Inf]
no_penalty_init(d::WeibullExpert) = [1.0 Inf 1.0 Inf]
penalize(d::WeibullExpert, p) = (p[1]-1)*log(d.k) - d.k/p[2] + (p[3]-1)*log(d.θ) - d.θ/p[4]

## statistics
mean(d::WeibullExpert) = mean(Distributions.Weibull(d.k, d.θ))
var(d::WeibullExpert) = var(Distributions.Weibull(d.k, d.θ))
quantile(d::WeibullExpert, p) = quantile(Distributions.Weibull(d.k, d.θ), p)
lev(d::WeibullExpert, u) = d.θ*gamma_inc(float(1/d.k+1), (u/d.θ)^d.k, 0)[1]*gamma(float(1/d.k+1)) + u*exp(-(u/d.θ)^d.k)
excess(d::WeibullExpert, u) = mean(d) - lev(d, u)

## Misc functions for E-Step

function _int_logy_func(d::WeibullExpert, x)
    return (iszero(x) || isinf(x)) ? 0.0 : log(x) * pdf.(d, x)
end

function _int_obs_logY_raw(d::WeibullExpert, yl, yu)
    if yl == yu
        return log(yl) * pdf.(d, yl)
    else
        return quadgk.(x -> _int_logy_func(d, x), yl, yu, rtol = 1e-8)[1]
    end
end

function _int_lat_logY_raw(d::WeibullExpert, tl, tu)
    return (tl==0 ? 0.0 : quadgk.(x -> _int_logy_func(d, x), 0.0, tl, rtol = 1e-8)[1]) + (isinf(tu) ? 0.0 : quadgk.(x -> _int_logy_func(d, x), tu, Inf, rtol = 1e-8)[1])
end

function _int_powy_func(d_old::WeibullExpert, k_new, l, u)
    term1 = isinf(l) ? 0.0 : gamma((d_old.k+k_new)/d_old.k) * gamma_inc((d_old.k+k_new)/d_old.k, (l/d_old.θ)^(d_old.k), 1)[2]
    term2 = isinf(u) ? 0.0 : gamma((d_old.k+k_new)/d_old.k) * gamma_inc((d_old.k+k_new)/d_old.k, (u/d_old.θ)^(d_old.k), 1)[2]
    return (d_old.θ)^(k_new) * (term1 - term2)
end

function _int_obs_powY(d_old::WeibullExpert, k_new, yl, yu, expert_ll_pos)
    if yl == yu
        return yl^k_new
    else
        return exp(-expert_ll_pos) * _int_powy_func(d_old, k_new, yl, yu)
    end
end

function _int_lat_powY(d_old::WeibullExpert, k_new, tl, tu, expert_tn_bar_pos)
    return exp(-expert_tn_bar_pos) * (_int_powy_func(d_old, k_new, 0.0, tl) + _int_powy_func(d_old, k_new, tu, Inf))
end

function _weibull_k_to_λ(k, sum_term_zkz, sum_term_zkzpowy;
                        penalty = true, pen_pararms_jk = [1.0 Inf 1.0 Inf])

    return (sum_term_zkzpowy / sum_term_zkz)^(1/k)
end

function _weibull_optim_k(logk,
                        d_old,
                        tl, yl, yu, tu,
                        expert_ll_pos, expert_tn_bar_pos,
                        z_e_obs, z_e_lat, k_e,
                        logY_e_obs, logY_e_lat;
                        penalty = true, pen_pararms_jk = [1.0 Inf 1.0 Inf])
    # Optimization in log scale for unconstrained computation
    k_tmp = exp(logk)

    # Further E-step
    powY_e_obs = _int_obs_powY.(d_old, k_tmp, yl, yu, expert_ll_pos)
    powY_e_lat = _int_lat_powY.(d_old, k_tmp, tl, tu, expert_tn_bar_pos)
    nan2num(powY_e_obs, 0.0) # get rid of NaN
    nan2num(powY_e_lat, 0.0) # get rid of NaN

    term_zkz = z_e_obs .+ (z_e_lat .* k_e)
    term_zkz_logY = (z_e_obs .* logY_e_obs) .+ (z_e_lat .* k_e .* logY_e_lat)
    term_zkz_powY = (z_e_obs .* powY_e_obs) .+ (z_e_lat .* k_e .* powY_e_lat)

    sum_term_zkz = sum(term_zkz)[1]
    sum_term_zkz_logY = sum(term_zkz_logY)[1]
    sum_term_zkz_powY = sum(term_zkz_powY)[1]

    θ_tmp = _weibull_k_to_λ(k_tmp, sum_term_zkz, sum_term_zkz_powY, penalty = penalty, pen_pararms_jk = pen_pararms_jk)

    obj = log(k_tmp)*sum_term_zkz - k_tmp*log(θ_tmp)*sum_term_zkz + (k_tmp-1)*sum_term_zkz_logY - (θ_tmp^(-k_tmp))*sum_term_zkz_powY
    p = penalty ? (pen_pararms_jk[1]-1)*log(k_tmp) - k_tmp/pen_pararms_jk[2] + (pen_pararms_jk[3]-1)*log(θ_tmp) - θ_tmp/pen_pararms_jk[4] : 0.0
    return (obj + p) * (-1.0)

end

## EM: M-Step
function EM_M_expert(d::WeibullExpert,
                     tl, yl, yu, tu,
                     exposure,
                     z_e_obs, z_e_lat, k_e;
                     penalty = true, pen_pararms_jk = [1.0 Inf 1.0 Inf])

    expert_ll_pos = expert_ll.(d, tl, yl, yu, tu)
    expert_tn_bar_pos = expert_tn_bar.(d, tl, yl, yu, tu)

    # Further E-Step
    yl_yu_unique = unique_bounds(yl, yu)
    int_obs_logY_tmp = _int_obs_logY_raw.(d, yl_yu_unique[:,1], yl_yu_unique[:,2])
    logY_e_obs = exp.(-expert_ll_pos) .* int_obs_logY_tmp[match_unique_bounds(hcat(vec(yl), vec(yu)), yl_yu_unique)]
    nan2num(logY_e_obs, 0.0) # get rid of NaN

    tl_tu_unique = unique_bounds(tl, tu)
    int_lat_logY_tmp = _int_lat_logY_raw.(d, tl_tu_unique[:,1], tl_tu_unique[:,2])
    logY_e_lat = exp.(-expert_tn_bar_pos) .* int_lat_logY_tmp[match_unique_bounds(hcat(vec(tl), vec(tu)), tl_tu_unique)]
    nan2num(logY_e_lat, 0.0) # get rid of NaN

    # Update parameters
    pos_idx = (yu .!= 0.0)
    logk_new = Optim.minimizer( Optim.optimize(x -> _weibull_optim_k(x, d,
                                                            tl[pos_idx], yl[pos_idx], yu[pos_idx], tu[pos_idx],
                                                            expert_ll_pos[pos_idx],  expert_tn_bar_pos[pos_idx],
                                                            z_e_obs[pos_idx], z_e_lat[pos_idx], k_e[pos_idx],
                                                            logY_e_obs[pos_idx], logY_e_lat[pos_idx],
                                                            penalty = penalty, pen_pararms_jk = pen_pararms_jk),
                                               max(log(d.k)-2.0, 0.0), log(d.k)+2.0 )) # ,
                                               # log(d.k)-2.0, log(d.k)+2.0 )) # ,
                                               # GoldenSection() ))
                                               # , rel_tol = 1e-8) )
    k_new = exp(logk_new)

    # Further E-step
    powY_e_obs = _int_obs_powY.(d, k_new, yl, yu, expert_ll_pos)
    powY_e_lat = _int_lat_powY.(d, k_new, tl, tu, expert_tn_bar_pos)
    nan2num(powY_e_obs, 0.0) # get rid of NaN
    nan2num(powY_e_lat, 0.0) # get rid of NaN
    inf2num(powY_e_obs, 0.0) # get rid of Inf
    inf2num(powY_e_lat, 0.0) # get rid of Inf

    term_zkz = z_e_obs .+ (z_e_lat .* k_e)
    # term_zkz_logY = (z_e_obs .* logY_e_obs) .+ (z_e_lat .* k_e .* logY_e_lat)
    term_zkz_powY = (z_e_obs .* powY_e_obs) .+ (z_e_lat .* k_e .* powY_e_lat)

    sum_term_zkz = sum(term_zkz)[1]
    # sum_term_zkz_logY = sum(term_zkz_logY)[1]
    sum_term_zkz_powY = sum(term_zkz_powY)[1]

    θ_new = _weibull_k_to_λ(k_new, sum_term_zkz, sum(sum_term_zkz_powY)[1], penalty = penalty, pen_pararms_jk = pen_pararms_jk)

    return WeibullExpert(k_new, θ_new)

end

## EM: M-Step, exact observations
function _weibull_optim_k_exact(logk,
                        ye,
                        z_e_obs,
                        logY_e_obs;
                        penalty = true, pen_pararms_jk = [1.0 Inf 1.0 Inf])
    # Optimization in log scale for unconstrained computation
    k_tmp = exp(logk)

    # Further E-step
    powY_e_obs = ye.^(k_tmp)
    nan2num(powY_e_obs, 0.0)

    term_zkz = z_e_obs
    term_zkz_logY = z_e_obs .* logY_e_obs
    term_zkz_powY = z_e_obs .* powY_e_obs

    sum_term_zkz = sum(term_zkz)[1]
    sum_term_zkz_logY = sum(term_zkz_logY)[1]
    sum_term_zkz_powY = sum(term_zkz_powY)[1]

    θ_tmp = _weibull_k_to_λ(k_tmp, sum_term_zkz, sum_term_zkz_powY, penalty = penalty, pen_pararms_jk = pen_pararms_jk)

    obj = log(k_tmp)*sum_term_zkz - k_tmp*log(θ_tmp)*sum_term_zkz + (k_tmp-1)*sum_term_zkz_logY - (θ_tmp^(-k_tmp))*sum_term_zkz_powY
    p = penalty ? (pen_pararms_jk[1]-1)*log(k_tmp) - k_tmp/pen_pararms_jk[2] + (pen_pararms_jk[3]-1)*log(θ_tmp) - θ_tmp/pen_pararms_jk[4] : 0.0
    return (obj + p) * (-1.0)

end
function EM_M_expert_exact(d::WeibullExpert,
                            ye, exposure,
                            z_e_obs;
                            penalty = true, pen_pararms_jk = [Inf 1.0 Inf])

    # Further E-Step
    logY_e_obs = log.(ye)
    nan2num(logY_e_obs, 0.0) # get rid of NaN

    # Update parameters
    pos_idx = (ye .!= 0.0)
    logk_new = Optim.minimizer( Optim.optimize(x -> _weibull_optim_k_exact(x,
                                                            ye[pos_idx],
                                                            z_e_obs[pos_idx],
                                                            logY_e_obs[pos_idx],
                                                            penalty = penalty, pen_pararms_jk = pen_pararms_jk),
                                               max(log(d.k)-2.0, 0.0), log(d.k)+2.0 ))

    k_new = exp(logk_new)

    # Further E-step
    powY_e_obs = ye.^(k_new)
    nan2num(powY_e_obs, 0.0)

    term_zkz = z_e_obs
    term_zkz_powY = z_e_obs .* powY_e_obs

    sum_term_zkz = sum(term_zkz)[1]
    sum_term_zkz_powY = sum(term_zkz_powY)[1]

    θ_new = _weibull_k_to_λ(k_new, sum_term_zkz, sum(sum_term_zkz_powY)[1], penalty = penalty, pen_pararms_jk = pen_pararms_jk)

    return WeibullExpert(k_new, θ_new)

end


function test_location_scale(
    rng::Union{AbstractRNG, Missing},
    μ::Real, σ::Real, ρ::UnivariateDistribution, dref::UnivariateDistribution,
)
    d = LocationScale(μ,σ,ρ)
    @test params(d) == (μ,σ,ρ)
    @test eltype(d) === eltype(dref)

    # Different ways to construct the LocationScale object
    if dref isa DiscreteDistribution
        # floating point division introduces numerical errors
        # Better: multiply with rational numbers
        d_dict = Dict(
            "original" => d,
            "sugar" => σ * ρ + μ,
        )
    else
        d_dict = Dict(
            "original" => d,
            "sugar" => σ * ρ + μ,
            "composed" =>  μ + ((2 * ρ) * σ - 2) / 2 + 1
        )
    end

    @test d == deepcopy(d)

    #### Support / Domain

    @testset "Support" begin
        function test_support(d, dref)
            @test minimum(d) == minimum(dref)
            @test maximum(d) == maximum(dref)
            @test extrema(d) == (minimum(d), maximum(d))
        end
        @testset "$k" for (k,dtest) in d_dict
            test_support(dtest, dref)
        end
    end

    #### Promotions and conversions

    @testset "Promotions and conversions" begin
        function test_promotions_and_conversions(d, dref)
            @test typeof(d.µ) === typeof(d.σ)
            @test location(d) ≈ μ atol=1e-15
            @test    scale(d) ≈ σ atol=1e-15
        end
        @testset "$k" for (k,dtest) in d_dict
            test_promotions_and_conversions(dtest, dref)
        end
    end

    #### Statistics

    @testset "Statistics" begin
        function test_statistics(d, dref)
            @test mean(d) ≈ mean(dref)
            @test median(d) ≈ median(dref)
            @test mode(d) ≈ mode(dref)
            @test modes(d) ≈ modes(dref)

            @test var(d) ≈ var(dref)
            @test std(d) ≈ std(dref)
            @test skewness(d) ≈ skewness(dref)
            @test kurtosis(d) ≈ kurtosis(dref)

            @test isplatykurtic(d) == isplatykurtic(dref)
            @test isleptokurtic(d) == isleptokurtic(dref)
            @test ismesokurtic(d) == ismesokurtic(dref)

            @test entropy(d) ≈ entropy(dref)
            @test mgf(d,-0.1) ≈ mgf(dref,-0.1)
        end
        @testset "$k" for (k,dtest) in d_dict
            test_statistics(dtest, dref)
        end
    end

    #### Evaluation & Sampling

    @testset "Evaluation & Sampling" begin
        function test_evaluation_and_sampling(rng, d, dref)
            xs = rand(dref, 5)
            x = first(xs)
            insupport(d, x) == insupport(dref, x)
            # might return `false` for discrete distributions
            insupport(d, -x) == insupport(dref, -x)

            @test pdf(d, x) ≈ pdf(dref, x)
            @test pdf.(d, xs) ≈ pdf.(dref, xs)
            @test logpdf(d, x) ≈ logpdf(dref, x)
            @test logpdf.(d, xs) ≈ logpdf.(dref, xs)
            @test loglikelihood(d, x) ≈ loglikelihood(dref, x)
            @test loglikelihood(d, xs) ≈ loglikelihood(dref, xs)

            @test cdf(d, x) ≈ cdf(dref, x)
            @test logcdf(d, x) ≈ logcdf(dref, x)
            @test ccdf(d, x) ≈ ccdf(dref, x) atol=1e-15
            @test logccdf(d, x) ≈ logccdf(dref, x) atol=1e-15

            @test quantile(d,0.1) ≈ quantile(dref,0.1)
            @test quantile(d,0.5) ≈ quantile(dref,0.5)
            @test quantile(d,0.9) ≈ quantile(dref,0.9)

            @test cquantile(d,0.1) ≈ cquantile(dref,0.1)
            @test cquantile(d,0.5) ≈ cquantile(dref,0.5)
            @test cquantile(d,0.9) ≈ cquantile(dref,0.9)

            @test invlogcdf(d,log(0.2)) ≈ invlogcdf(dref,log(0.2))
            @test invlogcdf(d,log(0.5)) ≈ invlogcdf(dref,log(0.5))
            @test invlogcdf(d,log(0.8)) ≈ invlogcdf(dref,log(0.8))

            @test invlogccdf(d,log(0.2)) ≈ invlogccdf(dref,log(0.2))
            @test invlogccdf(d,log(0.5)) ≈ invlogccdf(dref,log(0.5))
            @test invlogccdf(d,log(0.8)) ≈ invlogccdf(dref,log(0.8))

            r = Array{float(eltype(d))}(undef, 100000)
            if ismissing(rng)
                rand!(d,r)
            else
                rand!(rng,d,r)
            end
            @test mean(r) ≈ mean(dref) atol=0.02
            @test std(r) ≈ std(dref) atol=0.01
            @test cf(d, -0.1) ≈ cf(dref,-0.1)

            if dref isa ContinuousDistribution
                @test gradlogpdf(d, 0.1) ≈ gradlogpdf(dref, 0.1)
            end
        end
        @testset "$k" for (k,dtest) in d_dict
            test_evaluation_and_sampling(rng, dtest, dref)
        end
    end
end

function test_location_scale_normal(
    rng::Union{AbstractRNG, Missing}, μ::Real, σ::Real, μD::Real, σD::Real,
)
    ρ = Normal(μD, σD)
    dref = Normal(μ + σ * μD, σ * σD)
    return test_location_scale(rng, μ, σ, ρ, dref)
end

function test_location_scale_discretenonparametric(
    rng::Union{AbstractRNG, Missing}, μ::Real, σ::Real, support, probs,
)
    ρ = DiscreteNonParametric(support, probs)
    dref = DiscreteNonParametric(μ .+ σ .* support, probs)
    return test_location_scale(rng, μ, σ, ρ, dref)
end

@testset "LocationScale" begin
    rng = MersenneTwister(123)

    for _rng in (missing, rng)
        test_location_scale_normal(_rng, 0.3, 0.2, 0.1, 0.2)
        test_location_scale_normal(_rng, -0.3, 0.1, -0.1, 0.3)
        test_location_scale_normal(_rng, 1.3, 0.4, -0.1, 0.5)
    end
    test_location_scale_normal(rng, ForwardDiff.Dual(0.3), 0.2, 0.1, 0.2)

    probs = Distributions.pnormalize!(rand(10))
    for _rng in (missing, rng)
        test_location_scale_discretenonparametric(_rng, 1//3, 1//2, 1:10, probs)
        test_location_scale_discretenonparametric(_rng, -1//4, 1//3, (-10):(-1), probs)
        test_location_scale_discretenonparametric(_rng, 6//5, 3//2, 15:24, probs)
    end
end


function simulate(data,p,v,m,nsteps,dt,iprint)
  f = [ zeros(2) for i in 1:data.N ]
  for i in 1:nsteps
    u, f = energyandforce!(p,f,data)
    for ip in 1:length(p)
      @. p[i] = p[i] + v[i]*dt + f[i]*dt^2/(2*m)
      @. v[i] = v[i] + f[i]*dt/m
    end
    if i%iprint == 0
      println(" Step $i, Energy = $u ")
    end
  end
end


struct MeshPointNM{U,D,C,N,T}
    patch::Simplex{U,D,C,N,T}
    bary::SVector{D,T}
    cart::SVector{U,T}
end

Base.length(m::MeshPointNM) = length(m.cart)
Base.getindex(p::MeshPointNM, i::Int) = p.cart[i]

cartesian(mp::MeshPointNM) = mp.cart
parametric(mp::MeshPointNM) = mp.bary
chart(nbd::MeshPointNM) = mp.patch

"Return the barycentric coordinates of `mp`"
barycentric(mp::MeshPointNM) = SVector(mp.bary[1], mp.bary[2], 1-mp.bary[1]-mp.bary[2])

"""
A number defines a neighborhood in euclidian space
"""
jacobian(x::Number) = one(x)

jacobian(mp::MeshPointNM) = volume(mp.patch) * factorial(dimension(mp.patch))
tangents(mp::MeshPointNM, i) = mp.patch.tangents[i]
normal(mp::MeshPointNM) = mp.patch.normals[1]

function neighborhood(p::Simplex, bary)
  D = dimension(p)
  T = coordtype(p)
  P = SVector{D,T}
  cart = barytocart(p, bary)
  MeshPointNM(p, P(bary), cart)
end

@generated function center(p::Simplex{U,D,C,N,T}) where {U,D,C,N,T}
    uv = ones(T,D)/(D+1)
    :(neighborhood(p, $uv))
end


function get_test_spectrum(seed)
    seed!(seed)
    x = collect(0:0.1:200)
    y = @. exp(-(x-15)^2) + 2 * exp(-(x-30)^2)
    @. y += 1.0 + x*1.5e-2 - (x-50)^3*3e-7 # add polynomial baseline
    n = length(x)
    δx = x[2] - x[1]
    return Curve(x, y .+ (get_cov(δx, n, 0.1, 0.5) |> MvNormal |> rand))
end

@testset "regression tests" begin
    @testset "integration of single peak" begin
        c = crop(get_test_spectrum(1), 10, 40)
        uc = add_noise(c, MvGaussianNoiseModel(0.1, 0.1, 0.5))
        ub = UncertainBound(15.0, scale_shift_beta(2.0, 2.0, 3.0, 4.0), uc)
        area = mc_integrate(uc, ub, subtract_baseline=true)
        @test mean(area) ≈ 1.808961690 atol = 1e-8
        @test std(area) ≈ 0.2488841198 atol = 1e-8
    end
    @testset "integration of two peaks" begin
        c = crop(get_test_spectrum(1), 10, 40)
        uc = add_noise(c, MvGaussianNoiseModel(0.1, 0.1, 0.5))
        ubs = UncertainBound([15., 30.], scale_shift_beta(2.0, 2.0, 3.0, 4.0), uc)
        area1, area2 = mc_integrate(uc, ubs, subtract_baseline=true)
        @test mean(area1) ≈ 1.808961690 atol = 1e-8
        @test std(area1) ≈ 0.2488841198 atol = 1e-8
        @test mean(area2) ≈ 3.442573414 atol = 1e-8
        @test std(area2) ≈ 0.3122904192 atol = 1e-8
    end
end

@testset "pass custom integration function" begin
    f(args...; kw...) = 0.0 # this should make integration yield area = 0 ± 0
    c = crop(get_test_spectrum(1), 10, 40)
    uc = add_noise(c, MvGaussianNoiseModel(0.1, 0.1, 0.5))
    ub = UncertainBound(15.0, scale_shift_beta(2.0, 2.0, 3.0, 4.0), uc)
    area = mc_integrate(uc, ub; intfun=f)
    @test mean(area) == 0.0
    @test std(area) == 0.0
end

@testset "integrate using Simpsons rule from external package" begin

    using NumericalIntegration: integrate, SimpsonEven

    function simps(x, y, a, b; subtract_baseline=true)
        idx = [(xᵢ >= a) & (xᵢ <= b) for xᵢ in x]
        return integrate(x[idx], y[idx], SimpsonEven())
    end
    c = crop(get_test_spectrum(1), 10, 40)
    uc = add_noise(c, MvGaussianNoiseModel(0.1, 0.1, 0.5))
    ub = UncertainBound(15.0, scale_shift_beta(2.0, 2.0, 3.0, 4.0), uc)
    area_s = mc_integrate(uc, ub; intfun=simps)
    @test mean(area_s) ≈ 5.6868358 atol = 1e-7
    @test std(area_s) ≈ 0.32897085 atol = 1e-7
end

@testset "error if local_baseline and subtract_baseline true" begin
    c = NoisySignalIntegration.testdata_1()
    uc = add_noise(c, MvGaussianNoiseModel(0.1, 0.1, 0.5))
    ub = UncertainBound(15.0, scale_shift_beta(2.0, 2.0, 3.0, 4.0), uc)
    @test_throws ErrorException area = mc_integrate(uc, ub; local_baseline=true, subtract_baseline=true)
end

@testset "test _local_baseline" begin
    xs = 1.0:10.0 |> collect
    b = UncertainBound(Uniform(1.5, 2.5), Uniform(4.5, 6.5))
    @testset "test _local_baseline with linear dataset" begin
        ys = xs
        xₗ, xᵣ, yₗ, yᵣ = nsi._local_baseline(xs, ys, 1.75, 5.25, b)
        yyₗ = nsi.lininterp(1.75, xs, ys)
        yyᵣ = nsi.lininterp(5.25, xs, ys)
        @test yyₗ == yₗ
        @test yyᵣ == yᵣ
    end
    @testset "test _local_baseline with parabolic dataset" begin
        ys = xs.^2
        xₗ, xᵣ, yₗ, yᵣ = nsi._local_baseline(xs, ys, 2.25, 5.25, b)
        @test yₗ ≈ 6.1329 atol=1e-4  # results derived by doing computation by hand in Numpy
        @test yᵣ ≈ 28.8471 atol=1e-4
    end
    @testset "test _local_baseline with normal and uniform distribution on noise" begin
        noise = [
            1 1.51
            2 1.41
            3 1.2
            4 0.88
            5 0.5
            6 0.09
            7 -0.32
            8 -0.67
            9 -0.94
            10 -1.09
            11 -1.12
            12 -1.04
            13 -0.86
            14 -0.63
            15 -0.39
            16 -0.18
            17 -0.04
            18 0.02
            19 -0.01
            20 -0.12
            21 -0.25
            22 -0.36
            23 -0.43
            24 -0.43
            25 -0.4
            26 -0.36
            27 -0.35
            28 -0.39
            29 -0.48
            30 -0.6
            31 -0.73
            32 -0.8
            33 -0.8
            34 -0.71
            35 -0.53
            36 -0.3
            37 -0.06
            38 0.15
            39 0.27
            40 0.29
        ]
        bnd = UncertainBound(Normal(4.0, 2.0), Uniform(30.0, 35.0))
        xₗ, xᵣ, yₗ, yᵣ = nsi._local_baseline(noise[:,1], noise[:,2], 4.0, 32.5, bnd)
        @test yₗ ≈ 0.77916842211701 # manually derived in LibreCalc
        @test yᵣ ≈ -0.7210156
    end
end


using BenchmarkTools
using LinearAlgebra
using CUDA
using Test

free_parameters = 1000

#C = rand(ComplexF32, free_parameters, free_parameters)
C = rand(free_parameters, free_parameters)
C += C'
C_GPU = CuArray(convert(Matrix{Float32}, C))

@btime val, vec = eigen(C)
@btime CUDA.@sync val_GPU, vec_GPU = CUDA.CUSOLVER.syevd!('V', 'U', C_GPU)

#val, vec = eigen(C)
#val_GPU, vec_GPU = CUDA.CUSOLVER.syevd!('V', 'U', C_GPU)

#@test val ≈ Array(val_GPU)

println("Finished")


function create_agents(graph::AbstractGraph, rng::MersenneTwister)

    agent_list = Array{Agent, 1}(undef, length(vertices(graph)))
    for agent_idx in 1:length(agent_list)
        agent_list[agent_idx] = Agent(
            agent_idx,
            2 * rand(rng) - 1,
            rng
        )
    end
    return agent_list
end

# suppress output of include()
;

function generate_inclin_interact(rng::MersenneTwister, lambda=log(25))
    # this function was adapted from:
    # https://www.johndcook.com/julia_rng.html
    if lambda <= 0.0
        error("mean must be positive")
    end
    -(1 / lambda) * log(rand(rng))
end


type MoRMax<:FiniteHorizonAgent
    policy::Matrix{UInt64}

    ns::Array{UInt64, 4}
    n::Array{UInt64, 3}

    phat::Array{Float64, 4}
    rhat::Array{Float64, 3}

    pprime::Array{Float64, 4}
    rprime::Array{Float64, 3}

    rewards::Array{Float64, 3}

    τ::Array{Int64, 3}
    τstar::Int64

    Qhat::Array{Float64, 3}
    Qprime::Array{Float64, 3}
    policyprime::Matrix{UInt64}
    Qprev::Array{Float64, 3}

    δ::Float64
    ϵ::Float64
    rewards_known::Bool
    maxRet::Float64
    mfactor::Float64
end

function MoRMax(S, A, H, rewards, δ, ϵ, mfactor=1.)
    policy = zeros(UInt64, S, H)
    policyprime = copy(policy)

    ns = zeros(UInt64, S, S, A, H)
    n = zeros(UInt64, S, A, H)

    phat = zeros(S, S, A, H)
    pprime = copy(phat)

    maxrs = maximum(reshape(rewards, S*A, H), 1)
    rhat = copy(rewards)
    for t=1:H
        rhat[:, :, t] = sum(maxrs[t:end])
    end
    rprime = copy(rhat)
    τ = zeros(Int64, S, A, H)

    Qhat = zeros(S, A, H)
    Qprime = copy(Qhat)
    Qprev = zeros(S, A, H)
    compute_optQ!(Qhat, policy, phat, rhat)
    rand!(policy, 1:A) # Initialize with uniformly random policy

    MoRMax(policy, ns,n,  phat, rhat, pprime, rprime, rewards,
           τ, 1, Qhat, Qprime, policyprime, Qprev, δ, ϵ, true, sum(maxrs), mfactor)
end

maxV(m::MoRMax) = m.maxRet
function mval(m::MoRMax)
    S = nS(m)
    A = nA(m)
    H = horizon(m)
    mval = 1800*maxV(m)^2 / m.ϵ^2 * H^2
    mval *= log(144 * H * S^2 * A^2 * maxV(m) / m.δ / m.ϵ)
    mval
end

function update_policy!(m::MoRMax)
    S = nS(m)
    A = nA(m)
    H = horizon(m)
    mvalue = mval(m)
    if !m.rewards_known
        error("Not Implemented")
    end

    for t ∈ 1:H, a ∈ 1:A, s ∈ 1:S
        if m.n[s,a,t] >= mvalue * m.mfactor

            if (m.τ[s, a, t] == 0 ||
                (m.τstar > m.τ[s,a,t] && m.Qprev[s, a, t] > m.ϵ / H + m.Qhat[s, a, t]))
                    copy!(m.pprime, m.phat)
                    copy!(m.rprime, m.rhat)
                    m.pprime[:, s, a, t] .= m.ns[:, s, a, t] / m.n[s, a, t]
                    m.rprime[s, a, t] = m.rewards[s, a, t]

                    #println("Propose model update for s=$s, a=$a, t=$t")
                    compute_optQ!(m.Qprime, m.policyprime, m.pprime, m.rprime)
                    if m.Qprime[s, a, t] <= m.Qhat[s, a, t]
                        m.phat[:, s, a, t] = m.pprime[:, s, a, t]
                        m.rhat[s, a, t] = m.rprime[s, a, t]
                        copy!(m.Qhat, m.Qprime)
                        copy!(m.policy, m.policyprime)
                        #println("Accepted model update for s=$s, a=$a, t=$t")
                    end
                    m.τstar += 1
                    m.τ[s,a,t] = m.τstar
                    m.Qprev[s,a,t] = m.Qhat[s,a,t]
            end

            m.n[s,a,t] = 0
            m.ns[:, s, a, t] = 0
        end
    end
end

function compute_optQ!(Q, pol, P, R)
    S, A, H = size(R)
    V = zeros(S)
    for t ∈ H:-1:1
        Q[:, :, t] .= R[:, :, t] + squeeze(sum(V .* P[:, :, :, t], 1), 1)
        for s ∈ 1:S
            a = indmax(Q[s, :, t])
            pol[s, t] = a
            V[s] = Q[s, a, t]
        end
    end
end


module _XApEn
export XApEn
using Statistics: mean, std
    """
        XAp, Phi = XApEn(Sig)

    Returns the cross-approximate entropy estimates (`XAp`) and the average
    number of matched vectors (`Phi`) for m = [0,1,2], estimated for the data
    sequences contained in 'Sig' using the default parameters:
    embedding dimension = 2, time delay = 1,
    radius distance threshold= 0.2*SD(`Sig`), logarithm = natural

    **NOTE**: XApEn is direction-dependent. Thus, the first column of
    `Sig` is used as the template data sequence, and the second
    column is the matching sequence.``

        XAp, Phi = XApEn(Sig::AbstractArray{T,2} where T<:Real; m::Int=2, tau::Int=1, r::Real=0.2*std(Sig,corrected=false), Logx::Real=exp(1))

    Returns the cross-approximate entropy estimates (`XAp`) between the data
    sequences contained in 'Sig' using the specified 'keyword' arguments:

    # Arguments:
    `m`     - Embedding Dimension, a positive integer  [default: 2] \n
    `tau`   - Time Delay, a positive integer        [default: 1]  \n
    `r`     - Radius Distance Threshold, a positive scalar [default: 0.2*SD(`Sig`)] \n
    `Logx`  - Logarithm base, a positive scalar     [default: natural]  \n

    # See also `XSampEn`, `XFuzzEn`, `XMSEn`, `ApEn`, `SampEn`, `MSEn`

    # References:
        [1] Steven Pincus and Burton H. Singer,
            "Randomness and degrees of irregularity."
            Proceedings of the National Academy of Sciences
            93.5 (1996): 2083-2088.

        [2] Steven Pincus,
            "Assessing serial irregularity and its implications for health."
            Annals of the New York Academy of Sciences
            954.1 (2001): 245-267.

    """
    function XApEn(Sig::AbstractArray{T,2} where T<:Real; m::Int=2,
        tau::Int=1, r::Real=0.2*std(Sig,corrected=false), Logx::Real=exp(1))

    size(Sig,2) > size(Sig,1) ? Sig = transpose(Sig) : nothing

    N = size(Sig,1)
    (N>10 && size(Sig,2)==2) ? nothing :  error("Sig:   must be a 2-columns matrix")
    (m > 0) ? nothing : error("m:     must be an integer > 0")
    (tau>0) ? nothing : error("tau:   must be an integer > 0")
    (r >=0) ? nothing : error("r:     must be a positive value")
    (Logx>0) ? nothing : error("Logx: must be a positive number > 0")

    S1 = Sig[:,1]; S2 = Sig[:,2]
    Counter = 1*(abs.(S1 .- transpose(S2)) .<= r)
    M = vcat(m*ones(Int,N-(m*tau)), repeat((m-1):-1:1,inner=tau))
    XAp = zeros(m+1)
    Phi = zeros(m+2)
    for n = 1:N - tau
        ix = findall(Counter[n, :] .== 1)
        for k = 1:M[n]
            ix = ix[ix .+ (k*tau) .<= N]
            isempty(ix) ? break : nothing
            p1 = repeat(transpose(S1[n:tau:n+(tau*k)]), outer=length(ix))
            p2 = S2[ix .+ transpose(collect(0:tau:(k*tau)))]
            ix = ix[findall(maximum(abs.(p1 - p2),dims=2) .<= r)]
            Counter[n, ix] .+= 1
        end
    end

    Phi[1] = log(Logx, N)/N
    #Phi[2] = mean(log.(Logx, sum(Counter.>0,dims=1)/N))
    Temp = sum(Counter.>0,dims=1); Temp = Temp[Temp.!=0]
    Phi[2] = mean(log.(Logx, Temp/N))
    XAp[1] = Phi[1] - Phi[2]
    for k = 0:m-1
        ai = sum(Counter.>(k+1),dims=1)/(N-(k+1)*tau)
        bi = sum(Counter.>k,dims=1)/(N-(k*tau))
        ai = ai[ai.!=0]
        bi = bi[bi.!=0]
        Phi[k+3] = sum(log.(Logx, ai))/(N-(k+1)*tau)
        XAp[k+2]= sum(log.(Logx, bi))/(N-(k*tau)) - Phi[k+3]
    end

    return XAp, Phi
    end

end

"""
Copyright 2021 Matthew W. Flood, EntropyHub

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

For Terms of Use see https://github.com/MattWillFlood/EntropyHub
"""


using Oceananigans
using Oceananigans.Units
using GLMakie
using Printf

grid = RectilinearGrid(size=(64, 128), halo=(3, 3),
                       x=(0, 64), z=(0, 32),
                       topology=(Periodic, Flat, Bounded))

@inline u_drag(x, y, t, u, w, Cᵈ) = - Cᵈ * u * sqrt(u^2 + w^2)
u_bottom_bc = FluxBoundaryCondition(u_drag, field_dependencies=(:u, :w), parameters=1e-3)
u_bcs = FieldBoundaryConditions(bottom=u_bottom_bc)

sediment_bottom_bc = ValueBoundaryCondition(1)
sediment_bcs = FieldBoundaryConditions(bottom=sediment_bottom_bc)

r_sediment = 1e-4 # m (Fine sand)
ρ_sediment = 1400 # kg m⁻³
ρ_ocean = 1026 # kg m⁻³
Δb = - 9.81 * (ρ_sediment - ρ_ocean) / ρ_ocean
ν_molecular = 1.05e-6
@show w_sediment = 2/9 * Δb / ν_molecular * r_sediment^2
sinking = AdvectiveForcing(WENO5(), w=w_sediment)

model = NonhydrostaticModel(; grid,
                            advection = WENO5(),
                            timestepper = :RungeKutta3,
                            tracers = (:b, :sediment),
                            buoyancy = BuoyancyTracer(),
                            closure = ScalarDiffusivity(ν=1e-5, κ=(b=1e-5, sediment=1e-3)),
                            forcing = (; sediment = sinking),
                            boundary_conditions = (; u=u_bcs, sediment=sediment_bcs))

bᵢ(x, y, z) = 1e-4 * z
uᵢ(x, y, z) = 1 - tanh(z / 4)
wᵢ(x, y, z) = 1e-3 * rand() * exp(-z^2 / 2)
sᵢ(x, y, z) = 0.1 * exp(-((x - 32)^2 + (z - 16)^2) / 8)
set!(model, b=bᵢ, u=uᵢ, w=wᵢ, sediment=sᵢ)

Δz = grid.Δzᵃᵃᶜ
Δt = 0.2 * Δz / abs(w_sediment)
simulation = Simulation(model; Δt, stop_time=2hours)

wizard = TimeStepWizard(cfl=0.5, max_Δt=Δt)
simulation.callbacks[:wizard] = Callback(wizard, IterationInterval(10))

wall_clock = Ref(time_ns())
function progress(sim)
    elapsed = 1e-9 * (time_ns() - wall_clock[])

    @info @sprintf("Iter: %d, time: %s, Δt: %s, min(u): %.2e, min(s): %.2e, wall time: %s",
                   iteration(sim), prettytime(sim), prettytime(sim.Δt),
                   minimum(model.velocities.u), minimum(model.tracers.sediment),
                   prettytime(elapsed))

    wall_clock[] = time_ns()

    return nothing
end

simulation.callbacks[:progress] = Callback(progress, IterationInterval(100))

fig = Figure(resolution=(1600, 600))
ax_ξ = Axis(fig[1, 1])
ax_w = Axis(fig[1, 2])
ax_s = Axis(fig[1, 3])

sediment = model.tracers.sediment
u, v, w = model.velocities

ξ = compute!(Field(∂z(u) - ∂x(w)))

wlim = maximum(abs, w) / 2
ξlim = maximum(abs, ξ) / 2
smax = maximum(sediment)

hm_ξ = heatmap!(ax_ξ, interior(ξ, :, 1, :), colormap=:redblue, colorrange=(-wlim, wlim))
hm_w = heatmap!(ax_w, interior(w, :, 1, :), colormap=:redblue, colorrange=(-wlim, wlim))
hm_s = heatmap!(ax_s, interior(sediment, :, 1, :), colormap=:haline, colorrange=(0, smax))

display(fig)

function update_plot!(sim)
    compute!(ξ)
    wlim = maximum(abs, w) / 2
    ξlim = maximum(abs, ξ) / 2
    smax = maximum(sediment)

    hm_w.attributes.colorrange[] = (-wlim, wlim)
    hm_ξ.attributes.colorrange[] = (-ξlim, ξlim)
    # hm_s.attributes.colorrange[] = (0, smax/2)

    hm_s.input_args[1][] = interior(sediment, :, 1, :)
    hm_w.input_args[1][] = interior(w, :, 1, :)
    hm_ξ.input_args[1][] = interior(ξ, :, 1, :)

    return nothing
end

simulation.callbacks[:plot] = Callback(update_plot!, IterationInterval(100))

simulation.output_writers[:fields] =
    JLD2OutputWriter(model, merge(model.velocities, model.tracers, (; ξ)),
                     schedule = TimeInterval(1.0),
                     with_halos = false,
                     filename = "sediment_entrainment",
                     overwrite_existing = true)

run!(simulation)

#####
##### Visualize
#####

filepath = "sediment_entrainment.jld2"
ξt = FieldTimeSeries(filepath, "ξ")
st = FieldTimeSeries(filepath, "sediment")
wt = FieldTimeSeries(filepath, "w")
Nt = length(wt.times)

fig = Figure(resolution=(1600, 600))
ax_ξ = Axis(fig[1, 1], xlabel="x (m)", ylabel="z (m)", title="Vorticity", aspect=2)
ax_w = Axis(fig[1, 2], xlabel="x (m)", ylabel="z (m)", title="Vertical velocity", aspect=2)
ax_s = Axis(fig[1, 3], xlabel="x (m)", ylabel="z (m)", title="Sediment concentration", aspect=2)
slider = Slider(fig[2, :], range=1:Nt, startvalue=1)
n = slider.value

title = @lift string("Sediment entrainment at t = ", prettytime(wt.times[$n]))
Label(fig[0, :], title)

xξ, yξ, zξ = nodes(ξt)
xw, yw, zw = nodes(wt)
xs, ys, zs = nodes(st)

ξn = @lift interior(ξt[$n], :, 1, :)
sn = @lift interior(st[$n], :, 1, :)
wn = @lift interior(wt[$n], :, 1, :)

wlim = maximum(abs, wt[Nt]) / 2
ξlim = maximum(abs, ξt[Nt]) / 2
slim = maximum(st[Nt]) / 4

hm_ξ = heatmap!(ax_ξ, xξ, zξ, ξn, colormap=:redblue, colorrange=(-wlim, wlim))
hm_w = heatmap!(ax_w, xw, zw, wn, colormap=:redblue, colorrange=(-wlim, wlim))
hm_s = heatmap!(ax_s, xs, zs, sn, colormap=:haline, colorrange=(0, slim))

display(fig)

record(fig, "sediment_entrainment.mp4", 1:Nt, framerate=160) do nn
    n[] = nn
end


using JetPackTransforms, Jets, Test

@testset "JetSlantStack, dot product test" for T in (Float32, Float64)
    A = JopSlantStack(JetSpace(T, 64, 128); dz=10.0, dh=10.0, h0=-1000.0)

    m = rand(domain(A))
    d = A*m

    lhs, rhs = dot_product_test(A,rand(domain(A)),rand(range(A)))
    @test isapprox(lhs,rhs,rtol=1e-4)

    A = JopSlantStack(JetSpace(T, 64, 128); dz=10.0, dh=10.0, h0=-1000.0, taperz=(0.3,0.3), taperh=(0.3,0.3), taperkz=(0.3,0.3), taperkh=(0.3,0.3))
    lhs, rhs = dot_product_test(A,rand(domain(A)),rand(range(A)))
    @test isapprox(lhs,rhs,rtol=1e-4)
end

@testset "JetSlantStack, correctness" begin
    A = JopSlantStack(JetSpace(Float64, 64, 128); dz=10.0, dh=10.0, h0=-1000.0)
    m = zeros(domain(A))
    m[32,:] .= 1
    d = A*m
    v,i = findmax(d)
    @test i[1] == 32
    @test i[2] == findfirst(x->x≈0, state(A).cp)
end


#**************************************************************************************
# test_calculations.jl
# =============== part of the GeoEfficiency.jl package.
#
# this file contains all the required function to test calculate the Geometrical efficiency.
#
#**************************************************************************************

using GeoEfficiency
using Base.Test

info("Statrting `geoEff` test...")
info("Spectial case for cylinderical detector; very restrict test")
for raduis=[1,2,3,4,5,6,7,8,9,10.1,10.5,10.6]
	detector = CylDetector(raduis)
	@test_approx_eq 	geoEff(detector, Point(0)) 							0.5
	@test_approx_eq 	geoEff(detector, Point(0, raduis-eps(raduis)))		0.5
	@test_approx_eq 	geoEff(detector, Point(0, -raduis+eps(raduis)))		0.5
	@test_approx_eq 	geoEff(detector, Point(0, raduis/2-eps(raduis)))	0.5
	@test_approx_eq 	geoEff(detector, Point(0, -raduis/2+eps(raduis)))	0.5

	@test_approx_eq 	geoEff(detector, Point(eps()))					0.5
	@test_approx_eq 	geoEff(detector, Point(eps(), raduis-eps(raduis)))		0.5
	@test_approx_eq 	geoEff(detector, Point(eps(), -raduis+eps(raduis)))		0.5
	@test_approx_eq 	geoEff(detector, Point(eps(), raduis/2-eps(raduis)))	0.5
	@test_approx_eq 	geoEff(detector, Point(eps(), -raduis/2+eps(raduis)))	0.5

end #for
info("test pass")

info("Spectial case for well detector")
for raduis=[1,2,3,4,5,6,7,8,9,10.1,10.5,10.6], height=[1,2,3,4,5,6,7,8,9,10.1,10.5,10.6], k=[1.1,2,3,4,5,6,7,8,9,10.1,10.5,10.6]
	welldepth = height/k		# k > 1
	holeradius = raduis/k		# k > 1
	detector = WellDetector(raduis,height,holeradius, welldepth)

	@test_approx_eq 	geoEff(detector, Point(welldepth)) 									0.5
	@test_approx_eq 	geoEff(detector, Point(welldepth, holeradius-eps(holeradius)))		0.5
	@test_approx_eq 	geoEff(detector, Point(welldepth, -holeradius+eps(holeradius)))		0.5
	@test_approx_eq 	geoEff(detector, Point(welldepth, holeradius/2-eps(holeradius)))	0.5
	@test_approx_eq 	geoEff(detector, Point(welldepth, -holeradius/2+eps(holeradius)))	0.5

	@test_approx_eq 	geoEff(detector, Point(welldepth+eps(welldepth)))									0.5
	@test_approx_eq 	geoEff(detector, Point(welldepth+eps(welldepth), holeradius-eps(holeradius)))		0.5
	@test_approx_eq 	geoEff(detector, Point(welldepth+eps(welldepth), -holeradius+eps(holeradius)))		0.5
	@test_approx_eq 	geoEff(detector, Point(welldepth+eps(welldepth), holeradius/2-eps(holeradius)))		0.5
	@test_approx_eq 	geoEff(detector, Point(welldepth+eps(welldepth), -holeradius/2+eps(holeradius)))	0.5

end #for
info("test pass")

info("Statrting scaling test cylinderical detector with point source...")
for raduis=[1,2,3,4,5,6,7,8,9,10.1,10.5,10.6], j=2:100, k=2:100
	detector = CylDetector(raduis); detectork = CylDetector(raduis*k)

	@test_approx_eq 	geoEff(detector, Point(raduis/j)) 				geoEff(detectork, Point(k*raduis/j))

	@test_approx_eq 	geoEff(detector, Point(raduis*j, raduis/j))		geoEff(detectork, Point(k*raduis*j, k*raduis/j))
	println("scale ",k," ; test number ",j-1, " | sucsseful")
end #for
info("test pass")