using OrdinaryDiffEq, Test, Random
Random.seed!(100)
setprecision(400)

f = (u,p,t) -> (2u)
#(::typeof(f))(::Type{Val{:analytic}},u0,p,t) = u0*exp(t)
prob = ODEProblem(f,1/2,(0.0,1.0))

sol3 =solve(prob,RK4(),dt=1/2^(6),abstol=1,reltol=0)

prob = ODEProblem(f,BigInt(1)//BigInt(2),(BigInt(0)//BigInt(1),BigInt(1)//BigInt(1)))

integrator = init(prob,RK4(),dt=BigInt(1)//BigInt(2)^(6),abstol=1,reltol=0)

sol =solve(prob,RK4(),dt=BigInt(1)//BigInt(2)^(6),abstol=1,reltol=0)
sol2 =solve(prob,RK4(),dt=BigInt(1)/BigInt(2)^(6),abstol=1,reltol=0)

sol.u
sol2.u
sol3.u
@test eltype(sol.u) == Rational{BigInt}
@test eltype(sol2.u) == Rational{BigInt}
@test eltype(sol3.u) == Float64

sol4 =solve(prob,DP5(),dt=BigInt(1)//BigInt(2)^(3),adaptive=false)

@test eltype(sol4.u) == Rational{BigInt}

tabalg = ExplicitRK(tableau = constructDormandPrince(Rational{BigInt}))

integrator = init(prob,tabalg,dt=BigInt(1)//BigInt(2)^(3),abstol=1,reltol=0,adaptive=false)
sol5 =solve(prob,tabalg,dt=BigInt(1)//BigInt(2)^(3),abstol=1,reltol=0,adaptive=false)

@test typeof(sol5.u[end]) == Rational{BigInt}

prob = ODEProblem(f,1/2+1/2im,(0.0,1.0))


"""
    module TestLinearIntegerEquations - Test ModiaBase/src/LinearIntegerEquations.jl
"""
module TestLinearIntegerEquations

using Test
using ModiaBase

println("... Test LinearIntegerEquations.jl")

@testset "Test LinearIntegerEquations.jl" begin

    @testset "... Test Voltage source and resistor without ground" begin
        println("\n    --- Test Voltage source and resistor without ground")
        #=
            # Resistor
            1: R.v = R.p.v - R.n.v
            2: 0 = R.p.i + R.n.i
            3: R.v = R.R*R.p.i

            # Voltage source
            4: V.v = V.p.v - V.n.v
            5: 0   = V.p.i + V.n.i
            6: V.v = 10

            # Connect equations
            connect(V.p, R.p)
            connect(R.n, V.n)
            ->  7: V.p.v = R.p.v
                8: 0 = V.p.i + R.p.i
                9: R.n.v = V.n.v
            10: 0 = R.n.i + V.n.i

            # Variables:
            1: R.v
            2: R.p.v
            3: R.p.i
            4: R.n.v
            5: R.n.i
            6: V.v
            7: V.p.v
            8: V.p.i
            9: V.n.v
            10: V.n.i
        =#
        G = Vector{Int}[[1, 2, 4],
                        [3, 5],
                        [1, 3],
                        [6, 7, 9],
                        [8, 10],
                        [6],
                        [7, 2],
                        [8, 3],
                        [4, 9],
                        [5, 10]]

        eInt = Int[1,2,4,5,7,8,9,10]

        GcInt = Vector{Int}[[-1, 1, -1],
                            [1,1],
                            [-1,1,-1],
                            [1,1],
                            [-1,1],
                            [1,1],
                            [-1,1],
                            [1,1]]

        Avar = fill(0,10)

        vNames = ["R.v",
                "R.p.v",
                "R.p.i",
                "R.n.v",
                "R.n.i",
                "V.v",
                "V.p.v",
                "V.p.i",
                "V.n.v",
                "V.n.i"]

        name(v) = vNames[v]

        (vEliminated, vProperty, nvArbitrary, redundantEquations) = simplifyLinearIntegerEquations!(G, eInt, GcInt, Avar)

        printSimplifiedLinearIntegerEquations(G, eInt, GcInt, vEliminated, vProperty, nvArbitrary,
                                              redundantEquations, name, printTest=false)

        @test nvArbitrary == 1
        @test vEliminated == [9, 6, 2, 4, 10, 7, 8, 5]
        @test vProperty[vEliminated] == [0, 1, 1, 0, 3, 1, -3, -3]
        @test redundantEquations == [10]
        @test eInt  == [2, 5, 7, 8, 9, 1, 4, 10]
        @test G[eInt] == [Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[]]
        @test GcInt == [Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[]]

    end

    @testset "... Test Voltage source and resistor with ground" begin
        println("\n    --- Test Voltage source and resistor with ground")
        #=
            # Resistor
            1: R.v = R.p.v - R.n.v
            2: 0 = R.p.i + R.n.i
            3: R.v = R.R*R.p.i

            # Voltage source
            4: V.v = V.p.v - V.n.v
            5: 0   = V.p.i + V.n.i
            6: V.v = 10

            # Ground
            7: ground.p.v = 0

            # Connect equations
            connect(V.p, R.p)
            connect(R.n, V.n)
            connect(R.n, ground.p)
            ->   8: V.p.v = R.p.v
                 9: 0 = V.p.i + R.p.i
                10: R.n.v = V.n.v
                11: R.n.v = ground.p.v
                12: 0 = R.n.i + V.n.i + ground.p.i

            # Variables:
            1: R.v
            2: R.p.v
            3: R.p.i
            4: R.n.v
            5: R.n.i
            6: V.v
            7: V.p.v
            8: V.p.i
            9: V.n.v
            10: V.n.i
            11: ground.p.v
            12: ground.p.i
        =#
        G = Vector{Int}[[1, 2, 4],
                        [3, 5],
                        [1, 3],
                        [6, 7, 9],
                        [8, 10],
                        [6],
                        [11],
                        [7, 2],
                        [8, 3],
                        [4, 9],
                        [4,11],
                        [5, 10, 12]]

        eInt = Int[1,2,4,5,7,8,9,10,11,12]

        GcInt = Vector{Int}[[-1, 1, -1],
                            [1,1],
                            [-1,1,-1],
                            [1,1],
                            [1],
                            [-1,1],
                            [1,1],
                            [-1,1],
                            [-1,1],
                            [1,1,1]]

        Avar = fill(0,12)

        vNames = ["R.v",
                "R.p.v",
                "R.p.i",
                "R.n.v",
                "R.n.i",
                "V.v",
                "V.p.v",
                "V.p.i",
                "V.n.v",
                "V.n.i",
                "ground.p.v",
                "ground.p.i"]

        name(v) = vNames[v]

        (vEliminated, vProperty, nvArbitrary, redundantEquations) = simplifyLinearIntegerEquations!(G, eInt, GcInt, Avar)

        printSimplifiedLinearIntegerEquations(G, eInt, GcInt, vEliminated, vProperty, nvArbitrary,
                                              redundantEquations, name, printTest=false)

        @test nvArbitrary == 0
        @test vEliminated == [6, 12, 5, 10, 7, 2, 8, 9, 4, 11]
        @test vProperty[vEliminated] == [1, 0, -3, 3, 1, 1, -3, 0, 0, 0]
        @test redundantEquations == Int64[]
        @test eInt  == [7, 11, 10, 5, 1, 8, 9, 2, 12, 4]
        @test G[eInt] == [Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[]]
        @test GcInt == [Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[]]

    end

    @testset "... Test Voltage source and capacitor without ground" begin
        println("\n    --- Test Voltage source and capacitor without ground")
        #=
            # Capacitor
            1: C.v = C.p.v - C.n.v
            2: 0 = C.p.i + C.n.i
            3: C.C*der(C.v) = C.p.i

            # Voltage source
            4: V.v = V.p.v - V.n.v
            5: 0   = V.p.i + V.n.i
            6: V.v = 10

            # Connect equations
            connect(V.p, C.p)
            connect(C.n, V.n)
            ->  7: V.p.v = C.p.v
                8: 0 = V.p.i + C.p.i
                9: C.n.v = V.n.v
               10: 0 = C.n.i + V.n.i

            # Variables:
             1: C.v
             2: C.p.v
             3: C.p.i
             4: C.n.v
             5: C.n.i
             6: V.v
             7: V.p.v
             8: V.p.i
             9: V.n.v
            10: V.n.i
            11: der(C.v)
        =#
        G = Any[[1, 2, 4],
                [3, 5],
                [11, 3],
                [6, 7, 9],
                [8, 10],
                [6],
                [7, 2],
                [8, 3],
                [4, 9],
                [5, 10]]

        eInt = Int[1,2,4,5,7,8,9,10]

        GcInt = Any[[-1, 1, -1],
                    [1,1],
                    [-1,1,-1],
                    [1,1],
                    [-1,1],
                    [1,1],
                    [-1,1],
                    [1,1]]

        Avar = fill(0,10)
        pushfirst!(Avar,11)

        vNames = ["C.v",
                  "C.p.v",
                  "C.p.i",
                  "C.n.v",
                  "C.n.i",
                  "V.v",
                  "V.p.v",
                  "V.p.i",
                  "V.n.v",
                  "V.n.i",
                  "der(C.v)"]

        name(v) = vNames[v]

        (vEliminated, vProperty, nvArbitrary, redundantEquations) = simplifyLinearIntegerEquations!(G, eInt, GcInt, Avar)

        printSimplifiedLinearIntegerEquations(G, eInt, GcInt, vEliminated, vProperty, nvArbitrary,
                                              redundantEquations, name, printTest=false)

        @test nvArbitrary == 1
        @test vEliminated == [9, 6, 2, 4, 10, 7, 8, 5]
        @test vProperty[vEliminated] == [0, 1, 1, 0, 3, 1, -3, -3]
        @test redundantEquations == [10]
        @test eInt  == [2, 5, 7, 8, 9, 1, 4, 10]
        @test G[eInt] == Any[Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[]]
        @test GcInt == Any[Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[]]
    end

    @testset "... Test two inductances in series with parallel resistors without ground" begin
        println("\n    --- Test two inductances in series with parallel resistors without ground")
        #=
            # Inductor 1
            1: L1.v = L1.p.v - L1.n.v
            2: 0 = L1.p.i + L1.n.i
            3: L1.L*der(L1.p.i) = L1.v

            # Inductor 2
            4: L2.v = L2.p.v - L2.n.v
            5: 0 = L2.p.i + L2.n.i
            6: L2.L*der(L2.p.i) = L2.v

            # Resistor 1
            7: R1.v = R1.p.v - R1.n.v
            8:    0 = R1.p.i + R1.n.i
            9: R1.v = R1.R*R1.p.i

            # Resistor 2
            10: R2.v = R2.p.v - R2.n.v
            11:    0 = R2.p.i + R2.n.i
            12: R2.v = R2.R*R2.p.i

            # Voltage source
            13: V.v = V.p.v - V.n.v
            14: 0   = V.p.i + V.n.i
            15: V.v = 10

            # Connect equations
            connect(V.p, L1.p)
            connect(L1.n, R1.p)
            connect(L1.n, R2.p)
            connect(R1.n, L2.p)
            connect(R2.n, L2.p)
            connect(L2.n, V.n)
            ->  16: V.p.v = L1.p.v
                17: 0 = V.p.i + L1.p.i
                18: L1.n.v = R1.p.v
                19: L1.n.v = R2.p.v
                20: 0 = L1.n.i + R1.p.i + R2.p.i
                21: R1.n.v = L2.p.v
                22: R2.n.v = L2.p.v
                23: 0 = R1.n.i + R2.n.i + L2.p.i
                24: L2.n.v = V.n.v
                25: 0 = L2.n.i + V.n.i

            # Variables:
             1: L1.v
             2: L1.p.v
             3: L1.p.i
             4: L1.n.v
             5: L1.n.i
             6: L2.v
             7: L2.p.v
             8: L2.p.i
             9: L2.n.v
            10: L2.n.i
            11: R1.v
            12: R1.p.v
            13: R1.p.i
            14: R1.n.v
            15: R1.n.i
            16: R2.v
            17: R2.p.v
            18: R2.p.i
            19: R2.n.v
            20: R2.n.i
            21: V.v
            22: V.p.v
            23: V.p.i
            24: V.n.v
            25: V.n.i
            26: der(L1.p.i)
            27: der(L2.p.i)
        =#
        G = [[1, 2, 4],
             [3, 5],
             [26, 1],

             [6, 7, 9],
             [8,10],
             [27, 6],

             [11, 12, 14],
             [13, 15],
             [11, 13],

             [16, 17, 19],
             [18, 20],
             [16, 18],

             [21, 22, 24],
             [23, 25],
             [21],

             [22, 2],
             [23, 3],

             [4, 12],
             [4, 17],
             [5, 13, 18],

             [14, 7],
             [19, 7],
             [15, 20, 8],

             [9, 24],
             [10, 25]]

        eInt = Int[1,2,4,5,7,8,10,11,13,14,16,17,18,19,20,21,22,23,24,25]

        GcInt = [[-1, 1, -1],
                 [1,1],
                 [-1,1,-1],
                 [1,1],
                 [-1,1,-1],
                 [1,1],
                 [-1,1,-1],
                 [1,1],
                 [-1,1,-1],
                 [1,1],
                 [-1,1],
                 [1,1],
                 [-1,1],
                 [-1,1],
                 [1,1,1],
                 [-1,1],
                 [-1,1],
                 [1,1,1],
                 [-1,1],
                 [1,1]]

        Avar = fill(0,27)
        Avar[3] = 26
        Avar[8] = 27

        vNames = ["L1.v",
                  "L1.p.v",
                  "L1.p.i",
                  "L1.n.v",
                  "L1.n.i",
                  "L2.v",
                  "L2.p.v",
                  "L2.p.i",
                  "L2.n.v",
                  "L2.n.i",
                  "R1.v",
                  "R1.p.v",
                  "R1.p.i",
                  "R1.n.v",
                  "R1.n.i",
                  "R2.v",
                  "R2.p.v",
                  "R2.p.i",
                  "R2.n.v",
                  "R2.n.i",
                  "V.v",
                  "V.p.v",
                  "V.p.i",
                  "V.n.v",
                  "V.n.i",
                  "der(L1.p.i)",
                  "der(L2.p.i)"]

        var_name(v) = vNames[v]
        (vEliminated, vProperty, nvArbitrary, redundantEquations) = simplifyLinearIntegerEquations!(G, eInt, GcInt, Avar; log=true, var_name = var_name)

        printSimplifiedLinearIntegerEquations(G, eInt, GcInt, vEliminated, vProperty, nvArbitrary, redundantEquations, var_name, printTest=false)

        @test nvArbitrary == 1
        @test vEliminated == [7, 16, 12, 9, 19, 14, 17, 4, 25, 22, 23, 20, 15, 10, 5, 6]
        @test vProperty[vEliminated] == [0, 11, 11, 24, 0, 0, 11, 11, 3, 2, -3, -18, -13, -8, -3, -24]
        @test redundantEquations == [25]
        @test eInt  == [2, 5, 8, 11, 14, 16, 17, 18, 19, 21, 22, 24, 1, 13, 7, 10, 4, 23, 20, 25]
        @test G[eInt] == [Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], [2, 1, 11], [21, 2, 24], Int64[], Int64[], Int64[], [13, 8, 18], [3, 8], Int64[]]
        @test GcInt == [Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], [1, -1, -1], [-1, 1, -1], Int64[], Int64[], Int64[], [-1, 1, -1], [1, -1], Int64[]]

    end
end

end


using Distributions, PDMats
using Test, LinearAlgebra
import Distributions: ispossemdef

# RealInterval
r = RealInterval(1.5, 4.0)
@test minimum(r) == 1.5
@test maximum(r) == 4.0
@test extrema(r) == (1.5, 4.0)

@test partype(Gamma(1, 2)) == Float64
@test partype(Gamma(1.1, 2)) == Float64
@test partype(Normal(1//1, 2//1)) == Rational{Int}
@test partype(MvNormal(rand(Float32, 5), Matrix{Float32}(I, 5, 5))) == Float32

# special cases
@test partype(Kolmogorov()) == Float64
@test partype(Hypergeometric(2, 2, 2)) == Float64
@test partype(DiscreteUniform(0, 4)) == Float64

A = rand(1:10, 5, 5)
B = rand(Float32, 4)
C = 1//2
L = rand(Float32, 4, 4)
D = PDMats.PDMat(L * L')

# Ensure that utilities functions works with abstract arrays

@test isprobvec(GenericArray([1, 1, 1])) == false
@test isprobvec(GenericArray([1/3, 1/3, 1/3]))

# Positive definite matrix
M = GenericArray([1.0 0.0; 0.0 1.0])
# Non-invertible matrix
N = GenericArray([1.0 0.0; 1.0 0.0])

@test Distributions.isApproxSymmmetric(N) == false
@test Distributions.isApproxSymmmetric(M)

n = 10
areal = randn(n,n)/2
aimg  = randn(n,n)/2
@testset "For A containing $eltya" for eltya in (Float32, Float64, ComplexF32, ComplexF64, Int)
    ainit = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(areal, aimg) : areal)
    @testset "Positive semi-definiteness" begin
        notsymmetric = ainit
        notsquare    = [ainit ainit]
        @test !ispossemdef(notsymmetric)
        @test !ispossemdef(notsquare)
        for truerank in 0:n
            X = ainit[:, 1:truerank]
            A = truerank == 0 ? zeros(eltya, n, n) : X * X'
            @test ispossemdef(A)
            for testrank in 0:n
                if testrank == truerank
                    @test ispossemdef(A, testrank)
                else
                    @test !ispossemdef(A, testrank)
                end
            end
            @test !ispossemdef(notsymmetric, truerank)
            @test !ispossemdef(notsquare, truerank)
            @test_throws ArgumentError ispossemdef(A, -1)
            @test_throws ArgumentError ispossemdef(A, n + 1)
        end
    end
end


@testset "$TEST $G" begin

#### Graphs for testing
graphs = [
  # Graph with 8 vertices
  (8,
   [
     (1,2,10),(1,3,5),(1,4,15),(2,3,4),(2,5,9),
     (2,6,15),(3,4,4),(3,6,8),(4,7,16),(5,6,15),
     (5,8,10),(6,7,15),(6,8,10),(7,3,6),(7,8,10)
   ],
   1,8, # source/target
   3,   # answer for default capacity
   28,  # answer for custom capacity
   15,5 # answer for restricted capacity/restriction
  ),

  # Graph with 6 vertices
  (6,
   [
     (1,2,9),(1,3,9),(2,3,10),(2,4,8),(3,4,1),
     (3,5,3),(5,4,8),(4,6,10),(5,6,7)
   ],
   1,6, # source/target
   2,   # answer for default capacity
   12,  # answer for custom capacity
   8,5  # answer for restricted capacity/restriction
  )
]

for (nvertices,flow_edges,s,t,fdefault,fcustom,frestrict,caprestrict) in graphs
    flow_graph = DG(nvertices)
    capacity_matrix = zeros(Int,nvertices,nvertices)
    for e in flow_edges
        u,v,f = e
        add_edge!(flow_graph,u,v)
        capacity_matrix[u,v] = f
    end

    # Test DefaultCapacity
    d = Erdos.DefaultCapacity(flow_graph)
    @test typeof(d) <: AbstractMatrix{typeof(signed(V(0)))}
    @test d[s,t] == 0
    @test size(d) == (nvertices,nvertices)

    fdef1, Fdef1, labdef1 = maximum_flow(flow_graph,s,t)
    fdef2, Fdef2, labdef2 = maximum_flow(flow_graph,s,t, capacity_matrix)
    # Test all algorithms
    for ALGO in [EdmondsKarpAlgorithm, DinicAlgorithm, BoykovKolmogorovAlgorithm, PushRelabelAlgorithm]
        f, F, lab = maximum_flow(flow_graph,s,t,algorithm=ALGO())
        @test f == fdefault
        @test lab == labdef1

        f, F, lab = maximum_flow(flow_graph,s,t,capacity_matrix,algorithm=ALGO())
        @test f == fcustom
        @test lab == labdef2

        @test maximum_flow(flow_graph,s,t,capacity_matrix,algorithm=ALGO(),restriction=caprestrict)[1] == frestrict
    end
end

n = 30
s = n+1
t = n+2
g = random_regular_digraph(n, 3, DG)
c = spzeros(n+2,n+2)
for e in edges(g)
    c[src(e), dst(e)] = rand()
end
add_vertices!(g, 2)
for i=1:10
    j = rand(1:n)
    add_edge!(g, s, j)
    c[s, j] = rand()

    j = rand(1:n)
    add_edge!(g, j, t)
    c[j, t] = rand()
end

fdef, Fdef, labdef = maximum_flow(g,s,t)
fcustom, Fcustom, labcustom = maximum_flow(g,s,t, c)

enedef, cutdef, labd = minimum_cut(g,s,t)
enecustom, cutcustom, labc = minimum_cut(g,s,t, c)
@test enedef ≈ fdef
@test enecustom ≈ fcustom
@test labd == labdef
@test labc == labcustom
for e in cutcustom
    @test has_edge(g, e)
    @test labc[src(e)] == labc[s]
    @test labc[dst(e)] == labc[t]
end
for e in cutdef
    @test has_edge(g, e)
    @test labd[src(e)] == labd[s]
    @test labd[dst(e)] == labd[t]
end
for ALGO in [EdmondsKarpAlgorithm, DinicAlgorithm, BoykovKolmogorovAlgorithm, PushRelabelAlgorithm]
    f, F, lab = maximum_flow(g,s,t,algorithm=ALGO())
    @test f ≈ fdef
    @test lab == labdef
    f, F, lab = maximum_flow(g,s,t,c,algorithm=ALGO())
    @test f ≈ fcustom
    @test lab == labcustom
end

end # testset


function bubble_sort(a::Vector{Float64})
    n = length(a)
    for i = 1:n
        for j = 1:n-1
            if(a[j] < a[j+1])
                a[j], a[j+1] = a[j+1], a[j]
            end
        end
    end
end

function main()
    a = [1., 3, 2, 4, 5, 10, 50, 7, 1.5, 0.3]
    bubble_sort(a)
    println(a)
end

main()


@inline as_to_rad(x) = deg2rad(x / 3600.0)
@inline as_to_rad_mod(x) = deg2rad(rem(x, 1296000.0) / 3600.0)
@inline rad_to_as(x) = rad2deg(x) * 3600.0

@inline μas_to_rad(x) = deg2rad(x / 3600e6)
@inline rad_to_μas(x) = rad2deg(x) * 3600e6

@inline jc(ep) = ((julian1(ep) - J2000) + julian2(ep)) / DAYS_PER_CENTURY

function fundamental_arguments(tdb_jc)

    # Mean Anomaly of the Moon (l, F1)
    M_m  = as_to_rad( @evalpoly(tdb_jc,   485_868.249_036, 1_717_915_923.217_8,  31.879_2,  0.051_635, -0.000_244_70) )

    # Mean Anomaly of the Sun (l', F2)
    M_s  = as_to_rad( @evalpoly(tdb_jc, 1_287_104.793_048,   129_596_581.048_1,  -0.553_2,  0.000_136, -0.000_011_49) )

    # Mean Argument of Latitude of the Moon (F, F3)
    u_sm = as_to_rad( @evalpoly(tdb_jc,   335_779.526_232, 1_739_527_262.847_8, -12.751_2, -0.001_037,  0.000_004_17) )

    # Mean Elongation of the Moon from the Sun (D, F4)
    D_s  = as_to_rad( @evalpoly(tdb_jc, 1_072_260.703_692, 1_602_961_601.209_0,  -6.370_6,  0.006_593, -0.000_031_69) )

    # Mean Longitude of the ascending Node of the Moon (Ω, F5)
    Ω_m  = as_to_rad( @evalpoly(tdb_jc,   450_160.398_036,    -6_962_890.543_1,   7.472_2,  0.007_702, -0.000_059_39) )

    # Mean Heliocentric Longitudes of the Planets
    # Souchay et al. (1999)
    λ_M1 = @evalpoly(tdb_jc, 4.402_608_842, 2608.790_314_157_4) # Mercury
    λ_M2 = @evalpoly(tdb_jc, 3.176_146_697, 1021.328_554_621_1) # Venus
    λ_M3 = @evalpoly(tdb_jc, 1.753_470_314,  628.307_584_999_1) # Earth
    λ_M4 = @evalpoly(tdb_jc, 6.203_480_913,  334.061_242_670_0) # Mars
    λ_M5 = @evalpoly(tdb_jc, 0.599_546_497,   52.969_096_264_1) # Jupiter
    λ_M6 = @evalpoly(tdb_jc, 0.874_016_757,   21.329_910_496_0) # Saturn
    λ_M7 = @evalpoly(tdb_jc, 5.481_293_872,    7.478_159_856_7) # Uranus
    λ_M8 = @evalpoly(tdb_jc, 5.311_886_287,    3.813_303_563_8) # Neptune

    # General Precession
    # Kinoshita and Souchay (1990)
    p_λ  = @evalpoly(tdb_jc, 0.0, 0.024_381_75, 0.000_005_386_91)

    return SVector(M_m, M_s, u_sm, D_s, Ω_m, λ_M1, λ_M2, λ_M3, λ_M4, λ_M5, λ_M6, λ_M7, λ_M8, p_λ)
end


Random.seed!(7103)
rng = MersenneTwister(123)

@test_throws DomainError CompleteRandomisation([-0.5, 0.5])

CR1 = CompleteRandomisation([1, 1])
@test narms(CR1) == 2
@test target(CR1) == [0.5, 0.5]
@test sequence(CR1) == zeros(0)
@test dist(CR1) == zeros(narms(CR1))
@test imbalance(CR1) == 0.0
@test predictability(CR1) == 0.0
@test prob(CR1) == target(CR1)

# Check that randomise does not alter state
res = randomise(rng, CR1)
@test narms(CR1) == 2
@test target(CR1) == [0.5, 0.5]
@test sequence(CR1) == zeros(0)
@test dist(CR1) == zeros(narms(CR1))
@test imbalance(CR1) == 0.0
@test predictability(CR1) == 0.0

# Check that randomise! does alter state
res1 = randomise!(rng, CR1)
@test narms(CR1) == 2
@test target(CR1) == [0.5, 0.5]
@test sequence(CR1) == [res1]
@test imbalance(CR1) == euclidean(target(CR1), [0, 1])
@test dist(CR1) == Int.([x == res1 for x in 1:2])

res2 = randomise!(rng, CR1, 10)
@test length(res2) == 10


# _sequential_tasks_param
concept_sequential_tasks(x) = x[1] ≤ x[2] || x[3] ≤ x[4]

"""
    sum_equal_param
Constraint ensuring that the start and completion times of two tasks are not intersecting: `x[1] ≤ x[2] || x[3] ≤ x[4]`.
"""
const sequential_tasks = Constraint(
    args_length = 4,
    concept = concept_sequential_tasks,
    error = make_error(:sequential_tasks),
)


using Test
using Symbolics, StructArrays
using DifferentiableStateSpaceModels: order_vector_by_symbols, make_substitutions

const ∞ = Inf
@variables α, β, ρ, δ, σ
@variables t::Integer, k(..), z(..), c(..), q(..)
x = [k, z]
y = [c, q]
p = [α, β, ρ, δ, σ]
subs_x = StructArray(make_substitutions.(t, x))
subs_y = StructArray(make_substitutions.(t, y))
subs = vcat(subs_x, subs_y)
p_symbols = Symbol.(p)

p_val = (α = 0.1, β = 0.5, ρ = 0.1, δ = 1.9, σ= 1.9)
p_val_2 = (ρ = 0.1, α = 0.1, σ= 1.9, β = 0.5, δ = 1.9)
p_vec = order_vector_by_symbols(p_val, p_symbols)
p_vec_2 = order_vector_by_symbols(p_val_2, p_symbols)
@test p_vec ≈ p_vec_2


using Kalman, Base.Test

x0 = State([1.,0],[0.5 0; 0 1e-10])
dt = 0.01
a = [1 dt; dt*(-1) 1]
g = [0 0; 0 1]
q = [1e-10 0; 0 1e-10]
f = LinearModel(a,g,q)
h = [1 0]
r = [0.5]'
z = LinearObservationModel(h,r)

t = [0:dt:10]
x = zeros(length(t))
m = cos(t)

kf0 = BasicKalmanFilter(x0,f,z)

kf = kf0

kf1 = deepcopy(kf)

predict!(kf)
predict!(kf1)

@test kf1.x == kf.x

update!(kf,Observation([0.95]))
update!(kf1,Nullable([0.95]))

@test kf1.x == kf.x

predict!(kf)
predict!(kf1)

update!(kf,Observation([0.98]))
update!(kf1,Nullable{Vector{Float64}}())

@test kf1.x != kf.x

update!(kf1,Nullable([0.98]))

@test kf1.x == kf.x

## Extended Kalman Filter with Nullables

k = 1.0
p0 = 0.1*k
r = 10.0
dt = 0.0005

logit(p0,k,x) = (k*p0*exp(x))/(k+p0*(exp(x)-1))

function an(x,k,r,deltaT)
    x1 = zeros(x)
    x1[1] = x[1]
    x1[2] = logit(x[2],k,r*deltaT)
    return x1
end

fekf(x) = an(x,k,x[1],dt)
gekf(x) = eye(2)
hekf(x) = x[2]

Q = [1e-10 0.0; 0.0 1e-10]
R = [1e-2]'

x0 = State([5.0,0.1],[1e2 0.0; 0.0 1e-10])

ekff = NonlinearModel(fekf,gekf,Q)
z = NonlinearObservationModel(hekf,R)

kf0 = BasicExtendedKalmanFilter(x0,ekff,z)
kf = kf0

kf1 = deepcopy(kf)

predict!(kf)
predict!(kf1)

@test kf1.x == kf.x

update!(kf,Observation([0.1]))
update!(kf1,Nullable([0.1]))

@test kf1.x == kf.x

predict!(kf)
predict!(kf1)

update!(kf,Observation([0.09]))
update!(kf1,Nullable{Vector{Float64}}())

@test kf1.x != kf.x

update!(kf1,Nullable([0.09]))

@test kf1.x == kf.x


using Test, GpABC, DelimitedFiles, Statistics, LinearAlgebra
import GpABC.covariance, GpABC.get_hyperparameters_size

struct NoGradKernel <: AbstractGPKernel
    rbf_iso_kernel::GpABC.SquaredExponentialIsoKernel
end

function NoGradKernel()
    NoGradKernel(GpABC.SquaredExponentialIsoKernel())
end

function get_hyperparameters_size(ker::NoGradKernel, x::AbstractArray{Float64, 2})
    get_hyperparameters_size(ker.rbf_iso_kernel, x)
end

function covariance(ker::NoGradKernel, log_theta::AbstractArray{Float64, 1},
    x1::AbstractArray{Float64, 2}, x2::AbstractArray{Float64, 2})
    covariance(ker.rbf_iso_kernel, log_theta, x1, x2)
end

@testset "GP Reression Tests" begin
    training_x = readdlm("$(@__DIR__)/training_x1.csv")
    training_y = readdlm("$(@__DIR__)/training_y1.csv")
    test_x = readdlm("$(@__DIR__)/test_x1.csv")

    gpem = GPModel(training_x, training_y, test_x)

    theta_mle = gp_train(gpem)
    @test [58.7668, 2.94946, 5.84869] ≈ theta_mle rtol=1e-5
    @test -183.80873007668373 ≈ gp_loglikelihood(gpem)
    test_grad = gp_loglikelihood_grad(log.(gpem.gp_hyperparameters), gpem)
    @test all(abs.(test_grad - [46540815059614715e-22, 6451822871511581e-22, 20565826801544063e-22]) .< 1e-5)

    regression_mean = readdlm("$(@__DIR__)/test_y1_mean.csv")
    regression_var = readdlm("$(@__DIR__)/test_y1_var.csv")
    (r_mean, r_var) = gp_regression(gpem, batch_size=10)
    @test r_mean ≈ regression_mean
    @test r_var ≈ regression_var rtol=1e-3

    regression_cov = readdlm("$(@__DIR__)/test_y1_cov.csv", ',')
    (r_mean, r_cov) = gp_regression(gpem, batch_size=10, full_covariance_matrix=true)
    @test r_cov ≈ regression_cov rtol=1e-3

    gpem = GPModel(training_x, training_y, NoGradKernel())
    theta_mle = gp_train(gpem)
    @test [58.7668, 2.94946, 5.84869] ≈ theta_mle rtol=1e-3

    # Test that 1-d inputs work
    training_x = reshape(training_x, size(training_x, 1))
    training_y = reshape(training_y, size(training_y, 1))
    gpem = GPModel(training_x, training_y)
    theta_mle = gp_train(gpem)

end

function posterior_test(gp_mean, gp_var, test_x, gpem, n_samples_seq, full_cov_matrix=true, n_repeats = 5)

	norm_mean_diff = zeros(size(n_samples_seq,1), n_repeats)
	norm_var_diff = zeros(size(n_samples_seq,1), n_repeats)

	for (i, n_samples) in enumerate(n_samples_seq)
		for j in 1:n_repeats
			posterior_samples = gp_regression_sample(test_x, n_samples, gpem, full_cov_matrix)
			norm_mean_diff[i,j] = norm(gp_mean-mean(posterior_samples, dims=2))
			# norm_var_diff[i,j] = norm(gp_var-var(posterior_samples, dims=2, mean=gp_mean))
		end
	end

	# The norm between the empirical results and gp_regression result
	# should decrease monotonically as the number of samples increases
	issorted(mean(norm_mean_diff, dims=2), rev=true)
		# && issorted(mean(norm_var_diff, dims=2), rev=true) NB this is not true in the general case
end

@testset "GP Regression Sampler Tests" begin

    training_x = readdlm("$(@__DIR__)/training_x1.csv")
    training_y = readdlm("$(@__DIR__)/training_y1.csv")
    test_x = readdlm("$(@__DIR__)/test_x1.csv")
    n_samples = 10000

    gpem = GPModel(training_x, training_y, test_x)

    gp_mean, gp_var = gp_regression(gpem)
    posterior_samples = gp_regression_sample(test_x, n_samples, gpem)

    # Check shapes
    @test size(posterior_samples,1)==size(test_x,1)
    @test size(posterior_samples,2)==n_samples

    posterior_sample = gp_regression_sample(test_x, 1, gpem)
    @test size(posterior_sample)==(size(test_x,1),)

    n_samples_seq = [1000, 3000, 10000, 30000, 100000, 300000]
    @test posterior_test(gp_mean, gp_var, test_x, gpem, n_samples_seq)

    # test without full covariance matrix
    @test posterior_test(gp_mean, gp_var, test_x, gpem, n_samples_seq, false)

end


### A Pluto.jl notebook ###
# v0.14.7

using Markdown
using InteractiveUtils

# ╔═╡ b9dae3f2-5db7-4465-9c1e-f95df29fd48c
function ingredients(path::String)
	# this is from the Julia source code (evalfile in base/loading.jl)
	# but with the modification that it returns the module instead of the last object
	name = Symbol(basename(path))
	m = Module(name)
	Core.eval(m,
        Expr(:toplevel,
             :(eval(x) = $(Expr(:core, :eval))($name, x)),
             :(include(x) = $(Expr(:top, :include))($name, x)),
             :(include(mapexpr::Function, x) = $(Expr(:top, :include))(mapexpr, $name, x)),
             :(include($path))))
	m
end

# ╔═╡ db715d40-c599-11eb-0b28-f5c7b076c6ab
SVM = ingredients("./script.jl").SVM

# ╔═╡ 27defb3d-f5fb-4334-a441-50f7a347819d
import Plots

# ╔═╡ fc31cf17-5e20-4130-b02c-9c2bbf9d3b72
import Random

# ╔═╡ cc3033c8-4c74-4d28-ad2c-344246bbc827
JuTools = ingredients("../tools.jl").JuTools

# ╔═╡ 3c0bdafc-b98d-47c8-8b9f-69a7b94c5d85
begin
	X_data1, Y_data1 = JuTools.data_generate_cluster_2d(pos1=(20.0, 20.0), pos2=(50.0, 50.0), radius1=5.0, radius2=5.0, random_scale=8.0, data_size=1000)
	Y_data1 .= Y_data1 .* 2.0 .- 1.0;
end

# ╔═╡ b4f64a60-b8a7-4487-8897-6bd4efa37c76
size(X_data1)

# ╔═╡ 71ed19eb-602f-4b21-a874-3c0cd6ef19d7
size(Y_data1)

# ╔═╡ de69ceeb-c9fb-4038-bf3d-2c08430e973c
begin
	plot_X1 = [X_data1[i, 1] for i in 1:(size(X_data1)[1]) if Y_data1[i] == -1.0]
	plot_Y1 = [X_data1[i, 2] for i in 1:(size(X_data1)[1]) if Y_data1[i] == -1.0]
	plot_X2 = [X_data1[i, 1] for i in 1:(size(X_data1)[1]) if Y_data1[i] == 1.0]
	plot_Y2 = [X_data1[i, 2] for i in 1:(size(X_data1)[1]) if Y_data1[i] == 1.0]
end

# ╔═╡ e18d49b7-3ef2-4d5e-ae80-83af5836277f
let
	Plots.gr()
	p = Plots.scatter(plot_X1, plot_Y1, leg=false, c="red", background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(p, plot_X2, plot_Y2, leg=false, c="blue")
	Plots.xlims!(p, (0.0, 70.0))
	Plots.ylims!(p, (0.0, 70.0))
	p
end

# ╔═╡ 43aff583-73af-482a-82be-f3823fd40799
md"""
### Linear SVM
"""

# ╔═╡ 6e7a9a46-e0da-442d-8f45-6b5089f6d953
let
	weights = SVM.train_linear(X_data1, Y_data1, 10.0, learning_rate=0.05, max_iter=10, random_weights=false)
	JuTools.compute_accuracy(SVM.predict(X_data1, weights), Y_data1)
end

# ╔═╡ 394fb389-532f-41a7-a387-22d86e1c8e3e
let
	weights = SVM.train_linear(X_data1, Y_data1, 10.0, learning_rate=0.05, max_iter=100, random_weights=false)
	JuTools.compute_accuracy(SVM.predict(X_data1, weights), Y_data1)
end

# ╔═╡ 7ab6dbb1-194b-4816-8ad6-52abe6a7a80f
let
	weights = SVM.train_linear(X_data1, Y_data1, 10.0, learning_rate=0.05, max_iter=1000, random_weights=false)
	JuTools.compute_accuracy(SVM.predict(X_data1, weights), Y_data1)
end

# ╔═╡ 3bdb1ccd-18fe-4c91-ab1d-2e149de06097
let
	weights = SVM.train_linear(X_data1, Y_data1, 10.0, learning_rate=0.05, max_iter=1000, random_weights=false)
	X_test_1 = 0:0.1:70
	X_test_2 = 0:0.1:70
	f_test1(X1, X2) = begin
   		SVM.predict([X1, X2], weights)[1]
	end
	Plots.gr()
	Plots.contour(X_test_1, X_test_2, f_test1, leg=false, fill=true, c=Plots.cgrad(:matter, rev=true, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	Plots.xlims!((0.0, 70.0))
	Plots.ylims!((0.0, 70.0))
end

# ╔═╡ fcc3d16f-1545-40ca-b87d-df959fb25ed4
let
	weights = SVM.train_linear(X_data1, Y_data1, 10.0, learning_rate=0.05, max_iter=1000, random_weights=false)
	X_test_1 = 0:0.1:70
	X_test_2 = 0:0.1:70
	f_test2(X1, X2) = begin
   		abs(SVM.predict_proba([X1, X2], weights)[1])
	end
	Plots.gr()
	Plots.contour(X_test_1, X_test_2, f_test2, leg=false, fill=true, c=Plots.cgrad(:matter, rev=false, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	plot_X3 = collect(0:70)
	plot_Y3 = -(weights.b .+ (weights.w[1] .* plot_X3)) ./ weights.w[2]
	Plots.plot!(plot_X3, plot_Y3, c="purple", linewidth=5, alpha=0.5)
	Plots.xlims!((0.0, 70.0))
	Plots.ylims!((0.0, 70.0))
end

# ╔═╡ 869aacb1-74bd-4b8c-99f9-416732c4e783
begin
	# try with different data
	X_data2, Y_data2 = JuTools.data_generate_cluster_2d(pos1=(30.0, 80.0), pos2=(80.0, 30.0),
	    radius1=5.0, radius2=10.0, random_scale=8.0, data_size=1000)
	Y_data2 .= Y_data2 .* 2.0 .- 1.0
end

# ╔═╡ 76d280c5-3838-409c-b016-b4311ba6f071
let
	weights = SVM.train_linear(X_data2, Y_data2, 10.0, learning_rate=0.05, max_iter=1000, random_weights=false)
	JuTools.compute_accuracy(SVM.predict(X_data2, weights), Y_data2)
end

# ╔═╡ 9c94a40f-2dd0-4222-b7fd-4cb83fda0080
let
	weights = SVM.train_linear(X_data2, Y_data2, 10.0, learning_rate=0.05, max_iter=1000, random_weights=false)
	X_test_1 = 0:120
	X_test_2 = 0:120
	f_test1(X1, X2) = begin
   		SVM.predict([X1, X2], weights)[1]
	end
	plot_X1 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_Y1 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_X2 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	plot_Y2 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	Plots.gr()
	Plots.contour(X_test_1, X_test_2, f_test1, leg=false, fill=true, c=Plots.cgrad(:matter, rev=true, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	Plots.xlims!((0.0, 120.0))
	Plots.ylims!((0.0, 120.0))
end

# ╔═╡ e1788d22-2d0d-4bfd-962e-500d5ba67eab
let
	weights = SVM.train_linear(X_data2, Y_data2, 10.0, learning_rate=0.05, max_iter=1000, random_weights=false)
	X_test_1 = 0:120
	X_test_2 = 0:120
	f_test2(X1, X2) = begin
   		abs(SVM.predict_proba([X1, X2], weights)[1])
	end
	plot_X1 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_Y1 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_X2 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	plot_Y2 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	Plots.gr()
	Plots.contour(X_test_1, X_test_2, f_test2, leg=false, fill=true, c=Plots.cgrad(:matter, rev=false, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	plot_X3 = collect(0:120)
	plot_Y3 = -(weights.b .+ (weights.w[1] .* plot_X3)) ./ weights.w[2]
	Plots.plot!(plot_X3, plot_Y3, c="purple", linewidth=5, alpha=0.5)
	Plots.xlims!((0.0, 120.0))
	Plots.ylims!((0.0, 120.0))
end

# ╔═╡ 5e6de600-114c-495f-80a6-494565971fe2
begin
	weights_linear = SVM.train(X_data2, Y_data2, 1.0, kernel="linear", gamma="scale", coef=0.0, degree=2.0, verbose=false)
	string("Linear Kernel Accuracy: ", JuTools.compute_accuracy(SVM.predict(X_data2, X_data2, Y_data2, weights_linear), Y_data2))
end

# ╔═╡ b53fea06-c0bf-4cab-99fc-394a2f6ca0bd
begin
	weights_rbf = SVM.train(X_data2, Y_data2, 1.0, kernel="rbf", gamma="scale", coef=0.0, degree=2.0, verbose=false)
	string("RBF Kernel Accuracy: ", JuTools.compute_accuracy(SVM.predict(X_data2, X_data2, Y_data2, weights_rbf), Y_data2))
end

# ╔═╡ 82f44cd6-c1a4-4ad6-9d6c-484e4909aedc
begin
	weights_poly = SVM.train(X_data2, Y_data2, 1.0, kernel="polynomial", gamma="scale", coef=0.0, degree=2.0, verbose=false)
	string("Polynomial Kernel (degree=2) Accuracy: ", JuTools.compute_accuracy(SVM.predict(X_data2, X_data2, Y_data2, weights_poly), Y_data2))
end

# ╔═╡ af2ec3ff-3468-4954-b5b7-b35ee45f7833
begin
	weights_poly_3 = SVM.train(X_data2, Y_data2, 1.0, kernel="polynomial", gamma="scale", coef=0.0, degree=3.0, verbose=false)
	string("Polynomial Kernel (degree=3) Accuracy: ", JuTools.compute_accuracy(SVM.predict(X_data2, X_data2, Y_data2, weights_poly_3), Y_data2))
end

# ╔═╡ d0bd3eb0-b6ea-47a9-959e-e385e033cd49
begin
	weights_sigmoid = SVM.train(X_data2, Y_data2, 10.0, kernel="sigmoid", gamma="scale", coef=0.0, degree=2.0, verbose=false)
	string("Sigmoid Kernel Accuracy: ", JuTools.compute_accuracy(SVM.predict(X_data2, X_data2, Y_data2, weights_sigmoid), Y_data2))
end

# ╔═╡ 1390261d-f5e1-4b55-9865-78450a944885
let
	# plot the data
	X_test_1 = 0:120
	X_test_2 = 0:120
	plot_X1 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_Y1 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_X2 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	plot_Y2 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]

	X_test_linear = hcat(vec(repeat(reshape(X_test_1, :, 1), 1, 121)'), vec(repeat(X_test_2, 121, 1)))
	X_test_linear = abs.(reshape(SVM.predict_proba(X_test_linear, X_data2, Y_data2, weights_linear), 121, :))

	Plots.gr()
	plot_linear = Plots.contour(X_test_1, X_test_2, X_test_linear, fill=true, c=Plots.cgrad(:matter, rev=false, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	Plots.xlims!((0.0, 120.0))
	Plots.ylims!((0.0, 120.0))
	Plots.title!("SVM (Linear Kernel)")
end

# ╔═╡ dfde4be0-4d84-42b7-96eb-9cfa75976947
let
	# plot the data
	X_test_1 = 0:120
	X_test_2 = 0:120
	plot_X1 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_Y1 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_X2 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	plot_Y2 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]

	X_test_rbf = hcat(vec(repeat(reshape(X_test_1, :, 1), 1, 121)'), vec(repeat(X_test_2, 121, 1)))
X_test_rbf = abs.(reshape(SVM.predict_proba(X_test_rbf, X_data2, Y_data2, weights_rbf), 121, :))

	Plots.gr()
	plot_linear = Plots.contour(X_test_1, X_test_2, X_test_rbf, fill=true, c=Plots.cgrad(:matter, rev=false, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	Plots.xlims!((0.0, 120.0))
	Plots.ylims!((0.0, 120.0))
	Plots.title!("SVM (RBF Kernel)")
end

# ╔═╡ d2a3a80f-5096-440c-9cdd-bd6aa80d81f5
let
	# plot the data
	X_test_1 = 0:120
	X_test_2 = 0:120
	plot_X1 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_Y1 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_X2 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	plot_Y2 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]

	X_test_sigmoid = hcat(vec(repeat(reshape(X_test_1, :, 1), 1, 121)'), vec(repeat(X_test_2, 121, 1)))
X_test_sigmoid = abs.(reshape(SVM.predict_proba(X_test_sigmoid, X_data2, Y_data2, weights_sigmoid), 121, :))

	Plots.gr()
	plot_linear = Plots.contour(X_test_1, X_test_2, X_test_sigmoid, fill=true, c=Plots.cgrad(:matter, rev=false, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	Plots.xlims!((0.0, 120.0))
	Plots.ylims!((0.0, 120.0))
	Plots.title!("SVM (Sigmoid Kernel)")
end

# ╔═╡ f45c2f60-45e9-48f4-b81d-ea7111111f5c
let
	# plot the data
	X_test_1 = 0:120
	X_test_2 = 0:120
	plot_X1 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_Y1 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_X2 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	plot_Y2 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]

	X_test_poly = hcat(vec(repeat(reshape(X_test_1, :, 1), 1, 121)'), vec(repeat(X_test_2, 121, 1)))
X_test_poly = abs.(reshape(SVM.predict_proba(X_test_poly, X_data2, Y_data2, weights_poly), 121, :))

	Plots.gr()
	plot_linear = Plots.contour(X_test_1, X_test_2, X_test_poly, fill=true, c=Plots.cgrad(:matter, rev=false, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	Plots.xlims!((0.0, 120.0))
	Plots.ylims!((0.0, 120.0))
	Plots.title!("SVM (Polynomial Kernel, degree=2)")
end

# ╔═╡ ad5bb232-ed6b-416c-8472-a15a25c65b5b
let
	# plot the data
	X_test_1 = 0:120
	X_test_2 = 0:120
	plot_X1 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_Y1 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == -1.0]
	plot_X2 = [X_data2[i, 1] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]
	plot_Y2 = [X_data2[i, 2] for i in 1:(size(X_data2)[1]) if Y_data2[i] == 1.0]

	X_test_poly = hcat(vec(repeat(reshape(X_test_1, :, 1), 1, 121)'), vec(repeat(X_test_2, 121, 1)))
X_test_poly = abs.(reshape(SVM.predict_proba(X_test_poly, X_data2, Y_data2, weights_poly_3), 121, :))

	Plots.gr()
	plot_linear = Plots.contour(X_test_1, X_test_2, X_test_poly, fill=true, c=Plots.cgrad(:matter, rev=false, categorical=false), linewidth=0, background_color=Plots.RGB(0.2, 0.2, 0.2))
	Plots.scatter!(plot_X1, plot_Y1, leg=false, c="red")
	Plots.scatter!(plot_X2, plot_Y2, leg=false, c="blue")
	Plots.xlims!((0.0, 120.0))
	Plots.ylims!((0.0, 120.0))
	Plots.title!("SVM (Polynomial Kernel, degree=3)")
end

# ╔═╡ Cell order:
# ╟─b9dae3f2-5db7-4465-9c1e-f95df29fd48c
# ╠═db715d40-c599-11eb-0b28-f5c7b076c6ab
# ╠═27defb3d-f5fb-4334-a441-50f7a347819d
# ╠═fc31cf17-5e20-4130-b02c-9c2bbf9d3b72
# ╠═cc3033c8-4c74-4d28-ad2c-344246bbc827
# ╠═3c0bdafc-b98d-47c8-8b9f-69a7b94c5d85
# ╠═b4f64a60-b8a7-4487-8897-6bd4efa37c76
# ╠═71ed19eb-602f-4b21-a874-3c0cd6ef19d7
# ╠═de69ceeb-c9fb-4038-bf3d-2c08430e973c
# ╠═e18d49b7-3ef2-4d5e-ae80-83af5836277f
# ╟─43aff583-73af-482a-82be-f3823fd40799
# ╠═6e7a9a46-e0da-442d-8f45-6b5089f6d953
# ╠═394fb389-532f-41a7-a387-22d86e1c8e3e
# ╠═7ab6dbb1-194b-4816-8ad6-52abe6a7a80f
# ╠═3bdb1ccd-18fe-4c91-ab1d-2e149de06097
# ╠═fcc3d16f-1545-40ca-b87d-df959fb25ed4
# ╠═869aacb1-74bd-4b8c-99f9-416732c4e783
# ╠═76d280c5-3838-409c-b016-b4311ba6f071
# ╠═9c94a40f-2dd0-4222-b7fd-4cb83fda0080
# ╠═e1788d22-2d0d-4bfd-962e-500d5ba67eab
# ╠═5e6de600-114c-495f-80a6-494565971fe2
# ╠═b53fea06-c0bf-4cab-99fc-394a2f6ca0bd
# ╠═82f44cd6-c1a4-4ad6-9d6c-484e4909aedc
# ╠═af2ec3ff-3468-4954-b5b7-b35ee45f7833
# ╠═d0bd3eb0-b6ea-47a9-959e-e385e033cd49
# ╠═1390261d-f5e1-4b55-9865-78450a944885
# ╠═dfde4be0-4d84-42b7-96eb-9cfa75976947
# ╠═d2a3a80f-5096-440c-9cdd-bd6aa80d81f5
# ╠═f45c2f60-45e9-48f4-b81d-ea7111111f5c
# ╠═ad5bb232-ed6b-416c-8472-a15a25c65b5b


mutable struct Parameters{I, F}
      H::F            # meters               Physical
     Ly::F            # meters
     Lϕ::F            # radians
   ϕmin::F            # radials
   ϕmid::F            # radians
   ϕmax::F            # radials
      a::F            # meters

   TwoΩ::F            # 1/second             Coriolis
     f₀::F            # 1/second

      g::F            # meters/second^2      Buoyancy

      N::I            # Integer              Numerical

     Lj::F            # meters               Jet (specify profile)
     Uj::F            # meters/second

     dk::F            # 1/meters             Wavenumbers
   kmin::F            # 1/metres
   kmax::F            # 1/meters

     Fr::F            # Nondimensional       Nondimensional
     Ro::F            # Nondimensional
end

"""

Cartesian version of parameters function.

"""
function Parameters(geometry::Cartesian;
     H = 1,
    Ly = 20,
    Lϕ = nothing,
  ϕmin = nothing,
  ϕmid = nothing,
  ϕmax = nothing,
     a = nothing,

  TwoΩ = nothing,
    f₀ = 1,

     g = 9.81,

     N = 100,

    Lj = 1,
    Uj = 0.1,

    dk = 1e-1,
  kmin = dk,
  kmax = 2,

    Fr = 0.0,
    Ro = 0.0
    )
    return Parameters(H, Ly, f₀, g, N, Lj, Uj, N, dk, kmin, kmax, Fr, Ro)
end

"""

Spherical version of parameters function.

"""
function Parameters(geometry::Spherical;
     H = 1,
    Ly = nothing,
    Lϕ =  π/6,
  ϕmin =  π/8,
  ϕmid =  π/4,
  ϕmax = 3π/8,
     a = 1,

  TwoΩ = 1,
    f₀ = nothing,

     g = 9.81,

     N = 100,

    Lj = 1,
    Uj = 0.1,

    dk = 1e-1,
  kmin = dk,
  kmax = 2,

    Fr = 0.0,
    Ro = 0.0
  )
  return Parameters(H, Lϕ, ϕmin, ϕmid, ϕmax, a, TwoΩ, g, N, Lj, Uj, dk, kmin, kmax, Fr, Ro)
end


# # Two dimensional turbulence example
#
# In this example, we initialize a random velocity field and observe its viscous,
# turbulent decay in a two-dimensional domain. This example demonstrates:
#
#   * How to run a model with no buoyancy equation or tracers;
#   * How to create user-defined fields
#   * How to use differentiation functions
#
# For this example, we need `PyPlot` for plotting and `Statistics` for setting up
# a random initial condition with zero mean velocity.

using Oceananigans, Oceananigans.AbstractOperations, PyPlot, Statistics

# In addition to importing plotting and statistics packages, we import
# some types from `Oceananigans` that will aid in the calculation
# and visualization of voriticty.

using Oceananigans: Face, Cell

# `Face` and `Cell` represent "locations" on the staggered grid. We instantiate the
# model with a simple isotropic diffusivity.

model = Model(
        grid = RegularCartesianGrid(size=(128, 128, 1), length=(2π, 2π, 2π)),
    buoyancy = nothing,
     tracers = nothing,
     closure = ConstantIsotropicDiffusivity(ν=1e-3, κ=1e-3)
)

# Our initial condition randomizes `u` and `v`. We also ensure that both have
# zero mean for purely aesthetic reasons.

u₀ = rand(size(model.grid)...)
u₀ .-= mean(u₀)

set!(model, u=u₀, v=u₀)

# Next we create an object called an `Operation` that represents a vorticity calculation.
# We'll use this object to calculate vorticity on-line as the simulation progresses.

u, v, w = model.velocities

# Create an object that represents the 'operation' required to compute vorticity.
vorticity_operation = ∂x(v) - ∂y(u)

# The instance `vorticity_operation` is a binary subtraction between two derivative operations
# acting on `OffsetArrays` (the underyling representation of `u`, and `v`). In order to use
# `vorticity_operation` we create a field `ω` to store the result of the operation, and a
# `Computation` object for coordinate the computation of vorticity and storage in `ω`:

ω = Field(Face, Face, Cell, model.architecture, model.grid)

vorticity_computation = Computation(vorticity_operation, ω)

# We ask for computation of vorticity by writing
#
# `compute!(vorticity_computation)`,
#
# as shown below.
#
# Finally, we run the model.

fig, ax = subplots()

for i = 1:10
    time_step!(model, Nt=100, Δt=1e-1)

    compute!(vorticity_computation)

    cla()
    imshow(interior(ω)[:, :, 1])
    ax.axis("off")
    pause(0.1)
end

# We plot out the final vorticity field.
gcf()


using Test
using Plots
include("../computation/finitedifference.jl")

finitediff_tests = false
finitediff_in_place_tests = true

if finitediff_tests

println("Running checkDerivative tests...")

@testset "R^1 -> R^1" begin
    f = x -> x^2*exp(x)
    jacf = x -> 2x*exp(x) + x^2*exp(x)
    (_, _, _, pol) = checkDerivative(f, jacf, .64, nothing) #Float64 -> Float64
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1
    f = x -> x[1]^2*exp(x[1])
    jacf = x -> 2x[1]*exp(x[1])+x[1]^2*exp(x[1])
    (_, _, _, pol) = checkDerivative(f, jacf, [.64], nothing) #Array{Float64, 1} -> Float64
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1
    f = x -> [x[1]^2*exp(x[1])]
    jacf = x -> [2x[1]*exp(x[1])+x[1]^2*exp(x[1])]
    (_, _, _, pol) = checkDerivative(f, jacf, [.64], nothing) #Array{Float64, 1} -> Array{Float64, 1}
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1

    #f = x -> [x^2*exp(x)]
    #jacf = x -> [2x*exp(x) + x^2*exp(x)]
    #(_, _, _, pol) = checkDerivative(f, jacf, .64, nothing) #Float64 -> Array{Float64, 1}
    f = x -> x^2*exp(x)
    jacf = x -> 2x*exp(x) + x^2*exp(x)
    hess_f = x -> 4x*exp(x) + 2*exp(x) + x^2*exp(x)
    (_, _, _, pol) = checkDerivative(f, jacf, .64, hess_f) #Float64 -> Float64
    @test coeffs(pol)[end] ≈ 3 atol = 3e-1
end

@testset "R^3 -> R^1" begin
    ff = xx ->  xx[1]^2 * sin(xx[2]*xx[3])
    jacf = xx -> [2*xx[1]*sin(xx[2]*xx[3])  xx[1]^2*xx[3]*cos(xx[2]*xx[3])  xx[1]^2*xx[2]*cos(xx[2]*xx[3])]
    (_, _, _, pol) = checkDerivative(ff, jacf, [.4 .5 .7], nothing) #initial guess has shape (3, 1)

    @test coeffs(pol)[end] ≈ 2 atol = 3e-1
    (_, _, _, pol) = checkDerivative(ff, jacf, [.4, .5, .7], nothing) #initial guess has shape (3,)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1
    (_, _, _, pol) = checkDerivative(ff, jacf, reshape([.4, .5, .7], 1, 3), nothing) #initial guess has shape (1, 3)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1

    #check Hessian
    hess_f = x -> [2sin(x[2]x[3]) 2x[1]x[3]cos(x[2]x[3]) 2x[1]x[2]cos(x[2]x[3]) ;
                    2x[1]x[3]cos(x[2]x[3]) -x[1]^2 * x[3]^2 * sin(x[2]x[3]) x[1]^2*cos(x[2]x[3]) - x[1]^2*x[2]x[3]sin(x[2]x[3]);
                    2x[1]x[2]cos(x[2]x[3]) x[1]^2*cos(x[2]x[3]) - x[1]^2*x[2]x[3]sin(x[2]x[3]) -x[1]^2*x[2]^2 * sin(x[2]x[3])]
    (_, _, plt1, pol) = checkDerivative(ff, jacf, reshape([.4, .5, .7], 1, 3), hess_f, 2, 6) #initial guess has shape (1, 3)
    #display(plt1)
    @test coeffs(pol)[end] ≈ 3 atol = 3e-1
end

@testset "R^1 -> R^3" begin
    f = x -> [x;sin(x);log(x)]
    jacf = x -> [1;cos(x);1/x]
    (_, _, _, pol) = checkDerivative(f, jacf, .4, nothing)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1 #input is scalar

    g = x -> [x[1];sin(x[1]);log(x[1])]
    jacg = x -> [1;cos(x[1]);1/x[1]] #input is 1x1 array
    (_, _, _, pol) = checkDerivative(g, jacg, [.4], nothing)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1 #input is array
end

@testset "R^3 -> R^3" begin
    f = x -> [x[1]x[2]x[3]; sin(x[1]) + x[2]exp(x[3]); exp(x[2] - x[3])/x[1]]
    jacf = x -> [ x[2]x[3]  x[1]x[3] x[1]x[2];
                 cos(x[1]) exp(x[3]) x[2]exp(x[3]);
                -exp(x[2]-x[3])/x[1]^2 exp(x[2]-x[3])/x[1] -exp(x[2]-x[3])/x[1] ]
    (_, _, _, pol) = checkDerivative(f, jacf, [1;2;3], nothing)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1 #input is scalar
end
end

if finitediff_in_place_tests
println("Running checkDerivative_in_place tests...")

@testset "R^1 -> R^1" begin
    store = [0.0]
    f = x -> x^2*exp(x)
    jacf = (store, x) -> (store[1] = 2x*exp(x) + x^2*exp(x); return nothing)
    (_, _, _, pol) = checkDerivative_in_place(f, jacf, .64, store, nothing) #Float64 -> Float64
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1

    f = x -> x[1]^2*exp(x[1])
    jacf = (store, x) -> (store[1] = 2x[1]*exp(x[1])+x[1]^2*exp(x[1]); return nothing)
    (_, _, _, pol) = checkDerivative_in_place(f, jacf, [.64], store, nothing) #Array{Float64, 1} -> Float64
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1

    #f = x -> [x[1]^2*exp(x[1])]
    #jacf = (x, store) -> (store[1] = [2x[1]*exp(x[1])+x[1]^2*exp(x[1])]; return nothing)
    #(_, _, _, pol) = checkDerivative_in_place(f, jacf, [.64], store, nothing) #Array{Float64, 1} -> Array{Float64, 1}
    #@test coeffs(pol)[end] ≈ 2 atol = 3e-1

    #f = x -> [x^2*exp(x)]
    #jacf = x -> [2x*exp(x) + x^2*exp(x)]
    #(_, _, _, pol) = checkDerivative(f, jacf, .64, nothing) #Float64 -> Array{Float64, 1}
    if false
    f = x-> x^2*exp(x)
    jacf = (store, x) -> (store[1] = 2x*exp(x) + x^2*exp(x); return nothing)
    hess_f = x -> 4x*exp(x) + 2*exp(x) + x^2*exp(x)
    (_, _, _, pol) = checkDerivative_in_place(f, jacf, .64, store, hess_f) #Float64 -> Float64
    @test coeffs(pol)[end] ≈ 3 atol = 3e-1
    end
end

@testset "R^3 -> R^1" begin
    store = [0.0, 0.0, 0.0]

    ff = xx ->  xx[1]^2 * sin(xx[2]*xx[3])
    jacf = (store, xx) -> (store[1] = 2*xx[1]*sin(xx[2]*xx[3]); store[2] =  xx[1]^2*xx[3]*cos(xx[2]*xx[3]); store[3] = xx[1]^2*xx[2]*cos(xx[2]*xx[3]); return nothing)
    (_, _, _, pol) = checkDerivative_in_place(ff, jacf, [.4 .5 .7], store, nothing) #initial guess has shape (3, 1)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1
    (_, _, _, pol) = checkDerivative_in_place(ff, jacf, [.4, .5, .7], store, nothing) #initial guess has shape (3,)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1
    (_, _, _, pol) = checkDerivative_in_place(ff, jacf, reshape([.4, .5, .7], 1, 3), store, nothing) #initial guess has shape (1, 3)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1

    if false
    #check Hessian
    hess_f = x -> [2sin(x[2]x[3]) 2x[1]x[3]cos(x[2]x[3]) 2x[1]x[2]cos(x[2]x[3]) ;
                    2x[1]x[3]cos(x[2]x[3]) -x[1]^2 * x[3]^2 * sin(x[2]x[3]) x[1]^2*cos(x[2]x[3]) - x[1]^2*x[2]x[3]sin(x[2]x[3]);
                    2x[1]x[2]cos(x[2]x[3]) x[1]^2*cos(x[2]x[3]) - x[1]^2*x[2]x[3]sin(x[2]x[3]) -x[1]^2*x[2]^2 * sin(x[2]x[3])]
    (_, _, plt1, pol) = checkDerivative(ff, jacf, reshape([.4, .5, .7], 1, 3), hess_f, 2, 6) #initial guess has shape (1, 3)
    #display(plt1)
    @test coeffs(pol)[end] ≈ 3 atol = 3e-1
    end
end

@testset "R^1 -> R^3" begin
    store = [0.0, 0.0, 0.0]
    f = x -> [x;sin(x);log(x)]
    jacf = (store, x) -> (store[1] =1;store[2] = cos(x); store[3] = 1/x; return nothing)
    (_, _, _, pol) = checkDerivative_in_place(f, jacf, .4, store, nothing)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1 #input is scalar

    g = x -> [x[1];sin(x[1]);log(x[1])]
    jacg = (store, x) -> (store[1] = 1; store[2] = cos(x[1]); store[3] = 1/x[1]; return nothing) #input is 1x1 array
    (_, _, _, pol) = checkDerivative_in_place(g, jacg, [.4], store, nothing)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1 #input is array
end

@testset "R^3 -> R^3" begin
    store = zeros(3, 3)
    f = x -> [x[1]x[2]x[3]; sin(x[1]) + x[2]exp(x[3]); exp(x[2] - x[3])/x[1]]
    jacf = (store, x) -> (store[1, 1] = x[2]x[3]; store[1, 2] = x[1]x[3]; store[1, 3] = x[1]x[2];
                        store[2, 1] =  cos(x[1]); store[2, 2] = exp(x[3]); store[2, 3] = x[2]exp(x[3]);
                        store[3, 1] = -exp(x[2]-x[3])/x[1]^2; store[3, 2] = exp(x[2]-x[3])/x[1]; store[3, 3] = -exp(x[2]-x[3])/x[1]; return nothing)

    #[ x[2]x[3]  x[1]x[3] x[1]x[2];
    #             cos(x[1]) exp(x[3]) x[2]exp(x[3]);
    #            -exp(x[2]-x[3])/x[1]^2 exp(x[2]-x[3])/x[1] -exp(x[2]-x[3])/x[1] ]
    (_, _, _, pol) = checkDerivative_in_place(f, jacf, [1;2;3], store, nothing)
    @test coeffs(pol)[end] ≈ 2 atol = 3e-1 #input is scalar
end

end


################################################################################
#  Copyright 2022, Jose Antonio de la O Serna (UANL), Tom Van Acker (BASF)     #
################################################################################
# TFT.jl                                                                       #
# A Julia package for Taylor-Fourier Transform.                                #
# See http://github.com/timmyfaraday/TFT.jl                                    #
################################################################################

"""
    TFT.harmonic_estimator(prob::AbstractDTFTProblem, H::Int)

Function to obtain the up-to-Dth-degree derivative of the Hth-harmonic dynamic
phasor.

Input:
- `prob::AbstractDTFTProblem`   | DTFT problem struct
- `H::Int`                      | harmonic number [-]

Output:
- `X::Matrix{<:Complex}`        | up-to-Dth-degree derivative of the
                                | Hth-harmonic dynamic phasor
"""
function harmonic_estimator(prob::AbstractDTFTProblem, H::Int)
    # define the extended normalized time range `rU`
    Δu  = 1 / prob.N
    rU  = 0.0:Δu:(prob.K + 1 - Δu)

    # samples of up-to-Dth-degree derivative of the Kth-degree o-spline `Φ`
    Φ   = sample_ospline(prob.D, prob.K, prob.N)
    # update `Φ` to reflect the samples of up-to-Dth-degree derivative of the
    # Hth-harmonic bandpass filter
    Y   = Φ .* exp.((2 * pi * im * H) .* rU) ./ prob.N .*
            (prob.F).^collect(0:prob.D)'

    # define the appropriate range in the convolution `rC` using an offset `O`:
    # if (K+1)*N is even    → O = ((K+1)*N) / 2 + 1
    # if (K+1)*N is oneven  → O = ((K+1)*N - 1) / 2 + 1
    # NB: the Julia DSP pkg does not allow for the keyword 'same' in its conv-
    # function, as is the case in MATLAB, the range `rC` mimics that behavior
    O   = floor(Int, ((prob.K + 1) * prob.N) / 2) + 1
    rC  = O:O+length(prob.s)-1

    # return the up-to-Dth-degree derivative of the H-th harmonic dynamic phasor
    return _DSP.conv(prob.s, Y)[rC, :]
end

"""
    TFT.fast_estimator(prob::AbstractDTFTProblem)

Function to obtain the Fast Taylor Fourier transform FTFT. Uses the samples of
the up-to-Dth-degree derivatives of Kth-degree o-spline in matrix Φ.

Input:
- `prob::AbstractDTFTProblem`       | DTFT problem struct

Output:
- `X::Dict{Int,Matrix{<:Complex}}`  | dictionary contain up-to-Dth-degree
                                    | derivative of the dynamic phasors for the
                                    | full set of harmonics
"""
function fast_estimator(prob::AbstractDTFTProblem)
    # initialize a matrix of the size (|s|,|h|,D) to store the up-to-Dth-degree
    # derivative of the dynamic phasors for the full set of harmonics
    X   = zeros(Number, length(prob.s), prob.N, prob.D+1)

    # samples of up-to-Dth-degree derivative of the Kth-degree o-spline `Φ`
    Φ   = sample_ospline(prob.D, prob.K, prob.N)

    # determine the shift to center the 'current' signal point
    Δs  = floor(Int, prob.N * (prob.K+1) / 2)

    for (ni,ns) in enumerate(prob.s) if Δs < ni <= length(prob.s) - Δs
        # determine the selected range of the signal with 'center' ni
        rS  = (ni-Δs):(ni+Δs-1)
        # determine the Hadamar product of the osplines and the selected signal
        hd  = Φ .* prob.s[rS] .* (-prob.F).^collect(0:prob.D)'
        # sum the Hadamar product over o-spline degrees
        shd = [sum(hd[nh:prob.N:end, :], dims=1) for nh in 1:prob.N]
        # perform the fft and store the result at ni
        X[ni,:,:] = _FFTW.fft(reduce(vcat,shd), [1]) / prob.N
    end end

    # return a dictionary of spliced arrays over the harmonic numbers
    return Dict(nh => X[:,nh+1,:] for nh in prob.h)
end


using Test, BellScenario, LinearAlgebra

@testset "./src/certifying_signaling_dimension.jl" begin

using SignalingDimension

@testset "maximum_likelihood_lower_bound()" begin
    @test 3 == maximum_likelihood_lower_bound(Strategy([1 0 0;0 0.5 0;0 0 0.5;0 0.5 0.5]))
    @test 1 == maximum_likelihood_lower_bound(Strategy(1/5*ones(5,5)))
    @test 2 == maximum_likelihood_lower_bound(DeterministicStrategy([1 0 1 0;0 1 0 1;0 0 0 0;0 0 0 0]))

    @test 1 == maximum_likelihood_lower_bound(Strategy(ones(1000,1000)/1000))
end

@testset "ambiguous_lower_bound()" begin
    @testset "the number of ambiguous rows affects the lower bound" begin
        P = Strategy([
            0.35 0    0;
            0    0.35 0;
            0    0    0.35;
            0.35 0.35 0.35;
            0.3  0.3  0.3;
        ])

        @test 2 == ambiguous_lower_bound(P, 4)
        @test 3 == ambiguous_lower_bound(P, 3)

        # test over all numbers of ambiguous rows
        @test 3 == ambiguous_lower_bound(P)
    end

    @testset "witnesses d=2 signaling dimension" begin
        P = DeterministicStrategy([1 0 0 1;0 1 1 0;0 0 0 0;0 0 0 0])

        @test 2 == ambiguous_lower_bound(P)
    end

    @testset "pre-sorting edge cases" begin
        @testset "edge case: primary sort by row sum, secondary by max-min diff is not optimal" begin
            P1 = Strategy([
                0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1;
                0.9 0   0   0   0   0   0   0  ;
                0   0.9 0   0   0   0   0   0  ;
                0   0   0.9 0   0   0   0   0  ;
                0   0   0   0.9 0   0   0   0  ;
                0   0   0   0   0.9 0   0   0  ;
                0   0   0   0   0   0.9 0   0  ;
                0   0   0   0   0   0   0.9 0  ;
                0   0   0   0   0   0   0   0.9;
            ])

            P2 = Strategy([
                0.9 0   0   0   0   0   0   0  ;
                0   0.9 0   0   0   0   0   0  ;
                0   0   0.9 0   0   0   0   0  ;
                0   0   0   0.9 0   0   0   0  ;
                0   0   0   0   0.9 0   0   0  ;
                0   0   0   0   0   0.9 0   0  ;
                0   0   0   0   0   0   0.9 0  ;
                0   0   0   0   0   0   0   0.9;
                0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1;
            ])

            @test 8 == ambiguous_lower_bound(P1,8)
            @test 8 == ambiguous_lower_bound(P2,8)
        end

        @testset "edge case: primary sort by row sum, secondary by max-min diff is optimal" begin
            # primary sort by max - min diff, secondary by row sum
            P1a  = Strategy([
                0   0.1 0.5 0;
                0.6 0.6 0.5 1;
                0   0.3 0   0;
                0.4 0   0   0;
            ])
            P1b = Strategy([
                0.4 0.4 1 1;
                0.3 0   0 0;
                0.3 0   0 0;
                0   0.3 0 0;
                0   0.3 0 0;
            ])

            #  primary sort by row sum, secondary by max-min diff
            P2a = Strategy([
                0   0.3 0   0;
                0.4 0   0   0;
                0   0.1 0.5 0;
                0.6 0.6 0.5 1;
            ])
            P2b = Strategy([
                0.3 0   0 0;
                0.3 0   0 0;
                0   0.3 0 0;
                0   0.3 0 0;
                0.4 0.4 1 1;
            ])

            @test 3 == ambiguous_lower_bound(P1a,3)
            @test 3 == ambiguous_lower_bound(P2a,3)

            @test 3 == ambiguous_lower_bound(P1b,4)
            @test 3 == ambiguous_lower_bound(P2b,4)
        end
    end

    @testset "errors" begin
        P = Strategy([1 0 0;0 1 0;0 0 1])

        @test_throws DomainError ambiguous_lower_bound(P, 4)
        @test_throws DomainError ambiguous_lower_bound(P, 0)
    end
end

@testset "trivial_upper_bound()" begin
    @test 3 == trivial_upper_bound(Strategy([1 0 1;0 1 0;0 0 0;0 0 0]))
    @test 4 == trivial_upper_bound(Strategy([1 0 0 0 0 0;0 1 0 0 0 0;0 0 1 0 0 0;0 0 0 1 1 1]))
end

@testset "attains_trivial_upper_bound()"  begin
    @test attains_trivial_upper_bound(Strategy([1 0 0;0 1 0;0 0 1]))
    @test !attains_trivial_upper_bound(Strategy([1 0 1;0 1 0;0 0 0]))

    @testset "strategy where ambiguous game must be tested" begin
        P = Strategy(1/2*[1 0 0;0 1 0;0 0 1;1 1 1])
        @test 2 == maximum_likelihood_lower_bound(P)
        @test 3 == ambiguous_lower_bound(P,3)
        @test attains_trivial_upper_bound(P)
    end
end

@testset "upper_bound()" begin
    @test 4 == upper_bound(Strategy([1 0 0 0;0 1 0 0;0 0 1 0;0 0 0 1]))
    @test 3 == upper_bound(Strategy([1 0 1 0;0 1 0 1;0 0 0 0;0 0 0 0;0 0 0 0]))
end

end


#path = "C:\\Users\\wilhe\\Dropbox\\My PC (DESKTOP-P6322LG)\\Desktop\\GELU4_ANN.csv"
#path = "C:\\Users\\wilhe\\Dropbox\\My PC (DESKTOP-P6322LG)\\Desktop\\TANH2_ANN.csv"
path = "C:\\Users\\wilhe\\Dropbox\\My PC (DESKTOP-P6322LG)\\Desktop\\SILU2_ANN.csv"
nn_data_raw = CSV.read(path, DataFrame)
size_df = size(nn_data_raw)

input_len = 2
hidden_len = 6
output_len = 1
depth = 2
g = swish1

# extracts vector of W and b that
W = Matrix{Float64}[]
b = Vector{Float64}[]

# extract first hidden layer info
H1frame = filter(row -> row.Layer === "H1", nn_data_raw)
H1frame_W = H1frame[:, [Symbol("W"*string(i)) for i=1:input_len]]
H1frame_b = H1frame[:, [:b]]
Welement = Matrix{Float64}(H1frame_W)
belement = Matrix{Float64}(H1frame_b)
push!(W, Welement)
push!(b, Float64[belement[i] for i = 1:size(belement,1)])

# extract info for 2 to last hidden layer
for i = 2:depth
    Hiframe = filter(row -> row.Layer === "H$i", nn_data_raw)
    Hiframe_W = Hiframe[:, [Symbol("W"*string(i)) for i=1:hidden_len]]
    Hiframe_b = Hiframe[:, [:b]]
    Welement2 = Matrix{Float64}(Hiframe_W)
    belement2 = Matrix{Float64}(Hiframe_b)
    push!(W, Welement2)
    push!(b, Float64[belement2[i] for i = 1:size(belement2,1)])
end

# extract info for output layer
Hoframe = filter(row -> row.Layer === "Output", nn_data_raw)
Hoframe_W = Hoframe[:, [Symbol("W"*string(i)) for i=1:hidden_len]]
Hoframe_b = Hoframe[:, [:b]]
Welement = Matrix{Float64}(Hoframe_W)
belement = Matrix{Float64}(Hoframe_b)
push!(W, Welement)
push!(b, Float64[belement[i] for i = 1:size(belement,1)])

#mutable struct ANN1 <: Function
#end

xmin = zeros(2)
xmax = [40.0; 9.1]
ymin = 0.0
ymax = 0.638966464124928

#=
function ANN1(x)
    xs = (x .- xmin)./(xmax .- xmin)

    yi1 = W[1]*xs .+ b[1]
    y1 = swish1.(yi1)
    #@show yi1
    #@show y1

    yi2 = W[2]*y1 .+ b[2]
    y2 = swish1.(yi2)

    yi3 = W[3]*y2 .+ b[3]
    y3 = swish1.(yi3)

    yi4 = W[4]*y3 .+ b[4]
    y4 = swish1.(yi4)

    yi5 = W[5]*y4 .+ b[5]
    y5 = sigmoid.(yi5)

    y5[1]*(ymax - ymin) + ymin
end
=#

function ANN1(x)

    #@show x
    xs = (x .- xmin)./(xmax .- xmin)

    yi1 = W[1]*xs .+ b[1]
    y1 = swish1.(yi1)
    #@show yi1
    #@show y1

    yi2 = W[2]*y1 .+ b[2]
    y2 = swish1.(yi2)
    #@show yi2
    #@show y2

    yi3 = W[3]*y2 .+ b[3]
    y3 = sigmoid.(yi3)
    #@show yi4
    #@show y4

    #@show y4[1]*(ymax - ymin) + ymin
    return y3[1]*(ymax - ymin) + ymin
end

#=
# builds a nonallocating multilayer perceptron (vanilla ANN) with static storage types
calc_block = quote end

# used to revert minmax scaling
xmin = zeros(2)
xmax = [40.0; 9.1]

# unpacks input variables (CHECKED)
for i = 1:input_len
    sym = Symbol("Input_"*string(i))
    #push!(calc_block.args, :($sym = (x[$i] - $(xmin[i]))/($(xmax[i]) - $(xmin[i]))))
    push!(calc_block.args, :($sym = x[$i]))
end

# input to first hidden layer (CHECKED)
for i = 1:hidden_len
    sym = Symbol("H1_"*string(i))
    sum_expr = :($(b[1][i]))
    for j = 1:input_len
        prior_sym =  Symbol("Input_"*string(j))
        sum_expr = :($sum_expr + $(W[1][i,j])*$prior_sym)
    end
    push!(calc_block.args, :($sym = f($sum_expr)))
end

# 2 to last hidden_layers
for k = 2:depth
    @show k
    for i = 1:hidden_len
        sym = Symbol("H$(k)_"*string(i))
        sum_expr = :($(b[1][i]))
        for j = 1:input_len
            prior_sym =  Symbol("H$(k-1)_"*string(j))
            sum_expr = :($sum_expr + $(W[k][i,j])*$prior_sym)
        end
        push!(calc_block.args, :($sym = f($sum_expr)))
    end
end

output_tuple = :((Output_1,))
for i = 1:output_len
    sym = Symbol("Output_"*string(i))
    if i > 1
        push!(output_tuple, sym)
    end
    sum_expr = :($(b[end][i]))
    for j = 1:hidden_len
        prior_sym = Symbol("H$(depth)_"*string(j))
        sum_expr = :($sum_expr + $(W[end][i,j])*$prior_sym)
    end
    push!(calc_block.args, :($sym = $sum_expr))
end

@generated function ANN(x, f)
    return quote
        $calc_block
        return $output_tuple
    end
end

ANN1(x) = ANN(x, g)[1]

function ANN_GLM1(x)
    ANN1(x) + 0.0020*x[1] + 0.0245*x[2] + 0.4163
end
=#

#=
path = "C:\\Users\\wilhe\\Dropbox\\My PC (DESKTOP-P6322LG)\\Desktop\\GELU1_ANN.csv"
nn_data_raw = CSV.read(path, DataFrame)
size_df = size(nn_data_raw)

input_len = 2
hidden_len = 5
output_len = 1
depth = 1
g = swish1

# extracts vector of W and b that
W = Matrix{Float64}[]
b = Vector{Float64}[]

# extract first hidden layer info
H1frame = filter(row -> row.Layer === "H1", nn_data_raw)
H1frame_W = H1frame[:, [Symbol("W"*string(i)) for i=1:input_len]]
H1frame_b = H1frame[:, [:b]]
Welement = Matrix{Float64}(H1frame_W)
belement = Matrix{Float64}(H1frame_b)
push!(W, Welement)
push!(b, Float64[belement[i] for i = 1:size(belement,1)])

# extract info for 2 to last hidden layer
for i = 2:depth
    Hiframe = filter(row -> row.Layer === "H$i", nn_data_raw)
    Hiframe_W = Hiframe[:, [Symbol("W"*string(i)) for i=1:hidden_len]]
    Hiframe_b = Hiframe[:, [:b]]
    Welement2 = Matrix{Float64}(Hiframe_W)
    belement2 = Matrix{Float64}(Hiframe_b)
    push!(W, Welement2)
    push!(b, Float64[belement2[i] for i = 1:size(belement2,1)])
end

# extract info for output layer
Hoframe = filter(row -> row.Layer === "Output", nn_data_raw)
Hoframe_W = Hoframe[:, [Symbol("W"*string(i)) for i=1:hidden_len]]
Hoframe_b = Hoframe[:, [:b]]
Welement = Matrix{Float64}(Hoframe_W)
belement = Matrix{Float64}(Hoframe_b)
push!(W, Welement)
push!(b, Float64[belement[i] for i = 1:size(belement,1)])

# builds a nonallocating multilayer perceptron (vanilla ANN) with static storage types
calc_block = quote end

# used to revert minmax scaling
xmin = zeros(2)
xmax = [40.0; 9.1]

# unpacks input variables (CHECKED)
for i = 1:input_len
    sym = Symbol("Input_"*string(i))
    push!(calc_block.args, :($sym = (x[$i] - $(xmin[i]))/($(xmax[i]) - $(xmin[i]))))
end

# input to first hidden layer (CHECKED)
for i = 1:hidden_len
    sym = Symbol("H1_"*string(i))
    sum_expr = :($(b[1][i]))
    for j = 1:input_len
        prior_sym =  Symbol("Input_"*string(j))
        sum_expr = :($sum_expr + $(W[1][i,j])*$prior_sym)
    end
    push!(calc_block.args, :($sym = f($sum_expr)))
end

# 2 to last hidden_layers
for k = 2:depth
    @show k
    for i = 1:hidden_len
        sym = Symbol("H$(k)_"*string(i))
        sum_expr = :($(b[1][i]))
        for j = 1:input_len
            prior_sym =  Symbol("H$(k-1)_"*string(j))
            sum_expr = :($sum_expr + $(W[k][i,j])*$prior_sym)
        end
        push!(calc_block.args, :($sym = f($sum_expr)))
    end
end

output_tuple = :((Output_1,))
for i = 1:output_len
    sym = Symbol("Output_"*string(i))
    if i > 1
        push!(output_tuple, sym)
    end
    sum_expr = :($(b[end][i]))
    for j = 1:hidden_len
        prior_sym = Symbol("H$(depth)_"*string(j))
        sum_expr = :($sum_expr + $(W[end][i,j])*$prior_sym)
    end
    push!(calc_block.args, :($sym = $sum_expr))
end

@generated function ANN(x, f)
    return quote
        $calc_block
        return $output_tuple
    end
end

ANN1(x) = ANN(x, g)[1]

function ANN_GLM1(x)
    ANN1(x) + 0.0020*x[1] + 0.0245*x[2] + 0.4163
end
=#


function checkAM(neq, nlag, nlead, h, b)

#   substitute the reduced form, b, into the structural
#   equations, h, to confirm that the reduced form solves
#   the model.

# Append negative identity matrix to b

b = hcat(b, -Matrix(I, neq, neq))

# Define indexes into the lagged part (minus)
# and the current and lead part (plus) of h

minus = 1:(neq * nlag)
plus  = (neq * nlag + 1):(neq * (nlag + 1 + nlead))

# Initialize q

q = zeros(neq * (nlead + 1), neq * (nlag + 1 + nlead))

# Stuff b into the upper left-hand block of q

(rb,cb) = size(b)
q[(1:rb), (1:cb)] = b[(1:rb), (1:cb)]

# Copy b into the (nlead) row blocks of q, shifting right
# by neq once more for each block (this produces a coefficient matrix that
# solves for x(t), x(t+1), ..., x(t+nlead)) in terms of x(t-nlag),...,x(t-1).

for i in (1:nlead)
   rows = i * neq .+ (1:neq)
   q[rows,:] = shiftRight!( q[(rows .- neq), :], neq )
end

# Premultiply the left block of q by the negative inverse
# of the right block of q (use equation solver to avoid
# actually computing the inverse).

q[:,minus] =  -q[:,plus] \ q[:,minus]

# Define the solution error

error = h[:,minus] +  h[:,plus] * q[:,minus]

# Take maximum absolute value of the vec of error (error(:))

err = maximum( abs.( error ) );

# space
print("Maximum absolute error: ")
println(err)

# Resize q, removing the additional neq columns and rows

rows = 1:(neq * nlead);
cols = 1:(neq * (nlag + nlead))
q = q[rows,cols];

    return q, err

end


using SparseArrays
using LinearAlgebra
using Plots

include("vekit.jl")

let
    # A bestimmen
    A=sparse([1,1,1,1,2,2,3,3,4,4,4,5,5,5,6,6,6,6,7,7,7,8,8,9,9,9,10,10,10,10,11,11,12,12,12,13,13,14,14,15,15],[2,3,4,6,4,6,4,5,6,7,8,3,4,10,4,8,10,11,4,8,9,7,11,5,7,10,7,8,9,13,10,13,10,13,15,10,14,11,15,13,14],[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0])
    # L bestimmen
    L=A'
    for j in 1:15
        for i in 1:15
            if(L[i,j]!=0)
                L[i,j]=L[i,j]/sum(A[j,1:15])
            end
        end
    end
    # Eigenwert und Eigenvektor bestimmen
    print(vekit(L,[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]',10^-10))
    # Ich habe keine Ahnung, was das Ergebnis bedeuten soll, von daher bin ich auch nicht sicher, wie ich es am Besten dastellen soll.
end


using Test, LinearAlgebra, Random
using GenericLinearAlgebra: cholUnblocked!, cholBlocked!, cholRecursive!

@testset "Cholesky" begin

    Random.seed!(123)

    n = 50

    @testset "element type: $T" for T in (Float32, Float64, BigFloat)
        @testset "is complex: $cplx" for cplx in (true, false)
            if cplx
                A = complex.(T.(rand(n,n)), T.(rand(n,n)))
            else
                A = T.(rand(n,n))
            end
            AcA = A'A
            @test LowerTriangular(cholUnblocked!(copy(AcA), Val{:L}))   ≈ cholesky(Hermitian(AcA)).L
            @test LowerTriangular(cholBlocked!(copy(AcA), Val{:L}, 5))  ≈ cholesky(Hermitian(AcA)).L
            @test LowerTriangular(cholBlocked!(copy(AcA), Val{:L}, 10)) ≈ cholesky(Hermitian(AcA)).L
            @test LowerTriangular(cholRecursive!(copy(AcA), Val{:L}, 1)) ≈ cholesky(Hermitian(AcA)).L
            @test LowerTriangular(cholRecursive!(copy(AcA), Val{:L}, 4)) ≈ cholesky(Hermitian(AcA)).L
        end
    end
end


################################################################################
# Note: The following is based on the nomenclature in the paper
# "Revised Release on the IAPWS Formulation 1995 for the Thermodynamic
# Properties of Ordinary Water Substance for General and Scientific Use"
# by IAPWS, the International Association for the Properties of Water and Steam.
#
# It is more or less as follows:
#
# ϕ : Dimensionless Helmholtz energy of water as a fucntion of density and temperature
# δ : Dimensionless density of Water
# τ : Dimensionless temperature of water
# n, d, t, α, β, γ, ϵ, A, B, C, D : these are parameters used in fitting the
# equation of state to data. As far as we're concerned, they are constants with
# no real physical significance.
#
# Note that although the code was written by consulting the IAPWS paper, it is
# applicable to any
################################################################################

struct IAPWSTypeMultiparameterEOS{T_} <: AbstractEquationOfState where T_ <: AbstractFloat
    Tc::T_
    ρc::T_
    pc::T_
    ω::T_
    R::T_

    #parameters for ideal gas portion
    n₀::Vector{T_}
    γ₀::Vector{T_}

    # parameters for residual term 1
    n₁::Vector{T_}
    d₁::Vector{T_}
    t₁::Vector{T_}

    # parameters for residual term 2
    n₂::Vector{T_}
    d₂::Vector{T_}
    t₂::Vector{T_}
    c₂::Vector{T_}

    # parameters for residual term 3
    n₃::Vector{T_}
    d₃::Vector{T_}
    t₃::Vector{T_}
    α₃::Vector{T_}
    β₃::Vector{T_}
    γ₃::Vector{T_}
    ϵ₃::Vector{T_}

    # parameters for residual term 4
    n₄::Vector{T_}
    a₄::Vector{T_}
    b₄::Vector{T_}
    β₄::Vector{T_}
    A₄::Vector{T_}
    B₄::Vector{T_}
    C₄::Vector{T_}
    D₄::Vector{T_}

end

function ϕ₀(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    first = log(δ) + eos.n₀[1] + eos.n₀[2]*τ + eos.n₀[3]*log(τ)
    end_ = sum(eos.n₀[4:8].*log.(1-exp.(-eos.γ₀.*τ)))
    return first + end_
end

function res_1(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return sum(eos.n₁ .* (δ .^ eos.d₁) .* (τ .^ eos.t₁))
end

function res_2(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return sum(eos.n₂ .* (δ .^ eos.d₂) .* (τ .^ eos.t₂) .* exp.(-δ .^ eos.c₂))
end

function res_3(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return sum(eos.n₃ .* (δ .^ eos.d₃) .* (τ .^ eos.t₃) .* exp.(-eos.α₃ .* (δ - eos.ϵ₃).^2 - eos.β₃ .* (τ - eos.γ₃).^2))
end

function θ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return sum((1-τ) + eos.A₄.*((δ - 1).^2).^(1 ./(2 .* eos.β₄)))
end

function Δ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return sum(θ(eos, δ, τ).^2 + eos.B₄ .* ((δ - 1).^2) .* eos.a₄)
end

function Ψ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return sum(exp(-eos.C₄ .* (δ - 1)^2 - eos.D₄ .* (τ - 1)^2))
end

function res_4(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return sum(eos.n₄ .* Δ(eos,δ,τ).^eos.b₄ .* δ * Ψ(eos,δ,τ))
end

function ϕr(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return res_1(eos, δ, τ)+res_2(eos, δ, τ)+res_3(eos, δ, τ)+res_4(eos, δ, τ)
end

function ϕ₀_δ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(δ_) = ϕ₀(eos, δ_, τ)
    return ForwardDiff.derivative(f, δ)
end
function ϕ₀_δδ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(δ_) = ϕ₀_δ(eos, δ_, τ)
    return ForwardDiff.derivative(f, δ)
end
function ϕ₀_τ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(τ_) = ϕ₀(eos, δ, τ_)
    return ForwardDiff.derivative(f, τ)
end
function ϕ₀_ττ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(τ_) = ϕ₀_τ(eos, δ, τ_)
    return ForwardDiff.derivative(f, τ)
end
function ϕ₀_δτ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    return 0.0
end

function ϕr_δ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(δ_) = ϕr(eos, δ_, τ)
    return ForwardDiff.derivative(f, δ)
end
function ϕr_δδ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(δ_) = ϕr_δ(eos, δ_, τ)
    return ForwardDiff.derivative(f, δ)
end
function ϕr_τ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(τ_) = ϕr(eos, δ, τ_)
    return ForwardDiff.derivative(f, τ)
end
function ϕr_ττ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(τ_) = ϕr_τ(eos, δ, τ_)
    return ForwardDiff.derivative(f, τ)
end
function ϕr_δτ(eos::IAPWSTypeMultiparameterEOS, δ, τ)
    f(τ_) = ϕr_δ(eos, δ, τ_)
    return ForwardDiff.derivative(f, τ)
end

function p(eos::IAPWSTypeMultiparameterEOS, ρ, T)
    δ = ρ/eos.ρc
    τ = eos.Tc/T
    #@printf "δ = %f\n" δ
    #@printf "τ = %f\n" τ
    #@printf "derivative is %f\n" ϕr_δ(eos, δ, τ)
    p_nondim = 1 + δ*ϕr_δ(eos, δ, τ)
    return p_nondim * ρ*eos.R*T
end

function ρ_from_p(eos::IAPWSTypeMultiparameterEOS, p_, T)
    function solveit(ρ_guess)
        return abs(p_ - p(eos, ρ_guess, T))
    end
    a = optimize(solveit, 0.00001, 1500.0)
    print(a)
end

function u(eos::IAPWSTypeMultiparameterEOS, p_, T)
    ρ = ρ_from_p(eos, p_, T)
    δ = ρ/eos.ρc
    τ = eos.Tc/T
    u_nondim = τ * (ϕ₀_τ(eos, δ, τ) + ϕr_τ(eos, δ, τ))
    return u_nondim * T*eos.R
end

function s(eos::IAPWSTypeMultiparameterEOS, p_, T)
    ρ = ρ_from_p(eos, p_, T)
    δ = ρ/eos.ρc
    τ = eos.Tc/T
    s_nondim = τ * (ϕ₀_τ(eos, δ, τ) + ϕr_τ(eos, δ, τ)) - (ϕ₀(eos, δ, τ) + ϕr(eos, δ, τ))
    return s_nondim * eos.R
end

function H(eos::IAPWSTypeMultiparameterEOS, p_, T)
    ρ = ρ_from_p(eos, p_, T)
    δ = ρ/eos.ρc
    τ = eos.Tc/T
    H_nondim = 1 + τ * (ϕ₀_τ(eos, δ, τ) + ϕr_τ(eos, δ, τ)) + δ*ϕr_δ(eos, δ, τ)
    return H_nondim * T*eos.R
end

function Cp(eos::IAPWSTypeMultiparameterEOS, p_, T)
    ρ = ρ_from_p(eos, p_, T)
    δ = ρ/eos.ρc
    τ = eos.Tc/T
    part1 = -τ^2 * (ϕ₀_ττ(eos, δ, τ) + ϕr_ττ(eos, δ, τ))
    p2_top = (1 + δ*ϕr_δ(eos, δ, τ) - δ*τ*ϕr_δτ(eos, δ, τ))^2
    p2_bot = 1 + 2δ*ϕr_δ(eos, δ, τ) + δ^2*ϕr_δδ(eos, δ, τ)
    Cp_nondim = part1 + p2_top/p2_bot
    return Cp_nondim * eos.R
end

module IAPWS
using parameterized_eos
function gen_iapws()
    h2o_n₀ = [-8.3204464837497, 6.6832105275932, 3.00632, 0.012436, 0.97315, 1.27950,
              0.96956, 0.24873]
    h2o_γ₀ = [1.28728967, 3.53734222, 7.74073708, 9.24437796, 27.5075105]

    h2o_n₁ = [0.12533547935523e-1,
              0.78957634722828e1,
              -0.87803203303561e1,
              0.31802509345418,
              -0.26145533859358,
              -0.78199751687981e-2,
              0.88089493102134e-2]
    h2o_d₁ = [1, 1, 1, 2, 2, 3, 4]
    h2o_t₁ = [-0.5, 0.875, 1, 0.5, 0.75, 0.375, 1]

    h2o_n₂ = [-0.66856572307965,
              0.20433810950965,
              -0.66212605039687e-4,
              -0.19232721156002,
              -0.25709043003438,
              0.16074868486251,
              -0.40092828925807e-1,
              0.39343422603254e-6,
              -0.75941377088144e-5,
              0.56250979351888e-3,
              -0.15608652257135e-4,
              0.11537996422951e-8,
              0.36582165144204e-6,
              -0.13251180074668e-11,
              -0.62639586912454e-9,
              -0.10793600908932,
              0.17611491008752e-1,
              0.22132295167546,
              -0.40247669763528,
              0.58083399985759,
              0.49969146990806e-2,
              -0.31358700712549e-1,
              -0.74315929710341,
              0.47807329915480,
              0.20527940895948e-1,
              -0.13636435110343,
              0.14180634400617e-1,
              0.83326504880713e-2,
              -0.29052336009585e-1,
              0.38615085574206e-1,
              -0.20393486513704e-1,
              -0.16554050063734e-2,
              0.19955571979541e-2,
              0.15870308324157e-3,
              -0.16388568342530e-4,
              0.43613615723811e-1,
              0.34994005463765e-1,
              -0.76788197844621e-1,
              0.22446277332006e-1,
              -0.62689710414685e-4,
              -0.55711118565645e-9,
              -0.19905718354408,
              0.31777497330738,
              -0.11841182425981]
    h2o_c₂ = [1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,
              2,2,2, 3,3,3,3, 4,6,6,6,6]
    h2o_d₂ = [1,1,1,2,2,3,4,4,5,7,9,10,11,13,15,1,2,2,2,3,4,4,4,5,6,6,7,9,9,9,9,9,
              10,10,12,3,4,4,5,14,3,6,6,6]
    h2o_t₂ = [4,6,12,1,5,4,2,13,9,3,4,11,4,13,1,7,1,9,10,10,3,7,10,10,6,10,10,1,2,3,
              4,8,6,9,8,16,22,23,23,10,50,44,46,50]

    h2o_d₃ = [3, 3, 3]
    h2o_t₃ = [0,1,4]
    h2o_n₃ = [-0.31306260323435e2, 0.31546140237781e2, -0.25213154341695e4]
    h2o_α₃ = [20,20,20]
    h2o_β₃ = [150,150,150]
    h2o_γ₃ = [1.21,1.21,1.25]
    h2o_ϵ₃ = [1,1,1]

    h2o_a₄ = [3.5,3.5]
    h2o_b₄ = [0.85,0.95]
    h2o_β₄ = [0.3,0.3]
    h2o_n₄ = [-0.14874640856724, 0.31806110878444]
    h2o_A₄ = [0.32,0.32]
    h2o_B₄ = [0.2,0.2]
    h2o_C₄ = [28,32]
    h2o_D₄ = [700,800]

    Tc = 647.096
    ρc = 322
    R = 0.46151805*1000

    return MultiparameterEOS(Tc,ρc,R,h2o_n₀,h2o_γ₀,h2o_n₁,h2o_d₁,h2o_t₁,
      h2o_n₂,h2o_d₂,h2o_t₂,h2o_c₂,h2o_n₃,h2o_d₃,h2o_t₃,h2o_α₃,h2o_β₃,h2o_γ₃,
      h2o_ϵ₃,h2o_n₄,h2o_a₄,h2o_b₄,h2o_β₄,h2o_A₄,h2o_B₄,h2o_C₄,h2o_D₄)
end
end # ends module IAPWS

module co2
import parameterized_eos
function gen_co2()
    co2_n₀= [ 8.37304456, -3.70454304,  2.5       ,  1.99427042,  0.62105248,
        0.41195293,  1.04028922,  0.08327678]

    co2_γ₀ = [3.15163,   6.1119 ,   6.77708,
            11.32384,  27.08792]

    co2_n₁ = [ 0.38856823,  2.93854759, -5.58671885, -0.767532  ,  0.31729004,
            0.54803316,  0.12279411]
    co2_d₁ = [ 1.,  1.,  1.,  1.,  2.,  2.,  3.]
    co2_t₁ = [ 0.  ,  0.75,  1.  ,  2.  ,  0.75,  2.  ,  0.75]
    co2_n₂ = [  2.16589615e+00,   1.58417351e+00,  -2.31327054e-01,
             5.81169164e-02,  -5.53691372e-01,   4.89466159e-01,
            -2.42757398e-02,   6.24947905e-02,  -1.21758602e-01,
            -3.70556853e-01,  -1.67758797e-02,  -1.19607366e-01,
            -4.56193625e-02,   3.56127893e-02,  -7.44277271e-03,
            -1.73957049e-03,  -2.18101213e-02,   2.43321666e-02,
            -3.47401334e-02,   1.43387158e-01,  -1.34919691e-01,
            -2.31512251e-02,   1.23631255e-02,   2.10583220e-03,
            -3.39585190e-04,   5.59936518e-03,  -3.03351181e-04]

    co2_d₂ = [  1.,   2.,   4.,   5.,   5.,   5.,   6.,   6.,   6.,   1.,   1.,
             4.,   4.,   4.,   7.,   8.,   2.,   3.,   3.,   5.,   5.,   6.,
             7.,   8.,  10.,   4.,   8.]

    co2_c₂ = [ 1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  2.,  2.,  2.,  2.,
            2.,  2.,  2.,  3.,  3.,  3.,  4.,  4.,  4.,  4.,  4.,  4.,  5.,  6.]

    co2_t₂ = [  1.5,   1.5,   2.5,   0. ,   1.5,   2. ,   0. ,   1. ,   2. ,
             3. ,   6. ,   3. ,   6. ,   8. ,   6. ,   0. ,   7. ,  12. ,
            16. ,  22. ,  24. ,  16. ,  24. ,   8. ,   2. ,  28. ,  14. ]

    co2_n₃ = [  -213.65488688,  26641.56914927, -24027.21220456,  -283.41603424,
              212.472844  ]
    co2_n₄ = [-0.66642277,  0.72608632,  0.05506867]
    co2_d₃ = [2.,2.,2.,3.,3.]
    co2_t₃ = [1.,0.,1.,3.,3.]
    co2_α₃ = [25.,25.,25.,15.,20.]
    co2_β₃ = [325.,300.,300.,275.,275.]
    co2_γ₃ = [1.16,1.19,1.19,1.25,1.22]
    co2_ϵ₃ = [1.,1.,1.,1.,1.]

    co2_a₄ = [3.5,3.5,3.0]
    co2_b₄ = [0.875,0.925,0.875]
    co2_β₄ = [0.3,0.3,0.3]
    co2_A₄ = [0.7,0.7,0.7]
    co2_B₄ = [0.3,0.3,1.0]
    co2_C₄ = [10.0,10.0,12.5]
    co2_D₄ = [275,275,275]

    Tc = 304.1282
    ρc = 467.6
    pc = 7.38e6
    R = 188.9241
    ω = 0.228
    return MultiparameterEOS(Tc,ρc,pc,ω,R,co2_n₀,co2_γ₀,co2_n₁,co2_d₁,co2_t₁,
      co2_n₂,co2_d₂,co2_t₂,co2_c₂,co2_n₃,co2_d₃,co2_t₃,co2_α₃,co2_β₃,co2_γ₃,
      co2_ϵ₃,co2_n₄,co2_a₄,co2_b₄,co2_β₄,co2_A₄,co2_B₄,co2_C₄,co2_D₄)
end
#eos = gen_co2()
#Tc = 304.1282
#ρc = 467.6
#δ₁ = 838.025/ρc
#τ₁ = Tc/500
#test_peos(eos, δ₁, τ₁)
end #ends module co2


module Fast12thRoot

using Base.Math: @horner, leading_zeros

export fast12throot

@noinline function throw_complex_domainerror(f, x)
    throw(DomainError(x, string("$f will only return a complex result if called with a ",
                                "complex argument. Try $f(Complex(x)).")))
end

# 2.0^(-i/12) for i=0:11 expressed as components of double-double
const scale12thrthi = [1.0,0.94387431268169353,0.89089871814033927,0.8408964152537145,0.79370052598409979,0.74915353843834076,0.70710678118654757,0.66741992708501718,0.6299605249474366,0.59460355750136051,0.56123102415468651,0.52973154717964765]
const scale12thrtlo = [0.0,-3.1406841003741325e-17,3.0632750829804785e-17,4.0995050102907483e-17,-5.4345040970989115e-17,-1.0908524678974539e-17,-4.8336466567264567e-17,-1.4832693484307519e-18,-1.2949666876502535e-17,1.9910076157328231e-17,-1.7892535584267786e-17,-2.2640640009996703e-17]

@inline function r12_kernel_num(x::Float64)
    # numerator of rational polynomial
    c0 = -0.35405850406413902
    c1 = -9.7833971892115805
    c2 = -42.631025673407322
    c3 = -18.537498496277639
    c4 = 47.865188330720699
    c5 = 22.716858532042611
    c6 = 0.74348566716915454
    c7 = -0.019552666971785069

    #return x*(c1 + x*(c2 + x*(c3 + x*(c4 + x*(c5 + x*(c6 + x*c7)))))) + c0

    return @horner x c0 c1 c2 c3 c4 c5 c6 c7
end

@inline function r12_kernel_den(x::Float64)
    # denominator of rational polynomial
    c0 = 1.0
    c1 = 45.670990855589217
    c2 = 369.28185751904067
    c3 = 834.07884364001461
    c4 = 563.7758264531883
    c5 = 90.726249041583387

    #return x*(c1 + x*(c2 + x*(c3 + x*(c4 + x*c5)))) + c0

    return @horner x c0 c1 c2 c3 c4 c5
end

function fast12throot(x::Float64)

    if x > zero(Float64)
        # will include Inf but not NaN
        return _fast12throot(x)
    elseif x == zero(Float64)
        # 0^x = 0 for x in R
        return zero(Float64)
    elseif isnan(x)
        return x
    else
        # signal domain error on x in R-
        throw_complex_domainerror(:fast12throot, x)
        #error("number must be non-negative")
    end

end

@inline @inbounds function _fast12throot(x::Float64)

    # extract mantissa and exponent
    # convert x to element in [0.5, 1.0]
    xu = reinterpret(Unsigned, x)
    xs = xu & 0x7fff_ffff_ffff_ffff

    # NaNs won't flow through here
    xs >= 0x7ff0_0000_0000_0000 && return x # Inf

    k = Int(xs >> 52)

    if k == 0 # x is subnormal
        xs == 0 && return x # +-0
        #subnormal = true
        me = leading_zeros(xs) - 11
        xs <<= unsigned(me)
        xu = xs | (xu & 0x8000_0000_0000_0000)
        k = 1 - me
    end

    # final exponent
    k -= 1022

    # mantissa
    m = reinterpret(Float64, (xu & 0x800f_ffff_ffff_ffff) | 0x3fe0_0000_0000_0000)

    # convert exponent to multiple of 12 plus (negative) remainder
    r = rem(k, 12)
    if r > 0
        r -= 12
    end

    # exponent for final float value (i.e. after dividing by 12)
    ex2 = div(k-r,12)

    # index for lookup table in value reconstruction
    idx = 1 - r
    fhi = scale12thrthi[idx]

    # before scaling, should have error < 0.5ulp in [0.5,1.0]
    # poly*fhi is not exact. Relying on error being small
    # (poly+1)*f = (poly*fhi + flo) + fhi is exact
    poly = ((r12_kernel_num(m)/r12_kernel_den(m))*fhi + scale12thrtlo[idx]) + fhi

    # poly is a normalized, finite number so no need to invoke full checks of ldexp
    #return ldexp(poly, ex2)

    polyint = reinterpret(Unsigned, poly)
    xsp = polyint & 0x7fff_ffff_ffff_ffff #~0x8000_0000_0000_0000
    kp = Int(xsp >> 52)
    kp += ex2

    u = (polyint & ~0x7ff0_0000_0000_0000) | (kp << 52)
    return reinterpret(Float64, u)

end

end


using ModelingToolkit, StaticArrays, LinearAlgebra
using DiffEqBase
using Test

# Define some variables
@parameters t σ ρ β
@variables x(t) y(t) z(t)
D = Differential(t)

eqs = [D(x) ~ σ * (y - x),
    D(y) ~ -z - y,
    D(z) ~ y - β * z]

@test ModelingToolkit.islinear(@named sys = ODESystem(eqs))

eqs2 = [D(x) ~ σ * (y - x),
    D(y) ~ -z - 1 / y,
    D(z) ~ y - β * z]

@test !ModelingToolkit.islinear(@named sys = ODESystem(eqs2))

eqs3 = [D(x) ~ σ * (y - x),
    D(y) ~ -z - y,
    D(z) ~ y - β * z + 1]

@test ModelingToolkit.isaffine(@named sys = ODESystem(eqs))


module RandomMatrixDistributions

#import RandomMatrices

using Random, Distributions
using LinearAlgebra
using BandedMatrices
using KrylovKit

import LinearAlgebra: eigmax

import Distributions: minimum, maximum, quantile

import Distributions: ContinuousUnivariateDistribution,
    ContinuousMatrixDistribution,
    cdf, pdf, entropy, insupport, mean, median, modes, kurtosis, skewness, std, var, moment

export randeigvals, randeigstat,
    minimum, maximum, quantile
    cdf, pdf, entropy, insupport, mean, median, modes, kurtosis, skewness, std, var, moment,
    eigmax

# Generic eigenvalue sampling functions

function randeigvals(d)
    eigvals(randreduced(d))
end

function randeigstat(d, eigstat, nsims::Int)
    statvals = Array{Float64, 1}(undef, nsims)
    for i in 1:nsims
        statvals[i] = eigstat(randreduced(d))
    end
    statvals
end

include("BandedHelpers.jl")

include("SpikedWigner.jl")
include("SpikedWishart.jl")

include("Jacobi.jl")

include("densities/MarchenkoPastur.jl")
include("densities/TracyWidom.jl")
include("densities/Wachter.jl")

end


using SparseArrays
using SparseMatrixVBCs
using LinearAlgebra
using MatrixDepot
using BenchmarkTools
using Cthulhu
using UnicodePlots
using PrettyTables
using ChainPartitioners
using InteractiveUtils

for mtx in [
            #"DIMACS10/chesapeake",
            #"HB/bcsstk04",
            "Boeing/ct20stif",

            #"Simon/raefsky4",
            #"FIDAP/ex11",
            #"Vavasis/av41092",
            #"Goodwin/rim",
            #"GHS_psdef/bmw7st_1",
            #"Williams/cop20k_A",
            #"Boeing/pwtk",
            #"Bova/rma10",
            #"GHS_psdef/s3dkq4m2",
           ]
    A = permutedims(sparse(mdopen(mtx).A))

    println()
    println()
    println(spy(A, maxwidth=50, maxheight=50, title="$mtx"))
    rows = []

    begin
        x = rand(size(A, 1))
        y = rand(size(A, 2))
        z = A' * x
        setup_time = 0.0
        mem = sizeof(A.colptr) + sizeof(A.rowval) + sizeof(A.nzval)
        run_time = time(@benchmark TrSpMV!($y, $A, $x))

        @assert y ≈ z
        push!(rows, ["reference" setup_time mem run_time 0])
    end

    w_max = 8

    limit_width(mdl) = ConstrainedCost(mdl, WidthCost(), w_max)

    mdl_blocks_1D = model_SparseMatrix1DVBC_blocks()
    mdl_memory_1D = model_SparseMatrix1DVBC_memory(eltype(A), Int)
    mdl_time_1D = model_SparseMatrix1DVBC_TrSpMV_time(w_max, eltype(A), Int, Float64)

    mdl_blocks_2D = model_SparseMatrixVBC_blocks()
    mdl_memory_2D = model_SparseMatrixVBC_memory(eltype(A), Int)
    mdl_time_2D = model_SparseMatrixVBC_TrSpMV_time(3, w_max, w_max, eltype(A), Int, Float64)

    function print_model_grid(mdl, U, W)
        R = length(mdl.β_row)
        display([sum(ChainPartitioners.block_component(mdl.β_row[r], u) * ChainPartitioners.block_component(mdl.β_col[r], w) for r = 1:R) for u = 1:U, w = 1:W])
        println()
    end

    for (key, method) in [
        ("strict", StrictChunker(w_max)),
        ("overlap", OverlapChunker(0.9, w_max)),
        ("min blocks", DynamicTotalChunker(limit_width(mdl_blocks_1D))),
        ("min memory", DynamicTotalChunker(limit_width(mdl_memory_1D))),
        ("min time", DynamicTotalChunker(limit_width(mdl_time_1D))),
    ]
        B = SparseMatrix1DVBC{w_max}(A, method)
        setup_time = time(@benchmark SparseMatrix1DVBC{$w_max}($A, $method))

        x = rand(size(A, 1))
        y = rand(size(A, 2))
        z = A' * x

        mem = sizeof(B.Φ) + sizeof(B.pos) + sizeof(B.idx) + sizeof(B.ofs) + sizeof(B.val)

        run_time = time(@benchmark mul!($y, $B', $x, true, false))

        model_time = total_value(A, B.Φ, mdl_time_1D)

        @assert y ≈ z
        push!(rows, [key setup_time mem run_time model_time])
    end

    for (key, method) in [
        ("1D 2D", AlternatingPacker(DynamicTotalChunker(limit_width(mdl_blocks_1D)), EquiChunker(1))),
        ("strict 2D", AlternatingPacker(StrictChunker(w_max), StrictChunker(w_max))),
        ("overlap 2D 0.9", AlternatingPacker(OverlapChunker(0.9, w_max), OverlapChunker(0.9, w_max))),
        ("overlap 2D 0.8", AlternatingPacker(OverlapChunker(0.8, w_max), OverlapChunker(0.8, w_max))),
        ("overlap 2D 0.7", AlternatingPacker(OverlapChunker(0.7, w_max), OverlapChunker(0.7, w_max))),
        ("dynamic blocks 2D", AlternatingPacker(
            DynamicTotalChunker(limit_width(mdl_blocks_1D)),
            DynamicTotalChunker(limit_width(permutedims(mdl_blocks_2D))),
            DynamicTotalChunker(limit_width(mdl_blocks_2D)))),
        ("dynamic memory 2D", AlternatingPacker(
            EquiChunker(1),
            EquiChunker(1),
            DynamicTotalChunker(limit_width(mdl_memory_2D)),
            DynamicTotalChunker(limit_width(permutedims(mdl_memory_2D))),
            DynamicTotalChunker(limit_width(mdl_memory_2D)),
        )),
        ("dynamic time 2D", AlternatingPacker(
            EquiChunker(1),
            EquiChunker(1),
            DynamicTotalChunker(limit_width(mdl_time_2D)),
            DynamicTotalChunker(limit_width(permutedims(mdl_time_2D))),
            DynamicTotalChunker(limit_width(mdl_time_2D)),
        )),
    ]
        B = SparseMatrixVBC{w_max, w_max}(A, method)
        setup_time = time(@benchmark SparseMatrixVBC{$w_max, $w_max}($A, $method))

        x = rand(size(A, 1))
        y = rand(size(A, 2))
        z = A' * x

        mem = sizeof(B.Φ) + sizeof(B.Π) + sizeof(B.pos) + sizeof(B.idx) + sizeof(B.ofs) + sizeof(B.val)

        run_time = time(@benchmark mul!($y, $B', $x, true, false))

        model_time = total_value(A, B.Π, B.Φ, mdl_time_2D) + ChainPartitioners.row_component_value(B.Π, mdl_time_2D)

        @assert y ≈ z
        push!(rows, [key setup_time mem run_time model_time])
    end
    pretty_table(vcat(rows...), ["method", "setuptime", "memory", "runtime", "model"])
end


@inline function calc_A_second_order(x::VInt16, ::Val{N}) where N
    p = 15; r = 1; A = Int16(23170); C = Int16(-54292 >> N)
    rounding = one(x) << (p - N - 1)
    (rounding + A + (x * (x * C) >> N) >> r) >> (p - N)
end

@inline function calc_B_second_order(x::VInt16, ::Val{N}) where N
    r = 14; B = Int16(-16384 >> N);
    rounding = one(x) << (r - N - 1)
    (rounding + x * B) >> (r - N)
end

@inline function calc_A(x::VInt16, bits::B) where B <: Union{Val{3}, Val{4}, Val{5}}
    calc_A_second_order(x, bits)
end
@inline function calc_B(x::VInt16, bits::B) where B <: Union{Val{3}, Val{4}, Val{5}}
    calc_B_second_order(x, bits)
end


using SpikingNetworks
@unit
parseParameters"""
N=100
area = 20000*um^2
Cm = 1*ufarad*cm^(-2)*area
gl = 5e-5*siemens*cm^(-2)*area
El = -65*mV
EK = -90*mV
ENa = 50*mV
g_na = 100*msiemens*cm^(-2)*area
g_kd = 30*msiemens*cm^(-2)*area
VT = -63*mV
I=[Float64(0.7*nA*i/N) for i=0:N-1]"""

parseEquations"""
dv/dt=(gl*(El-v)-g_na*m^3*h*(v-ENa)-g_kd*n^4*(v-EK)+I)/Cm
dm/dt=0.32*(13-v+VT)/(exp((13-v+VT)/4)-1)*(1-m)-0.28*(v-VT-40)/(exp((v-VT-40)/5)-1)*m
dn/dt=0.032*(15-v+VT)/(exp((15-v+VT)/5)-1)*(1-n)-0.5*exp((10-v+VT)/40)*n
dh/dt=0.128*exp((17-v+VT)/18)*(1-h)-4/(1+exp((40-v+VT)/5))*h"""

net=init"""
N=N
v=1.0*El*ones(N)
m=0.*ones(N)
n=0.*ones(N)
h=1.*ones(N)"""
spike"""
v.>-40
3"""

@time run!(net,100/net.dt)
t,neuron=rasterPlot(net)
using MatlabPlot
mplot(SpikingNetwork.I,map(x->length(x),net.tSpike))


function ultra2cheb(c_ultra::AbstractVector{T},λ::T,plan::ChebyshevUltrasphericalPlan{FORWARD,T}) where T<:AbstractFloat
    M,N,nM₀,αN,K = getconstants(plan)
    i₁,i₂,j₁,j₂ = getindices(plan)
    p₁,p₂,rp,c₁,c₂,um,vm,θ,tempsin,tempsin2,tempsinλ,tempsinλm,tempmindices,cnλ,cnmλ = getplan(plan)

    v_cheb = zero(c_ultra)

    k=0
    for j=1:K
        k = j
        copyto!(cnmλ,cnλ)
        copyto!(tempsinλm,tempsinλ)

        j₁[k] < nM₀ && (k-=1; break)

        @inbounds for i=i₁[k]:i₁[k+1]-1 v_cheb[i] += backward_recurrence(c_ultra,j₂[k],θ[i],tempsin2[N+2-i],tempsin2[i],rp) end
        @inbounds for i=i₂[k+1]+1:i₂[k] v_cheb[i] += backward_recurrence(c_ultra,j₂[k],θ[i],tempsin2[N+2-i],tempsin2[i],rp) end

        for m=0:M-1
            # Initialize diagonal N-scaling multiplied by local coefficients and zero out excess
            init_c₁c₂!(c₁,c₂.parent,cnmλ,c_ultra,j₁[k],j₂[k])

            # Apply planned DCT-I and DST-I in-place
            applyTN!(c₁,p₁);applyUN!(c₂,p₂)

            # Compute u_m(θ) and v_m(θ)
            compute_umvm!(um,vm,λ,tempsinλm,m,θ,i₁[k+1]:i₂[k+1])

            # Multiply point-wise by u_m(θ) and v_m(θ) for valid indices
            @inbounds for i=i₁[k+1]:i₂[k+1] v_cheb[i] += um[i]*c₁[i] + vm[i]*c₂.parent[i] end

            # Update C_{n,m}^λ by recurrence in m
            cst = (λ+m)/2/(m+1)*(m+1-λ)
            @inbounds for i=1:N+1 cnmλ[i] *= cst/(i+λ+m) end

            # Update sin^{λ+m}(θ)
            @inbounds for i=1:N+1 tempsinλm[i] *= tempsin[i] end
        end
    end

    # Finish off recurrence
    @inbounds for i=i₁[k+1]:i₂[k+1] v_cheb[i] += backward_recurrence(c_ultra,j₂[k+1],θ[i],tempsin2[N+2-i],tempsin2[i],rp) end

    # perform IDCT-I
    applyTNinv!(v_cheb,p₁)
end