using Test
include("../Vec3.jl")

x1, y1, z1, x2, y2, z2 = rand(1:100,6)
l1 = sqrt(x1^2+y1^2+z1^2);
v1 = Vec3(x1,y1,z1);
v2 = Vec3(x2,y2,y2);
@testset "Space related stuff" begin
    @test x(v1) == x1;
    @test y(v1) == y1;
    @test z(v1) == z1;
end

@testset "Color related stuff" begin
    @test r(v1) == x1;
    @test g(v1) == y1;
    @test b(v1) == z1;
end

@testset "Vector operations" begin
    @test length(v1) == l1;
    @test make_unit_vector(v1) == Vec3(x1/l1, y1/l1, z1/l1);
end


# Falta adaptar a função para diferentes resoluções radiométricas
# Para tanto, deve-se mudar o zeros(256)

function otsu_threshold_(in_img,t="image")
    tipo = typeof(in_img)

    img_orig = convert(Array{Float64},in_img)
    img = convert(Array{Int32},round(Int,in_img*255))

    d = size(img)
    hist = zeros(256)

    # Making the histogram
    for i=1:d[1]
        for j=1:d[2]
            hist[img[i,j]+1] += 1
        end
    end
    # end of the making

    total = d[1]*d[2]

    sumB = 0
    wB = 0
    max = 0.0
    sum1 = sum(img)

    for i=1:156
        wB += hist[i]
        wF = total - wB
        if wB == 0 || wF == 0
            continue
        end
        sumB = sumB + (i-1) * hist[i]
        mF = (sum1 - sumB) / wF
        between = wB * wF * ((sumB / wB) - mF) * ((sumB / wB) - mF)
        if between >= max
            level = i
            maximum = between
        end
    end

    level = level/255.0

    if t=="lvl"
        return(level)
    elseif t=="image"
        img_result = zeros(d)
        for i=1:d[1]
            for j=1:d[2]
                if img_orig[i,j] >= level
                    img_result[i,j] = 1.0
                end
            end
        end
        return(img_result)
    end
end


# This script will usually fail as Stan gives up when the integration fails.

using StanSample, StatsPlots

ProjDir = @__DIR__

ben_model = "
functions {
  real[] dz_dt(real t,       // time
               real[] z,     // system state {prey, predator}
               real[] theta, // parameters
               real[] x_r,   // unused data
               int[] x_i) {
    real u = z[1];
    real v = z[2];

    real alpha = theta[1];
    real beta = theta[2];
    real gamma = theta[3];
    real delta = theta[4];

    real du_dt = (alpha - beta * v) * u;
    real dv_dt = (-gamma + delta * u) * v;

    return { du_dt, dv_dt };
  }
}

data {
  int<lower = 0> N;
  real t0;
  real ts[N];
  real y_init[2];
  real y[N, 2];
}

transformed data {
  real beta = 1.0;
  real gamma = 3.0;
  real delta = 1.0;
}

parameters {
  real alpha;
  real z_init[2];
  real<lower = 0> sigma[2];
}

transformed parameters {
  real theta[4] = { alpha, beta, gamma, delta };
  real z[N, 2] = integrate_ode_rk45(dz_dt, z_init, t0, ts, theta,
                       rep_array(0.0, 0), rep_array(0, 0),
                       1e-5, 1e-3, 500);
}

model {
  alpha ~ normal(1.5, 0.01);

  sigma ~ inv_gamma(3, 3);
  z_init ~ normal(1, 0.01);

  for (k in 1:2) {
    y_init[k] ~ normal(z_init[k], sigma[k]);
    y[ , k] ~ normal(z[, k], sigma[k]);
  }
}
";

sm = SampleModel("Ben", ben_model, tmpdir="$(@__DIR__)/tmp")

obs = reshape([
    2.75487369287842, 6.77861583902452, 0.977380864533721,
    1.87996617432547,6.10484970865812, 1.38241825210688, 1.32745986436496,
    4.35128045090906, 3.28082272129074, 1.02627964892546,
    0.252582832985904, 2.01848763447693, 1.91654508055532,
    0.33796473710305, 0.621805831293324, 3.47392930294277, 0.512736737236118,
    0.304559407399392, 4.56901766791213, 0.91966250277948], (10,2))

ben_data = Dict(
  :N => 10,
  :t0 => 0,
  :ts => 1:10,
  :y => obs,
  :y_init => [1.01789244983237, 0.994539126829031]
)

t = ben_data[:ts]

plot(xlab="t", ylab="prey and pred")
plot!(t, obs[:,1], lab="prey")
plot!(t, obs[:, 2], lab="pred")
savefig("$(ProjDir)/observations.png")

@time rc = stan_sample(sm; data=ben_data)

if success(rc)
  p = read_samples(sm, output_format=:particles)
  display(p)
end


include("../functions/Functions.jl")
include("../evaluate/Evaluate.jl")
include("../opt/SAGA.jl")
include("../data/data.jl")

X, y = getOldData()

x0 = zeros(size(X,2),1)

H = logreg_hessian(X,y,x0)

(vec,val) = eig(H)
println(vec)
println(val)


using Printf
using Plots

n =100
del, E, F ,B = init(n)

x = collect(0:n+1)*pi/(n+1)
U0 = 2.0*sin.(x)
x =x[2:n+1]
U0 = U0[2:n+1]

Ut = zeros(n)
Ux= 2.0*cos.(x)

w1 = del
w2 = del.*(E.+1.0)
w0 = hcat(w1,w2)

function runSim(w0,U0,Ut,Ux,tEnd)

tStart = 0;
t1 =tStart;
deltaT = 0.005;
#tEnd = 1.5;
w = zeros(n,2)

while t1 < tEnd

w, dt = FVMIBL(w0,U0,Ut,Ux);
ww = w
del = ww[:,1]
E = (ww[:,2]./ww[:,1]) .- 1.0
ncell = length(del)
Csep = zeros(ncell-1)
#xtrunc = x[2:end-1]
#for i = 2:ncell-1
    #Csep[i] = (del[i] - del[i-1])/(del[i+1] - del[i])
    #if Csep[i] > 5.
    #    println("singularity (separation) detected at t=$t1, x=$(x[i]./pi), Csep=$(Csep[i])")
    #end
#end
display(plot(x/pi,[del, E], xticks = 0:0.1:1, layout=(2,1), legend = false))
sleep(0.05)
w0 = w;

@printf("Time :%1.10f , Time step size %1.10f \n", t1, dt);
#map(x -> @sprintf("Seperation occure at :%1.10f  \n",x), xtrunc[seperation]./pi);

t1 = t1+dt
end

return del, E
end


using LinearAlgebra
import ExpmV.normAm
import LinearAlgebra:BlasFloat, checksquare

export expm3

"""
    expm3(X)

Matrix exponential computed using
a scaling and squaring algorithm with a Taylor approximation evaluated
using a new decomposition.

When the maximal order is selected, estimates of norm(A^9,1) are used
to reduce overscaling

The choice of order and scaling is based solely on norm(A,1)
"""
function expm3(A::StridedMatrix{T}) where {T<:BlasFloat}

    """
        expm_params(A)

    expm_helper Obtain scaling parameter and order of the Taylor
    approximant
    """
    function expm_params(A)

        m_vals = [1 2 4 8 12 18]
        if T == Float64

            # theta_m for m=2:18.
            theta = T[
                2.220446049250313e-16  # m_vals = 1
                2.580956802971767e-08  # m_vals = 2
                3.397168839976962e-04  # m_vals = 4
                4.991228871115323e-02  # m_vals = 8
                2.996158913811580e-01  # m_vals = 12
                1.090863719290036e+00
            ]# m_vals = 18

        elseif T == Float32

            # theta_m for m=1:7.
            theta = T[
                1.192092800768788e-7   # m_vals = 1
                5.978858893805233e-04  # m_vals = 2
                5.116619363445086e-02  # m_vals = 4
                5.800524627688768e-01  # m_vals = 8
               #7.795113374358031e-01
               #9.951840790004457e-01
               #1.223479542424143e+00
                1.461661507209034e+00  # m_vals = 12
                3.010066362817634e+00
            ]# m_vals = 18

        else
            throw("Input matrix element type must be Float32 or Float64")
        end

        s = 0

        normA = opnorm(A, 1)

        Tp = []
        scale = Int[]

        # Find appropriate order:
        push!(Tp, copy(A))
        push!(scale, 1)
        ## Check if degree 1 is sufficient (No info available)
        if (normA < theta[1])
            degree = 1
            return s, degree, Tp, scale
        end
        push!(Tp, A * A)
        push!(scale, 2)
        if (normA < theta[2])
            degree = 2
            return s, degree, Tp, scale
        end
        if (normA < theta[3])
            degree = 4
            return s, degree, Tp, scale
        end
        # At degree 8, we could do the first estimates
        # with pairs (2,3), (3,4), (3,5), (2,9) [Also (2,5), (2,7)]

        if (normA < theta[4])
            degree = 8
            return s, degree, Tp, scale
        end
        push!(Tp, A * Tp[2])
        push!(scale, 3)
        if (normA < theta[5])
            degree = 12
            return s, degree, Tp, scale
        end

        # Otherwise degree 18
        # Here, we only gain the pairs (3, 8) and (4, 7),
        # but both are not expected to
        # yield improvements, however, we also get (2, 2k+1) until (2,17)
        degree = 18
        push!(Tp, Tp[3] * Tp[3])
        push!(scale, 6)
        d2 = norm(Tp[2], 1)^(1 / 2)
        d3 = norm(Tp[3], 1)^(1 / 3)
        d6 = norm(Tp[4], 1)^(1 / 6)
        eta = min(max(d2, d3), normA)
        quotients = minimum([d2 / normA, d3 / normA, d6 / normA])
        if (quotients < 1 / 2^4)  ## strong decay
            d9 = normAm(A, 9)^(1 / 9)
            d19 = normA
            eta = min([eta, max(d2, d9), max(d2, d19)])
        end
        s = max(ceil(log2(eta / theta[6])), 0)
        if isinf(s)
            # Overflow in ell subfunction. Revert to old estimate.
            x = opnorm(T, 1) / theta[end]
            t, s = significand(x) / 2, exponent(x) + 1
            s = s - Int(t == 0.5) # adjust s if normA/theta(end) is a power of 2.
        end

        return s, degree, Tp, scale

    end

    s, degree, Tp, scale = expm_params(A)

    # Rescale the powers of A appropriately.

    if s != 0
        for i in eachindex(Tp)
            Tp[i] ./= 2 .^ (s * scale[i])
        end
    end

    """
        taylor_approximant_of_degree(order, Tp)

    Evaluate the Taylor polynomial to given degree.
    Improved Paterson Stockmeyer scheme, with Pade at end point
    """

    order = degree

    X = Tp[1]
    order >= 4 && (A2 = Tp[2])
    order > 8  && (A3 = Tp[3])

    if order == 1
        E = I + X
    elseif order == 2
        E = I + X
    elseif order == 4
        E = I + X + A2 / 2 * (I + X / 3 + A2 / 3 / 4)
    elseif order == 8
        # Minimizes ||coefficients||_1
        x3 = 2 / 3
        a1 = 1 / 88 * (1 + sqrt(177)) * x3
        a2 = 1 / 352 * (1 + sqrt(177)) * x3
        u0 = 1
        u1 = 1
        u2 = 1 / 630 * (857 - 58 * sqrt(177))
        c0 = (-271 + 29 * sqrt(177)) / (315 * x3)
        c1 = (11 * (-1 + sqrt(177))) / (1260 * x3)
        c2 = (11 * (-9 + sqrt(177))) / (5040 * x3)
        c4 = -((-89 + sqrt(177)) / (5040 * x3^2))
        # Matrix products
        A4 = A2 * (a1 * X + a2 * A2)
        A8 = (x3 * A2 + 1 * A4) * (c0 * I + c1 * X + c2 * A2 + c4 * A4)
        E = u0 * I + u1 * X + u2 * A2 + +A8
    elseif order == 12 # lower values of ||_||_1
        a01 = -0.0186023205146205532243437300433
        a02 = 4.60000000000000000000000000000
        a03 = 0.211693118299809442949323323336
        a04 = 0
        a11 = -0.00500702322573317730979741843919
        a12 = 0.992875103538486836140479571505
        a13 = 0.158224384715726725371768893252
        a14 = -0.131810610138301840156819349464
        a21 = -0.573420122960522263905952420789
        a22 = -0.132445561052799638845074997454
        a23 = 0.165635169436727415011171668419
        a24 = -0.0202785554058925907933568229945
        a31 = -0.133399693943892059700768926983
        a32 = 0.00172990000000000000000000000000
        a33 = 0.0107862779315792425026320640108
        a34 = -0.00675951846863086359778560766482
        q31 = a01 * I + a11 * X + a21 * A2 + a31 * A3
        q32 = a02 * I + a12 * X + a22 * A2 + a32 * A3
        q33 = a03 * I + a13 * X + a23 * A2 + a33 * A3
        q34 = a04 * I + a14 * X + a24 * A2 + a34 * A3
        # Matrix products
        q61 = q33 + q34 * q34
        E = (q31 + (q32 + q61) * q61)
    elseif order == 18
        # Minimizes ||coefficients||_1
        a01 = 0
        a11 = -0.100365581030144620014614939405
        a21 = -0.00802924648241156960116919515244
        a31 = -0.000892138498045729955685466128049
        b01 = 0
        b11 = 0.3978497494996450761451961277102845756965081084076856223845951607640145373149032030404660339703426170
        b21 = 1.367837784604117199225237068782228242106453540654915795267462332707000476284638745738812082761860458
        b31 = 0.4982896225253826775568588172622733561849181397319696269923450773179744578906675427707618377504305561
        b61 = -0.0006378981945947233092415500564919285518773827581013332055148653241353120789646323186965398317523194760
        b02 = -10.96763960529620625935174626753684124863041876254774214673058385106461743913502064396554589372626845
        b12 = 1.680158138789061971827854017248105095278579593010566858091585875627364747895724070033586802947436157
        b22 = 0.05717798464788655127028717132252481317274975093707600394506105236273081373356534970690710643085727120
        b32 = -0.006982101224880520842904664838015531664389838152077753258353447125605205335288966277257918925881337834
        b62 = 0.00003349750170860705383133673406684398020225996444991565389728295589367037178816169580298011831485225359
        b03 = -0.09043168323908105619714688770183495499470866281162649735086602288456671216199491949073419844120202066
        b13 = -0.06764045190713819075600799003797074854562826013273060070581796689746210078264908587143648477465431597
        b23 = 0.06759613017704596460827991950789370565071000768138331455747136830953224978586305376886338214283464385
        b33 = 0.02955525704293155274260691822422312437473046472743510159951445245685893720550532525991666174704105350
        b63 = -0.00001391802575160607011247399793437424028074102305234870853220069004278819595344896368546425681168813708
        b04 = 0
        b14 = 0
        b24 = -0.0923364619367118592764570775143
        b34 = -0.0169364939002081717191385115723
        b64 = -0.0000140086798182036159794363205726

        # Extra matrix product
        A6 = Tp[4]
        q31 = a01 * I + a11 * X + a21 * A2 + a31 * A3
        q61 = (b01 * I + b11 * X + b21 * A2 + b31 * A3 + b61 * A6)
        q62 = (b02 * I + b12 * X + b22 * A2 + b32 * A3 + b62 * A6)
        q63 = (b03 * I + b13 * X + b23 * A2 + b33 * A3 + b63 * A6)
        q64 = (b04 * I + b14 * X + b24 * A2 + b34 * A3 + b64 * A6)
        q91 = q31 * q64 + q63
        q18 = q61 + (q62 + q91) * q91
        E = q18
    else
        throw("expm3= degree:$order")
    end

    for i = 1:s
        E = E * E  # Squaring
    end

    E

end


abstract type MappingFunctions end

struct Sigmoid   <: MappingFunctions end
struct HardLimit <: MappingFunctions end
struct ReLU      <: MappingFunctions end
struct Cosine    <: MappingFunctions end
struct Qubit     <: MappingFunctions end
struct HyperbolicTangent <: MappingFunctions end

function mapping(W::AbstractMatrix{T}, b::AbstractMatrix{T}, X::AbstractMatrix{T}, mf::MappingFunctions) where {T<:BaseType}
    return map(x->mapfunc(x, mf), W*X .+ b)
end

function mapping(W::AbstractVector{T}, b::T, X::AbstractVector{T}, mf::MappingFunctions) where {T<:BaseType}
    return map(x-mapfunc(x, mf), dot(conj(W),X) + b)
end

function mapfunc(x::T, mf::Sigmoid) where {T<:BaseType}
    return one(T)/(one(T)+exp(-x))
end

function mapfunc(x::T, mf::HardLimit) where {T<:BaseType}
    return x >= zero(T) ? one(T) : zero(T)
end

function mapfunc(x::T, mf::ReLU) where {T<:BaseType}
    return x >= zero(T) ? x : zero(T)
end

function mapfunc(x::T, mf::Cosine) where {T<:BaseType}
    return cos(x)
end

function mapfunc(x::T, mf::Qubit) where {T<:BaseType}
    return sin(0.5*pi*x)^2
end

function mapfunc(x::T, mf::HyperbolicTangent) where {T<:BaseType}
    v = exp(-x)
    return (one(T)-v)/(one(T)+v)
end

# split-type functions
for mf in (:Sigmoid, :HardLimit, :ReLU, :Cosine, :Qubit, :HyperbolicTangent)
    @eval begin
        struct $(Symbol(mf,:Split)) <: MappingFunctions end
        export $(Symbol(mf,:Split))

        function mapfunc(x::T, mf::$(Symbol(mf,:Split))) where {T<:AbstractFloat}
            return mapfunc(x, $(mf)())
        end

        function mapfunc(x::Complex{T}, mf::$(Symbol(mf,:Split))) where {T<:AbstractFloat}
            return complex(mapfunc(real(x), $(mf)()),
                           mapfunc(imag(x), $(mf)()))
        end

        function mapfunc(x::Quaternion{T},  mf::$(Symbol(mf,:Split))) where {T<:AbstractFloat}
            return quaternion(mapfunc(real(x),  $(mf)()),
                              mapfunc(imagi(x), $(mf)()),
                              mapfunc(imagj(x), $(mf)()),
                              mapfunc(imagk(x), $(mf)()))
        end
    end
end


function yeast(;
  R=50, L=2, RL=0, G=50, Ga=0, Gbg=0, Gd=0,
  k1=0.0038, k2=4e-4, k3=0.042, k4=0.01, k5=0.011, k6=0.1, k7=1.05e-3, k8=3.21)
  m = Network("Yeast polarization")

  m <= Species("R", R) # receptor
  m <= Species("L", L) # ligand
  m <= Species("RL", RL) # receptor-ligand
  m <= Species("G", G) # G protein
  m <= Species("Ga", Ga) # G alpha subunit
  m <= Species("Gbg", Gbg) # G beta-gamma subunit
  m <= Species("Gd", Gd) # G delta subunit

  m <= Reaction("receptor upregulation", k1, "0 --> R")
  m <= Reaction("receptor downregulation", k2, "R --> 0")
  m <= Reaction("ligand binding", k3, "L + R --> RL + L")
  m <= Reaction("ligand degradation", k4, "RL --> R")
  m <= Reaction("protein activation", k5, "RL + G --> Ga + Gbg + RL")
  m <= Reaction("dephosphorylation", k6, "Ga --> Gd")
  m <= Reaction("rebinding", k7, "Gd + Gbg --> G")
  m <= Reaction("bound receptor upregulation", k8, "0 --> RL")

  return m
end

model = yeast()


using GlobalSensitivity, Test, OrdinaryDiffEq

@info "Morris Method"

A = reshape([1,0,2,3],2,2)
function f_morris(p)
    A*p
end

function linear_batch(X)
    A= 7
    B= 0.1
    @. A*X[1,:]+B*X[2,:]
end

function neg_linear_batch(X)
    A= -7
    B= 0.1
    @. A*X[1,:]+B*X[2,:]
end

function ishi(X)
    A= 7
    B= 0.1
    sin(X[1]) + A*sin(X[2])^2+ B*X[3]^4 *sin(X[1])
end

function ishi_linear(X)
    A= 7
    B= 0.1
    [sin(X[1]) + A*sin(X[2])^2+ B*X[3]^4 *sin(X[1]),A*X[1]+B*X[2]]
end

function ishi_linear_batch(X)
    A= 7
    B= 0.1
    X1 = @. sin(X[1,:]) + A*sin(X[2,:])^2+ B*X[3,:]^4 *sin(X[1,:])
    X2 = @. A*X[1,:]+B*X[2,:]
    vcat(X1',X2')
end

lb = -ones(4)*π
ub = ones(4)*π

m = gsa(f_morris,Morris(p_steps=[10,10],total_num_trajectory=1000,num_trajectory=150),[[1,5],[1,5]])
@test m.means[:,1] ≈ A[:,1] atol=1e-12
@test m.means[:,2] ≈ A[:,2] atol=1e-12
@test m.means_star[:,1] ≈ A[:,1] atol=1e-12
@test m.means_star[:,2] ≈ A[:,2] atol=1e-12
@test m.variances ≈ reshape([0,0,0,0],2,2) atol=1e-12

m = gsa(f_morris,Morris(p_steps=[10,10],relative_scale=true,total_num_trajectory=1000,num_trajectory=150),[[1,5],[1,5]])
@test m.means[2,1] ≈ 0 atol=1e-12
@test m.means_star[2,1] ≈ 0 atol=1e-12
@test m.variances[2,1] ≈ 0 atol=1e-12
@test m.means[1,2] < m.means[2,2]
@test m.means_star[1,2] < m.means_star[2,2]

m = gsa(linear_batch,Morris(p_steps=[10,10],num_trajectory=50000),[[1,5],[1,5]],batch=true)
@test m.means ≈ [7.0  0.1] atol = 1e-2
@test m.means_star ≈ [7.0  0.1] atol = 1e-2
@test m.variances ≈ reshape([0,0], 1, 2) atol=1e-12

m = gsa(neg_linear_batch,Morris(p_steps=[10,10],num_trajectory=50000),[[1,5],[1,5]],batch=true)
@test m.means ≈ [-7.0  0.1] atol = 1e-2
@test m.means_star ≈ [7.0  0.1] atol = 1e-2
@test m.variances ≈ reshape([0,0], 1, 2) atol=1e-12

m = gsa(ishi, Morris(num_trajectory=500000), [[lb[i],ub[i]] for i in 1:4])
@test m.means_star[1,:] ≈ [2.25341,4.40246,2.5049,0.0] atol = 5e-2
@test m.means[1, :] ≈ [-0.416876, -0.0077712, -0.015714,  0.0] atol = 5e-2
@test m.means_star[1,:] ≈ [2.25341,4.40246,2.5049,0.0] atol = 5e-2

m = gsa(ishi_linear,Morris(num_trajectory=500000),[[lb[i],ub[i]] for i in 1:4])
@test m.means_star[1,:] ≈ [2.25341,4.40246,2.5049,0.0] atol = 5e-2
@test m.means_star[2,:] ≈ [7.0,0.1,0.0,0.0] rtol = 1e-12

m = gsa(ishi_linear_batch,Morris(num_trajectory=500000),[[lb[i],ub[i]] for i in 1:4],batch=true)
@test m.means_star[1,:] ≈ [2.25341,4.40246,2.5049,0.0] atol = 5e-2
@test m.means_star[2,:] ≈ [7.0,0.1,0.0,0.0] rtol = 1e-12

function f(du,u,p,t)
  du[1] = p[1]*u[1] - p[2]*u[1]*u[2] #prey
  du[2] = -p[3]*u[2] + p[4]*u[1]*u[2] #predator
end

u0 = [1.0;1.0]
tspan = (0.0,10.0)
p = [1.5,1.0,3.0,1.0]
prob = ODEProblem(f,u0,tspan,p)
t = collect(range(0, stop=10, length=200))

f1 = function (p)
  prob1 = remake(prob;p=p)
  sol = solve(prob1,Tsit5();saveat=t)
  return sol
end

m = gsa(f1,Morris(total_num_trajectory=1000,num_trajectory=150),[[1,5],[1,5],[1,5],[1,5]])


struct Enc{T<:Group}
    pk::T
    g::T
end

(enc::Enc{T})(m::T, r::Integer) where T <: Group = (enc.g^r, m*enc.pk^r) ### Message first?
(enc::Enc)(r::Integer) = (enc.g^r, enc.pk^r)

a(x::Tuple{T, T}) where T <: Group = x[1]
b(x::Tuple{T, T}) where T <: Group = x[2]

*(x::Tuple{G, G}, y::Tuple{G, G}) where G <: Group = (a(x)*a(y), b(x)*b(y))

(enc::Enc)(e::Tuple{G, G}, r::Integer) where G <: Group = e * enc(r)

struct ElGamal{G <: Group} <: AbstractVector{G}
    a::Vector{G}
    b::Vector{G}

    function ElGamal{G}(a::Vector{G}, b::Vector{G}) where {G <: Group}
        @assert length(a) == length(b)
        return new(a, b)
    end
end

ElGamal(a::Vector{G}, b::Vector{G}) where G <: Group = ElGamal{G}(a, b)

ElGamal(e::Vector{Tuple{G, G}}) where G <: Group = ElGamal([a(i) for i in e], [b(i) for i in e])

function ElGamal{G}(a::Vector{T}, b::Vector{T}) where {T, G<:Group}
    a′ = convert(Vector{G}, a)
    b′ = convert(Vector{G}, b)

    return ElGamal{G}(a′, b′)
end

a(e::ElGamal) = e.a
b(e::ElGamal) = e.b

Base.getindex(e::ElGamal, i::Int) = (a(e)[i], b(e)[i])
Base.getindex(e::ElGamal{G}, ivec::Vector) where G <: Group = ElGamal{G}(a(e)[ivec], b(e)[ivec])
Base.length(e::ElGamal) = length(a(e))
Base.size(e::ElGamal) = size(a(e))

Base.copy(e::ElGamal{G}) where G <: Group = ElGamal{G}(copy(a(e)), copy(b(e)))
Base.sort(e::ElGamal) = sort!(copy(e))

### NEW METHOD
function Base.setindex!(e::ElGamal{G}, val::Tuple{G, G}, i) where G <: Group
    a(e)[i] = a(val)
    b(e)[i] = b(val)
end

function *(x::ElGamal{G}, y::ElGamal{G}) where G <: Group

    @assert length(x) == length(y)

    a′ = a(x) .* a(y)
    b′ = b(x) .* b(y)

    return ElGamal(a′, b′)
end

function *(x::ElGamal{G}, y::Tuple{G, G}) where G <: Group

    a′ = a(x) .* a(y)
    b′ = b(x) .* b(y)

    return ElGamal(a′, b′)
end

*(x::Tuple{G, G}, y::ElGamal{G}) where G <: Group = y * x

import Base: ^

^(x::Tuple{G, G}, k::Integer) where G <: Group = (x[1]^k, x[2]^k)
^(x::ElGamal, k::AbstractVector{<:Integer}) = ElGamal(a(x) .^ k, b(x) .^ k)
^(x::ElGamal, k::Integer) = ElGamal(a(x) .^ k, b(x) .^ k)

Base.broadcasted(::typeof(^), x::ElGamal, y::AbstractVector{<:Integer}) = x^y

(enc::Enc)(e::ElGamal, r::Integer) = enc(r) * e

function (enc::Enc{G})(m::Vector{G}, r::AbstractVector{<:Integer}) where G <: Group

    a′ = enc.g .^ r
    b′ = m .* (enc.pk .^ r)

    return ElGamal(a′, b′)
end

function (enc::Enc{G})(𝐞::ElGamal{G}, 𝐫::AbstractVector{<:Integer}) where G <: Group

    ### Need to improve this
    r = ElGamal(enc.(𝐫))
    𝐞′ = 𝐞 * r

    return 𝐞′
end

struct Dec
    sk::Integer
end

(dec::Dec)(e::Tuple{G, G}) where G = b(e) * a(e)^(-dec.sk)

(dec::Dec)(e::ElGamal) = [dec(ei) for ei in e]

Base.isless(x::Tuple{G, G}, y::Tuple{G, G}) where G <: Group = x[1] == y[1] ? x[2] < y[2] : x[1] < y[1]

Base.prod(e::ElGamal{G}) where G <: Group = (prod(e.a), prod(e.b))


function kmp(p,ATMp,Wip1)
    return p[C.kmp0]*(p[C.Dp0]+p[C.Dwp]*Wip1)/(p[C.Sp0]+p[C.Dp0]+p[C.Sap]*ATMp+p[C.Dwp]*Wip1)
end

function Kpm(p,ATMp,Wip1)
    return p[C.Kpm0]*(p[C.Sp0]+p[C.Dp0]+p[C.Sap]*ATMp+p[C.Dwp]*Wip1)/(p[C.Sp0]+p[C.Sap]*ATMp)
end

function Kpw(p,ATMp,Wip1)
    return p[C.Kpw0]*(p[C.Sp0]+p[C.Dp0]+p[C.Sap]*ATMp+p[C.Dwp]*Wip1)/(p[C.Sp0]+p[C.Sap]*ATMp)
end

function γm(p,ATMp,Wip1,γm0,γm1)
    return (γm0*(p[C.Dm0]+p[C.Dwm]*Wip1)+γm1*(p[C.Sm0]+p[C.Sam]*ATMp))/(p[C.Sm0]+p[C.Dm0]+p[C.Sam]*ATMp+p[C.Dwm]*Wip1)
end

function diffeq(du,u,h,p,t)

    du[V.p53] = p[C.kp0] - kmp(p,u[V.ATMp],u[V.Wip1])*u[V.p53]*u[V.Mdm2] - p[C.γp]*u[V.p53];

    du[V.Mdm2] = p[C.km0] + p[C.kpm]*(h(p,t-p[C.τm])[V.p53]^p[C.nm])/(Kpm(p,h(p,t-p[C.τm])[V.ATMp],h(p,t-p[C.τm])[V.Wip1])^p[C.nm]+h(p,t-p[C.τm])[V.p53]^p[C.nm]) - γm(p,u[V.ATMp],u[V.Wip1],p[C.γm0],p[C.γm1])*u[V.Mdm2];

    du[V.Wip1] = p[C.kw0] + p[C.kpw]*(h(p,t-p[C.τw])[V.p53]^p[C.nw])/(Kpw(p,h(p,t-p[C.τw])[V.ATMp],h(p,t-p[C.τw])[V.Wip1])^p[C.nw]+h(p,t-p[C.τw])[V.p53]^p[C.nw]) - p[C.γw]*u[V.Wip1];

    du[V.ATMp] = p[C.Sea]*p[C.Eto]*(p[C.ATMt] - u[V.ATMp]) - p[C.Da0]*u[V.ATMp];

end

function param_values_U2OS()
    p = zeros(C.NUM)

    p[C.kp0] = 1.3396
    p[C.km0] = 0.08
    p[C.kw0] = 0.105
    p[C.kpm] = 0.9172
    p[C.kpw] = 0.1496
    p[C.nm] = 4
    p[C.nw] = 4
    p[C.Kpm0] = 0.4025
    p[C.Kpw0] = 0.4025
    p[C.τm] = 2.1
    p[C.τw] = 2.1
    p[C.kmp0] = 4.038
    p[C.γp] = 0.1
    p[C.γm0] = 0.2579
    p[C.γm1] = 7.7362
    p[C.γw] = 0.4
    p[C.Sea] = 1
    p[C.Da0] = 80
    p[C.Sp0] = 0.25
    p[C.Sap] = 0.8335
    p[C.Dp0] = 0.75
    p[C.Dwp] = 0.25
    p[C.Sm0] = 0.0153
    p[C.Sam] = 0.283
    p[C.Dm0] = 0.5
    p[C.Dwm] = 0.5
    p[C.ATMt] = 16

    return p
end

function param_values_MCF7()
    p = zeros(C.NUM)

    p[C.kp0] = 2.4138
    p[C.km0] = 0.2
    p[C.kw0] = 0.35
    p[C.kpm] = 2.3854
    p[C.kpw] = 0.8702
    p[C.nm] = 4
    p[C.nw] = 4
    p[C.Kpm0] = 0.4025
    p[C.Kpw0] = 0.4025
    p[C.τm] = 2.1
    p[C.τw] = 2.1
    p[C.kmp0] = 2.7048
    p[C.γp] = 0.25
    p[C.γm0] = 0.1804
    p[C.γm1] = 3.6088
    p[C.γw] = 0.4
    p[C.Sea] = 1
    p[C.Da0] = 30
    p[C.Sp0] = 0.25
    p[C.Sap] = 0.4335
    p[C.Dp0] = 0.75
    p[C.Dwp] = 0.25
    p[C.Sm0] = 0.0479
    p[C.Sam] = 0.2830
    p[C.Dm0] = 0.5
    p[C.Dwm] = 0.5
    p[C.ATMt] = 3.5

    return p
end

function initialValues_U2OS()
    u0 = zeros(V.NUM)

    u0[V.p53] = 1
    u0[V.Mdm2] = 0.4
    u0[V.Wip1] = 0.3
    u0[V.ATMp] = 0

    return u0
end

function initialValues_MCF7()
    u0 = zeros(V.NUM)

    u0[V.p53] = 1
    u0[V.Mdm2] = 1
    u0[V.Wip1] = 1
    u0[V.ATMp] = 0

    return u0
end


abstract type FilterMethod
end
struct JosephForm <: FilterMethod
end
struct SimpleKalman <: FilterMethod
end

function correct(SSM::LinearStateSpaceModel, u::T, y) where T
    x, Ppred = meancov(u)
    H, R = SSM.obs.H, SSM.obs.R
    yres = y - H*x # innovation residual

    S = H*Ppred*H' + R # innovation covariance

    K = Ppred*H'/S # Kalman gain
    x = x + K*yres
    P = (I - K*H)*Ppred*(I - K*H)' + K*R*K'
    T(x, P), yres, S
end

correct(O::LinearObservation, u, y) = correct(JosephForm(), u, (Gaussian(y, O.R), O.H))

function correct(method::JosephForm, u::T, (v, H)::Tuple{<:Gaussian, <:Any}) where T
    x, Ppred = meancov(u)
    y, R = meancov(v)
    yres = y - H*x # innovation residual

    S = H*Ppred*H' + R # innovation covariance

    K = Ppred*H'/S # Kalman gain
    x = x + K*yres
    P = (I - K*H)*Ppred*(I - K*H)' + K*R*K'
    T(x, P), yres, S
end

"""

Single Kalman filter with control being the observation, consisting of a prediction step
(evolution of the underlying system) and a correction step `correct`.
Return filtered state `t => (x, P)` and predicted `(xpred, Ppred)`.

Computes and returns as well the log likelihood of the residual.
"""
function dyniterate(M::StateSpaceModel, ((s, u), ll)::Condition{<:Pair}, v)
    t, y = v
    t, upred = evolve(M.sys, s => u, t)
    u, yres, S = correct(M, upred, y)
    llᵒ = llikelihood(M, yres, S)
    (t => (u, upred, ll + llᵒ)), Condition(t => u, ll + llᵒ)
end

function dyniterate(M::StateSpaceModel, ((value,), dummy)::Condition{<:Start}, v)
    s, u = value
    t, y = v
    t, upred = evolve(M.sys, s => u, t)
    u, yres, S = correct(M, upred, y)
    ll = llikelihood(M, yres, S)
    (t => (u, upred, ll)), Condition(t => u, ll)
end

function dyniterate(O::LinearObservation{<:LinearEvolution}, ((s, u), ll)::Condition, v)
    t, y = v
    t, upred = evolve(O.P, s => u, t)
    u, yres, S = correct(O, upred, y)
    llᵒ = llikelihood(O, yres, S)
    (t => u), Condition(t => u, ll + llᵒ)
end

function dyniterate(O::LinearObservation{<:LinearEvolution}, ((s, u), ll)::Filter, v)
    t, y = v
    t, upred = evolve(O.P, s => u, t)
    u, yres, S = correct(O, upred, y)
    llᵒ = llikelihood(O, yres, S)
    (t => (u, upred, ll + llᵒ)), Filter(t => u, ll + llᵒ)
end

function dyniterate(O::LinearObservation{<:LinearEvolution}, start::Start{<:Filter}, v)
    ((s, u), ll) = start.value
    t, y = v
    t, upred = evolve(O.P, s => u, t)
    u, yres, S = correct(O, upred, y)
    llᵒ = llikelihood(O, yres, S)
    (t => (u, upred, ll + llᵒ)), Filter(t => u, ll + llᵒ)
end

llikelihood(yres, S) = logpdf(Gaussian(zero(yres), symmetrize!(S)), yres)
llikelihood(_, yres, S) = logpdf(Gaussian(zero(yres), symmetrize!(S)), yres)

#=
function dyniterate(M::StateSpaceModel, (s, u)::Pair, (v,)::Observe)
    t, (v, H) = v
    t, upred = evolve(M.sys, s => u, t)
    u, yres, S, K = correct(JosephForm(), upred, (v, H))
    ll = llikelihood(M, yres, S)
    t => (u, Ppred, ll, K), t => u
end
=#

"""
    kalmanfilter(M, t => x0) -> kf

`kf(iter)` is an iterator over `Gaussian`s or `Distributions` representing
the filtered distribution of `x` where `y` iterates over
(enumerated) signal values.

# Example

```
kf = kalmanfilter(M, 0 => prior) #
est1 = collect(kf(Y1))
est2 = collect(kf(Y2))
```
"""
kalmanfilter(M, prior) = iter -> filter(iter, from(M, prior))
kalmanfilter(M) = iter -> filter(iter, M)

function kalmanfilter(M, prior, Y)
    P = filter(Y, M)

    ϕ = dyniterate(P, Start(prior))
    ϕ === nothing && error("no observations")
    (t, u), state = ϕ

    X = trajectory((t => u[1],))
    while true
        ϕ = dyniterate(P, state)
        ϕ === nothing && break
        (t, u), state = ϕ
        push!(X, t => u[1])
    end
    ll = u[3]
    X, ll
end

function kalmanfilter(O::Observation, prior, Y)
    P = Bind(Y, O)

    ϕ = dyniterate(P, Start(Filter(prior, 0.0)))
    ϕ === nothing && error("no observations")
    (t, u), state = ϕ

    X = trajectory((t => u[1],))
    while true
        ϕ = dyniterate(P, state)
        ϕ === nothing && break
        (t, u), state = ϕ
        push!(X, t => u[1])
    end
    ll = u[3]
    X, ll
end


using TensorToolbox
using LinearAlgebra
using Arpack
using TensorDecompositions
using InvertedIndices
using Random
Random.seed!(123)
include("../methods.jl")

function get_tensor(A, reqrank, input_tensor_shape)
    N = length(input_tensor_shape)
    W = zeros(input_tensor_shape...)
    for r=1:reqrank
        Xr = ttt(A[1][:, r], A[2][:, r])
        for n=3:N
            Xr = ttt(Xr, A[n][:, r])
        end
        W = W + Xr
    end

    return W
end

function FastNTFAPG(Y, reqrank ; max_iter = 2, epsilon = 0.00001, tol = 0.0, verbose = true)
    # input is a nonnegative tensor
    # proposed by Guoxu Zhou in 2014
    # https://link.springer.com/content/pdf/10.1007/978-3-319-12436-0_51.pdf
    @assert length(Set(reqrank)) == 1 "Core Tensor should be cubic"
    reqrank = reqrank[1]

    N = ndims(Y)
    input_tensor_shape = size(Y)

    # any unconstrained CP decomposition algorithm
    init_pos = []
    for i=1:N
        push!(init_pos, randn(input_tensor_shape[i], reqrank))
    end
    # You can replace "ALS" by "SGSD".
    # The paper use CP-ALS. See implement detail in Sec4
    U = candecomp(Y, reqrank, Tuple(init_pos), verbose=false, method=Symbol("ALS"))

    for n = 1:N
        for r = 1 : reqrank
            U.factors[n][r] = U.factors[n][r] .* U.lambdas[r]
        end
    end
    U = U.factors

    # see ep.(13)
    A = []
    #for n = 1:N-1
    for n = 1:N
        # adjust sign
        for j = 1:reqrank
            if n != N
                i0 = argmax( abs.( U[n][:,j] ) )
                U[n][:, j] = U[n][:,j] .* sign(i0)
            else
                i0 = argmax( abs.( U[2][:,j] ) )
                U[N][:, j] = U[N][:,j] .* sign(i0)
            end
        end
        # initialiaze An
        push!(A, max.(U[n], epsilon)) #P+(Un) is max.(Un, 0)
    end

    iter = 1
    cost_at_init = norm( get_tensor(A, reqrank, input_tensor_shape) - Y)
    previous_cost = cost_at_init
    while iter <= max_iter
        for n = 1:N

            # Get G = Bn'Bn
            # See eq(11)
            ps = [1:N;][Not(n)]
            G = A[ps[1]]' * A[ps[1]]
            ps = ps[2:end]
            for p in ps
                G = G .* (A[p]'*A[p])
            end
            L = norm(G)

            # Get C=YnBn.
            # See ep(12)
            ps = [1:N;][Not(n)]
            UptAp = U[ps[1]]' * A[ps[1]]
            ps = ps[2:end]
            for p in ps
                UptAp = UptAp .* ( U[p]' * A[p] )
            end
            YnBn = U[n]*UptAp
            C = YnBn

            # sub lootuine in NTF_APG
            # see Algorithm 1. in paper
            k = 1
            previous_alphak = 1.0
            previous_Ak = A[n]
            Ak = A[n]
            Z = A[n]
            while k < 10
                Ak = max.(Z - 1.0 / L * ( C - previous_Ak*G), epsilon)
                alphak = (1.0 + sqrt( 4.0 * previous_alphak^2 + 1) ) / 2.0
                Z = Ak + (previous_alphak - 1.0) / alphak * (Ak - previous_Ak)

                k += 1
                previous_Ak = Ak
                previous_alphak = alphak
            end
            A[n] = Ak

            #if tol > 0 && iter % 1 == 0
            if true
                cost = norm( get_tensor(A, reqrank, input_tensor_shape) - Y)
                if verbose
                    println("iter: $iter cost: $cost")
                end
                #if (previous_cost - cost) / cost_at_init < tol
                #    break
                #end
                previous_cost = cost
            end

        end
        iter += 1

    end

    # reconstract
    G = zeros( fill(reqrank, N)... )
    for i = 1:reqrank
        idx = fill(i, N)
        G[idx...] = 1.0
    end

    X = ttm(G, A[1], 1)
    for n=2:N
        X = ttm(X, A[n], n)
    end

    return X
end

function main()
    Y = rand(6,6,6,6)
    X = FastNTFAPG(Y, [2,2,2,2], max_iter=1, epsilon = 1.0E-3)
    println( norm(Y-X) )

    X = lraSNTD(Y,[2,2,2,2])
    println( norm(Y-X) )

    X = NNCP(Y,[2,2,2,2])
    println( norm(Y-X) )
end

main()


function bellman_ford_shortest_paths(
    g::AbstractGraph{U},
    sources::AbstractVector{<:Integer},
    distmx::AbstractMatrix{T}=weights(g)
    ) where T<:Real where U<:Integer

    nvg = nv(g)
    active = Set{U}()
    sizehint!(active, nv(g))
    for s in sources
        union!(active, outneighbors(g, s))
    end
    dists = fill(typemax(T), nvg)
    parents = zeros(U, nvg)
    dists[sources] .= 0

    for i in one(U):nvg
        _loop_body!(g, distmx, dists, parents, active)

        isempty(active) && break
    end

    isempty(active) || throw(LightGraphs.NegativeCycleError())
    return BellmanFordState(parents, dists)
end

#Helper function used due to performance bug in @threads.
function _loop_body!(
    g::AbstractGraph{U},
    distmx::AbstractMatrix{T},
    dists::Vector{T},
    parents::Vector{U},
    active::Set{U}
    ) where T<:Real where U<:Integer

    prev_dists = deepcopy(dists)

    tmp_active = collect(active)
    @threads for v in tmp_active
        prev_dist_vertex = prev_dists[v]
        for u in inneighbors(g, v)
                relax_dist = (prev_dists[u] == typemax(T) ? typemax(T) : prev_dists[u] + distmx[u,v])
                if prev_dist_vertex > relax_dist
                    prev_dist_vertex = relax_dist
                    parents[v] = u
                end
        end
        dists[v] = prev_dist_vertex
    end

    empty!(active)
    for v in vertices(g)
        if dists[v] < prev_dists[v]
            union!(active, outneighbors(g, v))
        end
    end
end

function has_negative_edge_cycle(
    g::AbstractGraph{U},
    distmx::AbstractMatrix{T}
    ) where T<:Real where U<:Integer
    try
        Parallel.bellman_ford_shortest_paths(g, vertices(g), distmx)
    catch e
        isa(e, LightGraphs.NegativeCycleError) && return true
    end
    return false
end


using COSMO
using Convex, SparseArrays, LinearAlgebra

export filterConvexPrices, isArbitrageFree

function isArbitrageFree(strikes::Vector{T}, callPrices::Vector{T}, forward::T)::Tuple{Bool,Int} where {T}
    for (xi, yi) in zip(strikes, callPrices)
        if yi < max(forward - xi, 0)
            return (false, i)
        end
    end

    for i = 2:length(callPrices)-1
        s0 = (callPrices[i] - callPrices[i-1]) / (strikes[i] - strikes[i-1])
        s1 = (callPrices[i] - callPrices[i+1]) / (strikes[i] - strikes[i+1])
        if s0 <= -1
            return (false, i)
        end
        if s0 >= s1
            return (false, i)
        end
        if s1 >= 0
            return (false, i + 1)
        end
    end

    return (true, -1)
end

#create closest set of arbitrage free prices
function filterConvexPrices(
    strikes::Vector{T},
    callPrices::Vector{T}, #undiscounted!
    weights::Vector{T},
    forward::T;
    tol = 1e-8,
)::Tuple{Vector{T},Vector{T},Vector{T}} where {T}
    if isArbitrageFree(strikes, callPrices, forward)[1]
        return strikes, callPrices, weights
    end
    n = length(callPrices)
    z = Variable(n)
    G = spzeros(T, 2 * n, n)
    h = zeros(T, 2 * n)
    for i = 2:n-1
        dym = (strikes[i] - strikes[i-1])
        dy = (strikes[i+1] - strikes[i])
        G[i, i-1] = -1 / dym
        G[i, i] = 1 / dym + 1 / dy
        G[i, i+1] = -1 / dy
    end
    G[1, 1] = 1 / (strikes[2] - strikes[1])
    G[1, 2] = -G[1, 1]
    G[n, n] = 1 / (strikes[n] - strikes[n-1])
    G[n, n-1] = -G[n, n]
    for i = 1:n
        h[i] = -tol
        G[n+i, i] = -1
        h[n+i] = -max(forward - strikes[i], 0) - tol
    end
    h[1] = 1 - tol
    W = spdiagm(weights)
    problem = minimize(square(norm(W * (z - callPrices))), G * z <= h)
    #solve!(problem, () -> SCS.Optimizer(verbose = 0))
    Convex.solve!(problem, () -> COSMO.Optimizer(verbose = false, eps_rel = 1e-8, eps_abs = 1e-8))
    #println("problem status is ", problem.status, " optimal value is ", problem.optval)
    strikesf = strikes
    pricesf = Convex.evaluate(z)
    return strikesf, pricesf, weights
end

#transform to X coordinate for collocation
function makeXFromUndiscountedPrices(strikesf::Vector{T}, pricesf::Vector{T}; slopeTolerance = 1e-8) where {T}
    return makeXFromUndiscountedPrices(strikesf, pricesf, s -> -norminv(-s), slopeTolerance)
end

function makeXFromUndiscountedPrices(strikesf::Vector{T}, pricesf::Vector{T}, invphi, slopeTolerance = 1e-8) where {T}
    n = length(strikesf)

    pif = ones(T, n)
    for i = 2:n-1
        dxi = strikesf[i+1] - strikesf[i]
        dxim = strikesf[i] - strikesf[i-1]
        dzi = (pricesf[i+1] - pricesf[i]) / dxi
        dzim = (pricesf[i] - pricesf[i-1]) / dxim
        s = (dxim * dzi + dxi * dzim) / (dxim + dxi)
        previous = pif[i-1] < slopeTolerance ? pif[i-1] : pif[i-1] - slopeTolerance
        pif[i] = min(-s , previous)
    end
    dzi = (pricesf[n] - pricesf[n-1]) / (strikesf[n] - strikesf[n-1])
    pif[n] = -dzi
    dzim = -pif[n-2]
    if dzi * dzim < 0 || dzi < dzim || abs(dzi) < slopeTolerance
        pif = pif[1:n-1]
        strikesf = strikesf[1:n-1]
    end
    dzim = (pricesf[2] - pricesf[1]) / (strikesf[2] - strikesf[1])
    pif[1] = -dzim
    if dzim <= -1 + slopeTolerance
        pif = pif[2:end]
        strikesf = strikesf[2:e, d]
    end
    xif = invphi.(-pif)
    return strikesf, pif, xif
end


module Wigner

using StaticArrays
import ACE.Rotations3D.rotation_D_matrix

# D_{μm}^l(Ang2Mat_zyz(α,β,γ))
function wigner_big_D(μ,m,l,α,β,γ)
	return (exp(-im*α*m) * wigner_d(m,μ,l,β)  * exp(-im*γ*μ))'
end

# Wigner small d, modified from
# https://github.com/cortner/SlaterKoster.jl/blob/
# 8dceecb073709e6448a7a219ed9d3a010fa06724/src/code_generation.jl#L73
function wigner_d(μ, m, l, β)
    fc1 = factorial(l+m)
    fc2 = factorial(l-m)
    fc3 = factorial(l+μ)
    fc4 = factorial(l-μ)
    fcm1 = sqrt(fc1 * fc2 * fc3 * fc4)

    cosb = cos(β / 2.0)
    sinb = sin(β / 2.0)

    p = m - μ
    low  = max(0,p)
    high = min(l+m,l-μ)

    temp = 0.0
    for s = low:high
       fc5 = factorial(s)
       fc6 = factorial(l+m-s)
       fc7 = factorial(l-μ-s)
       fc8 = factorial(s-p)
       fcm2 = fc5 * fc6 * fc7 * fc8
       pow1 = 2 * l - 2 * s + p
       pow2 = 2 * s - p
       temp += (-1)^(s+p) * cosb^pow1 * sinb^pow2 / fcm2
    end
    temp *= fcm1

    return temp
end

function mat2ang(Q)
	return mod(atan(Q[2,3],Q[1,3]),2pi), acos(Q[3,3]), mod(atan(Q[3,2],-Q[3,1]),2pi);
end

# Rotation D matrix
function rot_D(L::Integer, Q)
	Mat_D = zeros(ComplexF64, 2L + 1, 2L + 1);
	D = rotation_D_matrix(L);
	α, β, γ = mat2ang(Q);
	for i = 1 : 2L + 1
		for j = 1 : 2L + 1
			Mat_D[i,j] = wigner_big_D(D[i,j].μ, D[i,j].m, L, α, β, γ);
		end
	end
	return SMatrix{2L+1, 2L+1, ComplexF64}(Mat_D)
end

function rand_QD(L)
	rotz(α) = [cos(α) -sin(α) 0; sin(α) cos(α) 0; 0 0 1]
	roty(α) = [cos(α) 0 sin(α); 0 1 0;-sin(α) 0 cos(α)]
	Ang2Mat_zyz(α,β,γ) = rotz(α)*roty(β)*rotz(γ)

	α = 2pi*rand();
	β = pi*rand();
	γ = 2pi*rand();

	# construct the Q matrix
	Q = Ang2Mat_zyz(α,β,γ)
	Q = SMatrix{3,3}(Q)

	rand_ref = rand((-1,1))

	return rand_ref * Q, rand_ref^L * rot_D(L, Q)
end

function rand_QD(L1, L2)
	rotz(α) = [cos(α) -sin(α) 0; sin(α) cos(α) 0; 0 0 1]
	roty(α) = [cos(α) 0 sin(α); 0 1 0;-sin(α) 0 cos(α)]
	Ang2Mat_zyz(α,β,γ) = rotz(α)*roty(β)*rotz(γ)

	α = 2pi*rand();
	β = pi*rand();
	γ = 2pi*rand();

	# construct the Q matrix
	Q = Ang2Mat_zyz(α,β,γ)
	Q = SMatrix{3,3}(Q)

	rand_ref = rand((-1,1))

	return rand_ref * Q, rand_ref^(L1) * rot_D(L1, Q), rand_ref^(L2) * rot_D(L2, Q)
end

end


@doc raw"""
    StudentTLikelihood(ν::T, σ::Real=one(T))

## Arguments
- `ν::Real` : degrees of freedom of the student-T
- `σ::Real` : standard deviation of the local scale

[Student-t likelihood](https://en.wikipedia.org/wiki/Student%27s_t-distribution) for regression:
```math
    p(y|f,ν,σ) = \frac{Γ(0.5(ν+1))}{\sqrt(νπ) σ Γ(0.5ν)} (1+\frac{(y-f)^2}{σ^2ν})^{(-0.5(ν+1))},
```
where `ν` is the number of degrees of freedom and `σ` is the standard deviation for local scale of the data.

---

For the augmented analytical solution, it is augmented via:
```math
    p(y|f,\omega) = N(y|f,\sigma^2 \omega)
```
Where ``\omega \sim \mathcal{IG}(0.5\nu,0.5\nu)`` where ``\mathcal{IG}`` is the inverse-gamma distribution.
See paper [Robust Gaussian Process Regression with a Student-t Likelihood](http://www.jmlr.org/papers/volume12/jylanki11a/jylanki11a.pdf)
"""
struct StudentTLikelihood{T<:Real} <: RegressionLikelihood
    ν::T
    α::T
    σ::T
    function StudentTLikelihood{T}(ν::T, σ::T=one(T)) where {T<:Real}
        ν > 0.5 || error("ν should be greater than 0.5")
        return new{T}(ν, (ν + one(T)) / 2.0, σ)
    end
end

function StudentTLikelihood(ν::T, σ::Real=one(T)) where {T<:Real}
    return StudentTLikelihood{T}(ν, σ)
end

function implemented(
    ::StudentTLikelihood, ::Union{<:AnalyticVI,<:QuadratureVI,<:GibbsSampling}
)
    return true
end

function (l::StudentTLikelihood)(y::Real, f::Real)
    return gamma(l.α) / (sqrt(l.ν * π) * gamma(l.ν / 2)) * (1 + abs2((y - f) / l.σ))^(-l.α)
    # tdistpdf(l.ν, (y - f) / l.σ) uses R so not differentiable
end

function Distributions.loglikelihood(l::StudentTLikelihood, y::Real, f::Real)
    return log(l(y, f))
    # tdistlogpdf(l.ν, (y - f) / l.σ) uses R so not differentiable
end

function Base.show(io::IO, l::StudentTLikelihood)
    return print(io, "Student-t likelihood (ν=", l.ν, ", σ=", l.σ, ")")
end

function compute_proba(
    l::StudentTLikelihood, μ::AbstractVector{<:Real}, σ²::AbstractVector{<:Real}
)
    return μ, max.(σ², zero(eltype(σ²))) .+ 0.5 * l.ν * l.σ^2 / (0.5 * l.ν - 1)
end

## Local Updates ##
function init_local_vars(::StudentTLikelihood, batchsize::Int, T::DataType=Float64)
    return (; c=rand(T, batchsize), θ=zeros(T, batchsize))
end

function local_updates!(
    local_vars,
    l::StudentTLikelihood,
    y::AbstractVector,
    μ::AbstractVector,
    diag_cov::AbstractVector,
)
    @. local_vars.c = 0.5 * (diag_cov + abs2(μ - y) + l.σ^2 * l.ν)
    @. local_vars.θ = l.α / local_vars.c
    return local_vars
end

function sample_local!(
    local_vars, l::StudentTLikelihood, y::AbstractVector, f::AbstractVector
)
    local_vars.c .= rand.(InverseGamma.(l.α, 0.5 * (abs2.(f - y) .+ l.σ^2 * l.ν)))
    local_vars.θ .= inv.(local_vars.c)
    return local_vars
end

## Global Gradients ##

@inline ∇E_μ(::StudentTLikelihood, ::AOptimizer, y::AbstractVector, state) = (state.θ .* y,)
@inline function ∇E_Σ(::StudentTLikelihood, ::AOptimizer, ::AbstractVector, state)
    return (0.5 .* state.θ,)
end

## ELBO Section ##

function expec_loglikelihood(
    l::StudentTLikelihood,
    ::AnalyticVI,
    y::AbstractVector,
    μ::AbstractVector,
    diag_cov::AbstractVector,
    state,
)
    tot = -0.5 * length(y) * (log(twoπ * l.σ^2))
    tot += -sum(log.(state.c) .- digamma(l.α))
    tot +=
        -0.5 * (
            dot(state.θ, diag_cov) + dot(state.θ, abs2.(μ)) - 2.0 * dot(state.θ, μ .* y) +
            dot(state.θ, abs2.(y))
        )
    return tot
end

AugmentedKL(l::StudentTLikelihood, state, ::Any) = InverseGammaKL(l, state)

function InverseGammaKL(l::StudentTLikelihood, state)
    α_p = l.ν / 2
    β_p = α_p * l.σ^2
    return InverseGammaKL(l.α, state.c, α_p, β_p)
end

## PDF and Log PDF Gradients ## (verified gradients)

function ∇loglikelihood(l::StudentTLikelihood, y::Real, f::Real)
    return (one(l.ν) + l.ν) * (y - f) / ((f - y)^2 + l.σ^2 * l.ν)
end

function hessloglikelihood(l::StudentTLikelihood, y::Real, f::Real)
    v = l.ν * l.σ^2
    Δ² = (f - y)^2
    return (one(l.ν) + l.ν) * (-v + Δ²) / (v + Δ²)^2
end


include("euler/euler.jl")
using .Primes: prime_sieve

function compute(n::Int)::Int
    sieve = prime_sieve(n)
    candidates = [i - 1 for (i, is_prime) ∈ enumerate(sieve) if is_prime && (i - 1) % 4 != 0]
    result = 0
    for candidate ∈ candidates
        if !sieve[candidate ÷ 2 + 2] continue end
        is_valid = true
        for i ∈ 3:trunc(Int, √n + 1)
            if candidate % i ≠ 0
                continue
            elseif !sieve[candidate ÷ i + i]
                is_valid = false
                break
            end
        end
        if is_valid result += candidate end
    end
    return result
end


@testset "Linear Spaces" begin
    @testset "AffineSubspace" begin
        A = AffineSubspace([1 0 3; 2 1 3], [5, -2])
        @test dim(A) == dim(intrinsic(A)) == dim(extrinsic(A)) == 1
        @test codim(A) == codim(intrinsic(A)) == codim(extrinsic(A)) == 2
        @test ambient_dim(A) == 3
        @test startswith(sprint(show, A), "1-dim. affine subspace")
        @test identity.(A) == A

        @test intrinsic(A) isa AffineIntrinsic
        @test startswith(sprint(show, intrinsic(A)), "AffineIntrinsic")
        @test extrinsic(A) isa AffineExtrinsic
        @test startswith(sprint(show, extrinsic(A)), "AffineExtrinsic")

        @test identity.(extrinsic(A)) == extrinsic(A)
        @test identity.(intrinsic(A)) == intrinsic(A)
        @test identity.(Intrinsic) == Intrinsic

        u = rand(1)
        x = A(u, Intrinsic)
        @test coord_change(A, Extrinsic, Intrinsic, x) ≈ u rtol = 1e-12
        @test coord_change(A, Intrinsic, Extrinsic, u) ≈ x rtol = 1e-12
        @test norm(A(x, Extrinsic)) ≈ 0 atol = 1e-14
        @test norm(AffineExtrinsic(intrinsic(A))(x)) ≈ 0.0 atol = 1e-12

        B = rand_affine_subspace(3; codim = 2)
        @test B != A
        copy!(B, A)
        @test extrinsic(B) == extrinsic(A)
        @test intrinsic(B) == intrinsic(A)
        @test B == A
        @test copy(A) == A
        @test copy(A) !== A

        C = rand_affine_subspace(3, dim = 1, real = true)
        @test geodesic_distance(A, C) > 0
        γ = geodesic(A, C)
        @test size(γ(1)) == size(intrinsic(A).Y)
    end

    @testset "AffineSubspaceHomotopy" begin
        @var x[1:4]
        f1 = rand_poly(x, 2)
        f2 = rand_poly(x, 2)
        F = System([f1, f2], x)

        A = rand_affine_subspace(4; codim = 2)
        # Commpute witness set
        L = A(x, Extrinsic)
        res = track.(total_degree(F ∩ L)...)
        W = solution.(res)

        B = rand_affine_subspace(4, codim = 2)

        graff_tracker = Tracker(
            AffineSubspaceHomotopy(F, A, B),
            options = (automatic_differentiation = 3,),
        )
        graff_result = track.(graff_tracker, W)
        @test all(is_success, graff_result)

        C = rand_affine_subspace(4, codim = 2)
        copy_A = copy(A)
        set_subspaces!(graff_tracker.homotopy, B, C)
        @test A == copy_A
        @test all(is_success, track.(graff_tracker, solution.(graff_result)))

        graff_path_tracker = EndgameTracker(AffineSubspaceHomotopy(F, A, B))
        graff_path_result = track.(graff_path_tracker, W)
        @test all(is_success, graff_path_result)

        @test all(isapprox.(
            solution.(graff_path_result),
            solution.(graff_result),
            rtol = 1e-12,
        ))
    end
end


F(n) = n < 1 ? one(n) : n - M(F(n - 1))
M(n) = n < 1 ? zero(n) : n - F(M(n - 1))


struct OrderStatistic{n,r,B} <: AbstractMeasure
    base::B
end

function basemeasure(d::OrderStatistic{n,r})
    WeightedMeasure(logbeta(n+1, r+1), d.base)
end

function logdensity(d::OrderStatistic{n,r}, x)
    logF(x) = logcdf(d.base, x)
    logFᶜ(x) = logccdf(d.base, x)
    return (r - 1) * logF(x) + (n - r) * logFᶜ(x)
end


export MF

"""
    MF(
        data::DataAccessor,
        k::Int
    )

Recommendation based on matrix factorization (MF). Number of factors is configured by `k`.

MF solves the following minimization problem for a set of observed user-item interactions ``\\mathcal{S} = \\{(u, i) \\in \\mathcal{U} \\times \\mathcal{I}\\}``:

```math
\\min_{P, Q} \\sum_{(u, i) \\in \\mathcal{S}} \\left( r_{u,i} - \\mathbf{p}_u^{\\mathrm{T}} \\mathbf{q}_i \\right)^2 + \\lambda \\ (\\|\\mathbf{p}_u\\|^2 + \\|\\mathbf{q}_i\\|^2),
```

where ``\\mathbf{p}_u, \\mathbf{q}_i \\in \\mathbb{R}^k`` are respectively a factorized user and item vector, and ``\\lambda`` is a regularization parameter to avoid overfitting. An optimal solution will be found by stochastic gradient descent (SGD). Ultimately, we can predict missing values in ``R`` by just computing ``PQ^{\\mathrm{T}}``, and the prediction directly leads recommendation.
"""
struct MF <: Recommender
    data::DataAccessor
    k::Int
    P::AbstractMatrix
    Q::AbstractMatrix

    function MF(data::DataAccessor, k::Int)
        n_user, n_item = size(data.R)
        P = matrix(n_user, k)
        Q = matrix(n_item, k)

        new(data, k, P, Q)
    end
end

MF(data::DataAccessor) = MF(data, 20)

isbuilt(recommender::MF) = isfilled(recommender.P)

function build!(recommender::MF;
               reg::Float64=1e-3, learning_rate::Float64=1e-3,
               eps::Float64=1e-3, max_iter::Int=100)
    n_user, n_item = size(recommender.data.R)

    # initialize with small values
    # (random is also possible)
    P = ones(n_user, recommender.k) * 0.1
    Q = ones(n_item, recommender.k) * 0.1

    pairs = vec([(u, i) for u in 1:n_user, i in 1:n_item])
    for it in 1:max_iter
        converged = true

        shuffled_pairs = shuffle(pairs)
        for (u, i) in shuffled_pairs
            r = recommender.data.R[u, i]
            if isnan(r); continue; end

            uv, iv = P[u, :], Q[i, :]

            err = r - dot(uv, iv)
            if abs(err) >= eps; converged = false; end

            grad = -2 * (err * iv - reg * uv)
            P[u, :] = uv - learning_rate * grad

            grad = -2 * (err * uv - reg * iv)
            Q[i, :] = iv - learning_rate * grad
        end

        if converged; break; end;
    end

    recommender.P[:] = P[:]
    recommender.Q[:] = Q[:]
end

function predict(recommender::MF, u::Int, i::Int)
    check_build_status(recommender)
    dot(recommender.P[u, :], recommender.Q[i, :])
end


# Hofbauer invariant measure h with C=1
heaviside(x::AbstractFloat) = ifelse(x < 0, zero(x), ifelse(x > 0, one(x), oftype(x,0.5)))
hofbauer(x,β,N=100) = heaviside(1-x) + [β^-j for j in 1:N]'*heaviside.(bernoulli_map(1e0,β,N)[2:end] .- x)


using InverseGaussTransform

# ==========================
# TESTS
# ==========================

function trap_rule_test()
    Narr = [20,40,80,160,320,320*2,320*4,320*8,320*16,320*32]
    exact = 4^3/3
    errorArr = zeros(length(Narr))
    for ti=1:length(Narr)
        xarr = collect(range(0,stop=4.0,length=Narr[ti]))
        farr = xarr.^2
        quad = trap_rule(xarr,farr)
        errorArr[ti] = abs((quad - exact)/exact)
    end

    for tj=2:length(Narr)
        println("ratio: ",errorArr[tj-1]/errorArr[tj])
    end
    nothing
end

function cost_test()
    x1 = 0.7
    s = 1.2
    h = 0.5

    N = 200
    y = collect(range(-2,stop=2,length=N))
    F = zeros(N)
    for ti=1:N
        F[ti] = s*GaussKernel(y[ti] - x1,h)
    end

    # initial conditions [x0,s0,h0]
    #θ = [x1,s,h]
    #θ = [-1.0,1.0,0.5,0.2,0.2]
    θ = [-1.0,0.5,0.1]

    plot_fields(θ,y,F)
    J = cost(θ,y,F)

    nothing
end

function grad_cost_test()
    x1 = 0.7
    s = 1.2
    h = 0.5

    N = 200
    y = collect(range(-2,stop=2,length=N))
    F = zeros(N)
    for ti=1:N
        F[ti] = s*GaussKernel(y[ti] - x1,h)
    end

    # initial conditions [x0,s0,h0]
    #θ = [x1,s,h]
    #θ = [-1.0,1.0,0.5,0.2,0.2]
    θ = [0.0,0.0,0.1]

    plot_fields(θ,y,F)
    ∇J = grad_cost_FD(θ,y,F)
    println(∇J)

    nothing
end

function gradient_descent_test()
    #x1 = [0.7,-0.2]
    #s = [1.2,0.4]
    #x1 = 0.7
    #s  = 1.2
    #h = 0.5
    M = 100
    x1 = 2*rand(M) .- 1.0
    s1  = rand(M)
    h = 0.5

    N = 200
    y = collect(range(-2,stop=2,length=N))
    F = zeros(N)
    for ti=1:N
        for tj = 1:M
            F[ti] += s[tj]*GaussKernel(y[ti] - x1[tj],h)
        end
    end

    # initial conditions [x0,s0,h0]
    #θ = [x1,s,h]
    θ0 = [-0.3,0.0,0.1]
    #θ0 = [0.0,0.0,0.1]
    #θ0 = #[-1.0,1.0,0.5,0.2,0.2]
    #θ0 = [0.0,0.5,0.1]

    θ = gradient_descent(θ0,y,F;NiterMAX=1000,TOL=1e-8,pause=true)

    println(θ)
end

function gradient_descent_test_print_for_dissertation()
    M = 5
    x1 = 2*rand(M) .- 1.0
    s1  = rand(M)
    h = 0.5

    N = 200
    y = collect(range(-3,stop=3,length=N))
    F = zeros(N)
    for ti=1:N
        for tj = 1:M
            F[ti] += s1[tj]*GaussKernel(y[ti] - x1[tj],h)
        end
    end

    #θ0 = [-0.3,0.0,0.1]
    K= M+20
    θ0 = vcat(collect(range(-1,stop=1,length=K)),0.01*ones(K),0.1)

    error = 100.0
    α = 0.04

    p = plot(y,F,label="true",xlabel="y",xaxis=[-2,2])

    iter = 1
    θ = θ0
    Jtemp = 100.0
    NiterMAX = 2000
    TOL = 1e-8
    while iter<NiterMAX && error > TOL
        println("\riteration ",iter,"          ")
        plot_fields(θ,y,F;iter=iter)

        #readline()

        J = cost(θ,y,F)
        error = abs(J - Jtemp)

        ∇J = grad_cost(θ,y,F)

        gradient_descent_step!(θ,∇J,α)

        G = get_gauss_field(θ,y)
        if mod(iter,40) == 0
            plot!(p,y,G,linestyle=:dash,label="iter $(iter)")
        end

        Jtemp = J
        iter += 1
    end

   savefig(p,"test.png")

    println(θ)
end

function gradient_descent_test_print_for_dissertation_COST()
    x1 = 0.7
    s1  = 1.2
    h = 0.5

    N = 200
    y = collect(range(-3,stop=3,length=N))
    F = zeros(N)
    for ti=1:N
        F[ti] += s1*GaussKernel(y[ti] - x1,h)
    end

    θ0 = [-0.3,0.0,0.1]

    error = 100.0
    α = 0.2

    Jarr = [cost(θ0,y,F)]

    iter = 1
    θ = θ0
    Jtemp = 100.0
    NiterMAX = 5000
    TOL = 1e-8
    while iter<NiterMAX && error > TOL
        println("\riteration ",iter,"          ")
        #plot_fields(θ,y,F;iter=iter)

        J = cost(θ,y,F)
        push!(Jarr,J)
        error = abs(J - Jtemp)

        ∇J = grad_cost(θ,y,F)

        gradient_descent_step!(θ,∇J,α)

        G = get_gauss_field(θ,y)

        Jtemp = J
        iter += 1
    end

    p = plot(0:(length(Jarr)-1),Jarr,ylabel="J (cost)",label="",xlabel="iteration #")

    savefig(p,"cost.png")

    println(θ)
end

function dissertation_STEP()
    N_pts = 200
    y = collect(range(-3,stop=3,length=N_pts))
    F = zeros(N_pts)
    for ti=1:N_pts
        F[ti] = step_function(y[ti])
    end

    iterArr = Array{Int}(undef,0)
    indArr  = Array{Int}(undef,0)
    minJArr = Array{Float64}(undef,0)

    for tj=19:20
        println("\r calculating N=$(tj)        ")
        push!(indArr,tj)
        θ0 = vcat(collect(range(-1,stop=1,length=tj)),0.01*ones(tj),0.1)

        error = 100.0
        α = 0.04

        Jarr = [cost(θ0,y,F)]
        J = zero(Float64)

        iter = 1
        θ = θ0
        Jtemp = 100.0
        NiterMAX = 10000
        TOL = 1e-16
        while iter<NiterMAX && error > TOL
            #println("\riteration ",iter,"          ")
            #plot_fields(θ,y,F;iter=iter)

            J = cost(θ,y,F)
            #println(J)
            #push!(Jarr,J)
            error = abs(J - Jtemp)

            ∇J = grad_cost(θ,y,F)

            gradient_descent_step!(θ,∇J,α)

            G = get_gauss_field(θ,y)

            Jtemp = J
            iter += 1
        end
        plot_fields(θ,y,F)
        plot(p,"test.png")
        readline()
        push!(iterArr,iter-1)
        push!(minJArr,J)
    end

    #p1 = plot(indArr,minJArr,ylabel="minimum J",label="",xlabel="M")
    #savefig(p1,"cost_multiN_J.png")
    #p2 = plot(indArr,iterArr,ylabel="iterations to convergence",label="",xlabel="M")
    #savefig(p2,"cost_multiN_iter.png")
    nothing
end

function step_function(x)
    if x > -0.5 && x < 0.5
        return 1.0
    else
        return 0.0
    end
end


using Dates

function write_oeis_bfile(anum, range, seq, comments, targetdir)

    if !occursin(r"^A[0-9]{6}$", anum)
        @warn("Not a valid A-number! Exiting.")
        return
    end

    filename = joinpath(targetdir, "b" * anum[2:end] * ".txt")
    @info("Writing " * anum * " to " * filename)

    file = open(filename, "w")
    timestamp = Dates.format(now(), "yyyy-mm-dd HH:MM")
    println(file, "# ", timestamp)
    for c in comments
        println(file, "# ", c)
    end

    for n in range
        val = seq(n)
        if length(string(val)) > 1000
            @error("Term too long, exiting.")
            break
        end
        println(file, n, " ", val)
    end
    close(file)
end

path = "C:/Users/Home/JuliaProjects/IntegerSequences/data"

# Example use:
comments = ["Author: Julia Verona",
           "The divergent series par excellence."]
a(n) = factorial(BigInt(n))
R = 11:200
write_oeis_bfile("A000142", R, a, comments, path)

# Example use:
write_oeis_bfile("A000290", 0:100, n -> n * n, "", path)


immutable Triweight <: ContinuousUnivariateDistribution
    μ::Float64
    σ::Float64

    Triweight(μ::Real, σ::Real) = (@check_args(Triweight, σ > zero(σ)); new(μ, σ))
    Triweight(μ::Real) = new(μ, 1.0)
    Triweight() = new(0.0, 1.0)
end

@distr_support Triweight d.μ - d.σ d.μ + d.σ

## Parameters

location(d::Triweight) = d.μ
scale(d::Triweight) = d.σ
params(d::Triweight) = (d.μ, d.σ)

## Properties
mean(d::Triweight) = d.μ
median(d::Triweight) = d.μ
mode(d::Triweight) = d.μ

var(d::Triweight) = d.σ^2 / 9.0
skewness(d::Triweight) = 0.0
kurtosis(d::Triweight) = -2.9696969696969697  # 1/33-3

## Functions
function pdf(d::Triweight, x::Real)
    u = abs(x - d.μ)/d.σ
    u >= 1 ? 0.0 : 1.09375*(1-u*u)^3/d.σ
end

function cdf(d::Triweight, x::Real)
    u = (x - d.μ)/d.σ
    u <= -1 ? 0.0 : u >= 1 ? 1.0 : 0.03125*(1+u)^4*@horner(u,16.0,-29.0,20.0,-5.0)
end

function ccdf(d::Triweight, x::Real)
    u = (d.μ - x)/d.σ
    u <= -1 ? 1.0 : u >= 1 ? 0.0 : 0.03125*(1+u)^4*@horner(u,16.0,-29.0,20.0,-5.0)
end

@quantile_newton Triweight

function mgf(d::Triweight, t::Float64)
    a = d.σ*t
    a2 = a*a
    a == 0 ? one(a) : 105.0*exp(d.μ*t)*((15.0/a2+1.0)*cosh(a)-(15.0/a2-6.0)/a*sinh(a))/(a2*a2)
end

function cf(d::Triweight, t::Float64)
    a = d.σ*t
    a2 = a*a
    a == 0 ? complex(one(a)) : 105.0*cis(d.μ*t)*((1.0-15.0/a2)*cos(a)+(15.0/a2-6.0)/a*sin(a))/(a2*a2)
end


using Swalbe, Plots

"""
    run_drop_coal()

Simulation of two droplets in close contact to study their coalesence behaviour.
"""
function run_drop_coal(
    sys::Swalbe.SysConst_1D;                # System relevant constants
    r₁=500,                                 # First droplets radius
    r₂=500,                                 # Second droplets radius
    θ₀=1/9,                                 # Contact angle
    centers=(sys.L/3, 2*sys.L/3),           # Drople centers
    dump = 100,                             # Data dumping interval
    fluid=zeros(sys.Tmax÷dump, sys.L),      # Data
    verbos=true                             # Simulation updates about the bridge height in consol
)
    println("Simulating droplet coalecense")
    state = Swalbe.Sys(sys)
    state.height .= Swalbe.two_droplets(sys, r₁=r₁, r₂=r₂, θ₁=θ₀, θ₂=θ₀, center=centers)
    Swalbe.equilibrium!(state)
    println("Starting the lattice Boltzmann time loop")
    for t in 1:sys.Tmax
        if t % sys.tdump == 0
            mass = 0.0
            mass = sum(state.height)
            if verbos
                println("Time step $t bridge height is $(round(state.height[Int(sys.L/2)], digits=3))")
            end
        end
        Swalbe.filmpressure!(state, sys)
        Swalbe.h∇p!(state)
        Swalbe.slippage!(state, sys)
        state.F .= -state.h∇p .- state.slip
        Swalbe.equilibrium!(state)
        Swalbe.BGKandStream!(state)
        Swalbe.moments!(state)
        # Make a snaphot of the configuration
        Swalbe.snapshot!(fluid, state.height, t, dumping = dump)
    end
    return fluid
end

"""
    bridge_height(data)

Compuation of the bridge height between two droplets based on input `data`.
"""
function bridge_height(data; t1=100, t_dur=size(data)[1], pos=size(data)[2]÷2)
    bridge_h = []
    for i in 1:t_dur
        push!(bridge_h, data[i, pos])
    end
    return bridge_h
end

"""
	τν(γ, R, η)

Computes the viscous time.

# Mathematics

``t_c = \\sqrt{\\frac{\\rho R^3}{\\gamma}}``
"""
function τν(γ; R=500, η=1/6)
	time_c = 0.0
	time_c = R*η/γ
	return time_c
end

# Different surface tension values
γs = [0.00001, 0.00005, 0.0001, 0.0005]
# Large array that contains the simulation data
data_merge = zeros(20000, 1024, length(γs))
# Drop radii
sphere_rad = 500.0
# Loop different surface tension values
for γ in enumerate(γs)
    sys = Swalbe.SysConst_1D(L=1024, Tmax=2000000, δ=50.0, γ=γ[2])
    data_merge[:,:,γ[1]] = run_drop_coal(sys, r₁=sphere_rad, r₂=sphere_rad)
end
# Compute the bridge heights for the data collect and store it in a single array
bridges = zeros(20000, length(γs))
for i in 1:length(γs)
	bridges[:, i] = bridge_height(data_merge[:,:,i])
end
# Actual LBM time, time steps without (physical) meaning
time_lbm = 100:100:2000000
p1 = plot(time_lbm,                                                 # x-data
		  bridges,                                                  # y-data
		  xlabel="t [Δt]",                                          # axis label
		  ylabel="h₀",                                              # axis label
		  label=["γ=0.00001" "γ=0.00005" "γ=0.0001" "γ=0.0005"],    # labels for the curves
		  xticks=([0:1000000:2000000;], ["0", "1×10⁶", "2×10⁶"]),   # ticks and labels
		  xlims=(0,2100000),                                        # well limits of display
		  ylims=(0, 40),                                            # well limits of display
		  w = 3, 							                        # line width
		  marker = (8, :auto, 0.6),			                        # marker size
		  legend=:bottomright,                                      # legend position
		  legendfontsize = lf,			                            # legend font size
          tickfont = (14),	                                        # tick font and size
          guidefont = (15),	                                        # label font and size
		  grid=:none                                                # no gird, was asked once by PRE to remove the grid
)
# Now to collapse the curves we non-dimensionalize the time with the visous time scale τν and the height with the sphere radius R₀
time_norm = zeros(length(time_lbm), length(γs))
for i in enumerate(γs)
	time_norm[:, i[1]] .= time_lbm ./ τν(i[2])
end
# And plot it again
p2 = plot(time_norm,                                                # x-data
		  bridges ./ 500.0,                                         # y-data
		  xlabel="t [Δt]",                                          # axis label
		  ylabel="h₀",                                              # axis label
		  label=["γ=0.00001" "γ=0.00005" "γ=0.0001" "γ=0.0005"],    # labels for the curves
		  xticks=([0:1000000:2000000;], ["0", "1×10⁶", "2×10⁶"]),   # ticks and labels
		  xlims=(0,2100000),                                        # well limits of display
		  ylims=(0, 40),                                            # well limits of display
		  w = 3, 							                        # line width
		  marker = (8, :auto, 0.6),			                        # marker size
		  legend=:bottomright,                                      # legend position
		  legendfontsize = lf,			                            # legend font size
          tickfont = (14),	                                        # tick font and size
          guidefont = (15),	                                        # label font and size
		  grid=:none                                                # no gird, was asked once by PRE to remove the grid
)
# In the inertial regime we assume that the bridge height grows as time to the power of 2/3, we can check it by simply adding this curve
t_scaling = collect(1e-5:1e-5:10)                                       # Finer time scale for the power law
plot!(t_scaling, 0.024 .* t_scaling.^(2/3), c=:black, w=4, l=:dash, label="t^(2/3)")


x = rand(4)
y = rand(4)
z = rand(4)
a = zeros(4)

function xx!(a,x,y,z)

    @inbounds @simd for i in 1:4
        a[i] = x[i]*y[i]*exp(z[i])
    end
    nothing
end

function qw()
    @time @. a = x*y*exp(z)
end

function qq()
    @time xx!(a,x,y,z)
end

qw()
qq()


"""
Throughout the docstrings, n gives the number of nodes.
"""

mutable struct IntensityFunction
    """
    range should be a vector of all valid inputs into Ω
    Range of P should be range
    """
    ω
    P
    range
    aggregator
    grad
end

function partitionsUpTo(kmax)
    return [p for k = 1:kmax for p in Combinatorics.partitions(k)]
end

function partitionIntensityFunction(ω, kmax; grad = nothing)
    range      = partitionsUpTo(kmax)
    P          = partitionize
    aggregator = identity
    return IntensityFunction(ω, P, range, aggregator, grad)
end

function allOrNothingIntensityFunction(ω, kmax; grad = nothing)
    range      = [(x, y) for x = 0:1 for y = 1:kmax]
    P          = z->(all(x->x==z[1],z), length(z))
    aggregator = p->(length(p) == 1, sum(p))
    return IntensityFunction(ω, P, range, aggregator, grad)
end

function sumOfExteriorDegreesIntensityFunction(ω, kmax; grad = nothing)
    range = [(1*x, y) for y = 1:kmax for x = 0:y]

    function P(z)
        len = length(z)
        return (length(unique!(z)), len)
    end

    aggregator = p->(length(p), sum(p))
    return IntensityFunction(ω, P, range, aggregator, grad)
end

function empiricalIntensityFunction(ω, kmax, aggregator; grad = nothing)
    range = [aggregator(p) for p in partitionsUpTo(kmax)]
    P = z->aggregator(partitionize(z))
    return IntensityFunction(ω, P, range, aggregator, grad)
end


export similar_basis_vector, function_slice

"""
    $SIGNATURES

Return a vector with `1` at index `i` and `0`s otherwise (ie a standard basis
vector), similar to the first argument.
"""
function similar_basis_vector(x::AbstractVector{T}, i::Integer) where T
    v = zeros(T, length(x))
    v[i] = one(T)
    v
end

similar_basis_vector(::SVector{N, T}, i::Integer) where {N, T} =
    SVector{N, T}(ntuple(j -> i == j ? one(T) : zero(T), N)...)

"""
    $SIGNATURES

Return a callable implementing ``λ ↦ f(x + λv)``.
"""
function function_slice(f, x::AbstractVector, v::AbstractVector)
    @argcheck length(x) == length(v)
    λ -> f(x .+ λ * v)
end

"""
    $SIGNATURES

Return a callable implementing ``λ ↦ f(x + λe)``, where ``e`` is the `i`th
standard basis vector.
"""
function_slice(f, x::AbstractVector, i::Integer) =
    function_slice(f, x, similar_basis_vector(x, i))


using PyPlot

function plot_matrix(M, posneg=true,eps=1e-5)

    nrow = size(M,1)
    ncol = size(M,2)

    ntot = nrow * ncol

    A = reshape(M, ntot)

    irow = zeros(Int,ntot)
    icol = zeros(Int,ntot)

    er = maxabs(A)*eps

    cnt = 1
    for j = 1:ncol
        for i = 1:nrow
            irow[cnt] = i
            icol[cnt] = j
            cnt = cnt + 1
        end
    end

    plot([0, ncol+1, ncol+1, 0, 0], [0, 0, nrow+1, nrow+1, 0], color="black", linewidth=3)
    for i = 1:ncol
        axvline(i, color="lightgray")
    end
    for i = 1:nrow
        axhline(i, color="lightgray")
    end

    if posneg
        ipos = A .> er
        ineg = A .< -er

        plot(icol[ipos], nrow + 1 - irow[ipos], "ro")
        plot(icol[ineg], nrow + 1 - irow[ineg], "bo")
    else
        idx = abs(A) .> er
        plot(icol[idx], nrow + 1 - irow[idx], "o", color="black")
    end

    return
end


import Random
import Zygote
import RestrictedBoltzmannMachines as RBMs

using Test: @test, @testset, @test_broken, @inferred
using Statistics: mean, var
using SpecialFunctions: erfcx
using Distributions: truncated, Normal

for a = -10:10
    d = truncated(Normal(); lower=a)
    @test RBMs.tnmean(a) ≈ mean(d)
    @test RBMs.tnvar(a)  ≈ var(d)
    @test a ≤ RBMs.tnmean(a) < Inf
    @test 0 ≤ RBMs.tnvar(a) ≤ 1
end
@test 1e20 ≤ RBMs.tnmean(1e20) < Inf
@test_broken 0 ≤ RBMs.tnvar(1e80) ≤ 1

@testset "sqrt1half" begin
    @test (@inferred RBMs.sqrt1half(5)) ≈ 5.1925824035672520156
    @test (@inferred RBMs.sqrt1half(0)) == 1
    @test RBMs.sqrt1half(-1) == RBMs.sqrt1half(1) ≈ 1.6180339887498948482
    @test isnan(@inferred RBMs.sqrt1half(NaN))
    @test RBMs.sqrt1half(Inf) == RBMs.sqrt1half(-Inf) == Inf
    @test RBMs.sqrt1half(1e300) ≈ 1e300
end

@testset "randnt" begin
    @test (@inferred RBMs.randnt(0)) > 0
    @test (@inferred RBMs.randnt(1e300)) == 1e300
    @test (@inferred RBMs.randnt(Inf)) == Inf
    @test isnan(@inferred RBMs.randnt(NaN))
    @test (@inferred RBMs.randnt(floatmax(Float64))) == floatmax(Float64)
end

Random.seed!(18)

@inferred RBMs.randnt_half(1.0, 2.0)
@inferred RBMs.randnt_half(Float32(1.0), Float32(2.0))
@test RBMs.randnt_half(Float32(1.0), Float32(2.0)) isa Float32

# compare exact 1st and 2nd moments to Monte Carlo estimates
m1(μ,σ) = μ + σ * √(2/π) / erfcx(-μ/σ/√2)
m2(μ,σ) = μ^2 + σ^2 + μ * σ * √(2/π) / erfcx(-μ/σ/√2)

for μ = -1:1, σ = 1:2
    samples = [RBMs.randnt_half(μ,σ) for _ = 1:10^6]
    @test mean(samples.^1) ≈ m1(μ,σ) atol=1e-2
    @test mean(samples.^2) ≈ m2(μ,σ) atol=1e-2
end

# broadcasted versions
μ = 3randn(2,2); σ = 3rand(2,2)
dμ, dσ = Zygote.gradient(μ,σ) do μ,σ
    mean(m1.(μ,σ))
end


xs = [[0.,0.] [1.,1.] [2.,1.5]]
ts = [0.0, 1.0, 2.0]

pa = Path(xs,ts)

@test samplepath(pa, 0.3) == [0.3, 0.3] &&
      samplepath(pa, 1.3) == [1.3, 1.15]

# Seg1: [0.0, 1.0]
# Seg2: [1.0, 2.0]

@test norm(samplepath(pa, [0.3, 1.3, 1.4]) -
            [0.3 1.3 1.4; 0.3 1.15 1.2]) <= 1e-12

@test_throws AssertionError samplepath(pa, 10.)

xs = [[0., 0.] [1., 1.] [2., 1.5] [3., 5.0] [3.4, -2.0]]
ts = [0.0, 1.0, 2.0, 2.5, 2.7]

pb = Path(xs, ts)

N  = 10000
ss = range(0.0, stop=ts[end], length=N)

samples = samplepath(pb, ss)
@test norm(sum(samples, dims=2) / N - pathmean(pb)) / norm(pathmean(pb)) <= 5e-3

#####
Random.seed!(15)
N  = 500
xs = zeros(2, N)
ts = zeros(N)
xs[:, 1] = randn(2)
for s ∈ 2:N
    xs[:, s] = randn(2)
    ts[s] = ts[s-1] + rand()
end
pb = Path(xs, ts)
N  = 10000
ss = range(0.0, stop=ts[end], length=N)
samples = samplepath(pb, ss)
@test norm(sum(samples, dims=2) / N - pathmean(pb)) / norm(pathmean(pb)) <= 5e-3


using NonlinearEigenproblemsTest
using Test
using NonlinearEigenproblems.RKHelper

@testset "discretizepolygon" begin
@bench @testset "concave polygon" begin
    poly = [0.0; 0+10im; 5+5im; 10+10im; 10+0im]

    expected_boundary_points = [
        0 + 0im, 0 + 2.2071068im, 0 + 4.4142136im,
        0 + 6.6213203im, 0 + 8.8284271im, 0.73223305 + 9.267767im,
        2.2928932 + 7.7071068im, 3.8535534 + 6.1464466im, 5.4142136 + 5.4142136im,
        6.9748737 + 6.9748737im, 8.5355339 + 8.5355339im, 10 + 9.863961im,
        10 + 7.6568542im, 10 + 5.4497475im, 10 + 3.2426407im,
        10 + 1.0355339im, 8.8284271 + 0im, 6.6213203 + 0im,
        4.4142136 + 0im, 2.2071068 + 0im]

    nr_boundary = 20
    nr_interior = 100
    boundary, interior = discretizepolygon(poly, true, nr_boundary, nr_interior)

    # returned result contains boundary points + polygon vertices + first vertex duplicated
    @test length(boundary) == nr_boundary + length(poly) + 1
    @test boundary[1:nr_boundary] ≈ expected_boundary_points
    @test boundary[nr_boundary+1:nr_boundary+length(poly)] == poly
    @test boundary[nr_boundary+length(poly)+1] == poly[1]

    # points on polygon's boundary should count as inside; expand polygon a tiny bit to counteract rounding errors
    t = eps()*5
    expanded_poly = poly + [-t-t*im, -t+t*im, 0+t*im, t+t*im, t-t*im]
    @test all(p -> inpolygon(real(p), imag(p), real.(expanded_poly), imag.(expanded_poly)), boundary)

    @test length(interior) >= nr_interior
    @test all(p -> inpolygon(real(p), imag(p), real.(poly), imag.(poly)), interior)
end

@bench @testset "narrow Σ" begin
    boundary, interior = discretizepolygon([-10.0-2im, 10-2im, 10+2im, -10+2im], true, 100, 5)
    @test length(boundary) == 100 + 5
    @test length(interior) >= 5
end

@bench @testset "too narrow Σ" begin
    @test_throws ErrorException discretizepolygon([-10.0-0.2im, 10-0.2im, 10+0.2im, -10+0.2im], true, 100, 5)
end

@bench @testset "unit disk" begin
    boundary, interior = discretizepolygon(Vector{ComplexF64}(), true, 100, 100)

    @test length(boundary) == 101
    @test all(isapprox.(abs.(boundary[1:100]), 1.0, rtol = 100*eps()))

    @test length(interior) >= 100
    @test all(abs.(interior) .< 1)
end

@bench @testset "Chebyshev points" begin
    p1 = -2.0 - 1im
    p2 = 2.0 + 1im
    boundary, interior = discretizepolygon([p1, p2], true, 100, 100)

    @test length(boundary) == 102
    @test all(imag((boundary .- p1) / (p2-p1)) .== 0)

    @test length(interior) >= 100
    @test all(imag((interior .- p1) / (p2-p1)) .== 0)
end
end

# To visualize, use something like this:
#using Plots
#import GR
#scatter(real(zz), imag(zz))
#scatter(real(Z), imag(Z))


include("integrands.jl")

using PyPlot

function plot_1d(integrand, xmin, xmax, xdivs, name, outdir)
    println("plotting 1D ", name, "...")
    xs = linspace(xmin, xmax, xdivs)
    ys = [integrand(x) for x = xs]
    fig = figure(figsize=(8, 6))
    plot(xs, ys)
    title("integrand 1D $(name)")
    savefig("$(outdir)/out_integrand_1D_$(name).png")
    close(fig)
end

function plot_2d(integrand, xmin, xmax, xdivs, name, outdir)
    println("plotting 2D ", name, "...")
    x1 = linspace(xmin, xmax, xdivs)
    x2 = linspace(xmin, xmax, xdivs)
    x1grid = repmat(x1', xdivs, 1)
    x2grid = repmat(x2 , 1, xdivs)
    z = [integrand([x1[i] x2[j]]) for i in 1:xdivs, j in 1:xdivs]
    fig = figure(figsize=(8, 6))
    ax = fig[:add_subplot](1, 1, 1,  projection = "3d")
    ax[:plot_surface](x1grid, x2grid, z, edgecolors="k", cmap=ColorMap("jet"),
                      alpha=0.8, linewidth=0.25)
    title("integrand 2D $(name)")
    tight_layout()
    savefig("$(outdir)/out_integrand_2D_$(name).png")
    close(fig)
end

function plot_1d_2d(integrand, xmin, xmax, xdivs, name)
    plot_1d(integrand, xmin, xmax, xdivs, name, outdir)
    plot_2d(integrand, xmin, xmax, xdivs, name, outdir)
end

outdir = "output"
if ~isdir(outdir)
    mkdir(outdir)
end

plot_1d_2d(integrands.constant, -2, 2, 100, "constant")
plot_1d_2d(integrands.linear, -2, 2, 100, "linear")
plot_1d_2d(integrands.sinus, -8, 8, 100, "sinus")
plot_1d_2d(integrands.sinusc, -8, 8, 100, "sinusc")
plot_1d_2d(integrands.sphere, -2, 2, 100, "sphere")
plot_1d_2d(integrands.oscillatory, -5, 5, 100, "oscillatory")
plot_1d_2d(integrands.productpeak, -1, 1, 100, "productpeak")
plot_1d_2d(integrands.cornerpeak, 0, 1, 100, "cornerpeak")
plot_1d_2d(integrands.gaussian, -4, 4, 100, "gaussian")
plot_1d_2d(integrands.continuous, -4, 4, 100, "continuous")
plot_1d_2d(integrands.discontinuous, -4, 4, 100, "discontinuous")


using Forge.DSL
using Colors

const MINIMUM = 1.0
const MAXIMUM = 20.
const STEP    = 2.0
const NELEMS  = (MAXIMUM-MINIMUM+1)/STEP
const DPOINTS = [5, 5, 5, 15, 15, 5, 15, 15]

points = []
dirs = []
for j in 0:NELEMS-1, i in 0:NELEMS-1
    y = MINIMUM + j*STEP
    x = MINIMUM + i*STEP
    push!(points, x, y)
    push!(dirs, sin(2pi*x/10), sin(2pi*y/10))
end

window = create_window(600, 480, "Vector Field Demo")
show_window(window)

chart = create_chart_2d()
set_axes_limits(chart, MINIMUM-1.0, MAXIMUM, MINIMUM-1.0, MAXIMUM)
set_axes_titles(chart, "x-axis", "y-axis")

plot = create_plot(chart, 4, Forge.FG_PLOT_SCATTER, Forge.FG_MARKER_CIRCLE)
set_color(plot, RGB(0.9, 0.9, 0))
set_legend(plot, "Convergence Points")
set_marker_size(plot, 24)

field = create_vector_field(chart, NELEMS*NELEMS)
set_color(field, RGB(0, 0.6, 0.3))

update_vertex_buffer(plot, DPOINTS)
update_vertex_buffer(field, points)
update_direction_buffer(field, dirs)

draw_chart(!isclosed, window, chart)
save_framebuffer(window, "field.png")


### A Pluto.jl notebook ###
# v0.12.20

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 8d4298e0-6c44-11eb-3406-31a39340d1aa
begin
	import Pkg; Pkg.add("Plotly")
	Pkg.add("PyPlot")
	Pkg.add("PlutoUI")
	Pkg.add("SpecialFunctions")
	Pkg.add("StatsBase")
	Pkg.add("Plots")
	Pkg.add("LinearAlgebra")
	Pkg.add("SparseArrays")
	using PlutoUI
	using Plots
	using LinearAlgebra
	using SparseArrays
	using SpecialFunctions
	using StatsBase
end

# ╔═╡ 9637fe40-6c44-11eb-1df3-eb0b0d36db34
md"""

# **The golden Nautilus wheel** - _Distributions and the law of large numbers_

**Captain Bayes and Captain Venn** are investigating the **golden Nautilus wheel** and are wondering how one can make predictions about the minimum free days ratio.

A **rough rule of thumb** says that for most distributions the standard deviation can be used as a measure of how much probability mass is **spread around the mean value**, namely that within the following intervals one can find:

$P(X ∈ [μ - σ, μ + σ])  ≈ 67\%,$
$P(X ∈ [μ - 2σ, μ + 2σ]) ≈ 95\%,$
$P(X ∈ [μ - 3σ, μ + 3σ]) ≈ 99\%.$

In the following diagram you see the **probability distribution of wages** for the the four tasks of the golden Nautilus wheel, You can **change the wages** Captain Venn pays for each task

"""

# ╔═╡ 9de6ada0-6c56-11eb-1055-1b29b78e6112
md"""
**Use the slider to change the number of measurements sample size $n$ Captain Venn performs and watch the histogram**
"""

# ╔═╡ 8b8cd540-6d86-11eb-339e-7b8a2df774f9
md"Choose the **number of measurements / the sample size $n$** 👉 $(@bind n_sample_size Slider(100:100:10000, default=1000, show_value = true)) "

# ╔═╡ 84bfd150-6d8a-11eb-011e-052c39c8db9c
x_rand_var = [1, 3, 4, 6]

# ╔═╡ b7a7b220-6c56-11eb-1e6c-6f7595f02439
md"""
In order to see the variation of the sample we **repeat the measurements with fixed sample size** ($n$) $N$ times. This way we can **sample the distribution of the sample mean value** (which is a random variable in contrast to the real sample mean). Use the slider to change the number of repeated measurements $N$ of samples of size $n$**

You can change between the **distributions of wages and free days**.
"""

# ╔═╡ 835da8c0-6c57-11eb-0a71-e776862dbfce
md"""
Example of some other distributions: **Poisson distriubtion**
"""

# ╔═╡ 1b51ce3e-6c58-11eb-2539-a34e0576c612
@bind lambda Slider(0.1:0.1:19, default=1, show_value = true)

# ╔═╡ 0e35a290-6c58-11eb-2004-0b4b90f9754e
k =0:0.1:20

# ╔═╡ 14661b90-6c58-11eb-28bd-4399c5896666
poisson = lambda.^k./gamma.(k.+1).*exp(-lambda)

# ╔═╡ b016de70-6c59-11eb-2ae2-b9ee6e5f2abd
begin
	plot(
    k, poisson,
    line = (0.0 , 2.0 , :bar),
    normalize = false,
	bar_width = 0.13,
    marker = (6, 0.5, :none),
    markerstrokewidth = 5.,
    color = [:steelblue],
    fill = 0.9,
    orientation = :v,
    title = "The Poisson distribution with lambda: "*string(lambda),
	ylabel = "probability mass function",
	xlabel = "Points",
	label = :none,
	ylim =[0,1],)

	plot!(
    k, poisson,
    line = ( 1, 0., :path),
    normalize = false,
    marker = (3, 0.5, :o),
    markerstrokewidth = 3.,
    color = [:steelblue],
    fill = 0.,
    orientation = :v,
    title = "The Poisson distribution with lambda: "*string(lambda),
	ylabel = "probability mass function",
	xlabel = "k - number of events",
	label = :none,
	ylim =[0,1],
)

end

# ╔═╡ 06a6c020-6c5a-11eb-3516-e5d4f4740be9
sum(poisson)

# ╔═╡ 3d66c0a0-6c5b-11eb-0fd0-0bd81c9672c9
begin
	@userplot CirclePlot
@recipe function f(cp::CirclePlot)
    x, y, i = cp.args
    n = length(x)
    inds = circshift(1:n, 1 - i)
    linewidth --> range(0, 10, length = n)
    seriesalpha --> range(0, 1, length = n)
    aspect_ratio --> 1
    label --> false
    x[inds], y[inds]
end

n = 150
t = range(0, 2π, length = n)
x = sin.(t)
y = cos.(t)

anim = @animate for i ∈ 1:n
    circleplot(x, y, i)
end
gif(anim, "anim_fps15.gif", fps = 15)
end

# ╔═╡ 48f7c480-6d84-11eb-1f24-e16d7f649c9c
phi = (1+sqrt(5))/2

# ╔═╡ 534e1970-6d84-11eb-3f16-bb6aaa46db6f
prob = phi.^(0:3)./sum(phi.^(0:3))

# ╔═╡ 1df828e0-6d86-11eb-0d3c-2398e6700066
sample = 5 .- sum(rand(1,n_sample_size) .<= cumsum(prob), dims=1)

# ╔═╡ cfdaa470-6d86-11eb-03a6-ef281daeebb2
sample_count = [count(==(i), sample) for i = 1:4]

# ╔═╡ 5e0bfb10-6d8a-11eb-06ef-9335eb90064c
begin
	plot(x_rand_var, sample_count,
    line = (0.0 , 2.0 , :bar),
    normalize = false,
	bar_width = 0.04,
    marker = (6, 0.5, :none),
    markerstrokewidth = 5.,
    color = [:steelblue],
    fill = 0.9,
    orientation = :v,
    title = "The Poisson distribution with lambda: ",
	ylabel = "probability mass function",
	xlabel = "Points",
	label = :none,
	ylim =[0,n_sample_size/2])

	plot!(
    x_rand_var, sample_count,
    line = ( 1, 0., :path),
    normalize = false,
    marker = (5, 1.0, :o),
    markerstrokewidth = 3.,
    color = [:steelblue],
    fill = 0.,
    orientation = :v,
    title = "The Poisson distribution with lambda: "*string(lambda),
	ylabel = "probability mass function",
	xlabel = "k - number of events",
	label = :none,
	ylim =[0,n_sample_size/2])
end

# ╔═╡ Cell order:
# ╠═8d4298e0-6c44-11eb-3406-31a39340d1aa
# ╟─9637fe40-6c44-11eb-1df3-eb0b0d36db34
# ╟─9de6ada0-6c56-11eb-1055-1b29b78e6112
# ╟─8b8cd540-6d86-11eb-339e-7b8a2df774f9
# ╠═84bfd150-6d8a-11eb-011e-052c39c8db9c
# ╟─5e0bfb10-6d8a-11eb-06ef-9335eb90064c
# ╟─b7a7b220-6c56-11eb-1e6c-6f7595f02439
# ╟─835da8c0-6c57-11eb-0a71-e776862dbfce
# ╟─1b51ce3e-6c58-11eb-2539-a34e0576c612
# ╟─b016de70-6c59-11eb-2ae2-b9ee6e5f2abd
# ╠═0e35a290-6c58-11eb-2004-0b4b90f9754e
# ╠═14661b90-6c58-11eb-28bd-4399c5896666
# ╠═06a6c020-6c5a-11eb-3516-e5d4f4740be9
# ╠═3d66c0a0-6c5b-11eb-0fd0-0bd81c9672c9
# ╠═48f7c480-6d84-11eb-1f24-e16d7f649c9c
# ╠═534e1970-6d84-11eb-3f16-bb6aaa46db6f
# ╠═1df828e0-6d86-11eb-0d3c-2398e6700066
# ╠═cfdaa470-6d86-11eb-03a6-ef281daeebb2


"""
    LLL(x::Array, α::Number[; return_c = false, verbose=Bool])

Return an α-reduced basis of the lattice generated by `x`, where 1/4 < α <= 1.
Handles matrices of size (m, n),  where m <= n.
If return_c is set to true, return the matrix C for which Y = C * X,
where Y is the α-reduced basis.
If verbose is set to true, also print the reduce/exchange operations.

# Example

```
julia> x = [[-2 7 7 -5]
       [3 -2 6 -1]
       [2 -8 -9 -7]
       [8 -9 6 -4]]
4×4 Array{Int64,2}:
 -2   7   7  -5
  3  -2   6  -1
  2  -8  -9  -7
  8  -9   6  -4

julia> LLL(x, 1, verbose=true)
iteration 1      exchange    k=2
iteration 2      reduce      k=2     ℓ=1     [μ[k,l]] = 1.0
iteration 3      reduce      k=3     ℓ=2     [μ[k,l]] = -1.0
iteration 4      reduce      k=3     ℓ=1     [μ[k,l]] = -1.0
iteration 5      exchange    k=4
iteration 6      reduce      k=3     ℓ=2     [μ[k,l]] = -1.0
iteration 7      exchange    k=3
iteration 8      reduce      k=2     ℓ=1     [μ[k,l]] = 1.0
iteration 9      reduce      k=3     ℓ=2     [μ[k,l]] = 1.0
iteration 10     reduce      k=3     ℓ=1     [μ[k,l]] = -1.0
iteration 11     reduce      k=4     ℓ=3     [μ[k,l]] = -1.0
iteration 12     exchange    k=4
iteration 13     reduce      k=3     ℓ=2     [μ[k,l]] = 1.0
iteration 14     exchange    k=3
iteration 15     exchange    k=2
iteration 16     exchange    k=3
iteration 17     reduce      k=2     ℓ=1     [μ[k,l]] = 1.0
iteration 18     exchange    k=2
iteration 19     exchange    k=4
iteration 20     reduce      k=3     ℓ=2     [μ[k,l]] = 1.0
iteration 21     exchange    k=3
iteration 22     reduce      k=2     ℓ=1     [μ[k,l]] = -1.0
iteration 23     exchange    k=2
4×4 Array{Float64,2}:
  2.0   3.0   1.0   1.0
  2.0   0.0  -2.0  -4.0
 -2.0   2.0   3.0  -3.0
  3.0  -2.0   6.0  -1.0
```

"""
function LLL{T1<:AbstractFloat, T2<:AbstractFloat}(x::Array{T1, 2}, α::T2; return_c = false, verbose::Bool = false)
    @assert 1/4 < α <= 1 ["Invalid value of α."]

    y = copy(x)
    n_rows = size(y)[1]

    ystar, μ = gram_schmidt(y, ret_coef_mat=true)

    gammax = sum(ystar .* ystar, 2)

    C = eye(y)

    ##### Internal Methods ##########################################
    function reduce(k, ℓ)
        if abs(μ[k, ℓ]) > 1/2
            if verbose
                println("iteration $n_iter \t reduce \t k=$k \t ℓ=$ℓ \t [μ[k,l]] = $(round_ties_down(μ[k,ℓ]))")
                n_iter += 1
            end

            # Step (1)
            y[k, :] -= round_ties_down(μ[k, ℓ]) * y[ℓ, :]
            C[k, :] -= round_ties_down(μ[k, ℓ]) * C[ℓ, :]

            for j in 1:ℓ-1
                μ[k, j] -= round_ties_down(μ[k, ℓ]) * μ[ℓ, j]
            end
            μ[k, ℓ] -= round_ties_down(μ[k, ℓ])
        end
    end

    function exchange(k)
        if verbose
            println("iteration $n_iter \t exchange \t k=$k")
            n_iter += 1
        end

        # Step (1)
        y[k-1, :], y[k, :] = y[k, :], y[k-1, :]
        C[k-1, :], C[k, :] = C[k, :], C[k-1, :]

        nu = μ[k, k-1]
        δ = gammax[k] + nu^2 * gammax[k-1]
        μ[k, k-1] = nu * gammax[k-1] / δ
        gammax[k] = gammax[k] * gammax[k-1] / δ
        gammax[k-1] = δ

        for j in 1:k-2
            μ[k-1, j], μ[k, j]= μ[k, j], μ[k-1, j]
        end

        for i in k+1:n_rows
            ξ = μ[i, k]
            μ[i, k] = μ[i, k-1] - nu * μ[i, k]
            μ[i, k-1] = μ[k, k-1] * μ[i, k] + ξ
        end
    end

    ##### Main Loop  ################################################
    n_iter = 1
    k = 2
    while k <= n_rows
        reduce(k, k-1)
        if gammax[k] >= (α - μ[k, k-1]^2) * gammax[k-1]
            for ℓ = k-2 : -1 : 1
                reduce(k, ℓ)
            end
            k += 1
        else
            exchange(k)
            if k > 2
                k -= 1
            end
        end
    end

    if return_c
        return C, y
    end

    return y
end

function LLL{T1<:AbstractFloat, T2<:Integer}(x::Array{T1, 2}, α::T2; verbose::Bool = false)
    LLL(x, float(α), verbose=verbose)
end

function LLL{T1<:Integer, T2<:AbstractFloat}(x::Array{T1, 2}, α::T2; verbose::Bool = false)
    LLL(float(x), α, verbose=verbose)
end

function LLL{T1<:Integer, T2<:Integer}(x::Array{T1, 2}, α::T2; verbose::Bool = false)
    LLL(float(x), float(α); verbose=verbose)
end


# # Plotting
#
# ## Basics
# There are a few different ways to plot in Julia (including calling PyPlot).

#
# Here we'll show you how to use `Plots.jl`.  If it's not installed yet, you need to use the package manager to install it, and Julia will precompile it for you the first time you use it:

## using Pkg
## Pkg.add("Plots")
using Plots

# One of the advantages to `Plots.jl` is that it allows you to seamlessly change backends. In this notebook, we'll try out the `gr()` and `plotlyjs()` backends.

#
# In the name of scientific inquiry, let's use this notebook to examine the relationship between the global temperature and the number of pirates between roughly 1860 and 2000.

globaltemperatures = [14.4, 14.5, 14.8, 15.2, 15.5, 15.8]
numpirates = [45000, 20000, 15000, 5000, 400, 17];

# Plots supports multiple backends — that is, libraries that actually do the drawing — all with the same API. To start out, let's try the GR backend.  You choose it with a call to `gr()`:

gr()

# and now we can use commands like `plot` and `scatter` to generate plots.

plot(numpirates, globaltemperatures, label="line")
scatter!(numpirates, globaltemperatures, label="points")

# The `!` at the end of the `scatter!` function name makes `scatter!` a mutating function, indicating that the scattered points will be added onto the pre-existing plot.
#
# In contrast, see what happens when you replace `scatter!` in the above with the non-mutating function `scatter`.
#
# Next, let's update this plot with the `xlabel!`, `ylabel!`, and `title!` commands to add more information to our plot.

xlabel!("Number of Pirates [Approximate]")
ylabel!("Global Temperature (C)")
title!("Influence of pirate population on global warming")

# This still doesn't look quite right. The number of pirates has decreased since 1860, so reading the plot from left to right is like looking backwards in time rather than forwards. Let's flip the x axis to better see how pirate populations have caused global temperatures to change over time!

xflip!()

# And there we have it!
#
# Note: We've had some confusion about this exercise. :) This is a joke about how people often conflate correlation and causation.
#
# **Without changing syntax, we can create this plot with the UnicodePlots backend**

Pkg.add("UnicodePlots")
unicodeplots()

#-

plot(numpirates, globaltemperatures, label="line")
scatter!(numpirates, globaltemperatures, label="points")
xlabel!("Number of Pirates [Approximate]")
ylabel!("Global Temperature (C)")
title!("Influence of pirate population on global warming")

# And notice how this second plot differs from the first!  Using text like this is a little silly in a Jupyter notebook where we have fancy drawing capabilities, but it can be very useful for quick and dirty visualization in a terminal.

#-

# ### Exercises
#
# #### 8.1
# Given
# ```julia
# x = -10:10
# ```
# plot y vs. x for $y = x^2$.  You may want to change backends back again.

# #### 8.2
# Execute the following code

p1 = plot(x, x)
p2 = plot(x, x.^2)
p3 = plot(x, x.^3)
p4 = plot(x, x.^4)
plot(p1, p2, p3, p4, layout = (2, 2), legend = false)

# and then create a $4x1$ plot that uses `p1`, `p2`, `p3`, and `p4` as subplots.


# Tests for empty unions of intervals
# I = ∅
I = IntervalUnion()
# J = [0,1] ∪ ]3,5]
J = IntervalUnion([Interval(0,1),Interval(3,true,5)])
@test empty(I)
@test number_of_components(I) == 0
@test string(I) == "∅"
@test cardinal(I) == 0
@test left(I) == []
@test right(I) == []
@test limits(I) == []
@test compact(I) == IntervalUnion()
@test compactness(I) == 0
@test 0 ∉ I
@test 1.2 ∉ I
@test I ∩ J == IntervalUnion()
@test J ∩ I == IntervalUnion()
@test I ∪ J == J
@test J ∪ I == J
@test I ⊆ J
@test J ⊈ I
@test I ⊆ I
@test complement(I) == IntervalUnion([Interval(-Inf,true,Inf,true)])
@test setdiff(I,J) == IntervalUnion()
@test setdiff(J,I) == J
@test symdiff(I,J) == J
@test symdiff(J,I) == J
@test jaccard(I,J) == 0
@test jaccard(J,I) == 0
@test overlap_coefficient(I,J) == 0
@test overlap_coefficient(J,I) == 0
@test dice_coefficient(I,J) == 0
@test dice_coefficient(J,I) == 0

# I = [0,1]
I = IntervalUnion([Interval(0,1)])
II = IntervalUnion(0,1)
@test I == II
@test string(I) == "[0,1]"
@test string(II) == "[0,1]"

# J = ]1,2.5]
J = IntervalUnion([Interval(1,true,2.5)])
JJ = IntervalUnion(1,true,2.5)
@test J == JJ
@test string(J) == "]1,2.5]"
@test string(JJ) == "]1,2.5]"

# K = ]0,1[ ∪ ]1,2.6] ∪ [2.7,4]
K = IntervalUnion([Interval(0,true,1,true),Interval(1,true,2.6),Interval(2.7,4)])
@test string(K) == "]0,1[ ∪ ]1,2.6] ∪ [2.7,4]"

# L = [-1,-0.998[
L = IntervalUnion([Interval(-1,-0.998,true)])
LL = IntervalUnion(-1,-0.998,true)
@test L == LL
@test string(L) == "[-1,-0.998["
@test string(LL) == "[-1,-0.998["

# L = ]-2.3,1.67[
M = IntervalUnion([Interval(-2.3,true,1.67,true)])
MM = IntervalUnion(-2.3,true,1.67,true)
@test M == MM
@test string(M) == "]-2.3,1.67["
@test string(MM) == "]-2.3,1.67["

# Tests for equality
@test I == I
@test I != J
@test K != J

# Tests for number of components
@test number_of_components(I) == 1
@test number_of_components(J) == 1
@test number_of_components(K) == 3

# Tests for limits
@test left(I) == [0]
@test right(I) == [1]
@test left(J) == right(I)
@test left(K) == [0,1,2.7]
@test right(K) == [1,2.6,4]
@test limits(I) == [0,1]
@test limits(J) == [1,2.5]
@test limits(K) == [0,1,2.6,2.7,4]

# Tests for ∈
@test 0.55 ∈ I
@test 0 ∈ I
@test 1 ∉ J
@test 1.000000001 ∈ J
@test 1 ∉ K
@test 2.77 ∈ K

# Tests for cardinal
@test cardinal(I) == 1.0
@test cardinal(J) == 1.5
@test cardinal(K) == 3.9

# Tests for ⊆
@test I ⊈ K
@test J ⊆ K
@test I ⊆ I
@test K ⊆ K

# Tests for ∪
# [0,1] ∪ ]1,2.5] = [0,2.5]
@test I ∪ J == IntervalUnion([Interval(0,2.5)])
# [0,1] ∪ ]0,1[ ∪ ]1,2.6] ∪ [2.7,4] = [0,2.6] ∪ [2.7,4]
@test I ∪ K == IntervalUnion([Interval(0,2.6),Interval(2.7,4)])
# ]1,2.5] ∪ ]0,1[ ∪ ]1,2.6] ∪ [2.7,4] = ]0,1[ ∪ ]1,2.6] ∪ [2.7,4]
@test J ∪ K == K
@test I ∪ I == I
@test J ∪ J == J
@test K ∪ K == K

# Tests for ∩
# [0,1] ∩ ]1,2.5] = ]0,0[
@test I ∩ J == IntervalUnion()
# [0,1] ∩ (]0,1[ ∪ ]1,2.6] ∪ [2.7,4]) = ]0,1[
@test I ∩ K == IntervalUnion([Interval(0,true,1,true)])
# ]1,2.5] ∩ (]0,1[ ∪ ]1,2.6] ∪ [2.7,4]) = ]1,2.5]
@test J ∩ K == J
@test I ∩ I == I
@test J ∩ J == J
@test K ∩ K == K

# Tests for complement
# comp([0,1]) = ]-inf,0[ ∪ ]1,inf[
@test complement(I) == IntervalUnion([Interval(-Inf,true,0,true),Interval(1,true,Inf,true)])
# comp([1,2.5]) = ]-inf,1] ∪ ]2.5,inf[
@test complement(J) == IntervalUnion([Interval(-Inf,true,1),Interval(2.5,true,Inf,true)])
# comp(]0,1[ ∪ ]1,2.6] ∪ [2.7,4]) = ]-inf,0] ∪ [1,1] ∪ ]2.6,2.7[ ∪ ]4,inf[
@test complement(K) == IntervalUnion([Interval(-Inf,true,0),Interval(1,1),Interval(2.6,true,2.7,true),Interval(4,true,Inf,true)])
# comp([1,1]) = ]-inf,1[ ∪ ]1,inf[
@test complement(IntervalUnion([Interval(1,1)])) == IntervalUnion([Interval(-Inf,true,1,true),Interval(1,true,Inf,true)])
@test complement(complement(I)) == I
@test complement(complement(J)) == J
@test complement(complement(K)) == K
# complement(]-Inf,Inf[) = ∅
@test complement(IntervalUnion([Interval(-Inf,true,Inf,true)])) == IntervalUnion()
# complement(]-Inf,2]) = ]2,Inf[
@test complement(IntervalUnion([Interval(-Inf,true,2)])) == IntervalUnion([Interval(2,true,Inf,true)])
# complement(]-Inf,-1.23[) = [-1.23,Inf[
@test complement(IntervalUnion([Interval(-Inf,true,-1.23,true)])) == IntervalUnion([Interval(-1.23,Inf,true)])
# complement(]1,Inf[) = ]-Inf,1]
@test complement(IntervalUnion([Interval(1,true,Inf,true)])) == IntervalUnion([Interval(-Inf,true,1)])
# complement([-2.76,Inf[) = ]-Inf,-2.76[
@test complement(IntervalUnion([Interval(-2.76,Inf,true)])) == IntervalUnion([Interval(-Inf,true,-2.76,true)])

# Tests for setdiff
# setdiff([0,2] ∪ [3,4], [1,3] ∪ [3.5,5]) = [0,1[ ∪ ]3,3.5[
@test setdiff(IntervalUnion([Interval(0,2),Interval(3,4)]),IntervalUnion([Interval(1,3),Interval(3.5,5)]))==IntervalUnion([Interval(0,1,true),Interval(3,true,3.5,true)])
# setdiff([0,1], ]0,1[ ∪ ]1,2.6] ∪ [2.7,4]) = [0,0] ∪ [1,1]
@test setdiff(I,K) == IntervalUnion([Interval(0,0),Interval(1,1)])

# Tests for symdiff
# symdiff([0,1], ]0,1[ ∪ ]1,2.6] ∪ [2.7,4]) =[0,0] ∪ [1,2.6] ∪ [2.7,4]
@test symdiff(I,K) == IntervalUnion([Interval(0,0),Interval(1,2.6),Interval(2.7,4)])

# Tests for compact
# compact(]0,1] ∪ [2,3])=[0,3]
@test compact(IntervalUnion([Interval(0,true,1),Interval(2,3)]))==IntervalUnion([Interval(0,3)])

# Tests for compactness
# compactness(]0,1] ∪ [2,3])=2/3
@test compactness(IntervalUnion([Interval(0,true,1),Interval(2,3)]))==2/3

# Tests for superset
# superset(]0,1] ∪ [2,3])=]0,3]
@test superset(IntervalUnion([Interval(0,true,1),Interval(2,3)]))==IntervalUnion([Interval(0,true,3)])

# Tests for super_complement
# super_complement(]0,1] ∪ [2,3]) = ]-Inf,0] ∪ ]3,Inf[
@test super_complement(IntervalUnion([Interval(0,true,1),Interval(2,3)]))==IntervalUnion([Interval(-Inf,true,0),Interval(3,true,Inf,true)])

# Tests for restricted_complement
# restricted_complement([0,1] ∪ [4,5]) = ]1,4[
@test restricted_complement(IntervalUnion([Interval(0,1),Interval(4,5)]))==IntervalUnion([Interval(1,true,4,true)])
# restricted_complement(]0,1[ ∪ ]2.5,3] ∪ [3.5,6]) = [1,2.5] ∪ ]3,3.5[
@test restricted_complement(IntervalUnion([Interval(0,true,1,true),Interval(2.5,true,3),Interval(3.5,6)]))==IntervalUnion([Interval(1,2.5),Interval(3,true,3.5,true)])

# Tests for complement_cardinal
# complement_cardinal([0,1] ∪ [4,5]) = 3
@test complement_cardinal(IntervalUnion([Interval(0,1),Interval(4,5)]))==3
# complement_cardinal(]0,1[ ∪ ]2.5,3] ∪ [3.5,6]) = 2
@test complement_cardinal(IntervalUnion([Interval(0,true,1,true),Interval(2.5,true,3),Interval(3.5,6)]))==2

# Tests for sampling unions of intervals
# I = ]-2.3,0[ ∪ ]0,1]
I = IntervalUnion([Interval(-2.3,true,0,true),Interval(0,true,1)])
s = sample(I)
@test (-2.3 < s < 0) || (0 < s <= 1)
samples = sample(I,100)
@test length(samples) == 100
for s in samples
	@test (-2.3 < s < 0) || (0 < s <= 1)
end
# I = [1,1] ∪ [3.4,3.4] ∪ [6,6]
I = IntervalUnion([Interval(1,1),Interval(3.4,3.4),Interval(6,6)])
s = sample(I)
@test s ∈ [1,3.4,6]


#=
Given a function that generates perfectly random numbers between 1 and k (inclusive), where k is an input, write a function that shuffles a deck of cards represented as an array using only swaps.

It should run in O(N) time.

Hint: Make sure each one of the 52! permutations of the deck is equally likely.
=#

function swap(deck::Array{Int,1}, card1_index::Int, card2_index::Int)
    newdeck = copy(deck)
    newdeck[card1_index] = deck[card2_index]
    newdeck[card2_index] = deck[card1_index]
    return newdeck
end

function randk(k::Int)
    return rand(1:k)
end

function shuffle(deck::Array{Int,1})
    for card_index in 1:52
        new_pos = randk(52)
        deck = swap(deck, card_index, new_pos)
    end
    return deck
end