using FractionalDiffEq, Plots
fun(x, y) = 1-y
prob = SingleTermFODEProblem(fun, 0.5, 0, (0, 5))
sol = solve(prob, 0.01, PECE())
tspan = collect(0:0.01:5)
#plot(result.t, result.u)
plot(sol, linewidth=2, legend=:bottomright)


using Laplacians
include("compareSolvers.jl")
compareSolversOut(1000,10)


export PowFunction

struct PowFunction{T, N} <: AbstractMathFun{T, N}
    pow::AbstractMathFun
    func::AbstractMathFun

    PowFunction{T, N}(func::AbstractMathFun, pow::T; force_emit=true) where {T, N} = new{T, N}(ConstFunction{T}(pow, force_emit = force_emit), func)
    PowFunction{T, N}(func::AbstractMathFun, pow::AbstractMathFun) where {T, N} = new{T, N}(pow, func)

    (f::PowFunction{T, N})(call::MFCall) where {T, N} = call(f.func(call) ^ f.pow(call))

    function Functions.string(f::PowFunction, emitter::FunctionEmitter)
        return "(" * string(f.func, emitter) * ")^" * string(f.pow, emitter)
    end

    function Functions.emit(f::PowFunction{T, N}, emitter::FunctionEmitter{T}) where {T, N}
        return Expr(:call, :(^), emit(f.func, emitter), emit(f.pow, emitter))
    end

    function Functions.simplify(f::PowFunction{T, N}, emit_constants::Bool) where {T, N}
        pows = simplify(f.pow, emit_constants)
        funcs = simplify(f.func, emit_constants)
        (is_constant(pows, emit_constants) && is_constant(funcs, emit_constants)) && (return ConstFunction{T}(functs.value ^ pows.value))
        (is_constant(pows, emit_constants) && pows.value == 0) && (return ConstFunction{T}(1))
        (is_constant(pows, emit_constants) && pows.value == 1) && (return funcs)
        return PowFunction{T, N}(funcs, pows)
    end
end


include("C:\\Users\\warner\\Desktop\\My Stuff\\Personal Learning\\Monte Carlo\\NautilusMC\\NautilusMC\\Materials.jl")

mutable struct Trans
    type::String
    params::Vector{Float64}
end

mutable struct Surface
    type::String
    bounds_func::Function
    inside_func::Function
    dist_to_edge_func::Function
    transformations::Vector{Trans}
end

mutable struct Cell
    surfs_within::Vector{Surface}
    surfs_outside::Vector{Surface}
    fill::Any
    transformations::Vector{Trans}
end

mutable struct Universe
    cells::Vector{Cell}
    transformations::Vector{Trans}
end

function inside_surf(
    surface::Surface,
    x::Vector{Float64})
    return surface.inside_func(x)
end

function dist_to_surf(
    surface::Surface,
    x::Vector{Float64},
    v::Vector{Float64})
    return surface.dist_to_edge_func(x, v)
end

function inf()
    function bounds_func()
        return [-Inf, -Inf, -Inf], [Inf, Inf, Inf]
    end

    function inside_func(x::Vector{Float64})
        return true
    end

    function dist_to_edge_func(x::Vector{Float64}, v::Vector{Float64})
            return Inf
    end

    return Surface("infinite", bounds_func, inside_func, dist_to_edge_func, [])

end

function px(X::Float64)
    function bounds_func()
        return [X, -Inf, -Inf], [X, Inf, Inf]
    end

    function inside_func(x::Vector{Float64})
        return (x[1] < X)
    end

    function dist_to_edge_func(x::Vector{Float64}, v::Vector{Float64})
        d = (X - x[1]) / v[1]
        if d > 0
            return d
        else
            return Inf
        end
    end

    return Surface("plane", bounds_func, inside_func, dist_to_edge_func, [])

end

function py(Y::Float64)
    function bounds_func()
        return [-Inf, Y, -Inf], [Inf, Y, Inf]
    end

    function inside_func(x::Vector{Float64})
        return (x[2] < Y)
    end

    function dist_to_edge_func(x::Vector{Float64}, v::Vector{Float64})
        d = (Y - x[2]) / v[2]
        if d > 0
            return d
        else
            return Inf
        end
    end

    return Surface("plane", bounds_func, inside_func, dist_to_edge_func, [])

end

function pz(Z::Float64)
    function bounds_func()
        return [-Inf, -Inf, Z], [Inf, Inf, Z]
    end

    function inside_func(x::Vector{Float64})
        return (x[3] < Z)
    end

    function dist_to_edge_func(x::Vector{Float64}, v::Vector{Float64})
        d = (Z - x[3]) / v[3]
        if d > 0
            return d
        else
            return Inf
        end
    end

    return Surface("plane", bounds_func, inside_func, dist_to_edge_func, [])

end

function planeEQ(A::Float64, B::Float64, C::Float64, D::Float64)
    #=
    Plane with equation Ax + By + Cz + D = 0
    =#
    function bounds_func()
        return [-Inf, -Inf, -Inf], [Inf, Inf, Inf]
    end

    function inside_func(x::Vector{Float64})
        val = A*x[1] + B*x[2] + C*x[3] + D
        return (val < 0)
    end

    function dist_to_edge_func(x::Vector{Float64}, v::Vector{Float64})
        d = -(D + A*x[1] + B*x[2] + C*x[3]) / (A*v[1] + B*v[2] + C*v[3])
        if d > 0
            return d
        else
            return Inf
        end
    end

    return Surface("plane", bounds_func, inside_func, dist_to_edge_func, [])

end

function planePts(x1::Vector{Float64}, x2::Vector{Float64}, x3::Vector{Float64})
    #=
    Plane through points x1, x2, and x3.
    =#

    u1 = x1 - x2
    u2 = x3 = x2

    A = u1[2]*u2[3] - u1[3]*u2[2]
    B = u1[1]*u2[3] + u1[3]*u2[1]
    C = u1[1]*u2[2] - u1[2]*u2[1]
    D = -A*x[1] - B*x[2] - C*x[3]

    function bounds_func()
        return [-Inf, -Inf, -Inf], [Inf, Inf, Inf]
    end

    function inside_func(x::Vector{Float64})
        vec = x - x2
        val = A*vec[1] + B*vec[2] + C*vec[3]
        return (val < 0)
    end

    function dist_to_edge_func(x::Vector{Float64}, v::Vector{Float64})
        d = -(D + A*x[1] + B*x[2] + C*x[3]) / (A*v[1] + B*v[2] + C*v[3])
        if d > 0
            return d
        else
            return Inf
        end
    end

    return Surface("plane", bounds_func, inside_func, dist_to_edge_func, [])

end

function box(x1::Vector{Float64}, x2::Vector{Float64})
    #=
    Rectangular prism drawn between corners x and x2
    =#

    function bounds_func()
        return x1, x2
    end

    function inside_func(x::Vector{Float64})
        return sum(x.>x1) + sum(x.<x2) == 6
    end

    function dist_to_edge_func(x::Vector{Float64}, v::Vector{Float64})
        dx1 = (x1[1] - x[1]) / v[1]
        dx2 = (x2[1] - x[1]) / v[1]
        dy1 = (x1[2] - x[2]) / v[2]
        dy2 = (x2[2] - x[2]) / v[2]
        dz1 = (x1[3] - x[3]) / v[3]
        dz2 = (x2[3] - x[3]) / v[3]
        distances = [dx1, dx2, dy1, dy2, dz1, dz2]
        distances = ifelse.(distances .< 0, Inf, distances)
        return minimum(distances)
    end

    return Surface("box", bounds_func, inside_func, dist_to_edge_func, [])

end


module SparseFEM

using Compat

if VERSION >= v"0.7.0-DEV.3389"
    using SparseArrays
end
if VERSION >= v"0.7.0-DEV.3449"
    using LinearAlgebra
end

# assemble the finite-difference laplacian
function fdlaplacian(N)
    # create a 1D laplacian and a sparse identity
    fdl1 = spdiagm(-1 => ones(N-1), 0 => -2*ones(N), 1 => ones(N-1))
    # laplace operator on the full grid
    I_N = sparse(1.0I, N, N)
    return kron(I_N, fdl1) + kron(fdl1, I_N)
end

# get the list of boundary dof-indices
function get_free(N)
    L = zeros(Int, N, N)
    L[2:N-1, 2:N-1] .= 1
    return findall(!iszero, L)
end

# timing of assembly, slice and solve
function perf_sparse_fem(N)
    Ifree = [LinearIndices((N, N))[i] for i in get_free(N)]
    # assembly
    A = fdlaplacian(N)
    # boundary condition
    B = A[Ifree, Ifree]
    # solver
    @static if VERSION <= v"0.7.0-DEV.5211"
        return lufact(B)
    else
        return lu(B)
    end
end

end # module


"""Coefficients of the generalised logistic function to describe the vertical coordinate.
Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.
See geometry.jl and function vertical_coordinate for more informaiton.

Following the notation of https://en.wikipedia.org/wiki/Generalised_logistic_function (Dec 15 2021).

Change default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer."""
@with_kw struct GenLogisticCoefs
    A::Real=-0.283     # obtained from a fit in /input_date/vertical_coordinate/vertical_resolution.ipynb
    K::Real= 0.871
    C::Real= 0.414
    Q::Real= 6.695
    B::Real=10.336
    M::Real= 0.602
    ν::Real= 5.812
end


immutable SkiLineDynamics{G<:Function}
    p::PowseekerMDP{G}
    pos::Vec2
    dir::Vec2
end

function rk4step(d::SkiLineDynamics, vel::Float64)
    h = d.p.dt
    vec = SVector(vel, 0.0)
    k1 = f(d, vec)
    k2 = f(d, vec+h/2*k1)
    k3 = f(d, vec+h/2*k2)
    k4 = f(d, vec+h*k3)
    return vec + h/6*(k1+2*k2+2*k3+k4)
end

function f(d::SkiLineDynamics, vec::Vec2)
    vel = vec[1]
    dist = vec[2]
    h = d.p.dt
    mfs = d.p.max_flat_speed
    theta = atan(dot(d.p.gradient(d.pos+dist*d.dir), d.dir)) # slope angle
    acc = d.p.force*max(0.0, mfs-vel)/mfs - 9.8*sin(theta) - sign(vel)*9.8*(vel/d.p.terminal_vel)^2
    return SVector(acc, vel*cos(theta))
end


@testset "check gradient for Chain" begin
    clear()

    # [1] prepare input data and its label
    TYPE = Array{Float64}
    x = randn(256, 62)
    l = rand(64, 62)
    l = l ./ sum(l, dims=1)
    x = Variable(x; type=TYPE)
    l = Variable(l; type=TYPE)

    blocks = [
        Dense(256,128,tanh; type=TYPE),
        Dense(128,128,cos;  type=TYPE),
        Dense(128,128,sin;  type=TYPE),
        Dense(128,128,cos!; type=TYPE),
        Dense(128,128,sin!; type=TYPE),
        Dense(128, 64,relu; type=TYPE),
        Maxout(64, 64; k=3, type=TYPE),
        Residual(
            Dense(64,32,sin;type=TYPE),
            Linear(32,64,   type=TYPE)
        )
    ]

    model = Chain(blocks)

    # [2] forward and backward propagation
    outs = softmax(forward(model, x); dims=1)
    COST1 = crossEntropyCost(outs, l)
    backward()

    # [3] with a samll change of a weight
    DELTA = 1e-5
    model[1].w.value[1] += DELTA
    GRAD = blocks[1].w.delta[1]

    # [4] forward and backward propagation
    outs = softmax(forward(model, x); dims=1)
    COST2 = crossEntropyCost(outs, l)
    backward()

    # [5] check if the auto-grad is true or not
    dLdW = (COST2 - COST1)/DELTA
    err  = abs((dLdW-GRAD)/(GRAD==0.0 ? 1.0 : GRAD))*100
    err  = err < 1e-3 ? 0.0 : err
    @test err < 5.0
end


using quantumoptics

N = 200
xmin = -10.
xmax = 10.
omega = 2.

x0 = 1.
p0 = 0.
sigma0 = 1.

T = [0.:0.2:2.;]

basis_position = quantumoptics.particle.PositionBasis(xmin, xmax, N)
basis_momentum = quantumoptics.particle.MomentumBasis(basis_position)
xsamplepoints = quantumoptics.particle.samplepoints(basis_position)
psamplepoints = quantumoptics.particle.samplepoints(basis_momentum)

Tpx = quantumoptics.particle.FFTOperator(basis_momentum, basis_position)
Txp = quantumoptics.particle.FFTOperator(basis_position, basis_momentum)

# Position Basis
#p_pos = SparseOperator(quantumoptics.particle.momentumoperator(basis_position))
p_pos = Txp*quantumoptics.particle.momentumoperator(basis_momentum)*Tpx
x_pos = quantumoptics.particle.positionoperator(basis_position)
V_pos = Operator(basis_position, diagm(omega^2*xsamplepoints.^2))
H_pos = p_pos*p_pos/2 + V_pos

psi0_pos = quantumoptics.particle.gaussianstate(basis_position, x0, p0, sigma0)
@time tout, psi_pos_t = timeevolution.schroedinger(T, psi0_pos, H_pos)
@time tout, psi_pos_t = timeevolution.schroedinger(T, psi0_pos, H_pos)

# Momentum Basis
p_mom = SparseOperator(quantumoptics.particle.momentumoperator(basis_momentum))
x_mom = SparseOperator(quantumoptics.particle.positionoperator(basis_momentum))
#x_mom = SparseOperator(Tpx*quantumoptics.particle.positionoperator(basis_position)*Txp)
V_mom = SparseOperator(Tpx*Operator(basis_position, diagm(omega^2*xsamplepoints.^2))*Txp)
H_mom = p_mom*p_mom/2 + V_mom

psi0_mom = quantumoptics.particle.gaussianstate(basis_momentum, x0, p0, sigma0)
@time tout, psi_mom_t = timeevolution.schroedinger(T, psi0_mom, H_mom)
@time tout, psi_mom_t = timeevolution.schroedinger(T, psi0_mom, H_mom)

# Mixed evolution
H_mix = LazyProduct(Txp, p_mom*p_mom/2, Tpx) + SparseOperator(V_pos)
# println(expect(H_mix, psi0_pos))
# println(expect(H_pos, psi0_pos))
# println(expect(H_mix, psi0_pos + 0.1im*H_mix*psi0_pos))
# println(expect(H_pos, psi0_pos + 0.1im*H_pos*psi0_pos))
@time tout, psi_mix_t = timeevolution.schroedinger(T, psi0_pos, H_mix)
@time tout, psi_mix_t = timeevolution.schroedinger(T, psi0_pos, H_mix)

for i=1:length(T)
    println(expect(p_mom, psi_mom_t[i]), ", ", expect(p_pos, psi_pos_t[i]))
    println(expect(x_mom, psi_mom_t[i]), ", ", expect(x_pos, psi_pos_t[i]))
    println(norm(Txp*psi_mom_t[i] - psi_pos_t[i]))
    println(norm(psi_mix_t[i] - psi_pos_t[i]))
    println("================================================")
end


using LinearAlgebra
function backtracking_line_search(objective,x,alpha,beta,step,arc,gradient)
    demanding_step = step
    while objective(x + demanding_step * arc) > objective(x) + alpha * demanding_step * gradient' * arc
        demanding_step = beta * demanding_step
    end
    return demanding_step
end


module RandomSequence

using SPPL

ns = @sppl (debug) begin
    X = array(3)
    W = array(3)
    X[0] ~ SPPL.Normal()
    for i in 1 : 2
        if X[i - 1] > 0
            X[i] ~ SPPL.Normal(0, 1)
            W[i] ~ SPPL.Atomic(0)
        else
            W[i] ~ SPPL.Bernoulli(0.5)
            W[i] == 0 ? X[i] .> SPPL.Fraction(1, 2) * X[i-1]^2 + X[i-1] : X[i] ~ SPPL.Normal(0, 1)
        end
    end
end

random_sequence = ns.model

# Observe X[1] > 0.
random_sequence_given_X1 = condition(model, ns.X[1] > 0)
println(probability(random_sequence, ns.X[1] > 0))
println(probability(random_sequence_given_X1, ns.X[1] > 0))

# Observe x[2] > 0.
random_sequence_given_X2 = random_sequence.condition(ns.X[2] > 0)
println(probability(random_sequence, ns.X[1] > 0))
println(probability(random_sequence_given_X2, ns.X[1] > 0))

# Compute mutual information.
mutual_information(random_sequence, ns.X[0]>0, ns.X[1]>0) |> println
mutual_information(random_sequence, ns.X[0]>0, ns.X[2]>0) |> println

end # module


randc(m,n) = randn(m,n)+im*randn(m,n)

function grf(m,n,C::StationaryCovarianceFunction)
    tx = collect(0:m-1)
    ty = collect(0:n-1)
    Rjs = zeros(n,m)
    for i in 1:m, j in 1:n
        Rjs[j,i] = C([tx[1],ty[1]],[tx[i],ty[j]])
    end
    Sjs = [Rjs Rjs[:,end] Rjs[:,end:-1:2]]
    Sjs2 = [Sjs ; Sjs[end,:] ; Sjs[end:-1:2,1] Sjs[end:-1:2,end:-1:2]]
    Γ = fft(Sjs2)
    Z = randc(size(Sjs2)...)
    fft(sqrt(Γ./(4*m*n)).*Z)[1:m,1:n]
end


########## Parameters for scripts ##########

###### bspline-MLE ######

#### cross-scale only ####

# sfct = 1 # factor of scale
# vm = 1  # vanishing moments

cscl = Int(Npd*sfct)  # central scale
wscl = 10  # half scale window width, e.g. <=10
minscl, maxscl = max(1, cscl-wscl), cscl+wscl
# maxscl = Npd - 0*Npd÷4
# # minscl = maxscl ÷ 2
# minscl = maxscl - Npd÷2

wfct = 40  # factor of moving window

parms = (
    estimator = "bspline_MLE_scale",
    method = :optim,  # method of optimizer

    # Parameters for B-Spline wavelet transform
    scalefactor = sfct,
    sclrng = 2*(minscl:maxscl),  # index range of inertial scales
    vm = 1,  # vanishing moment of the wavelet
    fmode = :causal,  # mode of filtration
    ρ = 0,  # threshold for truncation of small eigen values in the covariance matrix
    partial = true,  # true for whittle estimator. The case partial=false cannot be handled correctly for now.

    # Parameters for moving window
    windowfactor = wfct,
    wsize = cscl*wfct,  # size of moving window
    pov = Npd÷1,  # step of moving window
    boundary = :hard,  # boundary condition
)

base_estimator = x -> FracFin.bspline_MLE_estim(x, parms.sclrng, parms.vm; ρ=parms.ρ, partial=parms.partial)


# Fast sampling from truncated normal distributions

using Distributions

include("randtn_zigtables.jl")

const log2π = 1.83787706641
const tnzig_kmin = 5

# returns sample x ~ N(0,1) truncated at [a,b], using rejection sampling with
# a truncated exponential proposal
function randtn_truncexp(a::Real, b::Real)
    twoasq = 2abs2(a)
    c = exp(-a*(b - a)) - 1
    while true
        z = log(1 + c * rand())
        if -twoasq * log(rand()) > abs2(z)
            return a - z/a
        end
    end
end

# returns sample x ~ N(0,1) truncated at [a,b], using rejection sampling with
# a standard normal proposal
function randtn_norm(a::Real, b::Real)
    while true
        z = randn()
        if z >= a && z <= b
            return z
        end
    end
end

# returns sample x ~ N(0,1) truncated at [a,b], assuming b > a and |a| < |b|.
function randtnstdord(a::Real, b::Real, tp::Real)
    # a in left tail, or large mass, implies b > -a, use normal proposal
    if a < tnzig_xmin || tp > 0.3
        return randtn_norm(a, b)
    end

    # a in right tail implies b > a, use truncated exponential proposal
    if a > tnzig_xmax
        return randtn_truncexp(a, b)
    end

    # tnzig_xmin <= a <= tnzig_xmax, use Chopin's algorithm
    N = length(tnzig_x) - 1
    # a in interval ka or ka+1
    ka = tnzig_ncell[tnzig_i0 + ifloor(a * tnzig_invh)]
    kb = b < tnzig_xmax ?               # b in interval kb-1 or kb
        min(tnzig_ncell[tnzig_i0 + floor(b * tnzig_invh)] + 1, N) :
        N + 1                                     # b in right tail

    # for small kb - ka use rejection sampling with truncated exponential prop.
    if abs(kb - ka) < tnzig_kmin
        return randtn_truncexp(a, b)
    end

    while true
        # sample integer k from range [ka, kb]
        k = ka + ifloor((kb - ka + 1) * rand())

        if k == N + 1
            # right tail, rejection sampling with truncated exponential proposal
            xN1 = tnzig_x[end]
            z = -log(rand()) / xN1
            if abs2(z) < -2log(rand()) && z < b - xN1
                return xN1 + z
            end

        elseif k > ka + 1 && (k < kb - 1 || b >= tnzig_xmax)
            # all other intervals, away from boundaries
            u = rand()
            simy = tnzig_yu[k] * u;
            if simy < tnzig_yl[k]
                # happens most of the time
                return tnzig_x[k] + u * tnzig_delta[k]
            end
            # check if we are below the pdf curve
            sim = tnzig_x[k] + tnzig_d[k] * rand()
            if abs2(sim) + 2log(simy) + log2π < 0
                return sim
            end

        else
            # boundary intervals ka, ka+1, kb-1, kb
            sim = tnzig_x[k] + tnzig_d[k] * rand()
            if sim >= a && sim <= b
                simy = tnzig_yu[k] * rand()
                if simy < tnzig_yl[k] || abs2(sim) + 2log(simy) + log2π < 0
                    return sim
                end
            end
        end
    end
end
# same as randtnstdord, but does not require |a| < |b|
randtnstd(a::Real, b::Real, tp::Real) = abs(a) > abs(b) ?
                                        -randtnstdord(-b, -a, tp) :
                                        randtnstdord(a, b, tp)

function randtn(d::Truncated{Normal})
    d0 = d.untruncated
    μ = mean(d0)
    σ = std(d0)
    a = (d.lower - μ) / σ
    b = (d.upper - μ) / σ
    z = abs(a) > abs(b) ? -randtnstdord(-b, -a, d.tp) : randtnstdord(a, b, d.tp)
    return μ + σ * z
end


using MLDatasets: MNIST
using Plots
using Flux

xtrain, ytrain = MNIST.traindata();
xtest, ytest = MNIST.testdata();

ytrain_OneHot = Flux.onehotbatch(ytrain,0:9);
ytest_OneHot = Flux.onehotbatch(ytest,0:9);

xtrain_vec = zeros(28*28,60000)
xtest_vec = zeros(28*28,10000)

for i in 1:size(xtrain)[3]
    xtrain_vec[:,i] = vec(xtrain[:,:,i])
end

for i in 1:size(xtest)[3]
    xtest_vec[:,i] = vec(xtest[:,:,i])
end

xtrain_vec

flux_model = Chain(Dense(28*28, 30, sigmoid), Dense(30,10, sigmoid))

flux_loss(x,y) = Flux.mse(flux_model(x),y)

ps = params(flux_model)
dataset = [(xtrain_vec, ytrain_OneHot)];
opt = Descent(3.0)

cb = () -> println(flux_loss(xtrain_vec,ytrain_OneHot))

Flux.@epochs 30 Flux.train!(flux_loss, ps, dataset, opt, cb = cb)

acc = 0
for i in 1:10000
    x = Flux.onecold(flux_model(xtest_vec[:,1])).-1
    y = ytest[i]
    if x[1] ≈ y
        acc += 1
    end
end
acc /= 10000
flux_model(xtest_vec[:,2])


# Author: Gabrio Rizzuti, rizzuti.gabrio@gatech.edu
# Date: March, 2020
# Copyright: Georgia Institute of Technology, 2020
#
# Uncertainty quantification for AVP for Sleipner via Langevin dynamics

using LinearAlgebra, Statistics, PyPlot, Images, JLD2
push!(LOAD_PATH, "./src/"); using WaveOpsAVP
push!(LOAD_PATH, "../LangevinDynamics/src/"); using LangevinDynamics
using Random; Random.seed!(1)

## Load model, synthetics, and MAP result

@load "./data/Sleipner/Sleipner_data.jld"
@load "./data/Sleipner/results/MAP_results.jld"

## Setting log-posterior loss

# Pre- and post-processing for log-posterior
nz = size(Mtrue.m, 1)
mmin = R(1/3000^2)
mmax = Inf
function proj_bounds(m::Array{R}, mmin, mmax)
    m_ = copy(m)
    m_[m_ .< mmin] .= mmin
    m_[m_ .> mmax] .= mmax
    return m_
end
Mbg_ = Mbg
# Mbg_ = Mmap
# Mbg_ = Mtrue

# Log-likelihood
mloglike_fun(m::Model; compute_grad::Bool = false) = mLogLikelihood_gauss(dat_n, m, σ2_n, F; compute_grad = compute_grad)

# Log-prior
D = derivative_linop(Mbg.geom)
# D = LinFunctionMod2Mod(m->m, m->m)
M0 = Model(Mbg.geom, zeros(R, nz, 1))
# M0 = Mbg
Dx = D.eval(Mtrue-M0)
# (gauss)
# σ2_pr = R(2e-8)^2
σ2_pr = R(1e-8)^2
mlogpr_fun(m::Model; compute_grad::Bool = false) = mLogPrior_gauss(m, σ2_pr; m0 = M0, D = D, compute_grad = compute_grad)
# # (L1-smooth)
# ϵ2 = R(0.001)*norm(Dx.m, Inf)^2
# σ2_pr = R(1e-3)*sum(sqrt.(Dx.m.^R(2).+ϵ2))[1]
# mlogpr_fun(m::Model; compute_grad::Bool = false) = mLogPrior_L1smooth(m, σ2_pr, ϵ2; m0 = M0, D = D, compute_grad = compute_grad)

# Log-posterior
function mlogpost_fun(m::Array{R, 2})
    f, g = mLogPost(Model(Mbg.geom, proj_bounds(m, mmin, mmax)), mloglike_fun, mlogpr_fun; compute_grad = true)
    return f[1], g.m
end

## Langevin sampling

# SGLD parameters
maxit = 2^10
ϵ0 = R(1e-17)
ϵ1 = ϵ0*R(1e-2)
gamma = R(0.55)
# gamma = R(0)
burnin = 100
m0 = Mmap.m

# Run SGLD
time = @elapsed fval, μ, σ2, ϵ, minv = langevinSolver(mlogpost_fun, maxit, m0; ϵ0 = ϵ0, ϵ1 = ϵ1, gamma = gamma, burnin = burnin, save = true)

# Saving result
@save "./data/Sleipner/results/UQ_langevin_results.jld" dat_n σ2_pr σ2_n fval minv time μ σ2 ϵ


using Combinatorics

function parse_line(l)
    m = match(r"^(?<from>\w+) to (?<to>\w+) = (?<dist>\d+)$", l)
    (from=m[:from], to=m[:to], dist=parse(Int, m[:dist]))
end

data = readlines() .|> parse_line

function tsp(g, f)
    n = length(g)
    cities = keys(g) |> collect

    function cost(order)
        c = 0
        pos = order[1]
        for next_pos in order[2:end]
            c += g[cities[pos]][cities[next_pos]]
            pos = next_pos
        end
        c
    end

    f(cost.(permutations(1:n)))
end

function solve_1()
    g = Dict{String, Dict{String, Int}}()
    for d in data
        from, to, dist = d
        dift = get!(g, from, Dict())
        dift[to] = dist
        ditf = get!(g, to, Dict())
        ditf[from] = dist
    end

    tsp(g, minimum)
end

function solve_2()
    g = Dict{String, Dict{String, Int}}()
    for d in data
        from, to, dist = d
        dift = get!(g, from, Dict())
        dift[to] = dist
        ditf = get!(g, to, Dict())
        ditf[from] = dist
    end

    tsp(g, maximum)
end

solve_1() |> println
solve_2() |> println


## Let's solve the bidding problem
using SigmoidalProgramming
using Random

# problem data
Random.seed!(4)

function bidding(nvar=500; problemtype=:paper)
    nconstr = 1
    l = fill(-10, nvar)
    u = fill(10, nvar)
    A = ones(nconstr, nvar)

    # boring bidding
    if problemtype==:boring
    B = [.4*nvar]
    z = zeros(nvar)
    fs = Function[x -> i/(1 + exp(-x)) for i=1:nvar]
    dfs = Function[x-> i*exp(-x)/(1 + exp(-x))^2 for i=1:nvar]

    # bidding example from the paper
    elseif problemtype==:paper
        v = 4*rand(nvar)
        beta = -3*v
        alpha = 10
        B = [.2*sum(v)]
        z = -beta/alpha
        fs = Function[x->logistic(alpha*x + beta[i]) - logistic(beta[i]) for i=1:nvar]
        dfs = Function[x->alpha*logistic_prime(alpha*x + beta[i]) for i=1:nvar]
    else
        error("did not recognize problem type $problemtype")
    end

    problem = LinearSP(fs, dfs, z, A, B)

    # branch and bound
    pq, bestnodes, lbs, ubs = @time solve_sp(l, u, problem, verbose=false;
                                             TOL=nvar*1e-3, maxiters=1000)
    println("$(length(lbs)) iterations")
    node = bestnodes[end] #dequeue!(pq)
    # println("best node has node.ub = $(node.ub) and solution $(node.x)")
    println("lb: ",lbs[end])
    println("ub: ",ubs[end])
    println("ratio: ",(ubs[end]-lbs[end])/lbs[end])

    return pq, bestnodes, lbs, ubs
end

# for size=[1,10,20,50,100,500,1000,2000,5000,10000]
#     println("bidding $size")
#     bidding(size)
# end

pq, bestnodes, lbs, ubs = bidding(20)


##################################################
# Test star DF
##################################################

"""
    _HdHTest(a,b,c,d,x)

Computes the `H(a,b,c,d;x)` function used in the distribution function and its derivative.
Returns a tuple `(H, dH/dx, d2H/dx2)`.

# Arguments
- `x::Float64`: `x` parameter.
"""
function _HdHTest(a::Float64, b::Float64, c::Float64, d::Float64, x::Float64)
    if (x <= 1)
        pref = 1/(GAMMA_ca*GAMMA_ad)
        HG   = _₂F₁(a+b,1+a-c,a+d,x)#tabHGInt[1](x) #
        HGp  = _₂F₁(a+b+1,a-c+2,a+d+1,x)#tabHGInt[3](x) #
        HGpp = _₂F₁(a+b+2,a-c+3,a+d+2,x)#tabHGInt[5](x) #
        H = pref*x^a*HG
        dH = pref*x^(a-1)*(a*HG + (a+b)*(1+a-c)*x*HGp/(a+d))
        d2H = (pref*x^(a-2)*(a*(a-1)*HG + (2*a)*(a+b)*(1+a-c)*x*HGp/(a+d)
               + ((a+b)*(1+a-c)/(a+d))*((a+b+1)*(2+a-c)/(a+d+1)) *x^2*HGpp))
        return H, dH, d2H
    else
        pref = 1/(GAMMA_db*GAMMA_bc)
        HG   = _₂F₁(a+b,1+b-d,b+c,1/x)#tabHGInt[2](1/x) #
        HGp  = _₂F₁(a+b+1,b-d+2,b+c+1,1/x)#tabHGInt[4](1/x) #
        HGpp = _₂F₁(a+b+2,b-d+3,b+c+2,1/x)#tabHGInt[6](1/x) #
        H = pref*x^(-b)*HG
        dH = pref*x^(-b-1)*((-b)*HG - (1/x)*HGp*(a+b)*(1+b-d)/(b+c))
        d2H = (pref*x^(-b-2)*(b*(b+1)*HG + (2*b+2)*(a+b)*(1+b-d)*(1/x)*HGp/(b+c)
               + ((a+b)*(1+b-d)/(b+c))*((a+b+1)*(2+b-d)/(b+c+1)) *(1/x^2)*HGpp))
        return H, dH, d2H
    end
end

"""
    _tFdF(tE,tL,[q=qCalc])

Gradients of the non-dimensional distribution function of a Plummer sphere.
Returns a tuple `(dtF/dtE, dtF/dtL, d2tF/dtE2, d2tF/dtEtL, d2tF/dtL2)`.

# Arguments
- `tE::Float64`: Reduced energy `tE = E/E0`.
- `tL::Float64`: Reduced energy `tL = L/L0`.
- `q ::Float64`: Anisotropy parameter.
"""
function _tdFTest(tE::Float64, tL::Float64, q::Float64=qCalc)
    if (tE <= 0.0 || tL <= 0.0) # If E or L are negative, the DF vanishes
        dE = 0.0
        dL = 0.0
        dEE = 0.0
        dEL = 0.0
        dLL = 0.0
        return dE, dL, dEE, dEL, dLL
    else
        x = tL^2/(2*tE)
        if (q == 0.0) # Isotropic case
            dE = 3/(PI^3) * (2*tE)^(5/2)
            dL = 0.0
            dEE = 15/(PI^3) * (2*tE)^(3/2)
            dEL = 0.0
            dLL = 0.0
            return dE, dL, dEE, dEL, dLL
        else
            if (q == 2.0)
                if (x <= 1.0)
                    pref = 18/(2*PI)^3
                    dE = pref * (2*tE-tL^2)^(1/2)
                    dL = -pref*tL * (2*tE-tL^2)^(1/2)
                    dEE = pref* (2*tE-tL^2)^(-1/2)
                    dEL = -pref*tL * (2*tE-tL^2)^(-1/2)
                    dLL = -pref*((2*tE-tL^2)^(1/2) - tL^2*(2*tE-tL^2)^(-1/2))
                    # here L ???
                    return dE, dL, dEE, dEL, dLL
                else
                    dE = 0.0
                    dL = 0.0
                    dEE = 0.0
                    dEL = 0.0
                    dLL = 0.0
                    return dE, dL, dEE, dEL, dLL
                end
            else
                pref = 3*GAMMA_6q/(2*(2*PI)^(5/2))
                pref1 = pref*tE^(5/2-q)
                pref2 = pref*tE^(3/2-q)
                H, dH, d2H = _HdHTest(0.0,q/2,9/2-q,1.0,x)
                dE = pref1*((7/2-q)*H - x*dH)
                dL = pref1*(tL*dH)
                dEE = pref2*((7/2-q)*(5/2-q)*H - (5-2*q)*x*dH + x^2*d2H)
                dEL = pref2*tL*((5/2-q)*dH - x*d2H)
                dLL = pref2*(tE*dH +tL^2*d2H)
                return dE, dL, dEE, dEL, dLL
            end
        end
    end
end

"""
    _dTestF(E,L,[q=qCalc])

Gradients of the distribution function of a Plummer sphere.
Normalized to the total mass of the cluster `_M`.
Returns a tuple `(dF/dE, dF/dL, d2F/dE2, d2F/dEL, d2F/dL2)`.

# Arguments
- `E::Float64`: Energy.
- `L::Float64`: Angular momentum.
- `q ::Float64`: Anisotropy parameter.
"""
function _dFTest(E::Float64, L::Float64, q::Float64=qCalc)
    tE = _tE(E)
    tL = _tL(L)
    tdE, tdL, tdEE, tdEL, tdLL = _tdFTest(tE,tL,q)
    dE = (_M*_F0/_E0)*tdE
    dL = (_M*_F0/_L0)*tdL
    dEE = (_M*_F0/(_E0^2))*tdEE
    dEL = (_M*_F0/(_E0*_L0))*tdEL
    dLL = (_M*_F0/(_L0^2))*tdLL
    return dE, dL, dEE, dEL, dLL
end

##################################################
# Compute numerical-gradient flux and its divergence
##################################################

"""
    divflux_NR_num(Jr,L,m_field,[eps=10^(-5)*_L0,nbK=nbK_default,nbAvr=nbAvr_default,m_test=m_field])

Computes the divergence of the flux in action space.

# Remarks
- `dF/dt = -div(flux)`.

# Arguments
- `Jr::Float64`: Radial action parameter.
- `L::Float64`: Angular momentum parameter.
- `m_field::Float64`: Mass of field star.
- `eps::Float64`: Finite difference used in numerical gradient.
- `nbK::Int64`: Sampling number of Rosenbluth integrals.
- `nbAvr::Int64`: Sampling number of orbit average.
- `m_test::Float64`: Mass of test star.
"""
function divflux_NR_num(Jr::Float64, L::Float64, m_field::Float64, eps::Float64=10^(-5)*_L0 ,
                    nbK::Int64=nbK_default, nbAvr::Int64=nbAvr_default,
                    m_test::Float64=m_field)

    Jr_p = Jr+eps
    Jr_m = Jr-eps

    L_p = L+eps
    L_m = L-eps

    E = _E_from_Jr(Jr,L)
    E_Lp = _E_from_Jr(Jr,L_p,nbAvr)
    E_Lm = _E_from_Jr(Jr,L_m,nbAvr)
    E_p = _E_from_Jr(Jr_p,L,nbAvr)
    E_m = _E_from_Jr(Jr_m,L,nbAvr)
    E_p_Lp = _E_from_Jr(Jr_p,L_p,nbAvr)
    E_p_Lm = _E_from_Jr(Jr_p,L_m,nbAvr)
    E_m_Lp = _E_from_Jr(Jr_m,L_p,nbAvr)
    E_m_Lm = _E_from_Jr(Jr_m,L_m,nbAvr)

    # Distribution function
    Ftot = _F(E,L)
    dFtotdE, dFtotdL, d2FtotdE2, d2FtotdEL, d2FtotdL2 = _dFTest(E,L)

    # Compute dJr/dE and dJr/dL
    sp, sa = radius_s_bounds_action(Jr,L,nbAvr)
    a = sma_eff(sp,sa)
    ecc = ecc_eff(sp,sa)

    dJrdE = 0.0
    dJrdL = 0.0
    d2JrdE2 = 0.0
    d2JrdEL = 0.0
    d2JrdL2 = 0.0

    for iu=1:nbAvr
        uloc = -1+2*(iu-0.5)/nbAvr
        rloc = r_eff_anomaly(uloc,a,ecc)
        trloc = rloc/_b
        sloc = sqrt(trloc^2+1)

        jac_loc = dth1du(uloc,sp,sa)
        djacdE_loc, djacdL_loc, dsdL_loc = djac_and_ds(uloc,sp,sa,L)

        dJrdE += jac_loc
        dJrdL += jac_loc/rloc^2
        d2JrdE2 += djacdE_loc
        d2JrdEL += djacdL_loc
        d2JrdL2 += (jac_loc/trloc^2+L/trloc^4*
                (djacdL_loc*trloc^2-2*jac_loc*sloc*dsdL_loc))

    end

    dJrdE *= (2/nbAvr)*(1/_Omega0)/PI
    dJrdL *= (2/nbAvr)*(-L/PI)*(1/_Omega0)
    d2JrdE2 *= (2/nbAvr)*(1/_Omega0)/PI
    d2JrdEL *= (2/nbAvr)*(1/_Omega0)/PI
    d2JrdL2 *= (2/nbAvr)*(-1/PI)/_L0

    # Compute DFtot/DJr and DFtot/DL
    dEdL = -dJrdL/dJrdE
    dEdJr = 1/dJrdE

    d2EdJr2 = -d2JrdE2/(dJrdE)^3
    d2EdJrL = -(d2JrdE2*dEdL+d2JrdEL)/(dJrdE)^2
    d2EdL2 = -((dEdL*d2JrdE2+d2JrdEL)*dEdL+dEdL*d2JrdEL+d2JrdL2)/(dJrdE)

    DFtotDJr = (1/dJrdE)*dFtotdE
    DFtotDL = dEdL*dFtotdE + dFtotdL

    D2FtotDJr2 = d2EdJr2*dFtotdE+(dEdJr)^2*d2FtotdE2
    D2FtotDJrL = d2EdJrL*dFtotdE + dEdJr*(dEdL*d2FtotdE2+d2FtotdEL)
    D2FtotDL2 = d2EdL2*dFtotdE + dEdL*(dEdL*d2FtotdE2+d2FtotdEL)+dEdL*d2FtotdEL+d2FtotdL2

    # Diffusion coefficients
    DJr, DL, DJrJr, DJrL, DLL = avr_action_coefficients(Jr,L,m_field,nbK,nbAvr,m_test)
    DJr_p, _, DJrJr_p, DJrL_Jrp, _ = avr_action_coefficients(Jr_p,L,m_field,nbK,nbAvr,m_test)
    DJr_m, _, DJrJr_m, DJrL_Jrm, _ = avr_action_coefficients(Jr_m,L,m_field,nbK,nbAvr,m_test)
    _, DL_p, _, DJrL_Lp, DLL_p = avr_action_coefficients(Jr,L_p,m_field,nbK,nbAvr,m_test)
    _, DL_m, _, DJrL_Lm, DLL_m = avr_action_coefficients(Jr,L_m,m_field,nbK,nbAvr,m_test)
    _, _, _, DJrL_pp, _ = avr_action_coefficients(Jr_p,L_p,m_field,nbK,nbAvr,m_test)
    _, _, _, DJrL_mm, _ = avr_action_coefficients(Jr_m,L_m,m_field,nbK,nbAvr,m_test)
    _, _, _, DJrL_pm, _ = avr_action_coefficients(Jr_p,L_m,m_field,nbK,nbAvr,m_test)
    _, _, _, DJrL_mp, _ = avr_action_coefficients(Jr_m,L_p,m_field,nbK,nbAvr,m_test)

    # d(DJrF)/dJr
    termJr1 = (2*L*DJr_p-2*L*DJr_m)/(2*eps*_L0)*Ftot
    termJr2 = 2*L*DJr*DFtotDJr
    termJr = termJr1 + termJr2

    # d(DLF)/dL
    termL1 = (2*L_p*DL_p-2*L_m*DL_m)/(2*eps)*Ftot
    termL2 = 2*L*DL*DFtotDL
    termL = termL1 + termL2

    # d2(DJrJrF)/dJr2
    termJrJr1 = (2*L*DJrJr_p+2*L*DJrJr_m-2*2*L*DJrJr)/((eps)^2)*Ftot
    termJrJr2 = 2*(2*L*DJrJr_p-2*L*DJrJr_m)/(2*eps)*DFtotDJr
    termJrJr3 = 2*L*DJrJr*D2FtotDJr2
    termJrJr = termJrJr1 + termJrJr2 + termJrJr3

    # d2(DLLF)/dL2
    termLL1 = (2*L_p*DLL_p+2*L_m*DLL_m-2*2*L*DLL)/((eps)^2)*Ftot
    termLL2 = 2*(2*L_p*DLL_p-2*L_m*DLL_m)/(2*eps)*DFtotDL
    termLL3 = 2*L*DLL*D2FtotDL2
    termLL = termLL1 + termLL2 + termLL3

    # d2(DJrLF)/dJrL
    termJrL1 = (2*L_p*DJrL_pp+2*L_m*DJrL_mm-2*L_m*DJrL_pm-2*L_p*DJrL_mp)/(4*(eps)^2)*Ftot
    termJrL2 = (2*L*DJrL_Jrp-2*L*DJrL_Jrm)/(2*eps)*DFtotDL
    termJrL3 = (2*L_p*DJrL_Lp-2*L_m*DJrL_Lm)/(2*eps)*DFtotDJr
    termJrL4 = 2*L*DJrL*D2FtotDJrL
    termJrL = termJrL1 + termJrL2 + termJrL3 + termJrL4

    return termJr+termL-0.5*termJrJr-0.5*termLL-termJrL
end

"""
    flux_NR_num(Jr,L,m_field,[eps=10^(-5)*_L0,nbK=nbK_default,nbAvr=nbAvr_default,m_test=m_field])

Computes the flux in action space.

# Arguments
- `Jr::Float64`: Radial action parameter.
- `L::Float64`: Angular momentum parameter.
- `m_field::Float64`: Mass of field star.
- `eps::Float64`: Finite difference used in numerical gradient.
- `nbK::Int64`: Sampling number of Rosenbluth integrals.
- `nbAvr::Int64`: Sampling number of orbit average.
- `m_test::Float64`: Mass of test star.
"""
function flux_NR_num(Jr::Float64, L::Float64, m_field::Float64, eps::Float64=10^(-5)*_L0,
                    nbK::Int64=nbK_default, nbAvr::Int64=nbAvr_default,
                    m_test::Float64=m_field)

        Jr_p = Jr+eps
        Jr_m = Jr-eps

        L_p = L+eps
        L_m = L-eps

        E = _E_from_Jr(Jr,L)

        Ftot, dFtotdE, dFtotdL = _FdF(E,L)

        # Compute dJr/dE and dJr/dL
        sp, sa = radius_s_bounds_action(Jr,L,nbAvr)
        a = sma_eff(sp,sa)
        ecc = ecc_eff(sp,sa)

        dJrdE = 0.0
        dJrdL = 0.0

        for iu=1:nbAvr
            uloc = -1+2*(iu-0.5)/nbAvr
            rloc = r_eff_anomaly(uloc,a,ecc)

            jac_loc = dth1du(uloc,sp,sa)
            dJrdE += jac_loc
            dJrdL += jac_loc/rloc^2
        end

        dJrdE *= (2/nbAvr)*(1/_Omega0)/PI
        dJrdL *= (2/nbAvr)*(-L/PI)*(1/_Omega0)

        # Compute DFtot/DJr and DFtot/DL
        DFtotDJr = (1/dJrdE)*dFtotdE

        dEdL = -dJrdL/dJrdE
        DFtotDL = dEdL*dFtotdE + dFtotdL

        # Compute diffusion coefficients
        DJr, DL, DJrJr, DJrL, DLL = avr_action_coefficients(Jr,L,m_field,nbK,nbAvr,m_test) #
        _, _, DJrJr_Jrp, DJrL_Jrp, _ = avr_action_coefficients(Jr_p,L,m_field,nbK,nbAvr,m_test) #
        _, _, DJrJr_Jrm, DJrL_Jrm, _ = avr_action_coefficients(Jr_m,L,m_field,nbK,nbAvr,m_test) #
        _, _, _, DJrL_Lp, DLL_Lp = avr_action_coefficients(Jr,L_p,m_field,nbK,nbAvr,m_test) #
        _, _, _, DJrL_Lm, DLL_Lm = avr_action_coefficients(Jr,L_m,m_field,nbK,nbAvr,m_test) #

        # Flux Jr
        termJr1 = 2*L*DJr*Ftot
        termJr2 = -0.5*((2*L*DJrJr_Jrp-2*L*DJrJr_Jrm)/(2*eps))*Ftot
        termJr3 = -L*DJrJr*DFtotDJr
        termJr4 = -0.5*((2*L_p*DJrL_Lp-2*L_m*DJrL_Lm)/(2*eps))*Ftot
        termJr5 = -L*DJrL*DFtotDL

        FJr = termJr1+termJr2+termJr3+termJr4+termJr5

        # Flux Jr
        termL1 = 2*L*DL*Ftot
        termL2 = -0.5*((2*L*DJrL_Jrp-2*L*DJrL_Jrm)/(2*eps))*Ftot
        termL3 = -L*DJrL*DFtotDJr
        termL4 = -0.5*((2*L_p*DLL_Lp-2*L_m*DLL_Lm)/(2*eps))*Ftot
        termL5 = -L*DLL*DFtotDL

        FL = termL1+termL2+termL3+termL4+termL5

        return FJr, FL

end


mutable struct Asteroid <: GameObject
    r::Real
    pos::Vector
    total::Int
    offset::Vector
    vel::Vector{<:Real}
    @init function Asteroid(pos = nothing, r = rand(15:50))
        rpos = randompoint(0, 0, @width, @height)
        this.pos = isnothing(pos) ? [rpos.x, rpos.y] : pos
        this.r = r
        this.total = rand(5:15)
        this.offset = [random(-r, r) for i in 1:this.total]
        θ = random(0, 2π)
        this.vel = rand(1:5) .* [cos(θ), sin(θ)]
    end
end

function render(this::Asteroid)
    @layer begin
        sethue("white")
        translate(this.pos...)

        vertices = map(1:this.total) do i
            θ = mapr(i, 1, this.total, 0, 2π)
            r = this.r + this.offset[i]
            x = r * cos(θ)
            y = r * sin(θ)
            return Point(x, y)
        end

        poly(vertices, close = true, :stroke)
    end
end

function update!(this::Asteroid)
    this.pos += this.vel
end

function breakup!(this::Asteroid)
    if this.r >= 20
        return Asteroid[
            Asteroid(this.pos, this.r * .65),
            Asteroid(this.pos, this.r * .65)
        ]
    else
        return Asteroid[]
    end
end


println("Stats Tests")
println("...........")
using Lathe.stats: NormalDist, T_Dist, mean, std
# Distributions
@testset "Distributions" begin
x = randn(200)
dist = NormalDist(x)
mu = mean(x)
zero_std = dist.apply(mu)
@test Real(zero_std[1]) == 0
end


import GreyDecision.Utility: makeminmax
import GreyDecision.GreyNumbers: GreyNumber
import GreyDecision.Topsis: topsis

@testset "Topsis sample" begin
    atol = 0.001
    decmat = [
        GreyNumber(1.0, 2.0) GreyNumber(2.0, 3.0) GreyNumber(3.0, 4.0);
        GreyNumber(2.0, 3.0) GreyNumber(1.0, 2.0) GreyNumber(3.0, 4.0);
        GreyNumber(3.0, 4.0) GreyNumber(2.0, 3.0) GreyNumber(1.0, 2.0);
    ]
    w = [0.5, 0.4, 0.1]
    fns = makeminmax([maximum, maximum, minimum])

    scores = topsis(decmat, w, fns)
    @test isapprox(scores[1], GreyNumber{Float64}(0.23506, 0.83613), atol = atol)
    @test isapprox(scores[2], GreyNumber{Float64}(0.24317, 1.00294), atol = atol)
    @test isapprox(scores[3], GreyNumber{Float64}(0.10851, 1.23913), atol = atol)
end

@testset "Grey Topsis with real numbers" begin
    atol = 0.001
    num6 = GreyNumber{Float64}(6.0, 6.0)
    num7 = GreyNumber{Float64}(7.0, 7.0)
    num8 = GreyNumber{Float64}(8.0, 8.0)
    num9 = GreyNumber{Float64}(9.0, 9.0)
    decmat = [
        num9 num7 num6 num7;
        num8 num7 num9 num6;
        num7 num8 num6 num6
    ]
    w = [4.0, 2, 6, 8]
    fns = makeminmax([maximum, maximum, maximum, maximum])
    result = topsis(decmat, w, fns)
    @test isapprox(result[1], GreyNumber{Float64}(0.38768, 0.38768), atol = atol)
    @test isapprox(result[2], GreyNumber{Float64}(0.65032, 0.65032), atol = atol)
    @test isapprox(result[3], GreyNumber{Float64}(0.08347, 0.08347), atol = atol)
end


@testset "240.search-a-2d-matrix-ii.jl" begin
arr = [
    1   4   7   11  15;
    2   5   8   12  19;
    3   6   9   16  22;
    10  13  14  17  24;
    18  21  23  26  30;
]

@test searchMatrix(arr, 5) == true
@test searchMatrix(arr, 20) == false
end


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.8.0-DEV
#     language: julia
#     name: julia-1.8
# ---

# %%
using Plots
x = rand(10^6)
b = 10.0 .^ (-5:0)
histogram(x; alpha=0.3, label="", bin=b, norm=true)

# %%
using Plots, StatsBase
x = rand(10^6)
b = 10.0 .^ (-5:0)
h = fit(Histogram{Float64}, x, b)
h.weights ./= diff(h.edges[1])
plot(h; alpha=0.3, label="")

# %%
using Plots
n = 10^6
f(x) = n * x * log(10)
x = rand(n)
b = 10.0 .^ (-5:0)
histogram(x; alpha=0.3, label="", bin=b, xscale=:log10, xlim=extrema(b))
x = 10.0 .^ (-5:0.01:0)
plot!(x, f; label="n × pdf on log-scaled xaxis", legend=:topleft)

# %%
using Plots
n = 10^6
f(x) = n * x * log(10)
x = rand(n)
b = 10.0 .^ (-5:0.2:0)
histogram(x; alpha=0.3, label="", bin=b, xscale=:log10, xlim=extrema(b))
x = 10.0 .^ (-5:0.01:0)
plot!(x, f; label="n × pdf on log-scaled xaxis", legend=:topleft)

# %%
using Plots
n = 10^6
f(x) = n * x * log(10)
x = rand(n)
b = 10.0 .^ (-5:0.2:0)
histogram(x; alpha=0.3, label="", bin=b, xscale=:log10, xlim=extrema(b))
x = 10.0 .^ (-5:0.01:0)
plot!(x, f; label="n × pdf on log-scaled xaxis", legend=:topleft)

# %%
using Plots, StatsBase
n = 10^6
f(x) = n * x * log(10)
x = rand(n)
b = 10.0 .^ (-5:0.2:0)
h = fit(Histogram{Float64}, x, b)
h.weights ./= diff(log10.(h.edges[1]))
plot(h; alpha=0.3, label="", xscale=:log10, xlim=extrema(b))
x = 10.0 .^ (-5:0.01:0)
plot!(x, f; label="n × pdf on log-scaled xaxis", legend=:topleft)

# %%
using Plots, StatsBase, Random
X = 3randexp(10^6)
bin = [0; [2^(k/2) - 2^(-1/2) for k in 0:8]]
@show round.(bin; digits=2)
h = fit(Histogram{Float64}, X, bin)
h.weights ./= diff(h.edges[1])
plot(h; alpha=0.3, label="")

# %%
using Plots, StatsBase, Random
n = 10^6
f(x) = n * exp(-x/3)/3 * x * log(10)
X = 3randexp(10^6)
bin = [1e-3; [2^(k/2) - 2^(-1/2) for k in 0:8]]
@show round.(bin; digits=2)
h = fit(Histogram{Float64}, X, bin)
h.weights ./= diff(log10.(h.edges[1]))
plot(h; alpha=0.3, label="", xscale=:log10, xlim=extrema(bin))
x = exp.(range(log.(extrema(bin))...; length=500))
plot!(x, f; label="n × pdf on log-scaled xaxis", legend=:topleft)

# %%


function displace(x_prim::Float64, tau_x::Float64, lb_x::Float64, ub_x::Float64)

	delta_x::Float64 = tau_x * randn()
	x_bis::Float64 = x_prim + delta_x
	while !( lb_x < x_bis < ub_x )
		if x_bis < lb_x
			x_bis = x_bis + 2.0 * ( lb_x - x_bis )
		end
		if x_bis > ub_x
			x_bis = x_bis - 2.0 * ( x_bis - ub_x )
		end
	end

	return x_bis
end


# Fizzbuzz in Julia

for i in 1:100
    if (i % 15) == 0
        println("FizzBuzz")
    elseif (i % 3) == 0
        println("Fizz")
    elseif (i % 5) == 0
        println("Buzz")
    else
        println(i)
    end
end


include("operators.jl")

using GaussQuadrature
using OrdinaryDiffEq
using LinearAlgebra
using UnicodePlots

struct SpectralOP{T}
  x  :: Array{T, 1}
  ω  :: Array{T, 1}
  D  :: Array{T, 2}
  e0 :: Array{T, 1}
  eN :: Array{T, 1}
  function SpectralOP{T}(N) where T
    Nq = N + 1
    x, ω = legendre(T, Nq, both)
    x = (x .+ 1) / 2
    ω /= 2
    D = derivative(x)
    e0 = [1; zeros(T, N)]
    eN = [zeros(T, N); 1]
    new{T}(x, ω, D, e0, eN)
  end
end

function spectralodefun!(dq, q, p, t)
  o = p.o
  f = p.f
  Nq = length(o.x)
  v,  u  = @views  q[1:Nq],  q[Nq+1:2Nq]
  dv, du = @views dq[1:Nq], dq[Nq+1:2Nq]

  D = o.D
  ω = o.ω
  e0 = o.e0
  dv .= (-D' * (ω .* D * u) - e0 * f(v[1])) ./ ω
  du .= v
end

let
  N = 100
  T = Float64

  tspan = (T(0), T(3//10))

  Nq = N + 1
  o = SpectralOP{T}(N)
  q = [zeros(T, Nq);sin.(2o.x * π)]
  f(v) = asinh(v)

  p = (o=o, f=f)
  prob = ODEProblem(spectralodefun!, q, tspan, p)
  sol = solve(prob, Tsit5();
              saveat=range(tspan[1], stop=tspan[2], length = 4),
              atol = 1e-6, rtol = 1e-3,
              internalnorm=(x, _)->norm(x, Inf))

  for n = 1:length(sol.t)
    display(lineplot(o.x, sol.u[n][Nq+1:2Nq], width=100, xlim=(0,1),
                     title="t = $(sol.t[n])"))
  end
end


using NonlinearEigenproblems: nep_gallery, compute_Mder
using FEASTSolver

function info(Λ, X, residuals, c, r)
    inside(x) = in_contour(x, c, r)
    in_eig = Λ[inside.(Λ)]
    in_res = residuals[inside.(Λ)]
    print("number inside : ")
    println(size(Λ[inside.(Λ)])[1])
    if sum(inside.(Λ)) > 0
        in_res_conv = in_res[in_res .<= 1e-5]
        in_eig_conv = in_eig[in_res .<= 1e-5]
        print("number inside converged : ")
        println(size(in_eig_conv)[1])
        print("max res inside: ")
        println(maximum(residuals[inside.(Λ)]))
        if size(in_res_conv, 1) > 0
            print("max res inside non spurious: ")
            println(maximum(in_res_conv))
        end
    end
    # display(in_eig_conv)
end

nep = nep_gallery("nlevp_native_fiber")
T(x) = compute_Mder(nep, x)

n = size(nep, 1)
C = complex(99.5, 0.0)
R = 5.0

e, v, res = nlfeast_moments!(T, ComplexF64.(rand(Float64,n,10)), 2^4, 10, c=C, moments=10, r=R, debug=true, ϵ=10e-16, store=true)

info(e,v,res,C,R)
println(e)


using ReachabilityAnalysis, SparseArrays

using ReachabilityAnalysis: TaylorModelReachSet, AbstractLazyReachSet

const μ = 3.986e14 * 60^2
const r = 42164.0e3
const r² = r^2
const mc = 500.0
const n² = μ / r^3
const n = sqrt(n²)

const two_n = 2*n
const μ_r² = μ/r²

# columns correspond to x, y, vx, vy, t
const K₁ = [-28.8287 0.1005 -1449.9754 0.0046 0.0;
            -0.087 -33.2562 0.00462 -1451.5013 0.0]
const K₂ = [-288.0288 0.1312 -9614.9898 0.0 0.0;
            -0.1312 -288.0 0.0 -9614.9883   0.0]

const K₁mc = K₁/mc
const K₂mc = K₂/mc

function mymul!(v, A, x)
    @inbounds for ind = 1:length(v)
        v[ind] = zero(x[1])
        for jind = 1:length(x)
            v[ind] += A[ind, jind] * x[jind]
        end
    end
    return nothing
end

# dynamics in the 'approaching' mode
@taylorize function spacecraft_approaching!(du, u, p, t)
    x, y, vx, vy, t = u

    rx = r + x
    rx² = rx^2
    y² = y^2
    rc = sqrt(rx² + y²)
    rc³ = rc^3
    μ_rc³ = μ / rc³

    uxy = Vector{typeof(x)}(undef, 2)
    mymul!(uxy, K₁mc, u)

    # x' = vx
    du[1] = vx

    # y' = vy
    du[2] = vy

    # vx' = n²x + 2n*vy + μ/(r^2) - μ/(rc^3)*(r+x) + ux/mc
    du[3] = (n²*x + two_n*vy) + ((μ_r² - μ_rc³*rx) + uxy[1])

    # vy' = n²y - 2n*vx - μ/(rc^3)y + uy/mc
    du[4] = (n²*y - two_n*vx) - (μ_rc³*y - uxy[2])

    # t' = 1
    du[5] = one(x)

    return du
end

# dynamics in the 'rendezvous attempt' mode
@taylorize function spacecraft_attempt!(du, u, p, t)
    x, y, vx, vy, t = u

    rx = r + x
    rx² = rx^2
    y² = y^2
    rc = sqrt(rx² + y²)
    rc³ = rc^3
    μ_rc³ = μ / rc³

    uxy = Vector{typeof(x)}(undef, 2)
    mymul!(uxy, K₂mc, u)

    # x' = vx
    du[1] = vx

    # y' = vy
    du[2] = vy

    # vx' = n²x + 2n*vy + μ/(r^2) - μ/(rc^3)*(r+x) + ux/mc
    du[3] = (n²*x + two_n*vy) + ((μ_r² - μ_rc³*rx) + uxy[1])

    # vy' = n²y - 2n*vx - μ/(rc^3)y + uy/mc
    du[4] = (n²*y - two_n*vx) - (μ_rc³*y - uxy[2])

    # t' = 1
    du[5] = one(x)

    return du
end

# dynamics in the 'aborting' mode
@taylorize function spacecraft_aborting!(du, u, p, t)
    x, y, vx, vy, t = u

    rx = r + x
    rx² = rx^2
    y² = y^2
    rc = sqrt(rx² + y²)
    rc³ = rc^3
    μ_rc³ = μ / rc³

    # x' = vx
    du[1] = vx

    # y' = vy
    du[2] = vy

    # vx' = n²x + 2n*vy + μ/(r^2) - μ/(rc^3)*(r+x)
    du[3] = (n²*x + two_n*vy) + (μ_r² - μ_rc³*rx)

    # vy' = n²y - 2n*vx - μ/(rc^3)y
    du[4] = (n²*y - two_n*vx) - μ_rc³*y

    # t' = 1
    du[5] = one(x)

    return du
end

function spacecraft(; X0 = Hyperrectangle([-900., -400., 2.5, 2.5, 0.],
                                          [25., 25., 2.5, 2.5, 0.]),
                         init=[(1, X0)],
                         abort_time=(120.0, 150.0))

    # variables
    x = 1   # x position
    y = 2   # y position
    vx = 3  # x velocity
    vy = 4  # y velocity
    t = 5   # time
    n = 4 + 1  # number of variables
    t_abort_lower, t_abort_upper = abort_time[1], abort_time[2]

    automaton = LightAutomaton(3)

    # mode 1 "approaching"
    invariant = HalfSpace(sparsevec([x], [1.], n), -100.) # x <= -100
    approaching = @system(x' = spacecraft_approaching!(x), dim:5, x ∈ invariant)

    # mode 2 ("rendezvous attempt")
    invariant = HalfSpace(sparsevec([x], [-1.], n), 100.) # x >= -100
    attempt = @system(x' = spacecraft_attempt!(x), dim:5, x ∈ invariant)

    # mode 3 "aborting"
    invariant = Universe(n)
    aborting = @system(x' = spacecraft_aborting!(x), dim: 5, x ∈ invariant)

    # transition "approach" -> "attempt"
    add_transition!(automaton, 1, 2, 1)
    guard = HalfSpace(sparsevec([x], [-1.], n), 100.) # x >= -100
    t1 = @map(x -> x, dim: n, x ∈ guard)

    # transition "approach" -> "abort"
    add_transition!(automaton, 1, 3, 2)
    guard_time = HPolyhedron([HalfSpace(sparsevec([t], [-1.], n), -t_abort_lower),  # t >= t_abort_lower
                              HalfSpace(sparsevec([t], [1.], n), t_abort_upper)])   # t <= t_abort_upper
    t2 = @map(x -> x, dim: n, x ∈ guard_time)

    # transition "attempt" -> "abort"
    add_transition!(automaton, 2, 3, 3)
    t3 = @map(x -> x, dim: n, x ∈ guard_time)

    H = HybridSystem(automaton=automaton,
                     modes=[approaching, attempt, aborting],
                     resetmaps=[t1, t2, t3])

    return InitialValueProblem(H, init)
end

# variables
const x, y, vx, vy, t = 1:5
const numvars = 5
const tan30 = tand(30)

LineOfSightCone = HPolyhedron([HalfSpace(sparsevec([x], [-1.], numvars), 100.),   # x >= -100
                    HalfSpace(sparsevec([x, y], [tan30, -1.], numvars), 0.),  # y >= x tan(30°)
                    HalfSpace(sparsevec([x, y], [tan30, 1.], numvars), 0.),   # -y >= x tan(30°)
                   ])

Target = Hyperrectangle(zeros(2), [0.2, 0.2]);

function line_of_sight(sol)
    all_idx = findall(x -> x == 2, location.(sol))  # attempt
    for idx in all_idx
        verif = all(set(R) ⊆ LineOfSightCone for R in sol[idx][2:end])
        !verif && return false
    end
    return true
end

function absolute_velocity(R::AbstractLazyReachSet)
    vx = 3
    vy = 4
    vx2 = set(overapproximate(project(R, vars=(vx,)), Interval)).dat
    vy2 = set(overapproximate(project(R, vars=(vy,)), Interval)).dat
    sqrt(vx2 + vy2)
end

function absolute_velocity(R::TaylorModelReachSet)
    Z = overapprximate(R, Zonotope)
    absolute_velocity(Z)
end

function velocity_constraint(sol)
    all_idx = findall(x -> x == 2, location.(sol)) # attempt
    for idx in all_idx
        # maximum velocity measured in m/min
        verif = all(absolute_velocity(R) < 0.055 * 60. for R in sol[idx])
        !verif && return false
    end
    return true
end

function target_avoidance(sol)
    all_idx = findall(x -> x == 3, location.(sol)) # aborting
    for idx in all_idx
        verif = all(is_intersection_empty(set(Projection(R, [x, y])), Target) for R in sol[idx])
        !verif && return false
    end
    return true
end


using ThreeBodyProblem
using LinearAlgebra
using Test

sys = sun_earth()

Az = 0.001
Lpt = 2 # Which libration point will I center about
NS = 1
npts = 100
t, rvs, T, Ax = rich3(sys, Az, Lpt, NS, npts)

rv₀, T = differential_corrector(sys, rvs[1,:], myconst=3, tf=T)

Wsp, Wsn, Wup, Wun, Φₜ, ww = invariant_manifolds(sys, rv₀, T, tf=5*T, nPts=20)


abstract type AbstractComb{T} <: AbstractArray{T,1} end

"""
    Combination{T} <: AbstractComb{T}

Structure to generate all combinations of the given modules with given length
low_level structure that a user do not need to use, normaly

Fields:
    - `mod`: Array with modulels or where combination can be generated from
    - `len`: lenght of the made constructs
"""
struct Combination{T} <: AbstractComb{T}
    mod::Array{T}
    len::Int
end

"""
    length(c::Combination)
Returns the number of unique combinations that can be made
with a length k and n given modules.
The parameters k and are obtained from the `c` object.
"""
Base.length(c::Combination) = binomial(length(c.mod),c.len);

"""
    eltype(::Combination{T})

Returns the type of the iterator.
"""
Base.eltype(K::Combination{T}) where T = UnorderedConstruct{T} ;
"""
    Base.size(c::Combination)

size needed to display `AbstractArray{T,1}`, but has no furder use
"""
Base.size(c::Combination) = (length(c),1) ;
"""
    iterate(c::Combination{T} where T, state = [i for i in c.len:-1:1])

Iterater for Combinations, printed in lexicographical order.
"""
function Base.iterate(c::Combination{T} where T, state = [i for i in c.len:-1:1])
    n = length(c.mod)
    # check when done, last constructed is printed, stop Iterator
    if state[1] > n
        return nothing
    end

    # special case len = 1
    if c.len == 1
        return _len1(c,state)
    end

    # case len > 1

    # make the current construct based on given state
    construct = sum(c.mod[state])

    # generated the new state

    # i start at the last index, length of the construct
    i = c.len
    #evaluated if the difference between the last and the next is higher than 1 (than their is still a construct that can be made)
    # if not than lower the index with one and recheck if the difference is 1 . i>1 prevents error
    while i > 1  && state[i-1] - state[i] == 1
        i -=1
    end
    # upate the state at position i and reset all higher index to lowest values
    if i < c.len
        state = _restate(state,i)
    else
        state[i] +=1
    end
    return (construct,state)

end

"""
    _restate(state, i)

Update state at index i and reset all values behind to lowsted allow values.
function used in the Base.iterate(c::Combination{T} where T, state = [i for i in c.len:-1:1])
"""
function _restate(state, i)
    state[i] +=1;
    state[(i+1):end] = collect(length(state)-i:-1:1);
    return state
end

"""
    _len1(c,state)

part of Base.iterate(c::Combination{T} where T, state = [i for i in c.len:-1:1]),
used to generated all possibilities when length of the allowed constructs = 1.

"""
function _len1(c,state)
        construct = [c.mod[state[1]]]
        state[1] +=1
        return (UnorderedConstruct(construct), state)
end

"""
    function getindex(c::Combination,pos::Int)

Get the index of `c` at position `pos`.

Index based on rank Combination.
https://en.wikipedia.org/wiki/Combinatorial_number_system
"""
function getindex(c::Combination,pos::Int)

    # spacial case of lenght is 1
    if c.len == 1
        return UnorderedConstruct([c.mod[pos]])
    end
    # if not 1
    com = Array{Int}(undef, c.len, 1)
    for i in c.len:-1:1
        n = i-1
        b_val = binomial(n,i)
        while b_val < pos
            n += 1
            b_val = binomial(n,i)
        end
        pos -= binomial(n-1,i)
        com[c.len-i+1] = n-1
    end
    index =  com .+=1
    return (sum(c.mod[index]))
end;


using Klara

function plogtarget(x::Vector{Float64})
  s2 = exp(x[1])
  nx = length(x)-1
  -0.5*((x[1]-0.0)^2/9.0+dot(x[2:end], x[2:end])/s2+nx*log(s2))
end

p = BasicContMuvParameter(:p, logtarget=plogtarget)

model = likelihood_model(p, false)

sampler = SliceSampler(1., 5)

mcrange = BasicMCRange(nsteps=100000, burnin=10000)

v0 = Dict(:p=>[10.; zeros(4)])

job = BasicMCJob(model, sampler, mcrange, v0)

run(job)

chain = output(job)

mean(chain)


using SubsetSelection, SubsetSelectionCIO
using DataFrames
include("cv.jl")

##############################################
##SOLVER FOR CROSS-VALIDATING k/λ
##############################################
function oa_cv_constraint(ℓ::LossFunction, kRange, Γmax, Y, X;
    holdout = .3,
    anticycling = false, averaging = true, gradUp = 10, maxIter = 100, δ = 1e-3,
    TimeLimit = 60.)

  Path = DataFrame(k=Float64[], indices=Array[], w=Array[],
             cuts=Integer[], γ=Float64[], Δt = Float64[], error=Float64[])

  n, p = size(X)

  train, val = split_data(n, holdout) #Split data into train and validation sets

  indInit = SubsetSelection.ind_init(Constraint(kRange[1]), p)
  αInit=SubsetSelection.alpha_init(ℓ, Y[train]) #Initialization

  for c in 1:length(kRange)
    # indInit = partial_min(Constraint(Range[c]), X[train, :], αInit, γ)
    k = kRange[c]
    γ0 = 1. *p / k / (maximum(sum(X[train,:].^2,2))*n)
    factor = 1.

    stop = false
    for inner_epoch in 1:Γmax
      if !stop
        γ = factor * γ0

        result = subsetSelection(ℓ, Constraint(k), Y[train], X[train, :],
            indInit = indInit, αInit=αInit,
            anticycling = anticycling, averaging = averaging, gradUp = gradUp, γ = γ, maxIter = maxIter, δ = δ)

        indices_cio, w_cio, Δt_cio, status, Gap, cutCount = oa_formulation(ℓ, Y[train], X[train, :],
                                  k, γ, indices0=result.indices, ΔT_max=TimeLimit)

        valError = error(ℓ, Y[val], X[val, :], indices_cio, w_cio)
        push!(Path, [k, indices_cio, w_cio, cutCount, γ, Δt_cio, valError])

        αInit = result.α[:]
        indInit = indices_cio[:]

        g_old = norm(grad_primal(ℓ, Y[train], X[train, indices_cio], 0*w_cio, 2*γ))
        g_new = norm(grad_primal(ℓ, Y[train], X[train, indices_cio], w_cio, 2*γ))
        stop = (g_new / g_old < 1e-2)

        factor *= 2
      end
    end
  end

  return Path
end


# # Tracking transient deformation of a cantilever beam

# Source code: [`bending_wave_Ray_tut.jl`](bending_wave_Ray_tut.jl)

# ## Description

# A cantilever beam is given an initial velocity and then at time 0.0 it is
# suddenly stopped by fixing one of its ends. This sends a wave down the beam.

# The beam is modeled as a solid. Consistent mass matrix is used.
# Trapezoidal rule is used to integrate the
# equations of motion in time. Rayleigh mass-proportional damping is
# incorporated. The dynamic stiffness is factorized for efficiency.

# ## Goals

# - Show how to create the discrete model.
# - Demonstrate  trapezoidal-rule time stepping.

##
# ## Definitions

# Basic imports.
using LinearAlgebra
using Arpack

# This is the finite element toolkit itself.
using FinEtools

# The linear stress analysis application is implemented in this package.
using FinEtoolsDeforLinear
using FinEtoolsDeforLinear.AlgoDeforLinearModule

# Input parameters
E = 205000*phun("MPa");# Young's modulus
nu = 0.3;# Poisson ratio
rho = 7850*phun("KG*M^-3");# mass density
loss_tangent = 0.0001;
frequency = 1/0.0058;
Rayleigh_mass = 2*loss_tangent*(2*pi*frequency);
L = 200*phun("mm");
W = 4*phun("mm");
H = 8*phun("mm");
tolerance = W/500;
vmag = 0.1*phun("m")/phun("SEC");
tend = 0.013*phun("SEC");

##
# ## Create the discrete model

MR = DeforModelRed3D
fens,fes  = H8block(L,W,H, 50,1,4)

geom = NodalField(fens.xyz)
u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

nl = selectnode(fens, box=[L L -Inf Inf -Inf Inf], inflate=tolerance)
setebc!(u, nl, true, 1)
setebc!(u, nl, true, 2)
setebc!(u, nl, true, 3)
applyebc!(u)
numberdofs!(u)

corner = selectnode(fens, nearestto=[0 0 0])
cornerzdof = u.dofnums[corner[1], 3]

material = MatDeforElastIso(MR, rho, E, nu, 0.0)

femm = FEMMDeforLinear(MR, IntegDomain(fes, GaussRule(3,2)), material)
femm = associategeometry!(femm, geom)
K = stiffness(femm, geom, u)
femm = FEMMDeforLinear(MR, IntegDomain(fes, GaussRule(3,3)), material)
M = mass(femm, geom, u)

# Find the relationship of the sum of all the elements of the
# mass matrix and the total mass of the structure.
@show sum(sum(M))
@show L*W*H*rho

# Form the damping matrix.
C = Rayleigh_mass * M

# Figure out the highest frequency in the model, and use a time step that is
# considerably larger than the period of that highest frequency.
evals, evecs = eigs(K, M; nev=1, which=:LM);
@show dt = 350 * 2/sqrt(evals[1]);

# The time stepping loop is protected by `let end` to avoid unpleasant surprises
# with variables getting clobbered by globals.
ts, corneruzs = let dt = dt
    # Initial displacement, velocity, and acceleration.
    U0 = gathersysvec(u)
    v = deepcopy(u)
    v.values[:, 3] .= vmag
    V0 = gathersysvec(v)
    F0 = fill(0.0, length(V0))
    U1 = fill(0.0, length(V0))
    V1 = fill(0.0, length(V0))
    F1 = fill(0.0, length(V0))
    R  = fill(0.0, length(V0))

    # Factorize the dynamic stiffness
    DSF = cholesky((M + (dt/2)*C + ((dt/2)^2)*K))

    # The times and displacements of the corner will be collected into two vectors
    ts = Float64[]
    corneruzs = Float64[]
    # Let us begin the time integration loop:
    t = 0.0;
    step = 0;
    while t < tend
        push!(ts, t)
        push!(corneruzs, U0[cornerzdof])
        t = t+dt;
        step = step + 1;
        (mod(step,25)==0) && println("Step $(step): $(t)")
        # Zero out the load
        fill!(F1, 0.0);
        # Compute the out of balance force.
        R = (M*V0 - C*(dt/2*V0) - K*((dt/2)^2*V0 + dt*U0) + (dt/2)*(F0+F1));
        # Calculate the new velocities.
        V1 = DSF\R;
        # Update the velocities.
        U1 = U0 + (dt/2)*(V0+V1);
        # Switch the temporary vectors for the next step.
        U0, U1 = U1, U0;
        V0, V1 = V1, V0;
        F0, F1 = F1, F0;
        if (t == tend) # Are we done yet?
            break;
        end
        if (t+dt > tend) # Adjust the last time step so that we exactly reach tend
            dt = tend-t;
        end
    end
    ts, corneruzs # return the collected results
end

##
# ## Plot the results

using Gnuplot

@gp "set terminal windows 0 " :-
@gp  :- ts corneruzs./phun("mm") "lw 2 lc rgb 'red' with lines title 'Displacement of the corner' "
@gp  :- "set xlabel 'Time [s]'"
@gp  :- "set ylabel 'Displacement [mm]'"

# The end.
true


using BEAST

width, delay = 4.0, 6.0
f = BEAST.Gaussian(width=width, delay=delay)
g = BEAST.integrate(f)

step = width/150
x = range(delay-2*width, stop=delay+2*width, step=step)
# xc = 0.5*(x[1:end-1] + x[2:end])

y1 = g.(x)
y2 = cumsum(f.(x))*step

using LinearAlgebra
@assert norm(y1-y2, Inf) < 1e-2


function getkernel(parsed_args)

    afun = if parsed_args["activation"] == "triangle"
        TriangleWave()
    elseif parsed_args["activation"] == "prelu"
        PeriodicReLU()
    elseif parsed_args["activation"] == "sincos"
        SinCosActivation()
    else
        SinActivation()
    end

    if parsed_args["kernel"] == "Matern"
        return Matern(afun,parsed_args["nu"])
    elseif parsed_args["kernel"] == "RelU"
        return RELU()
    elseif parsed_args["kernel"] == "MaternLS"
        return MaternLS(parsed_args["nu"], parsed_args["ell"])
    elseif parsed_args["kernel"] == "RBFLS"
        return RBFLS()
    else
        return RBF(afun)
    end
end


@testset "normalizedkernel" begin
    rng = MersenneTwister(123456)
    x = randn(rng)
    y = randn(rng)

    k = 4 * SqExponentialKernel()
    kn = NormalizedKernel(k)
    @test kn(x, y) == k(x, y) / sqrt(k(x, x) * k(y, y))
    @test kn(x, x) ≈ one(x) atol = 1e-5

    # Standardised tests.
    TestUtils.test_interface(kn, Float64)
    test_ADs(x -> NormalizedKernel(exp(x[1]) * SqExponentialKernel()), rand(1))

    test_params(kn, k)
end


# check the Bernoulli numbers and polynomials
#    this is mostly just a check that code is correct
#    most errors are arising because of errors in the functions being used
using Polylogarithms
using BenchmarkTools
using DataFrames, CSV
using PyPlot
include("utilities.jl")

##############################################
# Bernoulli numbers
B = Symbol("B_n")
data1 = CSV.read("../data/bernoulli_test_data_1.csv", DataFrame)
m = size(data1,1)
B1 = zeros(Rational{Int64}, m)
B1_ref = zeros(Rational{Int64}, m)
error1 = zeros(Float64, m)
rel_error1 = zeros(Float64, m)
for i=1:m
    B1[i] = bernoulli( data1[i,:n] )
    B1_ref[i] = parse(Rational{Int64},data1[i,B])
    error1[i] = Float64( B1[i] - B1_ref[i]  )
    rel_error1[i] = error1[i]/Float64( B1_ref[i] )
end
println("max abs. error in Bernoulli numbers = $(maximum( abs.(error1) ))")

##############################################
# Bernoulli polynomials: identities
m = 20
B0 = zeros(Float64, m)
B0_ref = zeros(Rational{Int64}, m)
error0 = zeros(Float64, m)
for i=1:m
    B0_ref[i] = bernoulli( i )
    B0[i] = bernoulli( i, 0.0 )
    error0[i] = Float64( B0[i] - B0_ref[i]  )
end
figure(0)
clf()
semilogy( 1:m, abs.(error0) )
xlabel("n, (x=0.0)")
xticks(1:m)
ylabel("absolute error")
# ylim( 1.0e-17, 1.0e-15)
title("Bernoulli polynomials")
savefig("plots/bernoulli_bench_0.pdf")

##############################################
# Bernoulli polynomials: data v x
B = Symbol("B_n(x)")
data2 = CSV.read("../data/bernoulli_test_data_2.csv", DataFrame; delim=",", type=String)
data2[!,:n] = parse.(Float64, data2[!,:n] )
data2[!,:x] = parse.(Float64, data2[!,:x] )
data2[!,B] = parse.(Float64, data2[!,B] )
m = size(data2,1)
B2 = zeros(Float64, m)
B2_ref = zeros(Float64, m)
error2 = zeros(Float64, m)
rel_error2 = zeros(Float64, m)
for i=1:m
    B2[i] = bernoulli( Int(data2[i,:n]),  data2[i,:x])
    B2_ref[i] = data2[i,B]
    error2[i] = Float64( B2[i] - B2_ref[i]  )
    rel_error2[i] = error2[i] / B2_ref[i]
end
figure(2)
clf()
semilogy( data2[!,:x], abs.(rel_error2) )
xlabel("x, (n=12)")
ylabel("absolute rel. error")
# ylim( 1.0e-17, 1.0e-15)
title("Bernoulli polynomials")
savefig("plots/bernoulli_bench_2.pdf")

##############################################u
# Bernoulli polynomials: data v n
B = Symbol("B_n(x)")
data3 = CSV.read("../data/bernoulli_test_data_3.csv", DataFrame)
m = size(data3,1)
B3 = zeros(Float64, m)
B3_ref = zeros(Float64, m)
error3 = zeros(Float64, m)
rel_error3 = zeros(Float64, m)
for i=1:m
    B3[i] = bernoulli( Int(data3[i,:n]),  data3[i,:x])
    B3_ref[i] = data3[i,B]
    error3[i] = Float64( B3[i] - B3_ref[i]  )
    rel_error3[i] = error3[i] / B3_ref[i]
end
figure(3)
clf()
semilogy( data3[!,:n], abs.(rel_error3) )
xlabel("n, (x=1.5)")
ylabel("absolute rel. error")
# ylim( 1.0e-17, 1.0e-15)
title("Bernoulli polynomials")
savefig("plots/bernoulli_bench_3.pdf")


# This file is a part of AstroLib.jl. License is MIT "Expat".
# Copyright (C) 2016 Mosè Giordano.

include("adstring.jl")
export adstring

include("airtovac.jl")
export airtovac

include("aitoff.jl")
export aitoff

include("altaz2hadec.jl")
export altaz2hadec

include("bprecess.jl")
export bprecess

include("calz_unred.jl")
export calz_unred

include("ct2lst.jl")
export ct2lst

include("daycnv.jl")
export daycnv

include("deredd.jl")
export deredd

include("eci2geo.jl")
export eci2geo

include("eqpole.jl")
export eqpole

include("flux2mag.jl")
export flux2mag

include("gal_uvw.jl")
export gal_uvw

include("geo2eci.jl")
export geo2eci

include("geo2geodetic.jl")
export geo2geodetic

include("geo2mag.jl")
export geo2mag

include("geodetic2geo.jl")
export geodetic2geo

include("get_date.jl")
export get_date

include("get_juldate.jl")
export get_juldate

include("gcirc.jl")
export gcirc

include("hadec2altaz.jl")
export hadec2altaz

include("helio_jd.jl")
export helio_jd

include("helio_rv.jl")
export helio_rv

include("jdcnv.jl")
export jdcnv

include("jprecess.jl")
export jprecess

include("juldate.jl")
export juldate

include("kepler_solver.jl")
export kepler_solver

include("lsf_rotate.jl")
export lsf_rotate

include("mag2flux.jl")
export mag2flux

include("mag2geo.jl")
export mag2geo

include("month_cnv.jl")
export month_cnv

include("moonpos.jl")
export moonpos

include("mphase.jl")
export mphase

include("nutate.jl")
export nutate

include("paczynski.jl")
export paczynski

include("planck_freq.jl")
export planck_freq

include("planck_wave.jl")
export planck_wave

include("polrec.jl")
export polrec

include("posang.jl")
export posang

include("precess.jl")
export precess

include("precess_xyz.jl")
export precess_xyz

include("premat.jl")
export premat

include("radec.jl")
export radec

include("recpol.jl")
export recpol

include("rhotheta.jl")
export rhotheta

include("sixty.jl")
export sixty

include("sphdist.jl")
export sphdist

include("sunpos.jl")
export sunpos

include("ten.jl")
export ten, tenv

include("trueanom.jl")
export trueanom

include("vactoair.jl")
export vactoair

include("xyz.jl")
export xyz

include("ydn2md.jl")
export ydn2md

include("ymd2dn.jl")
export ymd2dn


"""
symmetrize matrix
"""
sym(M::Matrix{Float64}) = (M' + M) / 2

# link functions:
invLogit(xb::Float64) = 1 / (1 + exp(-xb))
logit(p::Float64) = log(p / (1-p))
# end of Link functions

const quants = [0., .025, .25, .5, .75, .975, 1.]

rig{A <: Real, B <: Real}(a::A,b::B) = 1 / rand( Gamma(a,1/b) )

function predict(X0::Matrix{Float64},β::Matrix{Float64}, inv_link)
  inv_link.(X0 * β)
end

function dic{T}(post_samples::Vector{T},loglikelihood)
  const D = -2 * loglikelihood.(post_samples)
  return mean(D) + var(D) / 2
end

"""
cpo = conditional posterior ordinate.
      which is the leave-one-out density evaluated at the
      actual observed y's. used for *comparing* models. Not
      for prediction.
"""
function cpo{T}(post_params::Vector{T}, den,
                y::Vector{Float64},
                X::Matrix{Float64})

  const N = size(X,1)
  const J = length(post_params)
  const CPO = Matrix{Float64}(N,J)

  for i in 1:N
    for j in 1:J
      CPO[i,j] = 1 / den(post_params[j], y[i], X[i,:])
    end
  end

  return 1 ./ mean(CPO,2)
end


"""
    GammaTrickHomotopy(start, target [, γ])
    GammaTrickHomotopy(start, target [, seed])

Construct the homotopy (1-t)target + tγ(t)start, with t from 1 to 0.
The gamma trick is a special type of homotopy interpolation.
One chooses an ``γ`` uniformly from the (complex) unit circle.
This yields a more generic interpolation.

Possible to create it optionally with a seed.
"""
struct GammaTrickHomotopy{T<:Number, Start<:AbstractPolySystem{T}, Target<:AbstractPolySystem{T}} <: AbstractHomotopy{T}
    start::Start
    target::Target
    γ::Complex128

    function GammaTrickHomotopy{T, Start, Target}(start::AbstractPolySystem{T}, target::AbstractPolySystem{T}, γ::Complex128) where {T<:Number, Start<:AbstractPolySystem{T}, Target<:AbstractPolySystem{T}}
        if (homogenized(start) != homogenized(target))
            return error("start and target have to be both either homogenized or not")
        end
        if (nvariables(start) != nvariables(target))
            return error("The number of unknowns of start and target system doesn't match." *
                "The start system of the homotopy has $(nvariables(start)) unknowns " *
                "and the target system has $(nvariables(target)) unknowns.")
        end

        if (length(target) != length(start))
            return error("The number of equations of start and target system doesn't match." *
                "The start system of the homotopy has $(length(start)) equations " *
                "and the target system has $(length(target)) equations.")
        end

        new(start, target, γ)
    end
end

function GammaTrickHomotopy(start::Start,target::Target, γ::Complex128) where {T<:Number, Start<:AbstractPolySystem{T}, Target<:AbstractPolySystem{T}}
    GammaTrickHomotopy{T, Start, Target}(start,target,γ)
end
# promote if they don't have the same coefficients
function GammaTrickHomotopy(start::AbstractPolySystem,target::AbstractPolySystem, γ::Complex128)
    T = promote_type(coefftype(start), coefftype(target))
    Start = promote_type(T, typeof(start))
    Target = promote_type(T, typeof(target))
    GammaTrickHomotopy{T, Start, Target}(convert(Start, start), convert(Target, target), γ)
end
GammaTrickHomotopy(s, t, γ::Complex128) = GammaTrickHomotopy(PolySystem(s), PolySystem(t), γ)

GammaTrickHomotopy(start, target) = GammaTrickHomotopy(start, target, exp(im * (rand() * 2π - π)))
function GammaTrickHomotopy(start, target, seed::Int)
    srand(seed)
    θ = rand() * 2π - π
    GammaTrickHomotopy(start, target, exp(im * θ))
end

function Base.promote_type(::Type{GammaTrickHomotopy{T, Start, Target}}, ::Type{S}) where {S,T, Start, Target}
    GammaTrickHomotopy{promote_type(T,S), promote_type(Start, S), promote_type(Target, S)}
end
function Base.promote_type(::Type{GammaTrickHomotopy{T, Start1, Target1}}, ::GammaTrickHomotopy{S, Start2, Target2}) where {S,T, Start1, Start2, Target1, Target2}
    GammaTrickHomotopy{promote_type(T,S), promote_type(Start1, Start2), promote_type(Target1, Target2)}
end

function Base.convert(::Type{GammaTrickHomotopy{T, Start, Target}}, H::GammaTrickHomotopy) where {T, Start, Target}
    GammaTrickHomotopy{T, Start, Target}(convert(Start, H.start), convert(Target, H.target), H.γ)
end

function Base.show(io::IO, H::GammaTrickHomotopy)
    print(io, typeof(H), ":\n")
    print(io, "* γ: ", H.γ, "\n")
    println(io, "* start:")
    println(io, H.start)
    println(io, "* target:")
    print(io, H.target)
end

function evaluate(H::GammaTrickHomotopy, x, t)
    (1 - t) * evaluate(H.target, x) + t * H.γ * evaluate(H.start, x)
end
(H::GammaTrickHomotopy)(x,t) = evaluate(H,x,t)

function substitute(H::GammaTrickHomotopy, pair::Pair{Symbol,<:Number})
    GammaTrickHomotopy(substitute(H.start, pair), substitute(H.target, pair), H.γ)
end

function removepoly(H::GammaTrickHomotopy, i::Int)
    GammaTrickHomotopy(removepoly(H.start,i), removepoly(H.target,i), H.γ)
end

"""
    differentiate(H::GammaTrickHomotopy)

Differentiates `H` w.r.t. the place and returns an evaluation function
`(x,t) -> t * H.γ * J_s(x) + (1 - t) * J_t(x)`
where `J_s` is the jacobian of the start system and
`J_t` is the jacobian of the target system.
"""
function differentiate(H::GammaTrickHomotopy)
    J_s = differentiate(H.start)
    J_t = differentiate(H.target)

    (x, t) -> t .* H.γ .* J_s(x) .+ (1 - t) .* J_t(x)
end

"""
    dt(H::GammaTrickHomotopy)

Differentiates `H` w.r.t. the the time and returns an evaluation function
`(x,t) -> T`
"""
dt(H::GammaTrickHomotopy) = (x, ::Number) -> H.γ * evaluate(H.start, x) .- evaluate(H.target, x)

"""
    homogenize(H::GammaTrickHomotopy)

Make the start and target system of `H` homogenous.
"""
function homogenize(H::GammaTrickHomotopy)
    GammaTrickHomotopy(homogenize(H.start), homogenize(H.target), H.γ)
end

"""
    homogenized(H::GammaTrickHomotopy)

Checks whether `H` was homogenized.
"""
homogenized(H::GammaTrickHomotopy) = homogenized(H.start)

ishomogenous(H::GammaTrickHomotopy) = ishomogenous(H.start) && ishomogenous(H.target)

"""
    nvariables(H::GammaTrickHomotopy)

The number variables of `H`.
"""
function nvariables(H::GammaTrickHomotopy)
    # Constructor guarantees that start and target have the same number of variables
    nvariables(H.start)
end
function length(H::GammaTrickHomotopy)
    # Constructor guarantees that start and target have the same length
    length(H.start)
end
"""
    degrees(H::GammaTrickHomotopy)

The (total) degrees of the polynomials of the system `P`.
"""
degrees(H::GammaTrickHomotopy) = max.(degrees(H.start), degrees(H.target))
"""
    startsystem(H::GammaTrickHomotopy)

The startsystem of `H`.
"""
startsystem(H::GammaTrickHomotopy) = H.start
"""
    targetsystem(H::GammaTrickHomotopy)

The targetsystem of `H`.
"""
targetsystem(H::GammaTrickHomotopy) = H.target

"""
    weylnorm(H, t)

Computes the weylnorm of the homotopy to the given time `t`.

## Explanation
For ``H = (1-t)F+tγG`` we have
```math
\begin{align*}
<H,H> &= <(1-t)F+tγG,(1-t)F+tγG> \\
      &= <(1-t)F,(1-t)F+tγG> + <tγG,(1-t)F+tγG> \\
      &= <(1-t)F,(1-t)F> + <(1-t)F,tγG> + <tγG,(1-t)F> + <tγG,tγG> \\
      &= <(1-t)F,(1-t)F> + 2*real(<(1-t)F,tγG>) + <tγG,tγG> \\
      &= (1-t)^2<F,F> + (1-t)t*2*real(γ)*real(<F,G>) + abs(γ)*t^2<G,G> \\
\end{align*}
```
"""
function weylnorm(H::GammaTrickHomotopy{T}, t::Float64) where {T<:Complex}
    F = H.target
    G = H.start
    sqrt((1 - t)^2 * weyldot(F, F) + (1 - t) * t * 2 * real(H.γ) * real(weyldot(F, G)) + t^2 * abs2(H.γ) * weyldot(G, G))
end


## port methods of simplify module of SymPy
## from http://docs.sympy.org/0.7.2/modules/simplify/simplify.html

## simple methods (x, args) -> y (y coercion happens via PyCall)
simplify_sympy_meths = (:collect, :rcollect, :separatevars,
                        :radsimp, :ratsimp, :trigsimp, :besselsimp,
                        :powsimp, :combsimp, :hypersimp,
                        :fraction,
                        :simplify, :nsimplify,
                        :posify, :powdenest, :sqrtdenest,
                        :logcombine, :hyperexpand)

expand_sympy_meths = (:expand_trig,
                      :expand_power_base, :expand_power_exp,
                      :expand_log,
                      :expand_func,
                      :hyperexpand
                      )

## special case cse due to output
## If this pattern is common, will need to collect and use metaprogramming.
@doc """
`$(cse)`: a SymPy function.
The SymPy documentation can be found through: http://docs.sympy.org/latest/search.html?q=cse
""" ->
cse{T<:SymbolicObject}(ex::T, args...; kwargs...) = sympy_meth(:cse, ex, args...; kwargs...)
cse{T<:SymbolicObject}(ex::Vector{T}, args...; kwargs...) = sympy_meth(:cse, map(project,ex), args...; kwargs...)

function cse{T<:SymbolicObject, N}(ex::Array{T, N}, args...; kwargs...)
    a,b = cse(ex[:], args...; kwargs...)
    bb = convert(Array{Sym},  reshape(b, size(ex)))
    a, bb
end
export(cse)

## didn't do traversal tools, EPath tools


import Voronoi

using Random
include("Draw.jl")

function randf(start, finish, n)
   v = rand(n)
   return map(x -> start + x*(finish-start), v)
end

function demoFortune()
   WIDTH = 300.0
   HEIGHT = 100.0
   n = 2
   for i in 1:1000
      #println("Random seed: ", i)
      #Random.seed!(i)
      #points = convert(Array{Tuple{Real, Real}}, collect(zip(randf(1, WIDTH-1, n), randf(1, HEIGHT-1, n))))
      #points = convert(Array{Tuple{Real, Real}}, [(90.0, 90.0), (90.0, 50), (90, 30)])
#      points = convert(Array{Tuple{Real, Real}}, [(90.0, 90.0), (80.0, 50), (70.0, 55.0), (30.0, 85.0), (30, 45), (35.0, 44.0),
#                                                  (1, 90),
#                                                  (5, 90), (10, 90), (15, 90),
#                                                  (10, 55), (10,60), (10, 20), (80, 20), (30, 20),
#                                                  (10, 1), (20, 1), (30, 1),
#                                                 ])
#      points = convert(Array{Tuple{Real, Real}}, [
#                                                  (100*0.28, 100*0.8), (100*0.28, 100*0.6), (100*0.28, 100*0.4), (100*0.28, 100*0.2),
#                                                  (100*0.8,  100*0.8), (100*0.8,  100*0.6), (100*0.8,  100*0.4), (100*0.8,  100*0.2),
#                                                  (100*1.32, 100*0.8), (100*1.32, 100*0.6), (100*1.32, 100*0.4), (100*1.32, 100*0.2),
#                                                  (100*1.84, 100*0.8), (100*1.84, 100*0.6), (100*1.84, 100*0.4), (100*1.84, 100*0.2),
#                                                  (100*2.36, 100*0.8), (100*2.36, 100*0.6), (100*2.36, 100*0.4), (100*2.36, 100*0.2),
#                                                  (100*2.84, 100*0.8), (100*2.84, 100*0.6), (100*2.84, 100*0.4), (100*2.84, 100*0.2)
#                                                 ])
      points = convert(Array{Tuple{Real, Real}}, [ (100*0.2219056800797507, 100*0.7000658172436546), (100*0.49169960385155936, 100*0.6636482987025588), (100*0.7618642131758435, 100*0.7432736698473899), (100*0.8846773855975993, 100*0.6317667493965284) ])

      # test for repeated points
      repeat = false
      for i in 1:length(points)
         for j in 1:length(points)
            if j == i
               continue
            end

            if points[i][1] == points[j][1] && points[i][2] == points[j][2]
               repeat = true
            end
         end
      end
      if repeat
         println("REPEATED POINTS!")
         continue
      end

      V, T, Q = Voronoi.Fortune.init(points, WIDTH, HEIGHT)
      command = nothing
      ly = HEIGHT
      Draw.init(WIDTH, HEIGHT)

      while (event = Voronoi.EventQueue.pop(Q)) != nothing
         Voronoi.Fortune.handleEvent(V, T, Q, event) # multiple dispatch decides if it's a site event or circle event

         ly = Voronoi.EventQueue.coordinates(event)[2] # sweep line height
         Draw.fortuneIteration(V, T, Q, points, ly)
         if command != "a"
            println("Press Return for a step or enter \"a\" to animate until the end.")
            command = readline(stdin)
         else
            sleep(0.001)
         end
      end

      Voronoi.Intersect.intersect(V, Voronoi.Intersect.Rectangle(WIDTH, HEIGHT))
      Draw.init(WIDTH, HEIGHT)
      Draw.voronoiDiagram(V)

      println("Press Return to finish.")
      readline(stdin)
      n += 1
   end
end

demoFortune()


using Base: AbstractFloat
import MLJModelInterface
import Tables
const MMI = MLJModelInterface

mutable struct RBFInterpolator{F <: AbstractFloat} <: MMI.Deterministic
	kernel_name :: Symbol
	kernel_args :: Vector{F}
	polynomial_degree :: Int
end

_precision( :: RBFInterpolator{F} ) where F = F

function MMI.clean!( m :: RBFInterpolator ) :: String
	warning = ""
	if m.kernel_name ∉ [:gaussian, :multiquadric, :inv_multiquadric, :cubic, :thin_plate_spline]
		warning *= "Parameter value $(m.kernel_name) for `kernel_name` not supported, using `:gaussian`.\n"
		m.kernel_name = :gaussian
	end

	φ_constructor = SymbolToRadialConstructor[ m.kernel_name ]
	try
		φ_constructor( m.kernel_args... )
	catch MethodError
		warning *= "Invalid kernel arguments. Using defaults for $(m.kernel_name).\n"
		m.kernel_args = collect(getfield.(φ_constructor(), fn for fn in fieldnames(φ_constructor)))
	end

	φ = φ_constructor( m.kernel_args... )
	if m.polynomial_degree < cpd_order(φ) - 1
		warning *= "Need a polynomial of degree at least $(cpd_order(φ) - 1).\n"
		m.polynomial_degree = cpd_order(φ) - 1
	end

	warning
end

function RBFInterpolator(; kernel_name :: Symbol = :gaussian, kernel_args :: Vector{<:Real} = [1,], polynomial_degree = 1)
	if !(eltype(kernel_args) <: AbstractFloat)
		kernel_args = Float16.(kernel_args)
	end
	model = RBFInterpolator(kernel_name, kernel_args,polynomial_degree)
	message = MMI.clean!( model )
	isempty(message) || @warn message
	return model
end

function MMI.fit( mod :: RBFInterpolator, verbosity, X :: AbstractVector{<:AbstractVector}, y :: AbstractVector )
	φ_constructor = SymbolToRadialConstructor[ mod.kernel_name ]
	φ = φ_constructor( mod.kernel_args... )

	inner_model = RBFInterpolationModel( X, y, φ , mod.polynomial_degree)
	return inner_model, nothing, nothing
end

function MMI.reformat( ::RBFInterpolator, X,  y :: AbstractVector )
	features = [ collect(Real,v)  for v in collect( Tables.rows(X) ) ]
	labels = Real.(y)
	return (features, labels)
end

function MMI.reformat( ::RBFInterpolator, X,  y )
	features = [ collect(Real,v)  for v in collect( Tables.rows(X) ) ]
	labels =  [ collect(Real,v)  for v in collect( Tables.rows(y) ) ]
	return (features, labels)
end

function MMI.reformat( ::RBFInterpolator, X )
	features = [ collect(Real,v)  for v in collect( Tables.rows(X) ) ]
	return (features,)
end

function MMI.selectrows( ::RBFInterpolator, I, X :: AbstractVector{<:AbstractVector} )
	return (X[I],)
end

function MMI.selectrows( ::RBFInterpolator, I, X :: AbstractVector{<:AbstractVector}, y :: Union{AbstractVector, Vector{<:AbstractVector}} )
	return (X[I],y[I])
end

function MMI.predict( :: RBFInterpolator, inner_model :: RBFInterpolationModel, Xnew :: AbstractVector{<:AbstractVector})
	return inner_model.(Xnew)
end

function MMI.fitted_params(model::RBFInterpolator, inner_model) ::NamedTuple
	return (
		rbf_weights = inner_model.model.rbf.weights,
		rbf_centers = [ k.c for k ∈ inner_model.model.rbf.kernels ],
		polynomial_weights = inner_model.model.psum.weights
	)
end

MMI.metadata_pkg(
	RBFInterpolator,
	name="RBFInterpolator",
        package_uuid="48790e7e-73b2-491a-afa5-62818081adcb",
        package_url="https://github.com/manuelbb-upb/RadialBasisFunctionModels.jl",
	package_license="MIT",
        is_pure_julia=true,
	is_wrapper=true,	# TODO is it a wrapper?
)

MMI.metadata_model(
    RBFInterpolator,
    input_scitype=MMI.Table(MMI.Continuous),
    target_scitype=Union{AbstractVector{MMI.Continuous}, MMI.Table(MMI.Continuous)},
    supports_weights=true,
    docstring="Radial Basis Function Interpolator",
    load_path="RadialBasisFunctionModels.RBFInterpolator"
)


import PyPlot
const plt = PyPlot

include("lagrange_interp.jl")

function myfunc(x)
    return log(x) + 1
end

function main()
    x = [1.0, 2.0, 3.0, 4.0, 5.0]
    y = myfunc.(x)

    NptsPlot = 100
    xo = collect(range(x[begin], stop=x[end], length=NptsPlot))
    yo = zeros(NptsPlot)
    for i in 1:NptsPlot
        @views yo[i] = lagrange_interp(x[1:4], y[1:4], xo[i])
    end

    plt.clf()
    plt.plot(x, y, marker="o", label="data")
    plt.plot(xo, yo, label="interp")
    plt.grid(true)
    plt.legend()
    plt.savefig("IMG_test_lagrange_interp.png", dpi=150)
end

@time main()
@time main()


module SMACOF

using LinearAlgebra:Matrix
using Reexport
@reexport using DSTools
using LinearAlgebra
using Distances
using Printf

import Base:axes, getindex, setindex!, last, *, size
import LinearAlgebra:AbstractMatrix,diagind
import LinearAlgebra:issymmetric, mul!
import Statistics: sum, mean

include("cmds.jl")
include("helpers.jl")
include("procrustes.jl")
include("smacof.jl")
include("wda_smacof.jl")

export align, align!
export classical_scaling
export stress
export smacof
export wda_smacof

end


# ===============================================================
# Homogeneous case
# ===============================================================
function reach_homog!(HR::Vector{ReachSet{Zonotope{Float64}}},
                      Ω0::Zonotope,
                      Φ::AbstractMatrix,
                      N::Int,
                      δ::Float64,
                      max_order::Int)

    # save timestamps with the reach set
    t0, t1 = zero(δ), δ

    # initial reach set
    HR[1] = ReachSet(Ω0, t0, t1)

    k = 2
    while k <= N
        HR_next = linear_map(Φ, HR[k-1].X)
        HR_next_red = reduce_order(HR_next, max_order)
        t0 = t1; t1 += δ
        HR[k] = ReachSet(HR_next_red, t0, t1)
        k += 1
    end
    return HR
end

# ===============================================================
# Inhomogeneous case
# ===============================================================
function reach_inhomog!(X::Vector{ReachSet{Zonotope{Float64}}},
                        Ω0::Zonotope,
                        U::ConstantInput,
                        Φ::AbstractMatrix,
                        N::Int,
                        δ::Float64,
                        max_order::Int)

    # initialization
    t0, t1 = zero(δ), δ
    V = next_set(U)

    X[1] = ReachSet(Ω0, t0, t1)
    Wk₊ = V
    Φ_power_k = copy(Φ)
    Φ_power_k_cache = similar(Φ)

    # update
    k = 2
    while k <= N
        Xk = minkowski_sum(linear_map(Φ_power_k, Ω0), Wk₊)
        Wk₊ = minkowski_sum(Wk₊, linear_map(Φ_power_k, V))

        t0 = t1; t1 += δ
        Xk_red = reduce_order(Xk, max_order)
        X[k] = ReachSet(Xk_red, t0, t1)

        Wk₊ = reduce_order(Wk₊, max_order)

        _A_mul_B!(Φ_power_k_cache, Φ_power_k, Φ)
        copyto!(Φ_power_k, Φ_power_k_cache)
        k += 1
    end

    return X
end


# Script to run the MIO formulation from Bertsimas and Mišić for n in [200,500], K in [10, 100, 200, 500, 100].
# This script will only solve the MIO directly, and will skip the n = 500, K = 1000 case (this is in MIOexpdata1_BMonly_n500_K1000.jl).
# This script produces part of the results needed for Table 3 of the paper.
#
# The script takes command line arguments to determine which instances from MIOexpdata1 to do. To run the first 20 instances
# (as per the paper), type
#
# > julia MIOexpdata1_BMonly.jl 1 20
#
# into the command line.

using JuMP, Gurobi, Stats, Distributions

include("../optimalPLD_code/optimalPLD_createPath.jl")
include("../optimalPLD_code/optimalPLD_solveSAO_constraints.jl")

srand(1204)
machine = 2;

n_vec = [200, 500]
numPerm_vec = [100, 200, 500, 1000]

numReps = 100;

counter = 0;

LR = parse(ARGS[1]);
UR = parse(ARGS[2]);

expdirpath = optimalPLD_createPath(machine, "exp_dir", "MIOexpdata1_BMonly_reps$(LR)to$(UR)");
mkdir(expdirpath)

b_vec = [5; 10];

MIP_timeLimit = 0 #1*3600;
isRelaxation = false;

data_prefix = "MIOexpdata1"

outcsvfilepath = string(expdirpath, "out.csv");
outcsvhandle = open(outcsvfilepath, "w")
row_string = string("n", ",",
					"numPermutations", ",",
					"rep", ",",
					"b_feas", ",",
					"sumxi", ",",
					"BM_expRevenue", ",",
					"BM_upperBound", ",",
					"BM_solutiontime", ",",
					"BM_MIPgap")
row_string = string(row_string, "\n");

print(outcsvhandle, row_string);
flush(outcsvhandle);

rep_range = LR:UR

counter = 0;
for nind = 1:length(n_vec)
	n = n_vec[nind]
	for nPind = 1:length(numPerm_vec)
		numPermutations = numPerm_vec[nPind];

		println("**** Reading from data directory...")
		data_name = string(data_prefix,"_neq",n,"_Keq",numPermutations)
		data_dir = optimalPLD_createPath(machine, "data_dir", data_name)
		revenues_mat = readcsv(string(data_dir, "revenues_mat.csv"))
		lambda_mat = readcsv(string(data_dir, "lambda_mat.csv"))
		orderings_mat = readcsv(string(data_dir, "orderings_mat.csv"))

		orderings_mat = convert(Array{Int64}, orderings_mat);

		# Skip the n = 500, K = 1000 case
		if ( (n == 500) & (numPermutations == 1000) )
			break;
		end

		for rep in rep_range

			revenues = revenues_mat[rep,:][:]
			lambda = lambda_mat[rep,:] #[1:numPermutations]
			orderings = orderings_mat[numPermutations*(rep-1)+1:rep*numPermutations,:]

			b_vec_actual = [b_vec; n]

			for b_feas in b_vec_actual

				A_feas = ones(1,n);

				println("**** Solving n = $n, nP = $numPermutations, rep = $rep ****")

				counter = counter + 1;
				isRelaxation = false;
				x_initial = [];

				if (b_feas >= n)
					println("**** Solving Bertsimas and Misic MIO (unconstrained) ****")
					x_val, y_val, BM_expRevenue, BM_upperBound, BM_solutiontime, BM_MIPgap = optimalPLD_solveSAO_constraints( n, revenues, numPermutations, lambda, orderings, MIP_timeLimit, isRelaxation, x_initial, [], [] )
				else
					println("**** Solving Bertsimas and Misic MIO (constrained) ****")
					x_val, y_val, BM_expRevenue, BM_upperBound, BM_solutiontime, BM_MIPgap = optimalPLD_solveSAO_constraints( n, revenues, numPermutations, lambda, orderings, MIP_timeLimit, isRelaxation, x_initial, A_feas, b_feas)
				end

				sumxi = sum(x_val);

				row_string = string(n, ",",
						numPermutations, ",",
						rep, ",",
						b_feas, ",",
						sumxi, ",",
						BM_expRevenue, ",",
						BM_upperBound, ",",
						BM_solutiontime, ",",
						BM_MIPgap) #, ",",

				row_string = string(row_string, "\n");

				print(outcsvhandle, row_string);
				flush(outcsvhandle);
			end
		end
	end
end

close(outcsvhandle)


using Evolutionary

println("Use Genetic Algorithm to fit the parameter")
#         objfun: Objective fitness function
#              N: Search space dimensionality
# initPopulation: Search space dimension ranges as a vector, or initial population values as matrix,
#                 or generation function which produce individual population entities.
# populationSize: Size of the population
#  crossoverRate: The fraction of the population at the next generation, not including elite children,
#                 that is created by the crossover function.
#   mutationRate: Probability of chromosome to be mutated
#              ɛ: Positive integer specifies how many individuals in the current generation
#                 are guaranteed to survive to the next generation.
#                 Floating number specifies fraction of population.

cost_function = build_loss_objective(prob1,Tsit5(),L2Loss(t,data),
                                     maxiters=10000,verbose=false)
N = 1
result, fitness, cnt = ga(cost_function, N;
                   initPopulation = Float64[1.2],
                   populationSize = 100,
                   ɛ = 0.1,
               selection = sus,
               crossover = intermediate(0.25),
               mutation = domainrange(fill(0.5,N)))
@test result[1] ≈ 1.5 atol=3e-1

cost_function = build_loss_objective(prob2,Tsit5(),L2Loss(t,data),
                                     maxiters=10000)
N = 2
result, fitness, cnt = ga(cost_function, N;
                   initPopulation = Float64[1.2,2.8],
                   populationSize = 500,
                   ɛ = 0.1,
               selection = sus,
               crossover = intermediate(0.25),
               mutation = domainrange(fill(0.5,N)))
@test result ≈ [1.5;3.0] atol=3e-1

cost_function = build_loss_objective(prob3,Tsit5(),L2Loss(t,data),
                                     maxiters=10000)
N = 4
result, fitness, cnt = ga(cost_function, N;
                   initPopulation = Float64[1.3,0.8,2.8,1.2],
                   populationSize = 1000,
                   ɛ = 0.1,
               selection = sus,
               crossover = intermediate(0.25),
               mutation = domainrange(fill(0.5,N)))
@test result ≈ [1.5;1.0;3.0;1.0] atol=3e-1


export ChanceConstraint

"""
Description:

    Chance constraint on the marginal q of the connected variable x, as
    ϵ ⩽ 1 - ∫_G q(x) dx, where G indicates a region. In other words,
    the probability mass of q is not allowed to overflow the region G
    by more than ϵ. Implementation according to (van de Laar et al.
    "Chance-Constrained Active Inference", MIT Neural Computation, 2021).

Interfaces:

    1. out

Construction:

    ChanceConstraint(out; G=(min,max), epsilon=epsilon, id=:my_node)
"""
mutable struct ChanceConstraint <: SoftFactor
    id::Symbol
    interfaces::Array{Interface,1}
    i::Dict{Symbol, Interface}

    G::Tuple # Range for G (1-D only)
    epsilon::Float64 # Threshold
    atol::Union{Float64, Nothing} # Tolerance for corrected belief

    function ChanceConstraint(out; G::Tuple, epsilon::Float64, atol=nothing, id=ForneyLab.generateId(ChanceConstraint))
        @ensureVariables(out)
        self = new(id, Vector{Interface}(undef, 1), Dict{Symbol,Interface}(), G, epsilon, atol)
        ForneyLab.addNode!(currentGraph(), self)
        self.i[:out] = self.interfaces[1] = associate!(Interface(self), out)

        return self
    end
end

slug(::Type{ChanceConstraint}) = "P"

# A breaker message is required if interface is partnered with an expectation constraint
requiresBreaker(interface::Interface, partner_interface::Interface, partner_node::ChanceConstraint) = true

breakerParameters(interface::Interface, partner_interface::Interface, partner_node::ChanceConstraint) = (Message{GaussianMeanVariance, Univariate}, ()) # Univariate only

# Constraints do not contribute to average energy
averageEnergy(::Type{ChanceConstraint}, marg_out::ProbabilityDistribution) = 0.0


using QuasinormalModes
using RootsAndPoles

# ------------------------------------------------------------------
# 1. Setting up Schwarzschild black hole data structure for
#    computing the QNMs numerically.
# ------------------------------------------------------------------

struct NSchwarzschildData{N,T} <: NumericAIMProblem{N,T}
    nIter::N
    x0::T
    l::N
    s::N
end

function NSchwarzschildData(nIter::N, x0::T, l::N, s::N) where {N,T}
    return NSchwarzschildData{N,T}(nIter, x0, l, s)
end

QuasinormalModes.λ0(::NSchwarzschildData{N,T}) where {N,T} = (x,ω) -> (-1 + (2*im)*ω + x*(4 - 3*x + (4*im)*(-2 + x)*ω))/((-1 + x)^2*x)
QuasinormalModes.S0(d::NSchwarzschildData{N,T}) where {N,T} = (x,ω) -> (d.l + d.l^2 + (-1 + d.s^2)*(-1 + x) + (4*im)*(-1 + x)*ω + 4*(-2 + x)*ω^2)/((-1 + x)^2*x)

QuasinormalModes.get_niter(d::NSchwarzschildData{N,T}) where {N,T} = d.nIter
QuasinormalModes.get_x0(d::NSchwarzschildData{N,T}) where {N,T} = d.x0

# ------------------------------------------------------------------
# 2. Benchmark
# ------------------------------------------------------------------

"""
    bench_iter(iter_start, iter_end, x0, l, s, xt, ft, nls_iter, reference_ωr, reference_ωi, repeat)
Measures the time required to compute a pair of quasinormal frequencies as a function of the
number of AIM iterations performed. The result is also compared with a know result in order
to provide a error measurement.
# Input
- `iter_start`: First number of iterations to perform.
- `iter_end`: Last number of iterations.
- `x0`: The point around which AIM functions will be Taylor expanded.
- `l`: The l value of the sought mode.
- `s`: The s value of the sought mode.
- `xt`: The `xtol` value passed to NLSolve.
- `ft`: The `ftol` value passed to NLSolve.
- `nls_iter`: The number of iterations that NLSolve will perform.
- `reference_ωr`: The reference value for the real QNM frequency.
- `reference_ωi`: The reference value for the imaginary QNM frequency.
- `repeat`: The amount of times to repeat the operation.
# Output
    nothing
"""
function bench_iter(iter_start, iter_end, x0, l, s, xt, ft, nls_iter, reference_ωr, reference_ωi, repeat)

    for i in 1:repeat
        println("Benchmark iteration ", i, ":")
        file = open("run_$(i).dat", "w")
        println(file, "# 1:time(ns) 2:iter 3:x0 4:l 5:s 6:xt 7:ft 8:w_r 9:w_i 10:error in w_r 11:error in w_i")

        for iter in iter_start:iter_end
            println("    AIM iteration ", iter)
            p_num = NSchwarzschildData(iter, x0, l, s);
            c_num = AIMCache(p_num)

            t0 = time_ns();
            ev = computeEigenvalues(Threaded(), p_num, c_num, typeof(x0)(reference_ωr, reference_ωi), nls_xtol = xt, nls_ftol = ft, nls_iterations = nls_iter)
            elapsed_time = time_ns() - t0

            if(ev.x_converged || ev.f_converged)
                println(file,
                        elapsed_time,          "    ",
                        iter,                  "    ",
                        convert(Float64, x0),  "    ",
                        l,                     "    ",
                        s,                     "    ",
                        convert(Float64, xt),  "    ",
                        convert(Float64, ft),  "    ",
                        ev.zero[1],            "    ",
                        ev.zero[2],            "    ",
                        ev.zero[1] - reference_ωr, "    ",
                        ev.zero[2] - reference_ωi
                        )

                flush(file)
            end
        end
        close(file)
    end
end

# We call the function once and discard the results in order so that the compilation time does not get included in the benchmark.
bench_iter(0x00001, 0x00064, Complex(big"0.39", big"0.0"), 0x00000, 0x00000, big"1.0e-55", big"1.0e-55", 5000000, big"0.2209098781608393", big"-0.2097914341737619", 1)
bench_iter(0x00001, 0x00064, Complex(big"0.39", big"0.0"), 0x00000, 0x00000, big"1.0e-55", big"1.0e-55", 5000000, big"0.2209098781608393", big"-0.2097914341737619", 20)