function demo()
    println("+-------------------------------------------+")
    println("|                                           |")
    println("|     LightML.jl demo for digits dataset    |")
    println("|                                           |")
    println("+-------------------------------------------+")
    # ...........
    #  LOAD DATA
    # ...........
    data = dat.load_digits()
    digit1 = 1
    digit2 = 8
    idx = vcat(find(data["target"] .== digit1), find(data["target"] .== digit2))
    y = data["target"][idx]
    # Change labels to {0, 1}
    y[y .== digit1] = -1
    y[y .== digit2] = 1

    X = data["data"][idx,:]
    X = normalize_(X)

    println("Dataset: The Digit Dataset (digits $(digit1) and $(digit2))" )
    # ..........................
    #  DIMENSIONALITY REDUCTION
    # ..........................
    pca = PCA(n_components=5)
    train!(pca, X) # Reduce to 5 dimensions
    X = transform(pca, X)

    # ..........................
    #  TRAIN / TEST SPLIT
    # ..........................
    X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.8)
    rescaled_y_train = (y_train + 1) ./ 2
    rescaled_y_test = (y_test + 1) ./2
    # .......
    #  SETUP
    # .......
    adaboost = Adaboost(n_clf = 10)
    naive_bayes = NaiveBayes()
    knn = KnnClassifier(k=4)
    logistic_regression = LogisticRegression()
    #mlp = NeuralNetwork(hidden = 3)
    decision_tree = ClassificationTree()
    random_forest = randomForest(n_estimators=20)
    support_vector_machine_rbf = svm(X_train, y_train, max_iter=500, kernel="rbf", C=0.6)
    support_vector_machine_linear = svm(X_train, y_train, max_iter=500, kernel="linear", C=0.6)
    #lda = LDA()
    gbc = GradientBoostingClassifier(n_clf=20, learning_rate=.9, max_depth=2)
    #xgboost = XGBoost(n_estimators=50, learning_rate=0.5)

    # ........
    #  TRAIN
    # ........

    println("\tk-nearst")
    train!(knn, X_train, y_train)
    println("\tAdaboost")
    train!(adaboost,X_train, y_train)
    println("\tDecision Tree")
    train!(decision_tree,X_train, y_train)
    println("\tGradient Boosting")
    train!(gbc,X_train, y_train)
    #println("\tLDA")
    #train!(lda,X_train, y_train)
    println("\tLogistic Regression")
    train!(logistic_regression,X_train, rescaled_y_train)
    #println("\tMultilayer Perceptron")
    #train!(mlp,X_train, y_train)
    println("\tNaive Bayes")
    train!(naive_bayes,X_train, y_train)
    #println("\tPerceptron")
    #train!(perceptron,X_train, y_train)
    println("\tRandom Forest")
    train!(random_forest,X_train, y_train)
    println("\tSupport Vector Machine with rbf kernel")
    train!(support_vector_machine_rbf)
    println("\tSupport Vector Machine with linear kernel")
    train!(support_vector_machine_linear)
    #println("\tXGBoost")
    #xgboost.fit(X_train, y_train)

    # .........
    #  PREDICT
    # .........
    y_pred = Dict()
    y_pred["Adaboost"] = predict(adaboost, X_test)
    y_pred["Gradient Boosting"] = predict(gbc,X_test)
    y_pred["Naive Bayes"] = predict(naive_bayes, X_test)
    y_pred["K Nearest Neighbors"] = predict(knn, X_test)
    y_pred["Logistic Regression"] = predict(logistic_regression, X_test)
    #y_pred["LDA"] = predict(lda, X_test)
    #y_pred["Multilayer Perceptron"] = sign(predict(mlp, X_test))
    #y_pred["Perceptron"] = perceptron.predict(X_test)
    y_pred["Decision Tree"] = predict(decision_tree, X_test)
    y_pred["Random Forest"] = predict(random_forest, X_test)
    y_pred["Support Vector Machine, rbf kernel"] = predict(support_vector_machine_rbf, X_test)
    y_pred["Support Vector Machine, linear kernel"] = predict(support_vector_machine_linear, X_test)

    #y_pred["XGBoost"] = xgboost.predict(X_test)

    # ..........
    #  ACCURACY
    # ..........
    println("Accuracy:")
    for clf in keys(y_pred)
        if clf == "Logistic Regression"
            temp = accuracy(rescaled_y_test,y_pred[clf])
            st = temp * 100
            st = @sprintf "%.3f" st
            println("\t$(clf): $(st) %")
        else
            temp = accuracy(y_test,y_pred[clf])
            st = temp * 100
            st = @sprintf "%.3f" st
            println("\t$(clf): $(st) %")
        end
    end

    # .......
    #  PLOT
    # .......

    x1 = X_test[:, 1]
    x2 = X_test[:, 2]
    df = DataFrame(x = x1, y = x2, clu = y_test)
    Gadfly.plot(df, x = "x", y = "y", color = "clu", Geom.point, Guide.title("PCA"))
end


function distributeZ2(D; p = 0.5)
    D0 = ceil(Int, p*D)
    D1 = D - D0
    return [(0, D0), (1, D1)]
end

distributeU1(D; p = 0.25) = distributeU1_poisson(D; p = p)

function distributeU1_exponential(D; p = 0.25)
    λ = (1-p)/(1+p)
    D0 = ceil(Int, p*D)
    if isodd(D-D0)
        D0 = D0 == 1 ? 2 : D0-1
    end
    sectors = [(0, D0)]
    Drem = D - D0
    n = 1
    while Drem > 0
        pn = p * λ^n
        Dn = ceil(Int, pn*D)
        sectors = push!(sectors, (n, Dn), (-n, Dn))
        Drem -= 2*Dn
        n += 1
    end
    return sort!(sectors, by = first)
end

function distributeU1_poisson(D; p = 0.25)
    λ = log((1/p+1)/2)
    D0 = ceil(Int, p*D)
    if isodd(D-D0)
        D0 = D0 == 1 ? 2 : D0-1
    end
    sectors = [(0, D0)]
    Drem = D - D0
    n = 1
    while Drem > 0
        pn = p * λ^n / factorial(n)
        Dn = ceil(Int, pn*D)
        sectors = push!(sectors, (n, Dn), (-n, Dn))
        Drem -= 2*Dn
        n += 1
    end
    return sort!(sectors, by = first)
end

module Timers
    struct Timer{F,D<:Tuple}
        f::F
        argsref::Base.RefValue{D}
        Timer(f, args...) = new{typeof(f), typeof(args)}(f, Ref(args))
    end

    @noinline donothing(arg) = arg
    function (t::Timer)(; inner = 1, outer = 1)
        args = t.argsref[]
        f = t.f
        f(args...) # run once to compile
        times = zeros(Float64, (outer,))
        gctimes = zeros(Float64, (outer,))
        @inbounds for i = 1:outer
            if inner == 1
                gcstart = Base.gc_num()
                start = Base.time_ns()
                donothing(f(args...))
                stop = Base.time_ns()
                gcstop = Base.gc_num()
            else
                gcstart = Base.gc_num()
                start = Base.time_ns()
                for _ = 1:inner
                    donothing(f(args...))
                end
                stop = Base.time_ns()
                gcstop = Base.gc_num()
            end
            times[i] = (stop - start)/(1e9)/inner
            gctimes[i] = Base.GC_Diff(gcstop, gcstart).total_time/1e9/inner
        end
        return times, gctimes
    end
end


module TestBlockFactorizations
using LinearAlgebra
using BlockFactorizations
using Test

@testset "block" begin

    element_types = (Float32, Float64, ComplexF32, ComplexF64)
    for elty_A in element_types
        @testset "eltype $elty_A" begin
            # strided block matrix
            di = 2
            dj = 3
            n = 5
            m = 7
            stride_matrices = [
                                [randn(elty_A, di, dj) for i in 1:n, j in 1:m], # generic strided matrix
                                Diagonal([randn(elty_A, di, dj) for i in 1:n]), # diagonal strided matrix
                                # IDEA: test with scalar elements as fallback
                              ]
            for A in stride_matrices
                # test general constructor
                di, dj = size(A[1])
                n, m = size(A)
                strided_factorizations = [BlockFactorization(A),
                                          BlockFactorization(A, isstrided = true),
                                          BlockFactorization(A, di, dj)]
                # stride_matrices = [A, A, A, D, D, D] # matrices corresponding to strided_factorizations
                for F in strided_factorizations
                    M = Matrix(F)
                    @test size(F) == (size(A, 1) * size(A[1], 1), size(A, 2) * size(A[1], 2)) # (n*di, m*dj)
                    @test F.nindices isa AbstractRange # testing that stridedness was correctly identified
                    @test F.mindices isa AbstractRange
                    @test eltype(F) == elty_A
                    @test eltype(M) == elty_A
                    for ni in 2:2, mi in 1:1
                        for i in 1:di, j in 1:dj
                            @test F[di*(ni-1)+i, dj*(mi-1)+j] == A[ni, mi][i, j]
                            @test M[di*(ni-1)+i, dj*(mi-1)+j] == A[ni, mi][i, j]
                        end
                    end
                    # matrix multiply
                    for elty_B in element_types
                        x = randn(elty_B, size(M, 2))
                        @test M*x ≈ F*x
                        k = 3
                        X = randn(elty_B, size(M, 2), k)
                        @test M*X ≈ F*X
                        # IDEA: test left multiply
                    end
                    # diagonal of factorization
                    diagF = diag(F)
                    @test length(diagF) == minimum(size(M))
                    @test diagF ≈ diag(M)
                end
                allo_1 = @allocated BlockFactorization(A, isstrided = true)
                allo_2 = @allocated BlockFactorization(A, isstrided = false)
                @test allo_1 < allo_2
            end

            # general, non-strided block matrix
            n, m = 4, 5 # this is the size of the non-block equivalent of the following matrices
            nindices = [1, 2, n+1]
            mindices = [1, 4, m+1]
            A = fill(randn(elty_A, 0, 0), 2, 2)
            A[1, 1] = randn(elty_A, 1, 3)
            A[1, 2] = randn(elty_A, 1, 2)
            A[2, 1] = randn(elty_A, 3, 3)
            A[2, 2] = randn(elty_A, 3, 2)
            # D = Diagonal([randn(elty_A, 1, 3), randn(elty_A, 3, 2)])
            matrices = [A, Diagonal(A)]

            for A in matrices
                factorizations = [BlockFactorization(A),
                                  BlockFactorization(A, isstrided = false),
                                  BlockFactorization(A, nindices, mindices)
                                  ]
                for F in factorizations
                    M = Matrix(F)
                    @test size(F) == size(M)
                    for i in 1:n, j in 1:m
                        @test F[i, j] == M[i, j]
                    end

                    # matrix multiply
                    for elty_B in element_types
                        x = randn(elty_B, size(M, 2))
                        @test M*x ≈ F*x
                        k = 3
                        X = randn(elty_B, size(M, 2), k)
                        @test M*X ≈ F*X
                        # IDEA: test left multiply
                    end
                end
            end

            # testing with vector elements
            n, m = 2, 5
            d = 3
            A = [randn(elty_A, d) for i in 1:n, j in 1:m]
            F = BlockFactorization(A)
            M = Matrix(F)
            for elty_B in element_types
                x = randn(elty_B, size(M, 2))
                @test M*x ≈ F*x
                k = 3
                X = randn(elty_B, size(M, 2), k)
                @test M*X ≈ F*X
                # IDEA: test left multiply
            end
            diagF = diag(F)
            @test length(diagF) == minimum(size(M))
            @test diagF ≈ diag(M)

            # testing with heterogeneous block types
            n = 3
            A11 = Diagonal(randn(elty_A, n))
            A12 = randn(elty_A, n, n)
            A21 = UpperTriangular(randn(elty_A, n, n))
            A22 = cholesky(Hermitian(A21'A21))
            A = reshape([A11, A12, A21, A22], 2, :)
            @test eltype(A) == Any
            B = BlockFactorization(A)
            @test eltype(B) == elty_A

            d, n = 2, 3
            A = Diagonal([randn(elty_A, d, d) for _ in 1:n])
            B = BlockFactorization(A)
            @test B isa BlockDiagonalFactorization
        end
    end
end

end # TestBlockFactorizations


using Catlab.CategoricalAlgebra, Catlab.Graphs, Catlab.Present, Catlab.Graphics, Catlab.Theories
using Catlab.CategoricalAlgebra.FinCats: FinCatGraphEq

using Distributions: Exponential, Geometric, cdf
using Random: randsubseq
using StatsBase: sample

using Plots
using ProgressBars

include("utils.jl")

# Lotka-Volterra
@present ThLV(FreeSchema) begin
    (Rabbit,Fox)::Ob
end

@acset_type LV(ThLV)

# birth of prey
L_birth = @acset LV begin Rabbit=1 end
I_birth = LV()
R_birth = @acset LV begin Rabbit=2 end

l_birth = ACSetTransformation(I_birth, L_birth)
r_birth = ACSetTransformation(I_birth, R_birth)

# predation
L_predation = @acset LV begin Rabbit=1; Fox=1 end
I_predation = @acset LV begin Fox=1 end
R_predation = @acset LV begin Fox=2 end

l_predation = ACSetTransformation(I_predation, L_predation; Fox=[1])
r_predation = ACSetTransformation(I_predation, R_predation; Fox=[1])

# death of predator
L_death = @acset LV begin Fox=1 end
I_death = LV()
R_death = LV()

l_death = ACSetTransformation(I_death, L_death)
r_death = ACSetTransformation(I_death, R_death)

# rules
rule_birth = Rule(l_birth, r_birth)
rule_predation = Rule(l_predation, r_predation)
rule_death = Rule(l_death, r_death)

# parameters
X = 50
Y = 10

Δt = 0.1
tmax = 365*2
steps = Int(tmax/Δt)

b = 1.0
mu = 0.01
β = 0.0025

# run model
state = @acset LV begin Rabbit=X; Fox=Y end

out = fill(-1, (steps, 2))

for t = ProgressBar(1:steps)

    # possible events
    birth = homomorphisms(L_birth, state, monic = true)
    predation = homomorphisms(L_predation, state, monic = true)
    death = homomorphisms(L_death, state, monic = true)

    # sample firings
    birth = sample_matches(birth, b, Δt)
    predation = sample_matches(predation, β, Δt)
    death = sample_matches(death, mu, Δt)

    # apply events
    events = MatchedRule[
        [MatchedRule(rule_birth, m) for m in birth]...,
        [MatchedRule(rule_predation, m) for m in predation]...,
        [MatchedRule(rule_death, m) for m in death]...
    ]
    while length(events) > 0
        ev = pop!(events)
        # apply event
        _, kg, _, kh = rewrite_match_maps(ev.rule.L, ev.rule.R, ev.match)
        state = codom(kh)
        # update the remaining matches post rewrite
        for j in 1:length(events)
            events[j].match = postcompose_partial(kg, kh, events[j].match)
        end
        events = filter((e) -> !isnothing(e.match), events)
    end
    # write output
    out[t, :] = [nparts(state, x) for x in [:Rabbit, :Fox]]
end

plot(
    (1:steps) * Δt,
    out,
    label=["Rabbit" "Fox"],
    xlabel="Time",
    ylabel="Number"
)


function kepler_init(time::T,mass::T,elements::Array{T,1}) where {T <: Real}
    # Takes orbital elements of a single Keplerian; returns positions & velocities.
    # This is 3D), so 6 orbital elements specified, the code returns 3D.  For
    # Inclination = pi/2, motion is in X-Z plane; sky plane is X-Y.
    # Elements are given by: period, t0, e*cos(omega), e*sin(omega), Inclination, Omega
    period = elements[1]
    # Compute the semi-major axis in AU (or other units specified by GNEWT):
    semi = cbrt(GNEWT*mass*period^2/4/pi^2)
    # Convert to eccentricity & longitude of periastron:
    ecc2=elements[3]^2+elements[4]^2
    ecc=sqrt(ecc2)
    #omega = atan2(elements[4],elements[3])
    omega = atan(elements[4],elements[3])
    # The true anomaly at the time of transit:
    f1 = 1.5*pi-omega
    # Compute the time of periastron passage:
    sqrt1mecc2 = sqrt(1.0-ecc^2)
    tp=(elements[2]+period*sqrt1mecc2/2.0/pi*(ecc*sin(f1)/(1.0+ecc*cos(f1))
                                              #    -2.0/sqrt1mecc2*atan2(sqrt1mecc2*tan(0.5*f1),1.0+ecc)))
    -2.0/sqrt1mecc2*atan(sqrt1mecc2*tan(0.5*f1),1.0+ecc)))
    # Compute the mean anomaly
    n = 2pi/period
    m=n*(time-tp)
    # Kepler solver:
    if ecc > 0.0
        f = kepler(m,ecc)
    else
        f = m
    end
    ecosfp1 = 1.0+ecc*cos(f)
    fdot = n*ecosfp1^2/sqrt1mecc2^3
    # Compute the radial distance:
    r=semi*(1.0-ecc^2)/ecosfp1
    rdot = semi*n/sqrt1mecc2*ecc*sin(f)
    # For now assume plane-parallel:
    #inc = pi/2
    #capomega = pi
    inc = elements[5]
    capomega = elements[6]
    # Now, compute the positions
    x = zeros(eltype(elements),3)
    v = zeros(eltype(elements),3)
    if abs(capomega-pi) > 1e-15
        coscapomega = cos(capomega) ; sincapomega = sin(capomega)
    else
        coscapomega = -1.0 ; sincapomega = 0.0
    end
    cosomegapf = cos(omega+f) ; sinomegapf = sin(omega+f)
    if abs(inc-pi/2) > 1e-15
        cosinc = cos(inc) ; sininc = sin(inc)
    else
        cosinc = 0.0 ; sininc = 1.0
    end
    x[1]=r*(coscapomega*cosomegapf-sincapomega*sinomegapf*cosinc)
    x[2]=r*(sincapomega*cosomegapf+coscapomega*sinomegapf*cosinc)
    x[3]=r*sinomegapf*sininc
    rdotonr = rdot/r
    # Compute the velocities:
    rfdot = r*fdot
    v[1]=x[1]*rdotonr+rfdot*(-coscapomega*sinomegapf-sincapomega*cosomegapf*cosinc)
    v[2]=x[2]*rdotonr+rfdot*(-sincapomega*sinomegapf+coscapomega*cosomegapf*cosinc)
    v[3]=x[3]*rdotonr+rfdot*cosomegapf*sininc
    return x,v
end

function kepler_init(time::T,mass::T,elements::Array{T,1},jac_init::Array{T,2}) where {T <: Real}
    # Takes orbital elements of a single Keplerian; returns positions & velocities.
    # This is 3D), so 6 orbital elements specified, the code returns 3D.  For
    # Inclination = pi/2, motion is in X-Z plane; sky plane is X-Y.
    # Elements are given by: period, t0, e*cos(omega), e*sin(omega), Inclination, Omega
    # Returns the position & velocity of Keplerian.
    # jac_init is the derivative of (x,v,m) with respect to (elements,m).
    period = elements[1]
    n = 2pi/period
    t0 = elements[2]
    # Compute the semi-major axis in AU (or other units specified by GNEWT):
    semi = cbrt(GNEWT*mass*period^2/4/pi^2)
    dsemidp = 2third*semi/period
    dsemidm = third*semi/mass
    # Convert to eccentricity & longitude of periastron:
    ecosomega = elements[3]
    esinomega = elements[4]
    ecc=sqrt(esinomega^2+ecosomega^2)
    deccdecos = ecosomega/ecc
    deccdesin = esinomega/ecc
    #omega = atan2(esinomega,ecosomega)
    # The true anomaly at the time of transit:
    #f1 = 1.5*pi-omega
    # Compute the time of periastron passage:
    sqrt1mecc2 = sqrt(1.0-ecc^2)
    #tp=(t0+period*sqrt1mecc2/2.0/pi*(ecc*sin(f1)/(1.0+ecc*cos(f1))
    #    -2.0/sqrt1mecc2*atan2(sqrt1mecc2*tan(0.5*f1),1.0+ecc)))
    den1 = esinomega-ecosomega-ecc
    tp = (t0 - sqrt1mecc2/n*ecosomega/(1.0-esinomega)-2/n*
          #     atan2(sqrt(1.0-ecc)*(esinomega+ecosomega+ecc),sqrt(1.0+ecc)*den1))
    atan(sqrt(1.0-ecc)*(esinomega+ecosomega+ecc),sqrt(1.0+ecc)*den1))
    dtpdp = (tp-t0)/period
    fac = sqrt((1.0-ecc)/(1.0+ecc))
    den2 = 1.0/den1^2
    theta = fac*(esinomega+ecosomega+ecc)/den1
    #println("theta: ",theta," tan(atan2()): ",tan(atan2(sqrt(1.0-ecc)*(esinomega+ecosomega+ecc),sqrt(1.0+ecc)*den1)))
    dthetadecc = ((ecc+ecosomega)^2+2*(1.0-ecc^2)*esinomega-esinomega^2)/(sqrt1mecc2*(1.0+ecc))*den2
    dthetadecos = 2fac*esinomega*den2
    dthetadesin = -2fac*(ecosomega+ecc)*den2
    dtpdecc = ecc/sqrt1mecc2/n*ecosomega/(1.0-esinomega)-2/n/(1.0+theta^2)*dthetadecc
    dtpdecos = dtpdecc*deccdecos -sqrt1mecc2/n/(1.0-esinomega)-2/n/(1.0+theta^2)*dthetadecos
    dtpdesin = dtpdecc*deccdesin -sqrt1mecc2/n*ecosomega/(1.0-esinomega)^2-2/n/(1.0+theta^2)*dthetadesin
    dtpdt0 = 1.0
    # Compute the mean anomaly
    m=n*(time-tp)
    dmdp = -m/period
    dmdtp = -n
    # Kepler solver: instead of true anomaly, return eccentric anomaly:
    ekep=ekepler(m,ecc)
    cosekep = cos(ekep); sinekep = sin(ekep)
    # Compute the radial distance:
    r=semi*(1.0-ecc*cosekep)
    drdekep = semi*ecc*sinekep
    drdecc = -semi*cosekep
    denom = semi/r
    dekepdecos = sinekep*denom*deccdecos
    dekepdesin = sinekep*denom*deccdesin
    dekepdm   = denom
    inc = elements[5]
    capomega = elements[6]
    # Now, compute the positions
    coscapomega = cos(capomega) ; sincapomega = sin(capomega)
    cosomega = ecosomega/ecc; sinomega = esinomega/ecc
    cosinc = cos(inc) ; sininc = sin(inc)
    # Define rotation matrices (M&D 2.119-2.120):
    P1 = [cosomega -sinomega 0.0; sinomega cosomega 0.0; 0.0 0.0 1.0]
    P2 = [1.0 0.0 0.0; 0.0 cosinc -sininc; 0.0 sininc cosinc]
    P3 = [coscapomega -sincapomega 0.0; sincapomega coscapomega 0.0; 0.0 0.0 1.0]
    P321 = P3*P2*P1
    # Express x & v in terms of eccentric anomaly (2.121, 2.41, 2.68):
    xplane = semi*[cosekep-ecc;  sqrt1mecc2*sinekep;  0.0] # position vector in orbital plane
    vplane = [-sinekep; sqrt1mecc2*cosekep; 0.0]
    x = P321*xplane
    # Now derivatives:
    dxda = x/semi
    dxdekep = P321*semi*vplane
    P32 = P3*P2
    #dxdecc  = -x/ecc + P321*semi*[-1.0; -ecc/sqrt1mecc2*sinekep; 0.0]
    # Get rid of cancellations in the prior expression:
    dxdecc  = -P321*semi/ecc*[cosekep; sinekep/sqrt1mecc2; zero(T)]
    # These may need to be rewritten to flag ecc = 0.0 case:
    dxdecos = dxdecc*deccdecos + P32/ecc*xplane
    dxdesin = dxdecc*deccdesin + P32/ecc*[-xplane[2]; xplane[1]; 0.0]
    dxdinc  = P3*[0.0 0.0 0.0; 0.0 -sininc -cosinc; 0.0 cosinc -sininc]*P1*xplane
    dxdcom  = [-sincapomega -coscapomega 0.0; coscapomega -sincapomega 0.0; 0.0 0.0 0.0]*P2*P1*xplane
    # Compute the velocities:
    v = P321*n*semi*denom*vplane
    #decc = 1e-5
    #dvp = P321*ecc/(ecc+decc)*n*semi/(1.0-(ecc+decc)*cosekep)*[-sinekep; sqrt(1.0-(ecc+decc)^2)*cosekep; 0.0]
    #dvm = P321*ecc/(ecc-decc)*n*semi/(1.0-(ecc-decc)*cosekep)*[-sinekep; sqrt(1.0-(ecc-decc)^2)*cosekep; 0.0]
    dvda    = v/semi
    dvdp    = -v/period
    dvdekep = -v*ecc*sinekep*denom+P321*n*semi*denom*[-cosekep; -sqrt1mecc2*sinekep; 0.0]
    dvdecc  = -v/ecc + v*cosekep*denom + P321*n*semi*denom*[0.0; -ecc/sqrt1mecc2*cosekep; 0.0]
    #dvdecc_num = .5*(dvp-dvm)/decc
    #println("dvdecc: ",dvdecc," dvdecc_num: ",dvdecc_num)
    #dvdecc = dvdecc_num
    dvdecos = dvdecc*deccdecos + P32*n*semi*denom/ecc*vplane
    dvdesin = dvdecc*deccdesin + P32*n*semi*denom/ecc*[-vplane[2]; vplane[1]; 0.0]
    dvdinc  = P3*[0.0 0.0 0.0; 0.0 -sininc -cosinc; 0.0 cosinc -sininc]*P1*n*semi*denom*vplane
    dvdcom  = [-sincapomega -coscapomega 0.0; coscapomega -sincapomega 0.0; 0.0 0.0 0.0]*P2*P1*n*semi*denom*vplane
    # Now, take derivatives (11/15/2017 notes):
    # Elements are given by: period, t0, e*cos(omega), e*sin(omega), Inclination, Omega
    fill!(jac_init,0.0)
    jac_init[1:3,1] = dxda*dsemidp + dxdekep*dekepdm*(dmdp+dmdtp*dtpdp)
    #if  T != BigFloat
    #  println("position vs. period: term 1 ",dxda*dsemidp," term 2: ",dxdekep*dekepdm*dmdp," term 3: ",dxdekep*dekepdm*dmdtp*dtpdp," sum: ",jac_init[1:3,1])
    #end
    jac_init[1:3,2] = dxdekep*dekepdm*dmdtp*dtpdt0
    jac_init[1:3,3] = dxdecos + dxdekep*(dekepdm*dmdtp*dtpdecos + dekepdecos)
    #if  T != BigFloat
    #  println("position vs. ecosom : term 1 ",dxdecos," term 2: ",dxdekep*dekepdm*dmdtp*dtpdecos," term 3: ",dxdekep*dekepdm*dekepdecos," sum: ",jac_init[1:3,3])
    #end
    jac_init[1:3,4] = dxdesin + dxdekep*(dekepdm*dmdtp*dtpdesin + dekepdesin)
    #if  T != BigFloat
    #  println("position vs. esinom : term 1 ",dxdesin," term 2: ",dxdekep*dekepdm*dmdtp*dtpdesin," term 3: ",dxdekep*dekepdm*dekepdesin," sum: ",jac_init[1:3,4])
    #end
    jac_init[1:3,5] = dxdinc
    jac_init[1:3,6] = dxdcom
    jac_init[1:3,7] = dxda*dsemidm
    jac_init[4:6,1] = dvdp + dvda*dsemidp +  dvdekep*dekepdm*(dmdp+dmdtp*dtpdp)
    #if  T != BigFloat
    #  println("velocity vs. period: term 1 ",dvdp," term 2: ",dvda*dsemidp," term 3: ",dvdekep*dekepdm*dmdp," term 4: ",dvdekep*dekepdm*dmdtp*dtpdp," sum: ",jac_init[4:6,1])
    #end
    jac_init[4:6,2] = dvdekep*dekepdm*dmdtp*dtpdt0
    jac_init[4:6,3] = dvdecos + dvdekep*(dekepdm*dmdtp*dtpdecos + dekepdecos)
    #if  T != BigFloat
    #  println("velocity vs. ecosom : term 1 ",dvdecos," term 2: ",dvdekep*dekepdm*dmdtp*dtpdecos," term 3: ",dvdekep*dekepdm*dekepdecos," sum: ",jac_init[4:6,3])
    #end
    jac_init[4:6,4] = dvdesin + dvdekep*(dekepdm*dmdtp*dtpdesin + dekepdesin)
    #if  T != BigFloat
    #  println("velocity vs. esinom : term 1 ",dvdesin," term 2: ",dvdekep*dekepdm*dmdtp*dtpdesin," term 3: ",dvdekep*dekepdm*dekepdesin," sum: ",jac_init[4:6,4])
    #end
    jac_init[4:6,5] = dvdinc
    jac_init[4:6,6] = dvdcom
    jac_init[4:6,7] = dvda*dsemidm
    jac_init[7,7] = 1.0
    #return x,v,tp,dtpdecos,dtpdesin
    #return x,v,ekep,dekepdm*dmdtp*dtpdecos + dekepdecos,dekepdm*dmdtp*dtpdesin + dekepdesin
    return x,v
end


module MultiprecisionLP

using NNLS
import Base: convert
export LinearProgram,
       PNNLSProblem,
       NNLSProblem,
       solve,
       solve_multiprecision

immutable PNNLSProblem{T, QR}
    A::Matrix{T}
    B::Matrix{T}
    c::Vector{T}
    B_QR::QR
end

function PNNLSProblem{T}(A::AbstractMatrix{T}, B::AbstractMatrix{T}, c::AbstractVector{T})
    B_QR = qrfact(B)
    PNNLSProblem{T, typeof(B_QR)}(A, B, c, B_QR)
end

immutable LinearProgram{T}
    f::Vector{T}
    G::Matrix{T}
    W::Vector{T}
end

immutable NNLSProblem{T}
    A::Matrix{T}
    b::Vector{T}
end

function convert{T1, T2}(::Type{NNLSProblem{T1}}, p::PNNLSProblem{T2})
    A = convert(Matrix{T1}, p.A)
    B = convert(Matrix{T1}, p.B)
    c = convert(Vector{T1}, p.c)
    Abar = A - B * (p.B_QR \ A)
    bbar = c - B * (p.B_QR \ c)
    NNLSProblem{T1}(Abar, bbar)
end

convert{T}(::Type{NNLSProblem}, p::PNNLSProblem{T}) = convert(NNLSProblem{T}, p)

function convert{T1, T2}(::Type{PNNLSProblem{T1}}, lp::LinearProgram{T2})
    q, n = size(lp.G)
    A = [lp.W'       zeros(1, q)
         zeros(q, q) eye(q)
         lp.G'       zeros(n, q)]
    B = [lp.f'
         lp.G
         zeros(n, n)]
    c = vcat(0, lp.W, -lp.f)
    PNNLSProblem(convert(Matrix{T1}, A),
        convert(Matrix{T1}, B),
        convert(Vector{T1}, c))
end

convert{T}(::Type{PNNLSProblem}, lp::LinearProgram{T}) = convert(PNNLSProblem{T}, lp)

convert{T1, T2}(::Type{LinearProgram{T1}}, lp::LinearProgram{T2}) = LinearProgram{T1}(lp.f, lp.G, lp.W)

function solve(p::NNLSProblem)
    work = NNLSWorkspace(p.A, p.b)
    nnls!(work)
    work.x, work.rnorm
end

function solve(p::PNNLSProblem)
    v, residual = solve(convert(NNLSProblem, p))
    u = -(p.B_QR \ (p.A * v - p.c))
    v, u, residual
end

function solve(p::LinearProgram)
    v, u, residual = solve(convert(PNNLSProblem, p))
    y = v[1:length(p.W)]
    s = v[(length(p.W) + 1):end]
    y, s, u, residual
end

function _solve_multiprecision(p::LinearProgram{BigFloat}, tolerance=1e-12)
    lastresidual = BigFloat(Inf)
    precision = 100
    while true
        y, s, u, residual = setprecision(BigFloat, precision) do
            solve(p)
        end
        @show residual
        is_feasible = residual < tolerance
        insufficient_progress = residual >= lastresidual^(3/2)
        at_iteration_limit = precision > 1000
        if is_feasible || insufficient_progress || at_iteration_limit
            return y, s, u, residual
        else
            precision += 100
            lastresidual = residual
        end
    end
end

function solve_multiprecision{T}(p::LinearProgram{T}, tolerance=1e-12)
    y, s, u, residual = solve(p)
    @show residual
    if residual < tolerance
        return y, s, u, residual, true
    else
        ybig, sbig, ubig, rbig = _solve_multiprecision(LinearProgram{BigFloat}(p), tolerance)
        return T.(ybig), T.(sbig), T.(ubig), T(rbig), rbig < tolerance
    end
end

end


# Important packages
println("== Compiling packages...")
using JLD

# Loading the array
println("== Loading data...")
count = load("backup_matrices/counts.jld")["counts"]

# Checks if the count array is symmetric
isSymmetric = Symmetric(count) == count
println("== The count array is symmetric: ", isSymmetric)
if (isSymmetric == false)
	exit
end

# Normalizing the count array
println("== Normalizing count array (ETA: 1h30 from start)...")
count /= norm(count)

# Saving the normalized array
println("== Exporting normalized array...")
save("backup_matrices/normalized.jld", "norm", count)

# Decomposing w/ eigendecomposition
println("== Eigendecomposition (ETA: 50min from start)...")
d, v = eig(count)

# Saving matrices
println("== Exporting eigenvalues and eigenvectors arrays...")
save("backup_matrices/eigenvalues.jld", "values", d)
save("backup_matrices/eigenvectors.jld", "vectors", v)


module TestVI
using GaussianProcesses, Distributions, PDMats, Plots, LinearAlgebra
gr()
using Test, Random
Random.seed!(203617)

@testset "VI" begin
    n = 20
    X = collect(range(-3,stop=3,length=n));
    f = 2*cos.(2*X);
    Y = [rand(Poisson(exp.(f[i]))) for i in 1:n];

    k = Matern(3/2,0.0,0.0)   # Matern 3/2 kernel
    l = PoisLik()             # Poisson likelihood
    gp = GP(X, vec(Y), MeanZero(), k, l)
    set_priors!(gp.kernel,[Normal(-2.0,4.0),Normal(-2.0,4.0)])
    Q, Ω, K = initialise_Q(gp)

    @testset "AutoDiff" begin
        exact = -0.5*exp(Q.m[end] + Q.V[end, end]/2)
        zyg_calc = dv_var_exp(l, Y[end], Q.m[end], Q.V[end, end])
        @test exact ≈ zyg_calc # Test AutoDiff is giving the same results as exact gradient computation
    end

    @testset "Poisson" begin
        Q = vi(gp, nits=10)
        println("Positive Definite: ", isposdef(Q.V))
        println("-------------------")

        xtest = range(minimum(gp.x),stop=maximum(gp.x),length=50)

        nsamps = 500
        ymean = [];
        visamples = Array{Float64}(undef, nsamps, size(xtest, 1))

        for i in 1:nsamps
            visamples[i, :] = rand(gp, xtest, Q)
            push!(ymean, predict_y(gp, xtest)[1])
        end

        q10 = [quantile(visamples[:,i], 0.1) for i in 1:length(xtest)]
        q50 = [quantile(visamples[:,i], 0.5) for i in 1:length(xtest)]
        q90 = [quantile(visamples[:,i], 0.9) for i in 1:length(xtest)]
        # plot(xtest, exp.(q50), ribbon=(exp.(q10), exp.(q90)), leg=true, fmt=:png, label="quantiles")
        plot(xtest, mean(ymean), label="posterior mean", w=2)
        xx = range(-3,stop=3,length=1000);
        f_xx = 2*cos.(2*xx);
        plot!(xx, exp.(f_xx), label="truth")
        scatter!(X,Y, label="data")
        savefig("vi_gp.png")
    end

    # @testset "Gaussian" begin
    #     n=10;                          #number of training points
    #     x = 2π * rand(n);              #predictors
    #     y = sin.(x) + 0.05*randn(n);   #regressors
    #     mZero = MeanZero()                   #Zero mean function
    #     kern = SE(0.0,0.0)                   #Sqaured exponential kernel (note that hyperparameters are on the log scale)
    #     l = GaussLik(0.1)             # Poisson likelihood

    #     logObsNoise = -1.0                        # log standard deviation of observation noise (this is optional)
    #     gp = GP(x, y, mZero, kern, l)       #Fit the GP

    #     Q = vi(gp)

    #     xtest = range(minimum(gp.x),stop=maximum(gp.x),length=50)

    #     nsamps = 500
    #     ymean = [];
    #     visamples = Array{Float64}(undef, nsamps, size(xtest, 1))

    #     for i in 1:nsamps
    #         visamples[i, :] = rand(gp, xtest, Q)
    #         push!(ymean, predict_y(gp, xtest)[1])
    #     end

    #     q10 = [quantile(visamples[i,:], 0.1) for i in 1:length(xtest)]
    #     q50 = [quantile(visamples[:,i], 0.5) for i in 1:length(xtest)]
    #     q90 = [quantile(visamples[:,i], 0.9) for i in 1:length(xtest)]
    #     plot(xtest, exp.(q50), ribbon=(exp.(q10), exp.(q90)), leg=true, fmt=:png, label="quantiles")
    #     plot!(xtest, mean(ymean), label="posterior mean", w=2)
    #     xx = range(-3,stop=3,length=1000);
    #     f_xx = 2*cos.(2*xx);
    #     plot!(xx, exp.(f_xx), label="truth")
    #     scatter!(X,Y, label="data")
    #     savefig("vi_gp.png")
    # end
 end
 end


#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Two Body orbit propagator.
#
#       This algorithm considers a perfect Keplerian orbit. In other words, no
#       perturbation is considered during the propagation and the Earth is
#       modeled as a perfect sphere.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
#
#   [1] Vallado, D. A (2013). Fundamentals of Astrodynamics and Applications.
#       Microcosm Press, Hawthorn, CA, USA.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

export TwoBody_Structure
export twobody_init, twobody!

################################################################################
#                                  Overloads
################################################################################

# Copy for TwoBody_Structure.
Base.copy(m::TwoBody_Structure) =
    TwoBody_Structure(m.epoch, m.a_0, m.e_0, m.i_0, m.Ω_0, m.ω_0, m.M_0, m.Δt,
                      m.a, m.M_k, m.f_k, m.μ)

################################################################################
#                                  Functions
################################################################################

"""
    function twobody_init(epoch::Number, a_0::Number, e_0::Number, i_0::Number, Ω_0::Number, ω_0::Number, f_0::Number, μ::T) where T

Initialize the data structure of Two Body orbit propagator algorithm.

# Args

* `epoch`: Epoch of the orbital elements [s].
* `a_0`: Initial semi-major axis [m].
* `e_0`: Initial eccentricity.
* `i_0`: Initial inclination [rad].
* `Ω_0`: Initial right ascension of the ascending node [rad].
* `ω_0`: Initial argument of perigee [rad].
* `f_0`: Initial true anomaly.
* `μ`: Standard gravitational parameter of the central body [m^3/s^2].

# Returns

The structure `TwoBody_Structure` with the initialized parameters.

"""
function twobody_init(epoch::Number, a_0::Number, e_0::Number, i_0::Number,
                      Ω_0::Number, ω_0::Number, f_0::Number, μ::T) where T

    # The propagator is only defined for 0 <= e < 1.
    if !(0 <= e_0 < 1)
        throw(ArgumentError("The Two Body propagator only supports eccentricities in the interval [0,1)"))
    end

    # Compute the mean motion using the semi-major axis.
    n_0 = sqrt(μ/a_0^3)

    # Compute the initial mean anomaly.
    M_0 = f_to_M(e_0, f_0)

    # Create and return the Two Body orbit propagator structure.
    TwoBody_Structure{T}(epoch, a_0, e_0, i_0, Ω_0, ω_0, M_0, f_0, 0, n_0, M_0, f_0, μ)
end

"""
    function twobody!(tbd::TwoBody_Structure, t::Number)

Propagate the orbit defined in `tbd` (see `TwoBody_Structure`) until the time
`t` [s]. Notice that the values in `tbd` will be modified.

# Returns

* The position vector represented in the inertial frame at time `t` [m].
* The velocity vector represented in the inertial frame at time `t` [m/s]

# Remarks

The inertial frame in which the output is represented depends on which frame it
was used to generate the orbit parameters. If the orbit parameters are obtained
from a TLE, then the inertial frame will be TEME.

"""
function twobody!(tbd::TwoBody_Structure, t::Number)
    # Time elapsed since epoch.
    Δt     = t
    tbd.Δt = Δt

    # Update the mean anomaly.
    tbd.M_k = tbd.M_0 + tbd.n_0*Δt

    # Convert the mean anomaly to true anomaly.
    tbd.f_k = M_to_f(tbd.e_0, tbd.M_k)

    # Compute the position and velocity vectors given the orbital elements.
    (r_i_k, v_i_k) =
        kepler_to_rv(tbd.a_0, tbd.e_0, tbd.i_0, tbd.Ω_0, tbd.ω_0, tbd.f_k)

    # Return the position and velocity vector represented in the inertial
    # reference frame.
    (r_i_k, v_i_k)
end