using FuzzySets, Test

step_size = 0.005
levels = collect(0:step_size:1)

A = TriangularFuzzyNumber(levels; number=0.0, width=0.5)
@test A.levels == levels
@test A.grades[1] == Interval(-0.5, 0.5)
@test A[1] == Interval(-0.5, 0.5)
@test support(A) == Interval(-0.5, 0.5)
@test core(A) == Interval(0) == Interval(0.0)
@test height(A) == 1.0
@test A(-0.2) == 0.6
@test A(0) == 1.0
@test A(0.2) == 0.6
@test A(0.5) == 0.0
@test A(1) == 0.0
@test length(A) == 201
@test round(centroid(A, step=0.01), digits=2) ≈ 0.0

B = TriangularFuzzyNumber(levels, number=5.0, width=0.5)
@test B.levels == levels
@test support(B) == Interval(4.5, 5.5)
@test core(B) == Interval(5.0)
@test height(B) == 1

C = TriangularFuzzyNumber(levels, number=5.0, width=0.5)
@test B == C
@test A != C

D = SingletonFuzzyNumber(levels, number=0)
@test support(D) == Interval(0)
@test D.grades == repeat([Interval(0)], length(levels)) # TODO: check with all(...)

A = TriangularFuzzyNumber(levels, number=1, width=0.5)
FuzzySets.vertical_cut!(A, α=0.2)
@test A.grades[1] == A.grades[41]
@test A.grades[2] == A.grades[41]
@test A.grades[40] == A.grades[41]
B = FuzzySets.vertical_cut(A, α=0.2)
@test B.grades[1] == B.grades[41]
@test B.grades[2] == B.grades[41]
@test B.grades[40] == B.grades[41]

A = SingletonFuzzyNumber(levels, number=5)
B = TriangularFuzzyNumber(levels, number=5)
@test FuzzySets.isSingleton(A) == true
@test FuzzySets.isSingleton(B) == false

p = 6
w_l = 2
w_r = 2
a = 2
@test FuzzySets.trapezoid(0; p, w_l, w_r, a) == Interval(2, 10)
@test FuzzySets.trapezoid(0.5; p, w_l, w_r, a) == Interval(3, 9)
@test FuzzySets.trapezoid(1.0; p, w_l, w_r, a) == Interval(4, 8)
@test_throws ErrorException FuzzySets.trapezoid(1.5; p, w_l, w_r, a)
A = TrapezoidalFuzzyNumber(levels; number=p, w_l, w_r, a)
@test A.grades[1] == Interval(2, 10)
@test A.grades[101] == Interval(3, 9)
@test A.grades[end] == Interval(4, 8)
@test round(centroid(A, step=0.01), digits=2) ≈ p

μ = 0
σ = 1
A = GaussianFuzzyNumber(levels; μ=μ, σ=σ)
@test A.grades[1] == A.grades[2]
@test A.grades[1].left == -A.grades[1].right
@test A.grades[101].left == -A.grades[101].right
@test A.levels[101] == 0.5
@test A.grades[101] == Interval(-1.1774100225154747, 1.1774100225154747)
@test A.grades[end] == Interval(μ)

# Specificity
A = TriangularFuzzyNumber(levels, number=0.5, width=0.5)
@test FuzzySets.specificity(A) == 0.5
@test FuzzySets.u_uncertainty(A) == 1

B = TriangularFuzzyNumber(levels, number=0.5, width=0.2)
@test FuzzySets.specificity(B) ≈ 0.8

C = TriangularFuzzyNumber(levels, number=8.0, width=1.0)
@test FuzzySets.specificity(C) ≈ 0.8

S = SingletonFuzzyNumber(levels, number=4.0)
@test FuzzySets.specificity(S) ≈ 1.0
@test FuzzySets.u_uncertainty(S) ≈ 0

step_size = 0.0001
levels = collect(0:step_size:1)

a = 0
b = 5
BoxFuzzyNumber = FuzzySets.BoxFuzzyNumber(levels, a=a, b=b)
w = b - a
@test round(FuzzySets.u_uncertainty(BoxFuzzyNumber), digits=3) ≈ round(log(1+w), digits=3)

A1 = TriangularFuzzyNumber(levels, number=2, width=2.0)
@test round(FuzzySets.u_uncertainty(A1), digits=3) ≈ 1.012

A2 = TriangularFuzzyNumber(levels, number=2, width=1.0)
@test round(FuzzySets.u_uncertainty(A2), digits=3) ≈ 0.648

# A3 = TriangularFuzzyNumber(levels, number=2, width=2.0)
# middle = Int((length(A3.levels) + 1)/2)
# A3.grades[1:middle] = A3.grades[middle:end]
# for i = middle+1:length(A3.levels)
#     A3.grades[i] = Interval(NaN)
# end
# @test round(FuzzySets.u_uncertainty(A3) * 2, digits=3) ≈ 0.648

A4 = TriangularFuzzyNumber(levels, number=2, width=0.5)
@test round(FuzzySets.u_uncertainty(A4), digits=3) ≈ 0.386 # not sure


fejer_plan1(μ) = FFTW.plan_r2r!(μ, FFTW.REDFT01)
fejer_plan2(μ) = FFTW.plan_r2r!(μ, FFTW.RODFT00)

"""
Compute nodes and weights of Fejer's first quadrature rule with a Jacobi weight.
"""
fejer1{T<:AbstractFloat}(N::Int,α::T,β::T) = fejer1(N,α,β,fejer_plan1(zeros(T,N)))

"""
Compute nodes and weights of Fejer's second quadrature rule with a Jacobi weight.
"""
fejer2{T<:AbstractFloat}(N::Int,α::T,β::T) = fejer2(N,α,β,fejer_plan2(zeros(T,N)))

fejer1{T<:AbstractFloat}(N::Int,α::T,β::T,plan) = T[sinpi((N-2k-one(T))/2N) for k=0:N-1],fejerweights1(N,α,β,plan)
fejer2{T<:AbstractFloat}(N::Int,α::T,β::T,plan) = T[cospi((k+one(T))/(N+one(T))) for k=0:N-1],fejerweights2(N,α,β,plan)

"""
Compute weights of Fejer's first quadrature rule with a Jacobi weight.
"""
fejerweights1{T<:AbstractFloat}(N::Int,α::T,β::T) = fejerweights1(N,α,β,fejer_plan1(zeros(T,N)))

"""
Compute weights of Fejer's second quadrature rule with a Jacobi weight.
"""
fejerweights2{T<:AbstractFloat}(N::Int,α::T,β::T) = fejerweights2(N,α,β,fejer_plan2(zeros(T,N)))

function fejerweights1{T<:AbstractFloat}(N::Int,α::T,β::T,plan)
    μ = chebyshevjacobimoments1(N,α,β)
    scale!(μ,inv(T(N)))
    return plan*μ
end

function fejerweights2{T<:AbstractFloat}(N::Int,α::T,β::T,plan)
    μ = chebyshevjacobimoments2(N,α,β)
    Np1 = N+one(T)
    scale!(μ,inv(Np1))
    plan*μ
    @inbounds for i=1:N μ[i] = sinpi(i/Np1)*μ[i] end
    return μ
end


using IntervalArithmetic, StaticArrays, IntervalLinearAlgebra, IntervalRootFinding

A = @SMatrix [4..6 -1..1 -1..1 -1..1;-1..1 -6.. -4 -1..1 -1..1;-1..1 -1..1 9..11 -1..1;-1..1 -1..1 -1..1 -11.. -9]

b = @SVector [-2..4, 1..8, -4..10, 2..12]

jac = Jacobi()
gs = GaussSeidel()
hbr = HansenBliekRohn()

@btime solve($A, $b, $gs)

@btime solve($A, $b, $jac)

@btime solve($A, $b, $hbr)

# comparison with IntervalRootFinding

@btime gauss_seidel_interval($A, $b)
@btime gauss_seidel_contractor($A, $b) #NOTE: THIS IS THE JACOBI method
@btime gauss_elimination_interval($A, $b)


import Pkg
Pkg.activate("RbfInterpolationTools")
using RbfInterpolationTools
using Plots

# Lets generate some observed points
xx = rand(500)
xy = rand(500)
X = hcat(xx, xy)
# The hidden function will be:
z_func(x, y) = (x + y) * exp(-6.0 * (x^2 + y^2))
# Taking the values in the points of
z_obs = [z_func(x, y) for (x, y) in zip(xx, xy)]
# To visualize it:
Plots.scatter(X[:, 1], X[:, 2], zcolor = z_obs)

# For example, lets generate a interpolation environment using the observed points.
# We will be using as a basis fuction the ThinPlate [https://en.wikipedia.org/wiki/Thin_plate_spline]
inter = RbfInterpolationTools.Interpolator(X = X, y = z_obs, basis_name = "ThinPlate")

# Now, lets generate a 10 times denser mesh to generate the points
new_domain = (hcat(rand(100000), rand(100000)))
# And we can actually see the it generates an interpolated value for each new coordinate
z_interpolated = RbfInterpolationTools.interpolateMesh(inter, new_domain)
# It can be easilly visualized using Plots and a scatter
Plots.scatter(new_domain[:, 1], new_domain[:, 2], zcolor = z_interpolated)


function sampleMarkovChain(n::Int, P::AbstractMatrix, πₚ::AbstractVector)
    s = Vector{Int}(undef, n)
    sampleMarkovChain!(s, n, P, πₚ)
end

function sampleMarkovChain!(s, n::Int, P::AbstractMatrix, πₚ::AbstractVector)
    s[1] = rand(Categorical(πₚ))
    @inbounds for i = 2:n
        s[i] = rand(Categorical(P[s[i-1], :]))
    end
    s
end

# categorical sequence encoding/decoding
@inline function encode(arr::Union{Vector{Int},Tuple}, k::Int)
  s = 1
  n = 1
  @inbounds for i = 1:length(arr)
    s += (arr[i] - 1) * n
    n *= k
  end
  s
end

@inline function decode(s::Int, k::Int, l::Int)
  arr = []
  n = k^(l-1)
  c = s - 1
  for i = 1:l
    pushfirst!(arr, div(c, n) + 1)
    c = mod(c, n)
    n = n // k
  end
  arr
end

@inline function logsumexp(x::Vector{T}) where T<:Real
  xmax = maximum(x)
  y = map(x -> exp.(x - xmax), x)

  log(sum(y)) + xmax
end


#=
Hackerrank problem - Geometric Distribution
https://www.hackerrank.com/challenges/s10-geometric-distribution-1/problem
https://www.hackerrank.com/challenges/s10-geometric-distribution-2/problem

Date: 2020-07-30
Author: Eric Nam
=#

using Printf

function dist_geomteric(p, n)
    (1. - p) ^ (n - 1) * p
end

# Part 1
function day4_1()
    # STDIN
    nu, denom = parse.(Int, split(readline(stdin)))
    nth = parse(Int, readline(stdin))

    result = dist_geomteric(nu / denom, nth)
    println("$(@sprintf("%.3f", result))")
end

# day4_1()

# Part 2
function day4_2()
    # STDIN
    nu, denom = parse.(Int, split(readline(stdin)))
    nth = parse(Int, readline(stdin))

    result = sum(x -> dist_geomteric(nu / denom, x), 1:5)
    println("$(@sprintf("%.3f", result))")
end

day4_2()


n = rand(1:10)

A = rand(n,n)

r = polarfact(A, alg =:hybrid);

# Test unitary matrix U

U = r.U
H = r.H

@test_approx_eq_eps U'*U eye(n) 1e-7

# Test Hermitian positive semifefinite matrix H

@test issym(H)

for i in eigvals(H)
    @test i >= 0.
end

@test_approx_eq_eps A U*H 1e-7

println("Hybrid method passed test...")


M(P) = [Int(sign(P[i]-P[j])) for i=1:length(P), j=1:length(P)]
preference(d::Dict{String,Int}) = [d[k] for k in sort([keys(d)...])]

"""
    ⦸(d₁, d₂[, dₛ...])

Return "light" preference matrix of given rankings.
A binary (infix) operation on two or more rankings.

# Examples
```jldoctest
julia> ranking"a₂,a₄,a₁,a₃" ⦸
       ranking"a₁,a₃∼a₄,a₂" ⦸
       ranking"a₂∼a₃,a₄,a₁" ⦸
       ranking"a₃,a₂,a₁∼a₄"
4×4 Array{Int64,2}:
  0   2  0   1
 -2   0  1  -2
  0  -1  0  -1
 -1   2  1   0

julia> ⦸(ranking"123",ranking"231",ranking"312")
3×3 Array{Int64,2}:
  0  -1   1
  1   0  -1
 -1   1   0
```
"""
function  ⦸(d₁::Dict{String,Int}, d₂::Dict{String,Int}, dₛ::Dict{String,Int}...)
    @assert length(d₁) == length(d₂)
    @assert sort([keys(d₁)...]) == sort([keys(d₂)...])
    R =  -M(preference(d₁))-M(preference(d₂))
    for d ∈ dₛ
        @assert size(R)[1] == size(R)[2] == length(d)
        @assert sort([keys(d₁)...]) == sort([keys(d)...])
        R = ⦸(R, d)
    end
    return R
end

⦸(R::Matrix{Int}, d::Dict{String,Int}) = R - M(preference(d))


using PoissonBox
using Test
using IterativeSolvers
import PoissonBox as PB
using Random: MersenneTwister

@testset "laplace eqn operator" begin
    forward = PoissonBox.forward
    @testset "1d" begin
        @test forward(1:5) ≈ [1,0,0,0,5]
        @test forward([0,0,1,0,0]) ≈ [0,0.5,-1,0.5,0]
    end
    @testset "2d" begin
        @test forward(
            [0 0 0 0 0
                 0 0 0 0 0
                 0 0 1 0 0
                 0 0 0 0 0
                 0 0 0 0 0
                ]) ≈ [
                 0 0    0    0    0
                 0 0    0.25 0    0
                 0 0.25 -1   0.25 0
                 0 0    0.25 0    0
                 0 0    0    0    0
                ]
    end
    @testset "3d" begin
        arr = fill(0, (5,5,5))
        arr[3,3,3] = 1
        expected = fill(0.0,(5,5,5))
        expected[3,3,3] = -1
        expected[2,3,3] = 1/6
        expected[4,3,3] = 1/6
        expected[3,2,3] = 1/6
        expected[3,4,3] = 1/6
        expected[3,3,2] = 1/6
        expected[3,3,4] = 1/6
        @test forward(arr) ≈ expected
    end
    @testset "3d bdry" begin
        arr = randn(3,4,5)
        out = forward(arr)
        expected = copy(arr)
        expected[2:2, 2:3, 2:4] .= 0
        out[2:2, 2:3, 2:4] .= 0
        @test expected ≈ out
    end
end

@testset "solve" begin
    solve = PoissonBox.solve
    y = Float64[1, 0, 0, 0, 5]
    @test solve(y) ≈ 1:5
    for shape in [(5,),
                  (4,3),
                  (5,4),
                  (4,3,4),
                 ]
        y = rand(MersenneTwister(0), shape...)
        x = solve(y)
        @test PB.forward(x) ≈ y rtol=1e-3
    end
end

@testset "solve low level" begin
    op, v = PB.poisson_problem([1, 0, 0, 0, 5])
    sol = bicgstabl(op, v, 2)
    u = PB.reshape_solution(op, sol)
    @test u ≈ 1:5
end


# Import needed libraries
using Plots
using XLSX
using DataFrames
using Statistics

# Read the relevant excel workbook
df = DataFrames.DataFrame(XLSX.readtable("Folds5x2_pp.xlsx", "Sheet5")...)

# Split into design matrix and target vector
X = df[:, 1:4]
design_matrix = convert(Matrix, X)

y = df[:, 5]  # Target vector

# Split into training and test
train_size = 0.80
data_size = size(design_matrix)[1]

train_index = trunc(Int, train_size * data_size)

# Split using the desired train size
X_train = design_matrix[1:train_index, :]
X_test = design_matrix[train_index+1:end, :]

y_train = y[1:train_index]
y_test = y[train_index+1:end]

"""
    scale_features(X)

This function attempts to normalise the design matrix (X) user pass.
The input data X is standardised and return along with other learned metrics.

A tuple with 3 elements is returned representing (Standardised data, mean, std deviation).

"""
function scale_features(X)

    μ = mean(X, dims=1)
    σ = std(X, dims=1)

    X_norm = (X .- μ) ./ σ

    return (X_norm, μ, σ)
end

"""
    transform_features(X, μ, σ)

This functions uses the mean and standard deviation values users pass to
normalise a new design matrix.
"""
function transform_features(X, μ, σ)
    X_norm = (X .- μ) ./ σ
    return X_norm
end

# Scale training features and get artificats for future use
X_train_scaled, μ, σ = scale_features(X_train)

# Transform the testing features by using the learned artifacts
X_test_scaled = transform_features(X_test, μ, σ)

"""
    mean_squared_cost(X, y, θ)

This function computes the batch cost based on the values of the design matrix (X),
target vector (y), and the weights (θ) passed to it.
"""
function mean_squared_cost(X, y, θ)
    # Sample size
    m = size(X)[1]

    # Vectorised Prediction loss
    preds = X * θ
    loss = preds - y

    # Half mean squared loss
    cost =  (1/(2m)) * (loss' * loss)

    return cost
end

"""
    lin_reg_grad_descent(X, y, α, fit_intercept=true, n_iter=1000)

This function uses gradient descent algorithm to find the best weights (θ)
that minimises the mean squared loss between the predictions that the model
generates and the target vector (y).

A tuple of 1D vectors representing the weights (θ)
and a history of loss at each iteration (𝐉) is returned.
"""
function lin_reg_grad_descent(X, y, α, fit_intercept=true, n_iter=2000)
    # Initialize some useful values
    m = length(y) # number of training examples

    if fit_intercept
        # Add a constant of 1s if fit_intercept is specified
        constant = ones(m, 1)
        X = hcat(constant, X)
    else
        X # Assume user added constants
    end

    # Use the number of features to initialise the theta θ vector
    n = size(X)[2]
    θ = zeros(n)

    # Initialise the cost vector based on the number of iterations
    𝐉 = zeros(n_iter)

    for iter in range(1, stop=n_iter)
        pred = X * θ

        # Calcaluate the cost for each iter
        𝐉[iter] = mean_squared_cost(X, y, θ)

        # Update the theta θ at each iter
        θ = θ - ((α/m) * X') * (pred - y);
    end
    return (θ, 𝐉)
end

θ, 𝐉 = lin_reg_grad_descent(X_train_scaled, y_train, 0.05, true, 3000)

plot(𝐉,
     label="Cost per iter",
     ylabel="Cost",
     xlabel="Number of Iteration",
     title="Cost Per Iteration")

savefig("cost_plot")

"""
    predict(X, θ, fit_intercept=true)

This function uses the learned weights (θ) to make predictions based on the
design matrix passed as (X).

The 1D vector representing these predictions is finally returned.
"""
function predict(X, θ, fit_intercept=true)
    m = size(X)[1]

    if fit_intercept
        constant = ones(m)
        X = hcat(constant, X)
    else
        X
    end

    predictions = X * θ

    return predictions
end

# Make predictions for both training and testing datasets
train_preds = predict(X_train_scaled, θ)
test_preds = predict(X_test_scaled, θ)

"""
    rmse_score(y_true, y_pred)

This function calculates the RMSE based on the values of ground truth (y_true)
and the predicted values generated by a model (y_pred)
"""
function rmse_score(y_true, y_pred)

    errors = y_pred - y_true
    errors² = errors .^ 2
    mse = mean(errors²)
    rmse = sqrt(mse)

    return rmse

end

println("RMSE for Training Set: ", rmse_score(y_train, train_preds))
println("RMSE for Testing Set: ", rmse_score(y_test, test_preds))

"""
    r_squared_score(y_pred, y_true)

This function returns the R squared value based on the predictions (y_pred)
and the ground truth values (y_true)passed to it.
"""
function r_squared_score(y_pred, y_true)
    # Just a convinient way of using notations
    ∑ = sum
    μ = mean

    # Compute sum of explained variance (SST) and sum of squares of residuals
    sst = ∑(((y_true .- μ(y_true)) .^ 2))
    ssr = ∑(((y_pred .- y_true) .^ 2))

    r² = 1 - (ssr / sst)

    return r²
end

# Get the r-squared score for training and test datasets
train_r² = r_squared_score(train_preds, y_train)
test_r² = r_squared_score(test_preds, y_test)

println("Training R² score for test sets: ", train_r²)
println("Testing R² score for test sets: ", test_r²)


# Ridge Regression (Tikhonov regularization)

#### auxiliary

function lreg_chkdims(X::AbstractMatrix, Y::AbstractVecOrMat, trans::Bool)
    mX, nX = size(X)
    dX = ifelse(trans, mX, nX)
    dY = ifelse(trans, nX, mX)
    size(Y, 1) == dY || throw(DimensionMismatch("Dimensions of X and Y mismatch."))
    return dX
end

lrsoltype{T}(::DenseVector{T}) = Vector{T}
lrsoltype{T}(::DenseMatrix{T}) = Matrix{T}

_vaug{T}(X::DenseMatrix{T}) = vcat(X, ones(T, 1, size(X,2)))::Matrix{T}
_haug{T}(X::DenseMatrix{T}) = hcat(X, ones(T, size(X,1), 1))::Matrix{T}

## linear least square

function llsq{T<:FloatingPoint}(X::DenseMatrix{T}, Y::DenseVecOrMat{T};
                                trans::Bool=false, bias::Bool=true)
    if trans
        mX, nX = size(X)
        size(Y, 1) == nX || throw(DimensionMismatch("Dimensions of X and Y mismatch."))
        mX <= nX || error("mX <= nX is required when trans is false.")
    else
        mX, nX = size(X)
        size(Y, 1) == mX || throw(DimensionMismatch("Dimensions of X and Y mismatch."))
        mX >= nX || error("mX >= nX is required when trans is false.")
    end
    _ridge(X, Y, zero(T), trans, bias)
end

## ridge regression

function ridge{T<:FloatingPoint}(X::DenseMatrix{T}, Y::DenseVecOrMat{T}, r::Real;
                                trans::Bool=false, bias::Bool=true)
    lreg_chkdims(X, Y, trans)
    r >= zero(r) || error("r must be non-negative.")
    _ridge(X, Y, convert(T, r), trans, bias)
end

function ridge{T<:FloatingPoint}(X::DenseMatrix{T}, Y::DenseVecOrMat{T}, r::DenseVector{T};
                                trans::Bool=false, bias::Bool=true)
    d = lreg_chkdims(X, Y, trans)
    length(r) == d || throw(DimensionMismatch("Incorrect length of r."))
    _ridge(X, Y, r, trans, bias)
end

function ridge{T<:FloatingPoint}(X::DenseMatrix{T}, Y::DenseVecOrMat{T}, r::DenseMatrix{T};
                                trans::Bool=false, bias::Bool=true)
    d = lreg_chkdims(X, Y, trans)
    size(r) == (d, d) || throw(DimensionMismatch("Incorrect size of r."))
    _ridge(X, Y, r, trans, bias)
end

## implementation

function _ridge{T<:FloatingPoint}(X::DenseMatrix{T}, Y::DenseVecOrMat{T},
                                  r::Union(Real, DenseVecOrMat), trans::Bool, bias::Bool)
    if bias
        if trans
            X_ = _vaug(X)
            A = cholfact!(_ridge_reg!(A_mul_Bt(X_, X_), r, bias)) \ (X_ * Y)
        else
            X_ = _haug(X)
            A = cholfact!(_ridge_reg!(X_'X_, r, bias)) \ (X_'Y)
        end
    else
        if trans
            A = cholfact!(_ridge_reg!(A_mul_Bt(X, X), r, bias)) \ (X * Y)
        else
            A = cholfact!(_ridge_reg!(X'X, r, bias)) \ (X'Y)
        end
    end
    return A::lrsoltype(Y)
end

function _ridge_reg!(Q::Matrix, r::Real, bias::Bool)
    if r > zero(r)
        n = size(Q, 1) - int(bias)
        for i = 1:n
            @inbounds Q[i,i] += r
        end
    end
    return Q
end

function _ridge_reg!(Q::Matrix, r::DenseVector, bias::Bool)
    n = size(Q, 1) - int(bias)
    @assert length(r) == n
    for i = 1:n
        @inbounds Q[i,i] += r[i]
    end
    return Q
end

function _ridge_reg!(Q::Matrix, r::Matrix, bias::Bool)
    n = size(Q, 1) - int(bias)
    @assert size(r) == (n, n)
    for j = 1:n, i = 1:n
        @inbounds Q[i,j] += r[i,j]
    end
    return Q
end


using LightGraphs
using LightGraphs.SimpleGraphs
using LinearAlgebra
using SparseArrays
using DifferentialEquations
using ProgressMeter

include("utils\\randutils.jl")
include("utils\\categorical_tree.jl")

"""
    contact_process_gillespie(g, Xi, β; kwargs)

Simulate SI spreading on network `g` using the Gillespie algorithm.

`g` is an `AbstractSimpleGraph{<:Integer}`, `β` is the infection rate and `Xi` is a bit vector whose true entries denote initially infected nodes. Additional arguments are
  * `nmax=1000`: the maximum number of iterations of the algorithm
  * `tmax=100.0`: the maximum time allowed
  * `sampling_method=:tree`: The algorithm for sampling which node gets infected at a given time step. Choose from `:array`, `:sparse` or `:tree`. Default is `:tree` which scales better with the number of nodes (log(N) vs. N^2).
  * `record=false`: Whether or not to record which node gets infected and by whom. If `true`, return a vector of pairs `(k,j)` describing which node `k` got infected and `j` its neighbor that infected it.
"""
function contact_process_gillespie(g::AbstractSimpleGraph{<:Integer}, Xi::BitVector, β::Real; nmax=length(Xi), tmax=100, sampling_method=:tree, record=false)
    N = nv(g)
    X = copy(Xi)
    #nreactions::Int64 = 0
    if sampling_method == :tree
        a = zeros(N)
    elseif sampling_method == :sparse
        a = spzeros(N)
    else
        a = zeros(N)
    end
    for k in 1:N
        if !X[k]
            l = length(filter(i->X[i], inneighbors(g,k)))
            a[k] = β*l# probability of k recieving infection is β time its number of infected incoming neighbors
            #nreactions += l
        end
    end
    if sampling_method == :tree # use a specialized binary tree
        a = CategoricalTree(a)
    end
    a0 = sum(a)
    #a0 = nreactions*β
    if record
        Es = Vector{Tuple{Int64,Int64}}(undef, nmax-1) # which node gets infected, and by whom
    end
    ts = zeros(nmax)
    t = 0.0
    n = 1
    while n < nmax && t < tmax && a0 > 0
        τ = log(1/rand())/a0
        k = rand_categorical(a, a0) # which node gets infected
        X[k] = true
        #nreactions -= Int(floor(a[k]/β))
        a[k] = 0.0
        for i in Iterators.filter(j->!X[j],outneighbors(g,k))
            a[i] += β
            #nreactions += 1
        end
        if sampling_method == :sparse
            SparseArrays.dropstored!(a,k) # O(1) cost?
        end
        #a0 = β*nreactions
        a0 = sum(a)
        t += τ
        ts[n+1] = t
        if record
            j = rand(filter(l->X[l],inneighbors(g,k)))
            Es[n] = (k,j)
        end
        n += 1
    end
    if record
        return ts[1:n], sum(Xi) .+ collect(0:n-1), Es[1:(n-1)]
    else
        return ts[1:n], sum(Xi) .+ collect(0:n-1)
    end
end

"""
    contact_process_ode(g, Xi, β; kwargs)

Integrate the mean field approximation for SI spreading on network `g`.

`Xi` is the initial condition, a vector whose elements denote infected nodes by 1 and susceptible by 0. `β` is the infection rate.

Keyword arguments:
  * `tmax = 100.0`: maximum time of the simulation.
"""
function contact_process_ode(g::AbstractSimpleGraph{<:Integer}, Xi, β::Real; tmax=100.0, saveat=[])
    A = adjacency_matrix(g)
    u0 = float(Xi)
    f! = function(du,u,p,t)
        du .= β*(1.0 .- u).*(A*u)
    end
    prob = ODEProblem(f!, u0, (0.0,tmax))
    sol = solve(prob, Tsit5(), saveat=saveat)
    return  sol.t, hcat(sol.u...)', sol
end

"""
    contact_process_montecarlo(g, Xi, β; kwargs)

Compute the average and variance of trajectories given by the gillespie algorithm over `nsims` simulations.

Keyword arguments:
  * `nmax=1000`: maximum number of iterations of each simulation
  * `tmax=100.0`: maximum time of the simulations
  * `nsims=100`: number of simulations to perform
  * `nbins`: the number of time steps at which the average is computed
"""
function contact_process_montecarlo(g::AbstractSimpleGraph{<:Integer}, Xi::BitVector, β::Real; nmax = length(Xi), tmax = 100.0, nsims=1000, nbins = 100)
    ts = LinRange(0.0, tmax, nbins)
    X_sum = zeros(nbins)
    M_sum = zeros(nbins) # running SSE
    p = Progress(nsims, dt=1.0)
    for n in 1:nsims
        t, X = contact_process_gillespie(g, Xi, β, nmax=nmax, tmax=tmax)
        l = 1
        for k in 1:nbins
            while l < length(t) && t[l+1] < ts[k]
                l += 1
            end
            Xn = X[l]
            Δn = Xn - X_sum[k]/(n>1 ? n-1 : 1)
            X_sum[k] += Xn
            M_sum[k] += Δn*(Xn-X_sum[k]/n)
        end
        ProgressMeter.next!(p; showvalues= [(:n,n)])
    end
    return ts, X_sum/nsims, sqrt.(M_sum/(nsims-1))
end


### Monte Carlo routines for
### the 2D Ising model

import StatsBase: countmap
import StaticArrays: SVector

"""
    randomConfiguration(L)

Returns a LxL Matrix filled randomly with ±1.
"""
function randomConfiguration(L::Int)
    return rand(Int8[-1,1], L,L)
end

"""
    frustratedConfiguration(L)

Returns a configuration where all bonds are frustrated.
"""
function frustratedConfiguration(L::Int)
    return Int8[(-1)^(i+j) for i=1:L,j=1:L]
end

"""
    H(state, J=1.,h=0.)

Energy of Ising configuration for a given coupling `J` and external field `h`.
"""
function H(state::Matrix{Int8},J=1.,h=0.)
    L = size(state,1)
    s = h*(sum(state))

    @inbounds for i in 1:L, j in 1:L
        s += -J*state[i,j]*(state[i%L+1,j]+state[i,j%L+1])
    end
    return s
end

"""
    dH(state,pos,J=1.,h=0.)

Energy difference between the current state `state`
and one spin flipped at position `(i,j)` given coupling and field strength.
"""
function dH(state::Matrix{Int8},i,j,J=1.,h=0.)
    L = size(state,1)
    @inbounds return 2*J*state[i,j]*( state[i%L+1,j]+state[i,j%L+1]+
        state[i==1 ? L : i-1,j]+state[i,j==1 ? L : j-1] ) + 2*h*state[i,j]
end

"""
    m(state)

Magnetization per spin
"""
m(state) = Float64(sum(state)/length(state))

@inline neighbors(i,j,L) = CartesianIndex.([(i%L+1,j),(i,j%L+1),(i==1 ? L : i-1,j),(i,j==1 ? L : j-1)])
@inline function neighbors(i,L)
    r = (i-1)%L
    c = div(i-1, L)
    # @info r,c
    SVector{4, Int32}(1 + (r+1)%L + c*L, 1 + ((r-1)>=0 ? r-1 : L-1) + c*L, 1 + r + L*((c+1)%L), 1 + r + L*((c-1)>=0 ? c-1 : L-1))
end
@inline function neighbors_leftup(i,L)
    r = (i-1)%L
    c = div(i-1, L)
    # @info r,c
    SVector{2, Int32}(1 + ((r-1)>=0 ? r-1 : L-1) + c*L, 1 + r + L*((c-1)>=0 ? c-1 : L-1))
end

"""
    cluster_sizes(state)

A cluster is a connected region of aligned spins. Retrieve the sizes of all of them.
"""
function cluster_sizes(state)
    # Check for each element if they belong to the same cluster
    # as their left or upper neighbor.
    cluster = zeros(Int32, size(state))
    max_cluster = 0
    pointer = 1
    new_cluster = true
    while pointer <= length(state)
        nn = neighbors_leftup(pointer, size(state, 1))
        new_cluster = true
        for n in nn
            if cluster[n]!=0 && state[pointer]==state[n]
                cluster[pointer] = cluster[n]
                new_cluster = false
            end
        end
        if new_cluster
            cluster[pointer] = max_cluster += 1
        end
        pointer += 1
    end

    return countmap(reshape(cluster, length(cluster))) |> values |> collect
end

"""
    metropolis_step!(state,beta,h)

Perform one step of the Metropolis algorithm.
`state` is mutated.
"""
function metropolis_step!(state::Matrix{Int8},beta,h)
    i = rand(1:size(state,1))
    j = rand(1:size(state,2))
    dh = dH(state,i,j,1.,h)
    if dh <= 0 || rand()<exp(-beta*dh)
        state[i,j] *= -1
    end
    return nothing
end

"""
    metropolis_sweep!(state,n,beta,h)

Perform n Metropolis steps.
"""
function metropolis_sweep!(state,n,beta,h)
    for _ in 1:n
        metropolis_step!(state,beta,h)
    end
end

"""
    init(L,beta,h,sweep)

Setup a configuration with `LxL` spins and perform an intial thermal sweep of
`sweep` timesteps.
"""
function init(L,beta,h,sweep)
    state = fill(Int8(-1), L, L)
    # Initial sweep to get into the steady state
    metropolis_sweep!(state,sweep,beta,h)
    return state
end

"""
    run_metropolis(L, beta, h;Tmax=1, sweep=0, sample_interval=1)

Sets up a random state and runs the Metropolis algorithm for a given set of parameters.
Samples in defined intervals along the Markov-Chain.

An initial thermal sweep to go to equilibrium may be specified.

Returns an array of averaged observables: [E, E^2, m, m^2, m^4] with total
energy E and magnetisation per spin m.

# Arguments
- L::Integer:            Linear system size
- beta::Float:            Inverse temperature
- h::Float:               External field
- Tmax::Integer:          Number of steps
- sweep::Integer:         Length of the initial sweep
- sample_interval::Integer: sample interval

# Example
```
julia> run_metropolis(50, 0., 0.;Tmax=50*10^3*50^2,sample_interval=10*50^2,sweep=10^3*50^2)
5-element Array{Float64,1}:
  -39.9664      # <E>
 1753.5168      # <E^2>
    0.010382079999999908 # <m>
    0.00011572633600000021 # <m^2>
    1.6924325969920386e-8 # <m^4>
```
"""
function run_metropolis(L::Int, beta,h;Tmax::Int=1,sweep::Int=0,sample_interval::Int=1)
    ## Initialise a random state
    state = init(L,beta,h,sweep)
    return _run_metropolis!(state,beta,h,Tmax=Tmax,sample_interval=sample_interval)
end

"""
    _run_metropolis!(state::Matrix{Int8},beta,h;Tmax::Int=1,sample_interval::Int=1)

Analogous to `run_metropolis` but mutates an existing state. Called by `run_metropolis`.
"""
function _run_metropolis!(state::Matrix{Int8},beta,h;Tmax::Int=1,sample_interval::Int=1)

    ## Define a matrix to record the observables.
    ## One entry for each observable, e.g E and m.
    ## Preallocating the matrix gives much better performance than
    ## constructing it on the fly.
    observables = zeros(Float64, 5)

    k = 0 #counts the number of samples

    t = 0 #simulation steps
    e = 0.
    mag = 0.

    @inbounds begin
        while(t<Tmax)
            ## Take the defined no. of steps before
            ## recording a measurement
            metropolis_sweep!(state,sample_interval,beta,h)

            ## Record observables
            e = H(state,1.,h)
            mag = m(state)
            observables[1] += e
            observables[2] += e^2
            observables[3] += mag
            observables[4] += mag^2
            observables[5] += mag^4

            ## increment counters
            k+=1
            t+=sample_interval
        end
    end
    ## Return sample means
    return observables/k
end

"""
    metropolis_timeseries(L, β, Tmax; sample_interval=L^2, sweep=1000)

Follows along a Markov chain in time.
Initialises a random configuration, sweeps it for `sweep*L^2` timsteps, and records
magnetetisation and energy every `sample_interval` until `Tmax`.

Returns a tuple of these time series.
"""
function metropolis_timeseries(L::Int, beta, Tmax; sample_interval=L^2, sweep=1000)
    state = init(L,beta,0.,sweep*L^2)
    #time series magnetization
    tsm = Vector{Float64}(undef, div(Tmax,sample_interval))
    #time series energy
    tse = Vector{Float64}(undef, div(Tmax,sample_interval))

    t=0
    k=1
    mag0 = m(state)
    while(t<Tmax)
        metropolis_sweep!(state,sample_interval,beta,0.)
        tsm[k] = m(state)
        tse[k] = H(state)
        t+=sample_interval
        k+=1
    end
    return tsm, tse
end

### Wolff algorithm ###
### --------------- ###

"""
    cluster!(cluster_state, state, i,j, p)

Builds a cluster around position `(i,j)` with acceptance rate `p`.

`cluster_state` is a boolean matrix of the same size as the system. It is set to `true` whenever
a site belongs to the cluster.
"""
function cluster!(cluster_state, state, i,j, p)
    @inbounds begin L = size(state, 1)
        s = state[i,j]
        cluster_state[i,j] = true
        for neighbor in neighbors(i,j,L)
            if state[neighbor] == s && !cluster_state[neighbor] && rand()<p
                cluster_state[neighbor] = true
                cluster!(cluster_state, state, Tuple(neighbor)..., p)
            end
        end
    end
end

"""
    wolff_step!(state, cluster_state, beta)

Build a cluster and flip it.
"""
function wolff_step!(state, cluster_state, beta, h)
    @inbounds begin
        L = size(state,1)
        i,j = rand(1:L,2)
        fill!(cluster_state, false)
        cluster!(cluster_state, state, i,j, 1.0-exp(-2.0*beta))
        state[cluster_state] *= -1
    end
    return nothing
end

function wolff_sweep!(state, cluster, n, args...)
    for _ in 1:n
        wolff_step!(state, cluster, args...)
    end
end

"""
    _run_wolff!(state,cluster,beta,h;Tmax=1,sample_interval=1)

Run the Wolff cluster algorithm on a given state. You need to provide a cluster matrix, e.g
`similar(state,Bool)`.

See also: [`_run_metropolis`]
"""
function _run_wolff!(state::Matrix{Int8},cluster,beta,h;Tmax::Int=1,sample_interval::Int=1)

    ## Define a matrix in which to record the observables.
    ## One row for each observable, e.g E and m.
    ## Preallocating the matrix gives much better performance than
    ## constructing it on the fly.
    observables = zeros(Float64,5)

    k = 0 #counts the number of samples

    # pick the middle lattice site for the spin correlation

    t=0 #number of simulation steps
    r=0 #repeat counter
    e=0.
    mag=0.
    @inbounds begin
        while(t<Tmax)
            ## Do the defined no. of steps before
            ## taking a measurement
            for _ in 1:sample_interval
                wolff_step!(state, cluster, beta, h)
            end
            ## Record observables
            e = H(state,1.,0.)
            mag = m(state)|>abs
            observables[1] += e
            observables[2] += e^2
            observables[3] += mag
            observables[4] += mag^2
            observables[5] += mag^4

            ## increment counters
            k+=1
            t+=sample_interval
        end
    end
    ## Return statistics about the observables
    return observables/k
end

"""
    run_wolff(L, beta,h;Tmax=1,sweep=0,sample_interval=1)

See also: [`run_metropolis`]
"""
function run_wolff(L::Int, beta,h;Tmax::Int=1,sweep::Int=0,sample_interval::Int=1)
    ## Initialise a random state
    function init(L,beta)
        state = randomConfiguration(L)
        cluster = zeros(Bool,L,L)
        # Initial sweep to get into the steady state
        for _ in 1:sweep
            wolff_step!(state, cluster, beta, h)
        end
        return state,cluster
    end

    state,cluster = init(L,beta)
    return _run_wolff!(state,cluster,beta,0.,Tmax=Tmax,sample_interval=sample_interval)
end


@doc raw"""
    translate_forces_moments(forces_1, moments_1, p1, p2)

Calculate equivalent moment at point 2 (p2) given the forces (forces_1)  and moments
(moments_1) at point 1 (p1).

`` r = p_1 - p_2 ``

``M_{2} = M_{1} + r \times F_{1}``
"""
function translate_forces_moments(forces_1, moments_1, p1, p2)
    r21 = p1 - p2
    moments_2 = moments_1 + r21 × forces_1
    return moments_2
end

@doc raw"""
    steiner_inertia(cg, inertia_g, mass, p2)

Calculate the inertia tensor of a rigid solid at point 2 (p2), given the inertia tensor
(inertia_g) at the center of gravity (cg) and the mass of the system.

`` r = p_2 - cg ``

``I_{2} = I_{cg} + m (r^T · r I - r · r^T) ``
"""
function steiner_inertia(cg, inertia1, mass, p2)
    r = p2 - cg
    inertia2 = inertia1 + mass * ((r' * r) * I - r*r')
    return inertia2
end

"""
    coordinated_turn_bank(ψ_dot, α, β, tas, γ[, g])

Calculate roll angle (ϕ) [rad] for a given turn rate, angle of attack, angle of sideslip,
tas and flight path angle in the absence of wind for a coordinated turn bank.

Imposes sum of forces along y body axis equal to zero.

# Arguments
- `ψ_dot`: turn rate (rad/s).
- `α`: angle of attack (rad).
- `β`: angle of sideslip (rad).
- `tas`: true air speed (m/s).
- `γ`: flight path angle (rad).
- `g`: gravity (m/s²). Optional. Default value is `gD`.
"""
function coordinated_turn_bank(ψ_dot, α, β, tas, γ, g=gD)
    G = ψ_dot * tas / g

    if abs(γ) < 1e-8
        ϕ = G * cos(β) / (cos(α) - G * sin(α) * sin(β))
        ϕ = atan(ϕ)
    else
        a = 1 - G * tan(α) * sin(β)
        b = sin(γ) / cos(β)
        c = 1 + G^2 * cos(β)^2

        sq = sqrt(c * (1 - b^2) + G^2 * sin(β)^2)

        num = (a - b^2) + b * tan(α) * sq
        den = a ^ 2 - b^2 * (1 + c * tan(α)^2)

        ϕ = atan(G * cos(β) / cos(α) * num / den)
    end
    return ϕ
end


# @testset "Savitzky-Golay definition" begin
#     sg=SavitzkyGolay_Filter(rand(11))
#     @test offset(sg) == 5
#     @test range(sg) == -5:5
#     @test length(sg) == 11
# end

@testset "Savitzky-Golay" begin

    m=SG_Filter(Float64,halfWidth=2,degree=0)

    @test filter(m,derivativeOrder=0) ≈ [1/5; 1/5; 1/5; 1/5; 1/5]

    m=SG_Filter(Float64,halfWidth=3,degree=2)

    @test filter(m,derivativeOrder=1) ≈ [-(3/28); -(1/14); -(1/28); 0;  (1/28); (1/14); (3/28)]
    @test filter(m,derivativeOrder=2) ≈ [5/42; 0; -(1/14); -(2/21); -(1/14); 0; 5/42]

    @test maxDerivativeOrder(m) == 2
    @test length(m) == 2*3+1
    @test polynomialOrder(m) == 2
end


module LPC

#
# TODO:
# [v] lpc-burg
# [ ] lattice-filter synth
# [ ] warped-lpc
# [ ] lpc-acorr
# [ ] lpc-covar
# [ ] Line-Spectral-Pairs
# [v] power-spectrum est
#

export lpc_burg, pow_spect
export log_area_ratio, inv_sine_coeffs

function lpc_burg{T <: Number}( x::AbstractVector{T}, p::Int)
# LPC (Linear-Predictive-Code) estimation, using the Burg-method
# @param: x - signal to predict
# @param: p - prediction order
# @retval: a - direct form coeffs
# @retval: r - reflection coefficients (usable for lattice-filter?)
# such that:
# minimizing x_prediction(n) = a*x
# ie: an IIR:
#  y = filt(1, a, white)
# should have similiar spectrum to x
# where 'white' is a signal with white spectrum
#
# The Burg-Method, which, provide both a minimal-phase (stable IIR)
# and estimates 'a', on a finite support [1]
# Combining the advantages on both traditional LPC methods
# (autocorrelation - minPhase, and covariance - finite-support)
#
# Implementing the loop from the article:
# [1] - ENHANCED PARTIAL TRACKING USING LINEAR PREDICTION
# (DAFX'03 article, Lagrange et al)
# http://www.elec.qmul.ac.uk/dafx03/proceedings/pdfs/dafx19.pdf
#
# Note: After implementing - I found matlab has an 'arburg()' - in
# the 'signal-processing-toolbox', with virtually identical code
# but hey, why use a library when you can easily reimplement it?
# anyhow, normalized interface.
#
# (c) Kobic, 2009, 2015, MIT License
# Translated my old(er) matlab code into julia::

# TODO: check memory allocations, gc etc!

    ef = x    # forward error
    eb = x    # backwards error
    a = [1; zeros(T, p)]  # prediction coeffs
    refl = zeros(T, p) # reflection coeffs
    k = zero(T)

# zero-mean - or not?
    prediction_err = dot(x,x) ./ length(x)  # variance

    for m in 1:p
        efp = ef[1:end-1]
        ebp = eb[2:end]
        k = -2 .* dot(ebp,efp) ./ ( dot(ebp,ebp) .+ dot(efp,efp) )
        refl[m] = k           # save reflection coeffs (if needed)
        ef = efp + k.*ebp
        eb = ebp + k.*efp
        a[1:m+1] = [a[1:m]; 0] + k.*[0; a[m:-1:1]]
        prediction_err *= (1 - k*k)
    end

    return a, prediction_err, refl
end

function pow_spect(ar, err, dft_order = 256)
    f = rfft( [ar; zeros(dft_order - length(ar))] )
    psd = err ./ (abs(f).^2 + eps() )

    return psd
end

function log_area_ratio(k)
    return log( (1 + k) ./ (1 - k) )
end

function inv_sine_coeffs(k)
    return 2asin(k) ./ pi
end

end # module


include("getdigit_n.jl")
include("myprintf.jl")
using Printf
using .GetDigit_n
using .MyPrintf

const DIGIT = 5
const MAXN = 500000

function wallis(n)
    res = BigFloat(1)
    for i = 1:div(n, 2)
        res *= BigFloat(4) * BigFloat(i * i) / (BigFloat(4) * BigFloat(i * i) - BigFloat(1))
    end

    return BigFloat(2) * res
end

function main()
    n = GetDigit_n.getn_even(wallis, MAXN, DIGIT, true)
    precision = GetDigit_n.getdigit(wallis, n, DIGIT, true)
    precision = GetDigit_n.getdigit_2(wallis, n, DIGIT, precision)

    setprecision(precision)
    MyPrintf.myprintf("π（計算値） = ", DIGIT, wallis(n))
    MyPrintf.myprintf("π（厳密値） = ", DIGIT, BigFloat(π))
    @printf("n = %d, 計算%d桁で、%d桁求まりました\n", n, precision, DIGIT)
end

main()


## this generates data from the simple supply-demand model
#  q = α1 + α2*p + α3*m + e1
#  q = β1 + β2*p +        e2
## setup
using Econometrics, Plots, DelimitedFiles
# number of obsn
n = 500
# model parameters
α1 = 100.0
α2 = -1.0
α3 = 1.0
β1 = 20.0
β2 = 1.0

## make variables and shocks
# exog var income
m = randn(n,5)
m = sum(m.*m,dims=2) # chi square df=5
# structural shocks
ϵ1 = 1.0*randn(n)
ϵ2 = 1.0*randn(n)
# rf shocks
ν1 = (β2*ϵ1-α2*ϵ2)/(β2-α2)
ν2 = (ϵ1-ϵ2)/(β2-α2)
# rf coefs
π11 = (β2*α1-α2*β1)/(β2-α2)
π21 = β2*α3/(β2-α2)
π12 = (α1-β1)/(β2-α2)
π22 = α3/(β2-α2)

##  generate endog variables computed from rf
q = π11 .+ π21*m + ν1
p = π12 .+ π22*m + ν2
## write and plot data
data = [q p m]
data = sortbyc(data,3)
nn = Int(n/4)
p = scatter(data[1:nn,2], data[1:nn,1], markersize=8, xlabel="price", ylabel="quantity", legend=false)
for i = 2:4
    scatter!(data[(i-1)*nn+1:i*nn,2], data[(i-1)*nn:i*nn,1], markersize=8, show=true)
end
display(p)
#writedlm("data.txt", data)


export metric_lorenz, distancecircle!, distancecircle

function metric_lorenz(Nx::Int64)
    #Pairwise distance matrix
    dist = zeros(Int64, Nx, Nx)
    for i=2:Nx
        for j=1:i-1
            dist[i,j] = distancecircle!(i, j, Nx)
        end
    end
    dist  += dist'
end

function distancecircle!(i, j, Nx)
d1 = abs(i-j)

if i<j
    d2 = i + Nx - j
elseif i>j
    d2 = j + Nx -i
else
    d2 = 0
end
return minimum([d1, d2])
end

distancecircle(Nx) = (i,j)-> distancecircle!(i, j, Nx)


# transmcp.jl
# translated by Changhyun Kwon from transmcp.gms
# http://www.gams.com/modlib/libhtml/transmcp.htm
#
# Transportation model as equilibrium problem (TRANSMCP,SEQ=126)
#
#    Dantzig's original transportation model (TRNSPORT) is
#    reformulated as a linear complementarity problem.  We first
#    solve the model with fixed demand and supply quantities, and
#    then we incorporate price-responsiveness on both sides of the
#    market.
#
#
# Dantzig, G B, Chapter 3.3. In Linear Programming and Extensions.
# Princeton University Press, Princeton, New Jersey, 1963.

using Complementarity
using Test

@testset "transmcp_numeric.jl 1" begin

    # plants = ["seattle", "san-diego"]
    plants = 1:2
    # markets = ["new-york", "chicago", "topeka"]
    markets = 1:3

    capacity = [350, 600]
    a = Dict(zip(plants, capacity))

    demand = [325, 300, 275]
    b = Dict(zip(markets, demand))

    elasticity = [1.5, 1.2, 2.0]
    esub = Dict(zip(markets, elasticity))

    distance = [ 2.5 1.7 1.8 ;
                 2.5 1.8 1.4  ]
    d = Dict()
    for i in 1:length(plants), j in 1:length(markets)
        d[plants[i], markets[j]] = distance[i,j]
    end

    f = 90

    m = MCPModel()
    @variable(m, w[i in plants] >= 0)
    @variable(m, p[j in markets] >= 0)
    @variable(m, x[i in plants, j in markets] >= 0)

    @NLexpression(m, c[i in plants, j in markets], f * d[i,j] / 1000)
    @mapping(m, profit[i in plants, j in markets],    w[i] + c[i,j] - p[j])
    @mapping(m, supply[i in plants],                  a[i] - sum(x[i,j] for j in markets))
    @mapping(m, fxdemand[j in markets],               sum(x[i,j] for i in plants) - b[j])

    @complementarity(m, profit, x)
    @complementarity(m, supply, w)
    @complementarity(m, fxdemand, p)

    status = solveMCP(m; convergence_tolerance=1e-8, output="yes", time_limit=3600)

    @show result_value.(x)
    @show result_value.(w)
    @show result_value.(p)

    @show status
    @test status == :Solved
    @test isapprox(result_value(x[1,2]), 300.0)
    @test isapprox(result_value(p[3]),  0.126)

end

@testset "transmcp_numeric.jl 2" begin

    # plants = ["seattle", "san-diego"]
    plants = 1:2
    # markets = ["new-york", "chicago", "topeka"]
    markets = 1:3

    capacity = [350, 600]
    a = Dict(zip(plants, capacity))

    demand = [325, 300, 275]
    b = Dict(zip(markets, demand))

    elasticity = [1.5, 1.2, 2.0]
    esub = Dict(zip(markets, elasticity))

    distance = [ 2.5 1.7 1.8 ;
                 2.5 1.8 1.4  ]
    d = Dict()
    for i in 1:length(plants), j in 1:length(markets)
        d[plants[i], markets[j]] = distance[i,j]
    end

    f = 90

    m = MCPModel()
    @variable(m, w[1:2] >= 0)
    @variable(m, p[1:3] >= 0)
    @variable(m, x[1:2, 1:3] >= 0)

    @NLexpression(m, c[i in 1:2, j in 1:3], f * d[i,j] / 1000)
    @mapping(m, profit[i in 1:2, j in 1:3], w[i] + c[i,j] - p[j])
    @mapping(m, supply[i in 1:2], a[i] - sum(x[i,j] for j in markets))
    @mapping(m, fxdemand[j in 1:3], sum(x[i,j] for i in plants) - b[j])

    @complementarity(m, profit, x)
    @complementarity(m, supply, w)
    @complementarity(m, fxdemand, p)

    status = solveMCP(m; convergence_tolerance=1e-8, output="yes", time_limit=3600)

    @show result_value.(x)
    @show result_value.(w)
    @show result_value.(p)

    @show status
    @test status == :Solved
    @test isapprox(result_value(x[1,2]), 300.0)
    @test isapprox(result_value(p[3]),  0.126)

end


#!/usr/bin/env julia

export pe25

function pe25(n::Integer=1000)
    fib(n::Integer) = ([1 1;1 0]^n)[1,2]
    i=big(1)
    while ndigits(fib(i))<n
        i+=1
    end
    i
end

if !haskey(ENV,"PROJECT_EULER_WITHOUT_RESULT")
    pe25()|>print
end


normalizecolumns(m::AbstractArray{T, 2}) where {T<:Number} = m ./ sqrt.(sum(m .^ 2, dims = 1) .+ eps(T))
function normalizecolumns!(m::AbstractArray{T, 2}) where {T<:Number}
	m .= normalizecolumns(m)
end

"""
		hsplitsoftp(x, ϵ = 1f-5)

		Splits x horizontally into two equal parts and use softplus to the lower part.
		ϵ is added to the soft-plus to prevent underflow and improve the numerical stability

"""
hsplitsoftp(x,ϵ = 1f-5) = x[1:size(x, 1) ÷ 2, :], softplus.(x[size(x, 1) ÷ 2 + 1 : 2 * (size(x, 1) ÷ 2), :] .+ ϵ)
hsplit1softp(x,ϵ = 1f-5) = x[1:size(x, 1) - 1, :], softplus.(x[end, :] .+ ϵ)

"""
		kldiv(μ,σ2)

		kl-divergence of a Gaussian min mean `μ` and diagonal variance `σ^2`
		to N(0,I)
"""
kldiv(μ,σ2) = - mean(sum((@.log(σ2) - μ^2 - σ2), dims = 1))

log_normal(x) = - sum((x.^2), dims = 1) ./ 2 .- size(x, 1) * log(2f0 * π) / 2
log_normal(x, μ) = log_normal(x - μ)
log_normal(x, μ, σ2::Vector{T}) where {T<:Number} = - sum((x - μ) .^ 2 ./ σ2' .+ log.(σ2' .* 2π), dims = 1) / 2
log_normal(x, μ, Σ::Matrix{T}) where {T<:Number} = - (log(det(Σ)) + (x - μ)' * inv(Σ) * (x - μ) + size(Σ, 1) * log(2π)) / 2

"""
	vmfentropy(m, κ)

	Entropy of Von Mises-Fisher distribution
"""
vmfentropy(m, κ) = .-κ .* besselix(m / 2, κ) ./ besselix(m / 2 - 1, κ) .- ((m ./ 2 .- 1) .* log.(κ) .- (m ./ 2) .* log(2π) .- (κ .+ log.(besselix(m / 2 - 1, κ))))

"""
	huentropy(m)

	Entropy of Hyperspherical Uniform distribution
"""
huentropy(m) = m / 2 * log(π) + log(2) - lgamma(m / 2)

# Likelihood estimation of a sample x under VMF with given parameters taken from https://pdfs.semanticscholar.org/2b5b/724fb175f592c1ff919cc61499adb26996b1.pdf
# normalizing constant for density function of VMF
c(p, κ) = κ ^ (p / 2 - 1) / ((2π) ^ (p / 2) * besseli(p / 2 - 1, κ))

# log likelihood of one sample under the VMF dist with given parameters
log_vmf_c(x, μ, κ) = κ * μ' * x .+ log(c(length(μ), κ))
log_vmf_c(x::AbstractMatrix, μ::AbstractMatrix, κ::T) where {T <: Number} = [log_vmf_c(x[:, i], μ[:, i], κ) for i in size(x, 2)]
log_vmf_c(x::AbstractMatrix, μ::AbstractMatrix, κ::AbstractVector) = [log_vmf_c(x[:, i], μ[:, i], κ[i]) for i in size(x, 2)]
log_vmf_wo_c(x, μ, κ) = κ * μ' * x

pairwisecos(x, y) = max.(1 .- (x' * y), 0) # This is a bit of a hack to avoid the distance being negative due to float
pairwisecos(x) = pairwisecos(x, x)

k_imq(x, y, c) = sum( c./ (c .+ pairwisecos(x, y))) / (size(x, 2) * size(y, 2))
k_imq(x::T, c) where {T <: AbstractMatrix} = sum(c ./ (c .+ pairwisecos(x))) / (size(x, 2) * (size(x, 2) - 1))
k_imq(x::T, c) where {T <: AbstractVector} = zero(eltype(x))

mmd_imq(x,y,c) = k_imq(x,c) + k_imq(y,c) - 2 * k_imq(x,y,c)

function gaussiansample(μ, σ2)
	ϵ = randn!(similar(μ,size(μ)))
	μ .+ sqrt.(σ2) .* ϵ
end

function samplehsuniform(size...)
	v = randn(size...)
	v = normalizecolumns(v)
end


using Distributions
using JuliaWebAPI
using Logging
Logging.configure(level=DEBUG)

f(u) = exp(-u^2/2)/√(2pi)

const u1=Uniform(-5.0,+5.0)
const u2=Uniform(0.0, 0.5)

function simulate_pt()
    x=rand(u1)
    y=rand(u2)
    y<f(x)
end

function simulate(num::Int)
     hits=0
     for (i in 1:num)
        hits = hits + simulate_pt()
    end
    return hits/num*(0.5*10)
end

function simulateTime(num)
    tic()
    r = simulate(num)
    t = toc()
    return (r, t)
end

simulate(num::String) = simulate(parseint(num))

#process([(simulate,false), (simulateTime,false)], "tcp://127.0.0.1:9999"; bind=true)


"""
    hilbert_transform(signal::Vector{Float64})

Compute the Hilbert transform using the DFT approximation.
"""
function hilbert_transform(signal::Vector{Float64})
    fourier = AbstractFFTs.fft(signal)
    n = length(fourier)
    h = zeros(n)

    for i in eachindex(h)
        if i == 1 || i == (n/2)+1
            h[i] = 1
        elseif 2 <= i <= (n/2)
            h[i] = 2
        else
            h[i] == 0
        end
    end

    dtft_hilbert = fourier .* h
    return AbstractFFTs.ifft(dtft_hilbert)
end

"""
    compute_instantaneous(signal::Vector{Float64}, imfs::Array{Float64, 2})

Return the instantaneous energy and instantaneous frequencies of the IMFS in the signal.
"""
function compute_instantaneous(signal::Vector{Float64}, imfs::Matrix{Float64})
    n = length(signal)
    analytic_signal = zeros(n)
    theta = zeros(n)
    imf_inst_energy = zeros(n, size(imfs, 1))
    imf_inst_freq = zeros(n, size(imfs, 1))
    r = zeros(n)
    time = collect(1:n)

    for j=1:size(imfs, 1)
        analytic_signal = signal + im*hilbert_transform(imfs[:, j])

        for i in eachindex(analytic_signal)
            theta[i] = angle(analytic_signal[i])
            imf_inst_energy[i, j] = abs2(analytic_signal[i])
        end

        spl = Spline1D(time, theta)

        g = x -> ForwardDiff.gradient(spl, time)

        for i in eachindex(analytic_signal)
            imf_inst_freq[i, j] = g(time[i])
        end
    end

    return imf_inst_energy, imf_inst_freq
end

"""
    hht(signal::Vector{Float64}, s::EMDSetting)

Return the instantaneous energies and frequencies of the IMFS computed via the
Hilbert-Huang transform with EMD settings specified.
"""
function hht(signal::Vector{Float64}, s::EMDSetting)
    imfs = emd(signal, s)
    return compute_instantaneous(input, imfs)
end

"""
    hht(signal::Vector{Float64}, s::EEMDSetting)

Return the instantaneous energies and frequencies of the IMFS computed via the
Hilbert-Huang transform with EEMD settings specified.
"""
function hht(signal::Vector{Float64}, s::EEMDSetting)
    imfs = eemd(signal, s)
    return compute_instantaneous(input, imfs)
end


function interp2D( x1Vec0, x2Vec0, yMat, x1Val, x2Val )
#UNTITLED Summary of this function goes here
#   Detailed explanation goes here

x1Vec=vec(x1Vec0)
x2Vec=vec(x2Vec0)

#Check that both x1Vec and x2Vec are n_i * 1 vectors
sizex1Vec = size(x1Vec);
sizex2Vec = size(x2Vec);
sizeyMat = size(yMat);

#println("Enter $sizex1Vec and $sizex2Vec and $sizeyMat")

# if (sizex1Vec[1]<=1) || (sizex2Vec[1]<=1) || (sizex1Vec[2]!=1) || (sizex2Vec[2]!=1)
#     error("Both x1Vec and x2Vec need to be n_i by 1 vectors. The length of the vectors may differ")
# end

#Check whether x1Vec and x2Vec are monotonic
# isx1VecMonotone = all(diff[x1Vec]>=0);
# isx2VecMonotone = all(diff[x2Vec]>=0);

# if (isx1VecMonotone*isx2VecMonotone == 0)
#     error("One of x1Vec or x2Vec is non-monotonic");
# end

n1 = sizex1Vec[1];

function nonNeg(args)
    return args>=0
end

#Find where x1val fits into x1Vec and pick out the vectors in yMat
#conditional on these values
if (x1Val<=x1Vec[1])
    x1LowerIndex = 1;
    x1UpperIndex = 2;
elseif (x1Val>=x1Vec[n1])
    x1UpperIndex = n1;
    x1LowerIndex = n1 - 1;
else #x1Val is between the lower and the upper components of x1Vec
    #Get a vect
    x1VecLessX1Val = x1Vec - x1Val;

    # Get the smallest non-negative
    posI=find(nonNeg, x1VecLessX1Val)
    (a,b)=findmin(x1VecLessX1Val[posI],1)
    x1UpperIndex=posI[b];
    x1LowerIndex = x1UpperIndex - 1.;
end

newYMatAfterInterpOnX2 = Array(Float64,2, 1);

yMatCondOnx1Lower = yMat[x1LowerIndex, :];
yMatCondOnx1Upper = yMat[x1UpperIndex, :];

intfL  = InterpIrregular(vec(x2Vec),vec(yMatCondOnx1Lower), BCnil, InterpLinear);
intfH  = InterpIrregular(vec(x2Vec),vec(yMatCondOnx1Upper), BCnil, InterpLinear);

# The last bit of interpolation!

newYMatAfterInterpOnX2[1]  =interp1extra(vec(x2Vec),vec(yMatCondOnx1Lower),x2Val,intfL);
newYMatAfterInterpOnX2[2]  =interp1extra(vec(x2Vec),vec(yMatCondOnx1Upper),x2Val,intfH);

newX1VecShort=[x1Vec[x1LowerIndex] x1Vec[x1UpperIndex] ]'

intfF  = InterpIrregular(vec(newX1VecShort),vec(newYMatAfterInterpOnX2), BCnil, InterpLinear);
yVal   = interp1extra(vec(newX1VecShort),vec(newYMatAfterInterpOnX2),x1Val,intfF); #  intfF[x1Val];

return yVal

end


# repeat can be useful to expand a grid
# as in R's expand.grid() function:

# <hide>
function printsum(a)
    println(summary(a), ": ", repr(a))
end
# </hide>

m1 = hcat(repeat([1,2],inner=[1],outer=[3*2]),
		  repeat([1,2,3],inner=[2],outer=[2]),
		  repeat([1,2,3,4],inner=[3],outer=[1]))
printsum(m1)
#> 12×3 Array{Int64,2}: [1 1 1; 2 1 1; 1 2 1; 2 2 2; 1 3 2; 2 3 2; 1 1 3; 2 1 3;
#> 	1 2 3; 2 2 4; 1 3 4; 2 3 4]

# for simple repetitions of arrays,
# use repeat
m2 = repeat(m1,1,2) 	# replicate a9 once into dim1 and twice into dim2
println("size: ", size(m2))
#> size: (12, 6)

m3 = repeat(m1,2,1) 	# replicate a9 twice into dim1 and once into dim2
println("size: ", size(m3))
#> size: (24, 3)

# Julia comprehensions are another way to easily create
# multidimensional arrays

m4 = [i+j+k for i=1:2, j=1:3, k=1:2]	# creates a 2x3x2 array of Int64
m5 = ["Hi Im # $(i+2*(j-1 + 3*(k-1)))" for i=1:2, j=1:3, k=1:2]
# expressions are very flexible
# you can specify the type of the array by just
# placing it in front of the expression

import LegacyStrings
m5 = LegacyStrings.ASCIIString["Hi Im element # $(i+2*(j-1 + 3*(k-1)))" for i=1:2, j=1:3, k=1:2]
printsum(m5)
#> 2×3×2 Array{LegacyStrings.ASCIIString,3}: LegacyStrings.ASCIIString[
#> 	"Hi Im element # 1" "Hi Im element # 3" "Hi Im element # 5";
#> 	"Hi Im element # 2" "Hi Im element # 4" "Hi Im element # 6"]
#>
#> LegacyStrings.ASCIIString["Hi Im element # 7" "Hi Im element # 9"
#> 	"Hi Im element # 11"; "Hi Im element # 8" "Hi Im element # 10" "Hi Im element # 12"]

# Array reductions
# many functions in Julia have an array method
# to be applied to specific dimensions of an array:

sum(m4, dims=3)		# takes the sum over the third dimension
sum(m4, dims=(1,3))	# sum over first and third dim

maximum(m4, dims=2)	# find the max elt along dim 2
findmax(m4, dims=3)	# find the max elt and its index along dim 3
					# (available only in very recent Julia versions)

# Broadcasting
# when you combine arrays of different sizes in an operation,
# an attempt is made to "spread" or "broadcast" the smaller array
# so that the sizes match up. broadcast operators are preceded by a dot:

m4 .+ 3		# add 3 to all elements
m4 .+ [1,2]		# adds vector [1,2] to all elements along first dim

# slices and views
m4=m4[:,:,1]	# holds dim 3 fixed
m4[:,2,:]	# that's a 2x1x2 array. not very intuititive to look at

# get rid of dimensions with size 1:
dropdims(m4[:,2,:], dims=2)	# that's better

# assign new values to a certain view
m4[:,:,1] = rand(1:6,2,3)
printsum(m4)
#> 2×3 Array{Int64,2}: [3 5 3; 1 3 5]

# (for more examples of try, catch see Error Handling above)
try
	# this will cause an error, you have to assign the correct type
	m4[:,:,1] = rand(2,3)
catch err
	println(err)
end
#> InexactError(:Int64, Int64, 0.7603891754678744)

try
	# this will cause an error, you have to assign the right shape
	m4[:,:,1] = rand(1:6,3,2)
catch err
	println(err)
end
#> DimensionMismatch("tried to assign 3×2 array to 2×3×1 destination")


module Perceptron
export fit
using Random
function fit(x,y,rate)
    a,b=size(x)
    arrOne=Array{Float64,2}(undef,a,1)
    fill!(arrOne,1)
    NewarrX=hcat(x,arrOne)
    w=Array{Float64,2}(undef,1,b+1)
    while true
        i=rand(1:a)
        if y[i]*(w*NewarrX[i,:])<=[0]
            w=w+(rate.*y[i].*NewarrX[i,:])'
            continue
        end
        pre=sgn(NewarrX,w)
        # println(w)
        # println(isequal(pre,y))
        if isequalm(pre,y)
            break
        end

    end
    return w
end
function isequalm(x,y)
    for i =1:length(x)
        if x[i]!=y[i]
            return false

        end
    end
    return true
end
function sgn(x,w)
    a,b=size(x)
    p=Array{Float64,2}(undef,a,1)
    for i =1:a
        c=w*x[i,:]
        # print(c)
        if c[1]>0
            c[1]=1
        else c[1]<0
            c[1]=-1
        end
        p[i]=c[1]
    end
    # print(typeof(p))
    return p
end

# k=[3 3;4 3;1 1]
# kkk=[1.0;1.0;-1.0]
# r=1
# fit(k,kkk,r)
end  # module Perceptron


immutable LogNormal <: ContinuousUnivariateDistribution
    meanlog::Float64
    sdlog::Float64
    function LogNormal(ml::Real, sdl::Real)
    	sdl > zero(sdl) || error("sdlog must be positive")
    	new(float64(ml), float64(sdl))
    end
end

LogNormal(ml::Real) = LogNormal(ml, 1.0)
LogNormal() = LogNormal(0.0, 1.0)

@_jl_dist_2p LogNormal lnorm

entropy(d::LogNormal) = 0.5 + 0.5 * log(2.0 * pi * d.sdlog^2) + d.meanlog

insupport(::LogNormal, x::Real) = zero(x) < x < Inf
insupport(::Type{LogNormal}, x::Real) = zero(x) < x < Inf

function kurtosis(d::LogNormal)
   exp(4.0 * d.sdlog^2) + 2.0 * exp(3.0 * d.sdlog^2) +
        3.0 * exp(2.0 * d.sdlog^2) - 6.0
end

mean(d::LogNormal) = exp(d.meanlog + d.sdlog^2 / 2)

median(d::LogNormal) = exp(d.meanlog)

# mgf(d::LogNormal)
# cf(d::LogNormal)

mode(d::LogNormal) = exp(d.meanlog - d.sdlog^2)
modes(d::LogNormal) = [mode(d)]

function skewness(d::LogNormal)
    (exp(d.sdlog^2) + 2.0) * sqrt(exp(d.sdlog^2) - 1.0)
end

function var(d::LogNormal)
    sigsq = d.sdlog^2
    (exp(sigsq) - 1) * exp(2d.meanlog + sigsq)
end

function fit_mle{T <: Real}(::Type{LogNormal}, x::Array{T})
    lx = log(x)
    LogNormal(mean(lx), std(lx))
end


using ReachabilityAnalysis, SparseArrays

LazySets.set_ztol(Float64, 1e-14)

function platoon_connected(; deterministic_switching::Bool=true,
                             c1=5.0)  # clock constraints
    n = 10 # 9 dimensions + time
    # x' = Ax + Bu + c
    A = Matrix{Float64}(undef, n, n)
    A[1, :] = [0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0]
    A[2, :] = [0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0]
    A[3, :] = [1.6050, 4.8680, -3.5754, -0.8198, 0.4270, -0.0450, -0.1942,  0.3626, -0.0946, 0.]
    A[4, :] = [0, 0, 0, 0, 1.0, 0, 0, 0, 0, 0,]
    A[5, :] = [0, 0, 1.0, 0, 0, -1.0, 0, 0, 0, 0]
    A[6, :] = [0.8718, 3.8140, -0.0754,  1.1936, 3.6258, -3.2396, -0.5950,  0.1294, -0.0796, 0.]
    A[7, :] = [0, 0, 0, 0, 0, 0, 0, 1.0, 0, 0]
    A[8, :] = [0, 0, 0, 0, 0, 1.0, 0, 0, -1.0, 0]
    A[9, :] = [0.7132, 3.5730, -0.0964,  0.8472, 3.2568, -0.0876,  1.2726,  3.0720, -3.1356, 0.]
    A[10, :] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0]; # t' = 1

    if deterministic_switching
        invariant = HalfSpace(sparsevec([n], [1.], n), c1) # t <= c1
    else
        invariant = Universe(n)
    end

    # acceleration of the lead vehicle + time
    B = sparse([2], [1], [1.0], n, 1)
    U = Hyperrectangle(low=[-9.], high=[1.])
    c = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0]
    @system(x' = Ax + Bu + c, x ∈ invariant, u ∈ U)
end

function platoon_disconnected(; deterministic_switching::Bool=true,
                                c2=5.0)  # clock constraints
    n = 10 # 9 dimensions + time
    # x' = Ax + Bu + c
    A = Matrix{Float64}(undef, n, n)
    A[1, :] = [0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0]
    A[2, :] = [0, 0, -1.0, 0, 0, 0, 0, 0, 0, 0]
    A[3, :] = [1.6050, 4.8680, -3.5754, 0, 0, 0, 0, 0, 0, 0]
    A[4, :] = [0, 0, 0, 0, 1.0, 0, 0, 0, 0, 0,]
    A[5, :] = [0, 0, 1.0, 0, 0, -1.0, 0, 0, 0, 0]
    A[6, :] = [0, 0, 0,  1.1936, 3.6258, -3.2396, 0, 0, 0, 0.]
    A[7, :] = [0, 0, 0, 0, 0, 0, 0, 1.0, 0, 0]
    A[8, :] = [0, 0, 0, 0, 0, 1.0, 0, 0, -1.0, 0]
    A[9, :] = [0.7132, 3.5730, -0.0964,  0.8472, 3.2568, -0.0876,  1.2726,  3.0720, -3.1356, 0.]
    A[10, :] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0]; # t' = 1

    if deterministic_switching
        invariant = HalfSpace(sparsevec([n], [1.], n), c2) # t <= c2
    else
        invariant = Universe(n)
    end

    # acceleration of the lead vehicle + time
    B = sparse([2], [1], [1.0], n, 1)
    U = Hyperrectangle(low=[-9.], high=[1.])
    c = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0]
    @system(x' = Ax + Bu + c, x ∈ invariant, u ∈ U)
end

function platoon(; deterministic_switching::Bool=true,
                   c1=5.0,  # clock constraints
                   c2=5.0,  # clock constraints
                   tb=10.0,  # lower bound for loss of communication
                   tc=20.0, tr=20.0) # upper bound for loss of communication (tc) and reset time (tr)

    # three variables for each vehicle, (ei, d(et)/dt, ai) for
    # (spacing error, relative velocity, speed), and the last dimension is time
    n = 9 + 1

    # transition graph
    automaton = LightAutomaton(2)
    add_transition!(automaton, 1, 2, 1)
    add_transition!(automaton, 2, 1, 2)

    # modes
    mode1 = platoon_connected(deterministic_switching=deterministic_switching, c1=c1)
    mode2 = platoon_disconnected(deterministic_switching=deterministic_switching, c2=c2)
    modes = [mode1, mode2]

    # common reset
    reset = Dict(n => 0.)

    # transition l1 -> l2
    if deterministic_switching
        guard = Hyperplane(sparsevec([n], [1.], n), c1) # t == c1
    else
        # tb <= t <= tc
        guard = HPolyhedron([HalfSpace(sparsevec([n], [-1.], n), -tb),
                             HalfSpace(sparsevec([n], [1.], n), tc)])
    end
    t1 = ConstrainedResetMap(n, guard, reset)

    # transition l2 -> l1
    if deterministic_switching
        guard = Hyperplane(sparsevec([n], [1.], n), c2) # t == c2
    else
        guard = HalfSpace(sparsevec([n], [1.], n), tr) # t <= tr
    end
    t2 = ConstrainedResetMap(n, guard, reset)
    resetmaps = [t1, t2]

    H = HybridSystem(automaton, modes, resetmaps, [AutonomousSwitching()])

    # initial condition is at the orgin in mode 1
    X0 = BallInf(zeros(n), 0.0)
    initial_condition = [(1, X0)]

    return IVP(H, initial_condition)
end

function dmin_specification(sol, dmin)
    return (-ρ(sparsevec([1], [-1.0], 10), sol) > dmin) &&
           (-ρ(sparsevec([4], [-1.0], 10), sol) > dmin) &&
           (-ρ(sparsevec([7], [-1.0], 10), sol) > dmin)
end


#######################################################################################################################################################################################################
#
# Changes to this function
# General
#     2021-Aug-04: add this function to acoid NaN calculations
#
#######################################################################################################################################################################################################
"""
    nanmean(x::Array)

Return the mean of array by ommiting the NaN, given
- `x` Array of numbers, can be NaN

---
Example
```julia
xs = [1, 2, 4, NaN];
nmean = nanmean(xs);
```
"""
function nanmean(x::Array)
    _x = filter(!isnan, x);

    if length(_x) == 0 return NaN end;

    return mean( _x )
end


immutable Beta <: ContinuousUnivariateDistribution
    alpha::Float64
    beta::Float64
    function Beta(a::Real, b::Real)
        (a > zero(a) && b > zero(b)) || error("alpha and beta must be positive")
        new(float64(a), float64(b))
    end
end

Beta(a::Real) = Beta(a, a) # symmetric in [0, 1]
Beta() = Beta(1.0) # uniform

@_jl_dist_2p Beta beta

function entropy(d::Beta)
    o = lbeta(d.alpha, d.beta)
    o -= (d.alpha - 1.0) * digamma(d.alpha)
    o -= (d.beta - 1.0) * digamma(d.beta)
    o += (d.alpha + d.beta - 2.0) * digamma(d.alpha + d.beta)
    o
end

function kurtosis(d::Beta)
    α, β = d.alpha, d.beta
    num = 6.0 * ((α - β)^2 * (α + β + 1.0) - α * β * (α + β + 2.0))
    den = α * β * (α + β + 2.0) * (α + β + 3.0)
    num / den
end

mean(d::Beta) = d.alpha / (d.alpha + d.beta)

median(d::Beta) = quantile(d, 0.5)

function mode(d::Beta)
    α, β = d.alpha, d.beta
    if α >= 1.0
        if β > 1.0
            (α - 1.0) / (α + β - 2.0)
        elseif α == 1.0 && β == 1.0
            # Uniform[0,1]: what should be returned?
            0.5
        else
            1.0
        end
    else
        if β >= 1.0
            0.0
        else
            # not unique: return largest
            α > β ? 1.0 : 0.0
        end
    end
end

function modes(d::Beta)
    α, β = d.alpha, d.beta
    if α >= 1.0
        if β > 1.0
            [(α - 1.0) / (α + β - 2.0)]
        elseif α == 1.0 && β == 1.0
            # Uniform[0,1]: what should be returned?
            Float64[]
        else
            [1.0]
        end
    else
        if β >= 1.0
            [0.0]
        else
            [0.0,1.0]
        end
    end
end

function skewness(d::Beta)
    num = 2.0 * (d.beta - d.alpha) * sqrt(d.alpha + d.beta + 1.0)
    den = (d.alpha + d.beta + 2.0) * sqrt(d.alpha * d.beta)
    num / den
end

function var(d::Beta)
    ab = d.alpha + d.beta
    d.alpha * d.beta / (ab * ab * (ab + 1.0))
end

function gradlogpdf(d::Beta, x::Real)
  insupport(Beta, x) ? (d.alpha - 1.0) / x - (d.beta - 1.0) / (1 - x) : 0.0
end

### handling support

@continuous_distr_support Beta 0.0 1.0

## Fit model

# TODO: add MLE method (should be similar to Dirichlet)

# This is a moment-matching method (not MLE)
#
function fit(::Type{Beta}, x::Array)
    for xi in x
        insupport(Beta, xi) || error("Beta observations must be in [0,1]")
    end
    x_bar = mean(x)
    v_bar = varm(x, x_bar)
    α = x_bar * (((x_bar * (1.0 - x_bar)) / v_bar) - 1.0)
    β = (1.0 - x_bar) * (((x_bar * (1.0 - x_bar)) / v_bar) - 1.0)
    Beta(α, β)
end


# This file was generated, do not modify it.

using HTTP, CSV, MLJ, StatsBase, PyPlot
req = HTTP.get("https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data")
data = CSV.read(req.body,
                header=["Class", "Alcool", "Malic acid",
                        "Ash", "Alcalinity of ash", "Magnesium",
                        "Total phenols", "Flavanoids",
                        "Nonflavanoid phenols", "Proanthcyanins",
                        "Color intensity", "Hue",
                        "OD280/OD315 of diluted wines", "Proline"])
# the target is the Class column, everything else is a feature
y, X = unpack(data, ==(:Class), colname->true);

scitype(y)

yc = coerce(y, OrderedFactor);

scitype(X)

sch = schema(X)
println(rpad(" Name", 28), "| Scitype")
println("-"^45)
for (name, scitype) in zip(sch.names, sch.scitypes)
    println(rpad("$name", 30), scitype)
end

X[1:5, :Proline]

Xc = coerce(X, :Proline=>Continuous, :Magnesium=>Continuous);

describe(Xc, :mean, :std)

@load KNNClassifier pkg="NearestNeighbors"
@load MultinomialClassifier pkg="MLJLinearModels";

@pipeline KnnPipe(std=Standardizer(), clf=KNNClassifier()) is_probabilistic=true
@pipeline MnPipe(std=Standardizer(), clf=MultinomialClassifier()) is_probabilistic=true

train, test = partition(eachindex(yc), 0.8, shuffle=true, rng=111)
Xtrain = selectrows(Xc, train)
Xtest = selectrows(Xc, test)
ytrain = selectrows(yc, train)
ytest = selectrows(yc, test);

knn = machine(KnnPipe(), Xtrain, ytrain)
multi = machine(MnPipe(), Xtrain, ytrain)

opts = (resampling=Holdout(fraction_train=0.9), measure=cross_entropy)
res = evaluate!(knn; opts...)
round(res.measurement[1], sigdigits=3)

res = evaluate!(multi; opts...)
round(res.measurement[1], sigdigits=3)

mcr_k = misclassification_rate(predict_mode(knn, Xtrain), ytrain)
mcr_m = misclassification_rate(predict_mode(multi, Xtrain), ytrain)
println(rpad("KNN mcr:", 10), round(mcr_k, sigdigits=3))
println(rpad("MNC mcr:", 10), round(mcr_m, sigdigits=3))

# @pipeline PCAPipe(std=Standardizer(), t=PCA(maxoutdim=2))
# pca = machine(PCAPipe(), Xtrain)
# fit!(pca, Xtrain)
# W = transform(pca, Xtrain)

@load PCA

pca = Xc |> Standardizer() |> PCA(maxoutdim=2)
fit!(pca)
W = pca(rows=train);

x1 = W.x1
x2 = W.x2

mask_1 = ytrain .== 1
mask_2 = ytrain .== 2
mask_3 = ytrain .== 3

figure(figsize=(8, 6))
plot(x1[mask_1], x2[mask_1], linestyle="none", marker="o", color="red")
plot(x1[mask_2], x2[mask_2], linestyle="none", marker="o", color="blue")
plot(x1[mask_3], x2[mask_3], linestyle="none", marker="o", color="magenta")

xlabel("PCA dimension 1", fontsize=14)
ylabel("PCA dimension 2", fontsize=14)
legend(["Class 1", "Class 2", "Class 3"], fontsize=12)
xticks(fontsize=12)
yticks(fontsize=12)

savefig("assets/literate/EX-wine-pca.svg") # hide

perf_k = misclassification_rate(predict_mode(knn, Xtest), ytest)
perf_m = misclassification_rate(predict_mode(multi, Xtest), ytest)
println(rpad("KNN mcr:", 10), round(perf_k, sigdigits=3))
println(rpad("MNC mcr:", 10), round(perf_m, sigdigits=3))


export cycles

function _find_cycle(f::DiscreteFunction, s::Int)
    hits = Set{Int}()
    push!(hits, s)
    current = s

    while true
        current = f(current)
        if in(current, hits)  # we found a cycle!
            result = Array{Int,1}()
            push!(result, current)
            while true
                current = f(current)
                if current == result[1]
                    return result
                end
                push!(result, current)
            end
        end
        push!(hits, current)
    end
    error("This can't happen")
end

function _standardize_cycle(c::Array{Int,1})
    (x,idx) = findmin(c)
    front = c[idx:end]
    back  = c[1:idx-1]
    return vcat(front,back)
end

"""
`cycles(f::DiscreteFunction)` returns a list of the cycles in `f`.
"""
function cycles(f::DiscreteFunction)::Array{Array{Int,1},1}
    result = Set{Array{Int,1}}()

    n = length(f)
    for s=1:n
        c = _find_cycle(f,s)
        c = _standardize_cycle(c)
        push!(result,c)
    end

    return sort(collect(result))
end


@testset "Manifold Newton" begin
    function f_1(x::Vector)
        (x[1] - 5.0)^4
    end

    function g!_1(storage::Vector, x::Vector)
        storage[1] = 4.0 * (x[1] - 5.0)^3
    end

    function h!_1(storage::Matrix, x::Vector)
        storage[1, 1] = 12.0 * (x[1] - 5.0)^2
    end
    initial_x = [0.0]

    retract_flat!(x) = x
    method = ManifoldNewton(retract_flat!)
    Optim.optimize(NonDifferentiable(f_1, initial_x), [0.0], method)
    Optim.optimize(OnceDifferentiable(f_1, g!_1, initial_x), [0.0], method)

    options = Optim.Options(store_trace = false, show_trace = false,
                            extended_trace = true)
    results = Optim.optimize(f_1, g!_1, h!_1, [0.0], method, options)
    @test_throws ErrorException Optim.x_trace(results)
    @test Optim.g_converged(results)
    @test norm(Optim.minimizer(results) - [5.0]) < 0.01

    eta = 0.9

    function f_2(x::Vector)
      (1.0 / 2.0) * (x[1]^2 + eta * x[2]^2)
    end

    function g!_2(storage::Vector, x::Vector)
      storage[1] = x[1]
      storage[2] = eta * x[2]
    end

    function h!_2(storage::Matrix, x::Vector)
      storage[1, 1] = 1.0
      storage[1, 2] = 0.0
      storage[2, 1] = 0.0
      storage[2, 2] = eta
    end

    results = Optim.optimize(f_2, g!_2, h!_2, [127.0, 921.0], method)
    @test_throws ErrorException Optim.x_trace(results)
    @test Optim.g_converged(results)
    @test norm(Optim.minimizer(results) - [0.0, 0.0]) < 0.01
    @test summary(results) == "Manifold Newton's Method"
end


"""
    function hamming_distance(s1::String, s2::String)

In information theory, the Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different.
In other words, it measures the minimum number of substitutions required to change one string into the other, or the minimum number of errors that could have transformed one string into the other.
In a more general context, the Hamming distance is one of several string metrics for measuring the edit distance between two sequences. It is named after the American mathematician Richard Hamming.

Reference (https://en.wikipedia.org/wiki/Hamming_distance)

# Arguments:
- `s1`: First string to compare
- `s2`: Second string to compare

# Examples/Tests
```julia
julia> hamming_distance("karolin", "kathrin")
3

julia> hamming_distance("0000", "1111")
4

julia> hamming_distance("ABCDEFG", "AZCDWGM")
4
```

# Contributors:
- [Sedat Aybars Nazlica](https://github.com/saybarsnazlica)
"""

function hamming_distance(s1::String, s2::String)
    @assert length(s1) == length(s2) "String lengths must be the same"

    distance = 0

    for i in 1:length(s1)
        if s1[i] != s2[i]
            distance += 1
        end
    end
    return distance
end


monicderivative(p, i::Int) = derivative(p, degree(p)-i)*factorial(i)/factorial(degree(p))

isapproxreal(x::Real) = true
isapproxreal(x::Complex{T}; atol::Real=1e-7) where {T} = abs(imag(x)) < atol
realroots(poly) = real.(filter(isapproxreal, _ROOTS[](poly)))

# define a function that returns a Plots.Shape
# code from https://discourse.julialang.org/t/how-to-draw-a-rectangular-region-with-plots-jl/1719/4
rectangle(x1, y1, x2, y2) = ([x1,x2,x2,x1], [y1,y1,y2,y2])
function ithrectangles(sortrealroots, i;
                       mininf=-5, inf=5, w=.5)
    xs = [mininf; sortrealroots]
    xs2 = [sortrealroots; inf]
    return [rectangle(x1, i-w, x2, i+w) for (x1,x2) in zip(xs, xs2)]
end

function ithcolors(_iseven::Bool, nboxes, gray, black)
    grays = _iseven ? iseven.(1:nboxes) : isodd.(1:nboxes)
    blacks = (!).(grays)
    return gray*grays + black*blacks
end

function find_bounds(allsortvectors)
    nonempty_vectors = filter((!)∘isempty, allsortvectors)
    mins, maxs = first.(nonempty_vectors), last.(nonempty_vectors)
    return minimum(mins), maximum(maxs)
end


@testset "$(rpad("Gauß-Legendre",80))" begin

    for s in 1:10
        @test GaussLegendreQuadrature(s) ≈  GaussLegendreQuadrature(s; fast=true)
        @test GaussLegendreQuadrature(s) == GaussLegendreQuadrature(Float64, s)

        @test sum(weights(GaussLegendreQuadrature(s))) ≈ 1
    end

end


#

function jet(N::Integer)
	cm = RGB{Float64}[
	    RGB(
	        clamp(min(4x - 1.5, -4x + 4.5) ,0.0,1.0),
	        clamp(min(4x - 0.5, -4x + 3.5) ,0.0,1.0),
	        clamp(min(4x + 0.5, -4x + 2.5) ,0.0,1.0))
	    for x in linspace(0.0,1.0,N)]
	end

jet() = jet(64);

access_dict["jet"] = jet


julia> function f(n)
    s = 0.0
    for k = 1:n
      s += 1/k^2
    end
    return s
end

julia> f(1000)
1.6439345666815615


module Constants

export μ₀, ϵ₀, c₀, η₀, twopi, fourpi, tol, dbmin, tdigits

"Permeability of free space [H/m]"
const μ₀ = 1.25663706212*1e-6

"Speed of light [m/s]"
const c₀ = 299792458.0

"Permeability of free space [F/m]"
const ϵ₀ = 1/(μ₀*c₀^2)

"Intrinsic impedance of free space [F/m]"
const η₀ = sqrt(μ₀/ϵ₀)

const twopi = 2π
const fourpi = 4π
const tol = 1e-4

"Minimum modal attenuation"
const dbmin = 30.0

"Min. elect. length for a layer to not be included in a `GBLOCK`"
const min_elength = twopi * 0.1

"Number of digits to use in printing elapsed time"
const tdigits = 4

end


using StatsDiscretizations
using Test
using Infinity

@test cdf(Binomial(5,0.5), Interval{:open,:closed}(0, ∞)) == 1.0
@test cdf(Binomial(5,0.7), Interval{:open,:closed}(-∞,0)) ≈ pdf(Binomial(5,0.7), 0)

#pdf(Normal(), NormalSample(IntervalSets.Interval(0.0,Inf),1.0))


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

X, y = getFullData()

# init initial point x_0
x0 = zeros(size(X,2),1)

lambda = 5

iter = 250
step =0.01

f = f_logreg(X,y,x0,lambda)
g = sub_g_logreg()

#f = f_svm(X,y,x0,lambda)
#g = sub_g_svm()

@time (theta, strings, iter) = SAG(X,y,x0,f,g,1e-8,lambda,iter,step,size(X,1))

grad = g_logreg(X,y,theta,lambda)(theta)
grad_norm = norm(grad, Inf)
@printf("Inf-Norm of gradient is %f.\n",grad_norm)
println(evaluate(X,y,theta,"logreg",false))

# Lippschitz = largest eigenvalue of Hessian*Hessian'


# functions related to gamma distribution

import .RFunctions:
    gammapdf,
    gammalogpdf,
    gammacdf,
    gammaccdf,
    gammalogcdf,
    gammalogccdf,
    gammainvcdf,
    gammainvccdf,
    gammainvlogcdf,
    gammainvlogccdf

# pdf for numbers with generic types
gammapdf(k::Real, θ::Real, x::Number) = 1 / (gamma(k) * θ^k) * x^(k - 1) * exp(-x / θ)

# logpdf for numbers with generic types
gammalogpdf(k::Real, θ::Real, x::Number) = -loggamma(k) - k * log(θ) + (k - 1) * log(x) - x / θ


using Revise
using PortfolioBedtest
import PortfolioBedtest: order, execute
using Dates
using Statistics
using Setfield
using Convex, SCS # It can be simplifed, but existing solutions are fine

########################################
# Auxiliary functions
########################################
function get_optim_weights(w, risk, ret, λ)
    p = minimize(λ * risk - (1 - λ) * ret,
                 sum(w) == 1,
                 0.0 <= w,
                 w <= 1.0)
    solve!(p, () -> SCS.Optimizer(verbose = false))

    return sqrt(evaluate(risk)), evaluate(w)
end

function find_weights(M, r, risk0, tol = 1e-8)
    w = Variable(length(r))
    risk = quadform(w, M)
    ret = dot(w, r)
    l1 = 0.01
    risk1, _ = get_optim_weights(w, risk, ret, l1)
    l2 = 0.99
    risk2, _ = get_optim_weights(w, risk, ret, l2)
    l3 = (l1 + l2)/2
    risk3, _ = get_optim_weights(w, risk, ret, l3)
    while abs(l1 - l2) > tol
        if risk1 >= risk0 >= risk3
            l2 = l3
        else
            l1 = l3
        end
        l3 = (l1 + l2) / 2
        risk3, _ = get_optim_weights(w, risk, ret, l3)
    end
    risk3, w3 = get_optim_weights(w, risk, ret, l3)

    return risk3, w3
end

function build(assts, rng, risk = 0.1)
    # not very efficient, but ok
    wnd = assts[rng]
    wnd2 = wnd[2:end] ./ wnd[1:end - 1]
    ret = exp.((252 * mean(map.(log, wnd2))).event) .- 1
    # It can be done in row-wise format, but optimization is not important here
    # So we transform data to column-wise matrix
    M = 252 .* cov(hcat(map(i -> getindex.(getfield.(wnd2, :event), i), 1:length(ret))...))
    _, w = find_weights(M, ret, risk)

    return w
end

function create_signal(assts, risk = 0.1, period = 36)
    indx = filter_eom_indices(assts)
    w = build(assts, indx[1]:indx[period+1], risk)
    w2 = tuple(w...)
    res = [TimedEvent(assts[indx[period + 1]].ts, w2)]
    for i in period+2:length(indx)
        w = build(assts, indx[i - period]:indx[i], risk)
        w2 = tuple(w...)
        push!(res, TimedEvent(assts[indx[i]].ts, w2))
    end
    Signal(res)
end

########################################
# Simulation auxiliary structures
########################################
struct RatioStrategy end

struct Order{T} <: AbstractOrder
    shares::T
end

function order(strat::RatioStrategy, broker, signal, prices)
    a = assets(broker, prices)
    ntuple(length(signal)) do i
        floor(Int, a * signal[i] / prices[i])
    end |> Order
end

function execute(broker, ord::Order, prices)
    a = assets(broker, prices)
    for i in 1:length(ord.shares)
        a -= ord.shares[i] * prices[i]
    end
    Broker(a, ord.shares)
end

########################################
# Calculations
########################################

# ETF
spyts = yahoo("SPY") |> x -> map(x -> TimedEvent(x.Date, getproperty(x, Symbol("Adj Close"))), x);
aggts = yahoo("AGG") |> x -> map(x -> TimedEvent(x.Date, getproperty(x, Symbol("Adj Close"))), x);
gldts = yahoo("GLD") |> x -> map(x -> TimedEvent(x.Date, getproperty(x, Symbol("Adj Close"))), x);

assts = glue(spyts, aggts, gldts) do v1, v2, k
    k == 0 && return (v1..., v2...)
    k == 1 && return (v1..., map(_ -> NaN, v2)...)
    return (map(_ -> NaN, v1)..., v2...)
end

assts = filter(x -> !any(isnan.(x.event)), assts)
signal = create_signal(assts)
broker = Broker(1_000_000.0, ntuple(_ -> 0, length(assts[1].event)))
strat = RatioStrategy()
recorder = Recorder{Date, typeof((; shares = broker.shares, total = 0.0))}([])

sim(broker, strat, assts, signal, recorder)

########################################
# Volatility 7.5%
########################################
vol75_signal = create_signal(assts, 0.075)
vol75_recorder = Recorder{Date, typeof((; shares = broker.shares, total = 0.0))}([])

sim(broker, strat, assts, vol75_signal, vol75_recorder)

########################################
# 60/40 comparison
########################################

sfsignal = Signal(mutate.(_ -> (0.6, 0.4, 0.0), signal.signal))
sfrecorder = Recorder{Date, typeof((; shares = broker.shares, total = 0.0))}([])

sim(broker, strat, assts, sfsignal, sfrecorder)

########################################
# Pure SPY
########################################
spysignal = Signal(mutate.(_ -> (1.0, 0.0, 0.0), signal.signal))
spyrecorder = Recorder{Date, typeof((; shares = broker.shares, total = 0.0))}([])

sim(broker, strat, assts, spysignal, spyrecorder)

########################################
# Plotting
########################################
using Plots
tsts = getfield.(signal.signal, :ts)
plot(tsts, getfield.(mutate.(x -> x[1], signal.signal), :event), legend = :outerright, label = "SPY")
plot!(tsts, getfield.(mutate.(x -> x[2], signal.signal), :event), label = "AGG")
plot!(tsts, getfield.(mutate.(x -> x[3], signal.signal), :event), label = "GLD")

# Equities
equity_curve = mutate.(x -> x.total, recorder.data)
plot(getfield.(equity_curve, :ts), getfield.(equity_curve, :event), legend = :topleft, label = "10% volatilty rebalance", linewidth = 0.5)

sfequity_curve = mutate.(x -> x.total, sfrecorder.data)
plot!(getfield.(sfequity_curve, :ts), getfield.(sfequity_curve, :event), label = "60/40 SPY/AGG", linewidth = 0.5)

spyequity_curve = mutate.(x -> x.total, spyrecorder.data)
plot!(getfield.(spyequity_curve, :ts), getfield.(spyequity_curve, :event), label = "100% SPY", linewidth = 0.5)

vol75_equity_curve = mutate.(x -> x.total, vol75_recorder.data)
plot!(getfield.(vol75_equity_curve, :ts), getfield.(vol75_equity_curve, :event))

########################################
# Volatility spectre
########################################
function build_vol_plot(assts, rng)
    isfirst = true
    broker = Broker(1_000_000.0, ntuple(_ -> 0, length(assts[1].event)))
    strat = RatioStrategy()
    recorder = Recorder{Date, typeof((; shares = broker.shares, total = 0.0))}([])
    local g
    for vol in rng
        signal = create_signal(assts, vol)
        sim(broker, strat, assts, signal, recorder)
        equity_curve = mutate.(x -> x.total, recorder.data)
        g = if isfirst
            isfirst = false
            plot(getfield.(equity_curve, :ts), getfield.(equity_curve, :event), legend = :topleft, label = "Risk: $vol")
        else
            plot!(getfield.(equity_curve, :ts), getfield.(equity_curve, :event), label = "Risk: $vol")
        end
    end

    return g
end

build_vol_plot(assts, 0.06:0.02:0.12)

########################################
# Sliding window spectre
########################################

function build_win_plot(assts, rng, risk = 0.1)
    isfirst = true
    broker = Broker(1_000_000.0, ntuple(_ -> 0, length(assts[1].event)))
    strat = RatioStrategy()
    recorder = Recorder{Date, typeof((; shares = broker.shares, total = 0.0))}([])
    local g
    for wnd in rng
        signal = create_signal(assts, risk, wnd)
        sim(broker, strat, assts, signal, recorder)
        equity_curve = mutate.(x -> x.total, recorder.data)
        g = if isfirst
            isfirst = false
            plot(getfield.(equity_curve, :ts), getfield.(equity_curve, :event), legend = :topleft, label = "Window: $wnd", linewidth = 0.5)
        else
            plot!(getfield.(equity_curve, :ts), getfield.(equity_curve, :event), label = "Window: $wnd", linewidth = 0.5)
        end
    end

    return g
end

build_win_plot(assts, 12:6:48)
build_win_plot(assts, 20:2:28)
build_win_plot(assts, 20:2:28, 0.12)


export get_discrete_Grad

"""
2D version
 input:
# Arguments
 - n1 : number of grid points in the first dimension
 - n2 : number of grid points in the second dimension
 - h1 : distance between grid points in the first dimension
 - h2 : distance between grid points in the second dimension
 - TD_type : type of derivative operator as a string

 output :
 - TD_OP : transform domain operator as a sparse matrix
"""
function get_discrete_Grad(n1,n2,h1::TF,h2::TF,TD_type::String) where {TF<:Real}
    TI=Int64

    #define difference matrix D acting on vectorized model using Kronecker products
    Ix = SparseMatrixCSC{TF}(LinearAlgebra.I,n1,n1) #x
    Iz = SparseMatrixCSC{TF}(LinearAlgebra.I,n2,n2) #z
    Dx = spdiagm(0 => ones(TF,n1-1)*-1, 1 => ones(TF,n1-1)*1); Dx = Dx[1:end-1,:] ./ h1
    Dz = spdiagm(0 => ones(TF,n2-1)*-1, 1 => ones(TF,n2-1)*1); Dz = Dz[1:end-1,:] ./ h2

    if TD_type=="D_z"
      D_OP = kron(Dz,Ix) #D2z
    elseif TD_type=="D_x"
      D_OP = kron(Iz,Dx) #D2x
    elseif  TD_type=="TV" || TD_type=="D2D"
      D2z  = kron(Dz,Ix)
      D2x  = kron(Iz,Dx)
      D_OP = vcat(D2z,D2x) #D2D
    end

    return D_OP
end

"""
  3D version
  input: n1 : number of grid points in the first dimension
         n2 : number of grid points in the second dimension
         n3 : number of grid points in the third dimension
         h1 : distance between grid points in the first dimension
         h2 : distance between grid points in the second dimension
         h3 : distance between grid points in the third dimension
         TD_type : type of derivative operator as a string
   output : TD_OP : transform domain operator as a sparse matrix
"""
function get_discrete_Grad(n1,n2,n3,h1::TF,h2::TF,h3::TF,TD_type::String) where {TF<:Real}
    TI = Int64

    #define difference matrix D acting on vectorized model using Kronecker products
    Ix = SparseMatrixCSC{TF}(LinearAlgebra.I,n1,n1) #x
    Iy = SparseMatrixCSC{TF}(LinearAlgebra.I,n2,n2) #x
    Iz = SparseMatrixCSC{TF}(LinearAlgebra.I,n3,n3) #z
    Dx = spdiagm(0 => ones(TF,n1-1)*-1, 1 => ones(TF,n1-1)*1); Dx = Dx[1:end-1,:] ./ h1
    Dy = spdiagm(0 => ones(TF,n2-1)*-1, 1 => ones(TF,n2-1)*1); Dy = Dy[1:end-1,:] ./ h2
    Dz = spdiagm(0 => ones(TF,n3-1)*-1, 1 => ones(TF,n3-1)*1); Dz = Dz[1:end-1,:] ./ h3

    if TD_type=="D_z"
      D_OP = kron(Dz,Iy,Ix)
    elseif TD_type=="D_y"
      D_OP = kron(Iz,Dy,Ix)
    elseif TD_type=="D_x"
      D_OP = kron(Iz,Iy,Dx)
    elseif TD_type=="TV" || TD_type=="D3D"
      D3z = kron(Dz,Iy,Ix)
      D3y = kron(Iz,Dy,Ix)
      D3x = kron(Iz,Iy,Dx)
      D_OP = vcat(D3z,D3y,D3x)
    end

    return D_OP
end


import NearlyNewton: minimize!
function theta(x)
    if x[1] > 0
        return atan(x[2] / x[1]) / (2.0 * pi)
    else
        return (pi + atan(x[2] / x[1])) / (2.0 * pi)
    end
end

function fletcher_powell_fg!(∇f, x)
    theta_x = theta(x)

    if !(∇f==nothing)
        if ( x[1]^2 + x[2]^2 == 0 )
            dtdx1 = 0;
            dtdx2 = 0;
        else
            dtdx1 = - x[2] / ( 2 * pi * ( x[1]^2 + x[2]^2 ) );
            dtdx2 =   x[1] / ( 2 * pi * ( x[1]^2 + x[2]^2 ) );
        end
        ∇f[1] = -2000.0*(x[3]-10.0*theta_x)*dtdx1 +
            200.0*(sqrt(x[1]^2+x[2]^2)-1)*x[1]/sqrt( x[1]^2+x[2]^2 );
        ∇f[2] = -2000.0*(x[3]-10.0*theta_x)*dtdx2 +
            200.0*(sqrt(x[1]^2+x[2]^2)-1)*x[2]/sqrt( x[1]^2+x[2]^2 );
        ∇f[3] =  200.0*(x[3]-10.0*theta_x) + 2.0*x[3];
    end

    fx = 100.0 * ((x[3] - 10.0 * theta_x)^2 + (sqrt(x[1]^2 + x[2]^2) - 1.0)^2) + x[3]^2

    return fx
end

tp_fletch_powell_fg! = TestProblem(fletcher_powell_fg!, [-1.0, 0.0, 0.0], I, NearlyNewton.OptOptions())
tp_fletch_powell_fg!_alt = TestProblem(fletcher_powell_fg!, [-0.5, 0.0, 0.0], I, NearlyNewton.OptOptions())

minimize!(tp_fletch_powell_fg!, BFGS(InverseApprox()))
minimize!(tp_fletch_powell_fg!, BFGS(DirectApprox()))

minimize!(tp_fletch_powell_fg!, SR1(InverseApprox()))
minimize!(tp_fletch_powell_fg!, SR1(DirectApprox()))

minimize!(tp_fletch_powell_fg!, DFP(InverseApprox()))
minimize!(tp_fletch_powell_fg!, DFP(DirectApprox()))

minimize!(tp_fletch_powell_fg!_alt, BFGS(InverseApprox()))
minimize!(tp_fletch_powell_fg!_alt, BFGS(DirectApprox()))

minimize!(tp_fletch_powell_fg!_alt, SR1(InverseApprox()))
minimize!(tp_fletch_powell_fg!_alt, SR1(DirectApprox()))

minimize!(tp_fletch_powell_fg!_alt, DFP(InverseApprox()))
minimize!(tp_fletch_powell_fg!_alt, DFP(DirectApprox()))


using Distributions
using Random

struct SpreadingParams
  dist::Truncated{Pareto{Float64},Continuous,Float64}
  spreading::Vector{Float32}
end

function SpreadingParams(rng::AbstractRNG, N::Integer; alpha::Real, x0::Real=1, truncation::Real=Inf)
  dist=truncated(Pareto(alpha, x0), x0, truncation)
  spreading = Vector{Float32}(undef, N)
  rand!(rng, dist, spreading)
  SpreadingParams(dist, spreading)
end

function saveparams(dict, p::SpreadingParams, prefix::AbstractString="")
  dict[prefix*"dist"] = params(p.dist)
  dict[prefix*"values"] = p.spreading
  nothing
end

function show(io::IO, p::SpreadingParams)
  print(io, "Spreading params for ", numindividuals(p), " individuals, distribution=", p.dist)
end

spreading(p::SpreadingParams, source_id::Integer) = Float64(p.spreading[source_id])


using Test, DiscreteExteriorCalculus
using LinearAlgebra: norm, det
using StaticArrays: SVector

@testset "norm, wedge, and volume" begin
    m = Metric(3)
    @test norm(m, SVector{3, Float64}([1,2,3])) == norm([1,2,3])
    a1, a2 = 3.4, 2.8
    b1, b2 = -2.4, 20.3
    s = Simplex(Point(0, 0, 0), Point(a1, a2, 0), Point(b1, b2, 0))
    @test volume(m, s) ≈ det([[a1, a2] [b1, b2]])/2
    a1, a2, a3 = 3.4, 2.8, 3.9
    b1, b2, b3 = -2.4, 20.3, -3.2
    c1, c2, c3 = .1, -49.2, 12.0
    s = Simplex(Point(0, 0, 0), Point(a1, a2, a3), Point(b1, b2, b3), Point(c1, c2, c3))
    @test volume(m, s) ≈ det([[a1, a2, a3] [b1, b2, b3] [c1, c2, c3]])/6
    m = Metric(2)
    s = Simplex(Point(1,2))
    volume(m, s) == 1.0
end


function counting_sort_letters(A, position)
	c = Array{Int, 1}(undef, 26)
    for i in 1:26
        c[i] = 0
    end
	for i in A
        c[abs(Int(i[position]) - 97) % 26 + 1] += 1
	end
	for i in 2:26
		c[i] += c[i - 1]
	end
	ret = Array{String, 1}(undef, length(A))
	for i in length(A) : -1 : 1
        ret[c[abs(Int(A[i][position]) - 97) % 26 + 1]] = A[i]
        c[abs(Int(A[i][position]) - 97) % 26 + 1] -= 1
	end
	return ret
end

function counting_sort_length(A::Array)
	max_len = 0
	for i in A
		if max_len < length(i)
			max_len = length(i)
		end
	end
	c = Array{Int, 1}(undef, max_len + 1)
	for i in 1 : max_len + 1
		c[i] = 0
	end

	for i in A
		c[length(i) + 1] += 1
	end

	for i in 2 : max_len + 1
		c[i] += c[i - 1]
	end

	ret = Array{String, 1}(undef, length(A))

	for i in length(A) : -1 : 1
		ret[c[length(A[i]) + 1]] = A[i]
		c[length(A[i]) + 1] -= 1
	end
	return ret
end

function copy_into!(to, from, p, r)
	for i in p : r
		to[i] = from[i - p + 1]
	end
end

function flexradix(A::Array, max_len)
	A = counting_sort_length(A)

	num_empty = 0

	for i in A
		if length(i) == 0
			num_empty += 1
		end
		if max_len < length(i)
			max_len = length(i)
		end
	end

	if max_len == 0
		return A
	end

	c = zeros(Int, max_len + 1)

	for i in A
		c[length(i) + 1] += 1
	end

	for i in max_len : -1 : 1
		c[i] += c[i + 1]
	end

	r = length(A)

    for cur_len in (max_len + 1) : -1 : 2
		p = r - c[cur_len] + 1

		if r - p > 0
			copy_into!(A, counting_sort_letters(A[p : r], cur_len - 1), p, r)
		end
	end

	copy_into!(A, counting_sort_letters(A[(num_empty + 1) : r], 1), (num_empty + 1), r)

	return A
end


module Metropolis

export metropolis, metropolis_uniform

using Distributions

"""
Generate a series of random samples from a given probability distribution
using the metropolis algorithm

# Arguments
- `distribution`: the target probability distribution
- `step`: the probability distribution of the steps
- `samples`: the number of samples
- `init`: the initial value of the series (starting point of the "walker")

# Returns

the series of randome samples from the distribution and the acceptance rate
"""
function metropolis(distribution::Function, step::Function, samples::Integer;
        init::Real=0.0)
    series = Vector{Float64}(undef, samples)

    position = init
    probability = distribution(position)
    series[1] = position

    acceptcount = 0
    for i in 2:samples
        newposition = position + step(position)
        newprobablity = distribution(newposition)

        if newprobablity / probability > 1 || rand() <= newprobablity / probability
            position, probability = newposition, newprobablity
            acceptcount += 1
        end

        series[i] = position
    end

    return series, acceptcount / (samples - 1)
end

"""
Metropolis algorithm with uniform step probability between `-stepsize` and `+stepsize`
"""
function metropolis_uniform(distribution::Function, stepsize::Real, samples::Integer;
        init::Real=0.0)
    return metropolis(distribution, x -> rand(Uniform(-stepsize, stepsize)), samples,
        init=init)
end

end


function rk4(f, t, y, dt, P, k)
    k1 = f(t, y, P)
    k2 = f(t + 1/2*dt, y + 1/2*dt*k1, P)
    k3 = f(t + 1/2*dt, y + 1/2*dt*k2, P)
    k4 = f(t + dt, y + dt*k3, P)
    y + dt*(k1+2*k2+2*k3+k4)/6
end

struct Rk4 <: ODESolverType end

#=
        FOUR components
=#
function update(::Rk4, fs, t, A, B, C, D, dt, P, tableau=NaN)
    kA1 = update(fs[1], t, A, B, C, D, P)
    kB1 = update(fs[2], t, A, B, C, D, P)
    kC1 = update(fs[3], t, A, B, C, D, P)
    kD1 = update(fs[4], t, A, B, C, D, P)

    Ai = A + 1/2*dt*kA1
    Bi = B + 1/2*dt*kB1
    Ci = C + 1/2*dt*kC1
    Di = D + 1/2*dt*kD1

    kA2 = update(fs[1], t + 1/2*dt, Ai, Bi, Ci, Di, P)
    kB2 = update(fs[2], t + 1/2*dt, Ai, Bi, Ci, Di, P)
    kC2 = update(fs[3], t + 1/2*dt, Ai, Bi, Ci, Di, P)
    kD2 = update(fs[4], t + 1/2*dt, Ai, Bi, Ci, Di, P)

    Ai = A + 1/2*dt*kA2
    Bi = B + 1/2*dt*kB2
    Ci = C + 1/2*dt*kC2
    Di = D + 1/2*dt*kD2

    kA3 = update(fs[1], t + 1/2*dt, Ai, Bi, Ci, Di, P)
    kB3 = update(fs[2], t + 1/2*dt, Ai, Bi, Ci, Di, P)
    kC3 = update(fs[3], t + 1/2*dt, Ai, Bi, Ci, Di, P)
    kD3 = update(fs[4], t + 1/2*dt, Ai, Bi, Ci, Di, P)

    Ai = A + dt*kA3
    Bi = B + dt*kB3
    Ci = C + dt*kC3
    Di = D + dt*kD3

    kA4 = update(fs[1], t + dt, Ai, Bi, Ci, Di, P)
    kB4 = update(fs[2], t + dt, Ai, Bi, Ci, Di, P)
    kC4 = update(fs[3], t + dt, Ai, Bi, Ci, Di, P)
    kD4 = update(fs[4], t + dt, Ai, Bi, Ci, Di, P)

    (A + dt*(kA1 + 2*kA2 + 2*kA3 + kA4)/6, B + dt*(kB1 + 2*kB2 + 2*kB3 + kB4)/6,
     C + dt*(kC1 + 2*kC2 + 2*kC3 + kC4)/6, D + dt*(kD1 + 2*kD2 + 2*kD3 + kD4)/6)
end

#=
        THREE components
=#
function update(::Rk4, fs, t, A, B, C, dt, P, tableau=NaN)
    kA1 = update(fs[1], t, A, B, C, P)
    kB1 = update(fs[2], t, A, B, C, P)
    kC1 = update(fs[3], t, A, B, C, P)

    Ai = A + 1/2*dt*kA1
    Bi = B + 1/2*dt*kB1
    Ci = C + 1/2*dt*kC1

    kA2 = update(fs[1], t + 1/2*dt, Ai, Bi, Ci, P)
    kB2 = update(fs[2], t + 1/2*dt, Ai, Bi, Ci, P)
    kC2 = update(fs[3], t + 1/2*dt, Ai, Bi, Ci, P)

    Ai = A + 1/2*dt*kA2
    Bi = B + 1/2*dt*kB2
    Ci = C + 1/2*dt*kC2

    kA3 = update(fs[1], t + 1/2*dt, Ai, Bi, Ci, P)
    kB3 = update(fs[2], t + 1/2*dt, Ai, Bi, Ci, P)
    kC3 = update(fs[3], t + 1/2*dt, Ai, Bi, Ci, P)

    Ai = A + dt*kA3
    Bi = B + dt*kB3
    Ci = C + dt*kC3

    kA4 = update(fs[1], t + dt, Ai, Bi, Ci, P)
    kB4 = update(fs[2], t + dt, Ai, Bi, Ci, P)
    kC4 = update(fs[3], t + dt, Ai, Bi, Ci, P)

    (A + dt*(kA1 + 2*kA2 + 2*kA3 + kA4)/6, B + dt*(kB1 + 2*kB2 + 2*kB3 + kB4)/6,
     C + dt*(kC1 + 2*kC2 + 2*kC3 + kC4)/6)
end

#=
        TWO components
=#
function update(::Rk4, fs, t, A, B, dt, P, tableau=NaN)
    kA1 = update(fs[1], t, A, B, P)
    kB1 = update(fs[2], t, A, B, P)

    Ai = A + 1/2*dt*kA1
    Bi = B + 1/2*dt*kB1

    kA2 = update(fs[1], t + 1/2*dt, Ai, Bi, P)
    kB2 = update(fs[2], t + 1/2*dt, Ai, Bi, P)

    Ai = A + 1/2*dt*kA2
    Bi = B + 1/2*dt*kB2

    kA3 = update(fs[1], t + 1/2*dt, Ai, Bi, P)
    kB3 = update(fs[2], t + 1/2*dt, Ai, Bi, P)

    Ai = A + dt*kA3
    Bi = B + dt*kB3

    kA4 = update(fs[1], t + dt, Ai, Bi, P)
    kB4 = update(fs[2], t + dt, Ai, Bi, P)

    (A + dt*(kA1 + 2*kA2 + 2*kA3 + kA4)/6, B + dt*(kB1 + 2*kB2 + 2*kB3 + kB4)/6)
end