@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


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


## 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)


#!/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


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


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


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


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

julia> f(1000)
1.6439345666815615


# 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()))


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


"""
MLEPoisson(data::AbstractVector)
MLEPoisson estimates intensity of homogenous poisson process. Homogenous poisson process has one independent prameter, intensity $\lambda$, and the result of maximum likelihood shows following relationship
$$
\lambda = E[t_{i}-t_{i-1}].
$$
"""

function MLEPoisson(x::AbstractVector;data::AbstractVector = x)
    mu = diff(x)
    return 1/mean(mu)
end


using LinearAlgebra

A = [1 2 3; 4 5 6; 7 8 9]

for t in [
Symmetric
Hermitian
UpperTriangular
LowerTriangular
Tridiagonal
Diagonal
]
    show(stdout, MIME("text/plain"), t(A))
    println()
end

A = [1, 2, 3]
B = [5, 6]
for t in [
SymTridiagonal
]
    show(stdout, MIME("text/plain"), t(A, B))
    println()
end

A = [1, 2, 3]
B = [5, 6]
for t in [
Bidiagonal
]
    show(stdout, MIME("text/plain"), t(A, B, :U))
    println()
    show(stdout, MIME("text/plain"), t(A, B, :L))
    println()
end

for t in [
UniformScaling
]
    println(t(2))
end

#=
3×3 Symmetric{Int64,Array{Int64,2}}:
 1  2  3
 2  5  6
 3  6  9
3×3 Hermitian{Int64,Array{Int64,2}}:
 1  2  3
 2  5  6
 3  6  9
3×3 UpperTriangular{Int64,Array{Int64,2}}:
 1  2  3
 ⋅  5  6
 ⋅  ⋅  9
3×3 LowerTriangular{Int64,Array{Int64,2}}:
 1  ⋅  ⋅
 4  5  ⋅
 7  8  9
3×3 Tridiagonal{Int64,Array{Int64,1}}:
 1  2  ⋅
 4  5  6
 ⋅  8  9
3×3 Diagonal{Int64,Array{Int64,1}}:
 1  ⋅  ⋅
 ⋅  5  ⋅
 ⋅  ⋅  9
3×3 SymTridiagonal{Int64,Array{Int64,1}}:
 1  5  ⋅
 5  2  6
 ⋅  6  3
3×3 Bidiagonal{Int64,Array{Int64,1}}:
 1  5  ⋅
 ⋅  2  6
 ⋅  ⋅  3
3×3 Bidiagonal{Int64,Array{Int64,1}}:
 1  ⋅  ⋅
 5  2  ⋅
 ⋅  6  3
UniformScaling{Int64}
2*I
=#


"""
A reservoir Outlet should always have the following:
    - An elevation field z
    - A discharge function.

The discharge function depends on the hydralic head measured from the elevation
of the outlet (z) to the reservoir water surface.
"""

abstract type Outlet end

discharge(o::Outlet; z) = z > o.z ? discharge(o, z - o.z) : 0.0

discharge(o::Outlet, h) =
    error("Please implement a discharge function for type ", typeof(o))

Base.@kwdef struct Orifice <: Outlet
    C::Float64
    A::Float64
    z::Float64
    g::Float64 = 9.81
end

discharge(o::Orifice, h) = o.C * o.A * √(2.0 * o.g * h)

Base.@kwdef struct Weir <: Outlet
    C::Float64
    B::Float64
    z::Float64
end

discharge(o::Weir, h) = o.C * o.B * h^1.5


module ContinuedFractions

import Base: start, done, next, length, eltype, collect

export ContinuedFraction, quotients, convergents, ConvergentIterator

immutable ContinuedFraction{T<:Integer}
	quotients::Vector{T}
end

quotients(cf::ContinuedFraction) = cf.quotients

immutable ConvergentIterator{T<:Integer}
    qs::Vector{T}
end

start(::ConvergentIterator) = 1
done(it::ConvergentIterator, state::Int) = state > length(it.qs)
length(it::ConvergentIterator) = length(it.qs)

function next(it::ConvergentIterator, state::Int)
    convergent = Rational(ContinuedFraction(it.qs[1:state]))
    convergent, state + 1
end

eltype(it::ConvergentIterator) = Rational{eltype(it.qs)}
collect(it::ConvergentIterator) = collect(eltype(it), it)

convergents(cf::ContinuedFraction) = convergents(quotients(cf))
convergents{T<:Integer}(qs::Vector{T}) = ConvergentIterator(qs)

function Base.Rational(cf::ContinuedFraction)
    qs = quotients(cf)
    isempty(qs) && return 0 // 1
    length(qs) == 1 && return qs[1] // 1

    remainder = qs[2:end]
    rat = Rational(ContinuedFraction(remainder))
    (qs[1] * rat.num + rat.den) // rat.num
end

function ContinuedFraction{T<:Integer}(rat::Rational{T})
    a = div(rat.num, rat.den)
    a * rat.den == rat.num && return ContinuedFraction(T[a])  # Exact!

    cf = ContinuedFraction(rat.den//(rat.num - a*rat.den))
    unshift!(quotients(cf), a) # insert at index 1
    cf
end

# let rationalize handle conversion from floating point
ContinuedFraction(x::BigFloat) = ContinuedFraction(rationalize(BigInt, x))
ContinuedFraction(x::AbstractFloat) = ContinuedFraction(rationalize(x))

ContinuedFraction(x::Integer) = ContinuedFraction([x])

end # module ContinuedFractions


module PDE

using GPUBenchmarks, BenchmarkTools, Primes

description = """
Kuramoto-Sivashinsky algorithm benchmark ([original benchmark](https://github.com/johnfgibson/julia-pde-benchmark/blob/master/1-Kuramoto-Sivashinksy-benchmark.ipynb)).

This benchmark is dominated by the cost of the FFT, leading to worse results for OpenCL with
CLFFT compared to the faster CUFFT.
Similarly the multithreaded backend doesn't improve much over base with the same FFT implementation.
Result of the benchmarked PDE:
![](https://github.com/JuliaGPU/GPUBenchmarks.jl/blob/master/results/plots/pde_result.png?raw=true)
"""

sq3(x) = Complex64(x * x * x) # cudanative doesn't like Complex64 directly broadcasted.

# source: http://nbviewer.jupyter.org/url/homepages.warwick.ac.uk/staff/C.Ortner/julia/PlaneWaves.ipynb
# an optimised implementation of CH_vectorised!

function inner_loop(
        nruns,
        planv, planw, planwi, u0, u, v, w, Â,
        c1, c2, c3, c4
    )
    u .= identity.(u0)
    w .= tocomplex64.(u0)
    for n = 1:nruns
        v .= sq3.(u)
        planv * v
        planw * w
        @. w = ((1f0+c1*Â)*w - (c2*Â)*v) / ((1f0+c3*Â+c4)*Â)
        planwi * w
        u .= real.(w)
    end
    synchronize(u)
    return u
end

function makeresult(bench, N, device, hardware, mdiff)
    BenchResult(
        "PDE",
        bench,
        N,
        Float32,
        string(device),
        hardware,
        @__FILE__,
        mdiff
    )
end

function execute2(device)
    hardware, array_type = init(device)
    T = Float32
    results = BenchResult[]
    for x in (4,)
        N = 2 ^ x
        # initialisations
        h = 2*π/N; epsn = h * 3; C = 2/epsn; tau = epsn * h
        k = [0:N/2; -N/2+1:-1]
        acpu = kron(k.^2, ones(1,N)) + kron(ones(N), k'.^2)
        ucpu = 2*(rand(N, N)-0.5)

        Â = array_type(T.(acpu))
        u = array_type(T.(ucpu))

        w = tocomplex64.(u)
        v = copy(w)
        c1 = (C*tau + tau/epsn)
        c2 = (tau / epsn)
        c3 = (epsn * tau)
        c4 = (C*tau)
        planv = plan_fft!(v)
        planw = plan_fft!(w)
        planwi = plan_ifft!(w)
        nruns = 5
        u0 = copy(u)
        bench = @benchmark $(inner_loop)(
                $nruns,
                $planv, $planw, $planwi,$u0, $u, $v, $w, $Â,
                $c1, $c2, $c3, $c4
        )
        wcpu = tocomplex64.(ucpu)
        vcpu = copy(wcpu)
        planvcpu = plan_fft!(vcpu)
        planwcpu = plan_fft!(wcpu)
        planwicpu = plan_ifft!(wcpu)
        inner_loop(
                nruns,
                planvcpu, planwcpu, planwicpu, copy(ucpu), ucpu, vcpu, wcpu, acpu,
                c1, c2, c3, c4
        )
        println(Array(ucpu))
        # mdiff = meandifference(ucpu, u)
        # @show mdiff
        # push!(results, makeresult(bench, N, device, hardware, mdiff))
        # free(Â); free(u0); free(u); free(v); free(w)
    end
    results
end

# execute(Float32, 512, :opencl)

function CH_memory_af!(nruns, N, AT)
    # initialisations
    h = Float32(2*π/N); epsn = Float32(h * 3); C = Float32(2/epsn); tau = Float32(epsn * h)
    k = [0:N/2; -N/2+1:-1]
    Â = AT((Float32.(kron(k.^2, ones(1,N)) + kron(ones(N), k'.^2))))
    u = AT(Float32.((2*(rand(N, N)-0.5))))

    # ============= ACTUAL CODE THAT IS BEING TESTED ======================
    # allocate arrays and define constants
    w = AT{Complex64}(u)
    v = copy(w)
    c1 = (C*tau + tau/epsn)
    c2 = (tau / epsn)
    c3 = (epsn * tau)
    c4 = (C*tau)
    tic()
    for n = 1:nruns
        v .= complex.(u .* u .* u)
        v = fft(v)
        w = fft(w)
        w .= (( (1f0 + c1) .* Â) .* w .- (c2 .* Â) .* v) ./ ((1f0 + c3 .* Â .+ c4) .* Â)
        w = ifft(w)
        u .= real.(w)
    end
    GPUArrays.synchronize(u)
    toc()
    # ======================================================================
    u
end

# source: https://github.com/johnfgibson/julia-pde-benchmark/blob/master/1-Kuramoto-Sivashinksy-benchmark.ipynb
function inner_ks(IFFT!, FFT!, Nt, Nn, Nn1, u, G, A_inv, B, dt2, dt32)
    for n = 1:Nt
        Nn1 .= Nn       # shift nonlinear term in time
        Nn .= u         # put u into Nn in prep for comp of nonlinear term

        IFFT! * Nn
            # transform Nn to gridpt values, in place
        Nn .= Nn .* Nn   # collocation calculation of u^2
        FFT!*Nn        # transform Nn back to spectral coeffs, in place

        Nn .= G .* Nn    # compute Nn == -1/2 d/dx (u^2) = -u u_x

        # loop fusion! Julia translates the folling line of code to a single for loop.
        u .= A_inv .* (B .* u .+ dt32 .* Nn .- dt2 .* Nn1)
    end
    synchronize(u)
end

function makeresult(bench, N, device, hardware, mdiff)
    BenchResult(
        "PDE",
        bench,
        N,
        Complex64,
        string(device),
        hardware,
        @__FILE__,
        mdiff
    )
end
function execute(device)
    hardware, AT = init(device)
    results = BenchResult[]
    is_gpuarrays(device) || device == :julia_base || return results
    T = Float32
    for i = 1:7
        N = 10^i
        Lx = T(64*pi)
        Nx = T(N)
        dt = T(1/16)
        Nt = 50

        x = Lx*(0:Nx-1)/Nx
        u = T.(cos.(x) + 0.1*sin.(x/8) + 0.01*cos.((2*pi/Lx)*x))

        u = AT((T(1)+T(0)im)*u)             # force u to be complex
        Nx = length(u)                      # number of gridpoints
        kx = T.(vcat(0:Nx/2-1, 0:0, -Nx/2+1:-1))# integer wavenumbers: exp(2*pi*kx*x/L)
        alpha = T(2)*pi*kx/Lx                  # real wavenumbers:    exp(alpha*x)

        D = T(1)im*alpha                       # spectral D = d/dx operator

        L = alpha.^2 .- alpha.^4            # spectral L = -D^2 - D^4 operator

        G = AT(T(-0.5) .* D)               # spectral -1/2 D operator, to eval -u u_x = 1/2 d/dx u^2

        # convenience variables
        dt2  = T(dt/2)
        dt32 = T(3*dt/2)
        A_inv = AT((ones(T, Nx) - dt2*L).^(-1))
        B = AT(ones(T, Nx) + dt2*L)

        # compute in-place FFTW plans
        FFT! = plan_fft!(u)
        IFFT! = plan_ifft!(u)

        # compute nonlinear term Nn == -u u_x
        powed = u .* u
        Nn = G .* fft(powed);    # Nn == -1/2 d/dx (u^2) = -u u_x
        Nn1 = copy(Nn);        # Nn1 = Nn at first time step
        FFT! * u;

        # timestepping loop
        bench = @benchmark $inner_ks($(IFFT!), $(FFT!), $Nt, $Nn, $Nn1, $u, $G, $A_inv, $B, $dt2, $dt32)
        push!(results, makeresult(bench, N, device, hardware, 0.0))
    end
    results
end

function ksintegrateNaive(u, Lx, dt, Nt, nsave)
    Nx = length(u)                  # number of gridpoints
    x = collect(0:(Nx-1)/Nx)*Lx
    kx = vcat(0:Nx/2-1, 0, -Nx/2+1:-1)  # integer wavenumbers: exp(2*pi*kx*x/L)
    alpha = 2*pi*kx/Lx              # real wavenumbers:    exp(alpha*x)
    D = 1im*alpha;                  # D = d/dx operator in Fourier space
    L = alpha.^2 - alpha.^4         # linear operator -D^2 - D^4 in Fourier space
    G = -0.5*D                      # -1/2 D operator in Fourier space

    Nsave = div(Nt, nsave)+1        # number of saved time steps, including t=0
    t = (0:Nsave)*(dt*nsave)        # t timesteps
    U = zeros(Nsave, Nx)            # matrix of u(xⱼ, tᵢ) values
    U[1,:] = u                      # assign initial condition to U
    s = 2                           # counter for saved data

    dt2  = dt/2
    dt32 = 3*dt/2;
    A_inv = (ones(Nx) - dt2*L).^(-1)
    B     =  ones(Nx) + dt2*L

    Nn  = G.*fft(u.*u) # -u u_x (spectral), notation Nn = N^n     = N(u(n dt))
    Nn1 = copy(Nn)     #                   notation Nn1 = N^{n-1} = N(u((n-1) dt))
    u  = fft(u)        # transform u to spectral

    # timestepping loop
    for n = 1:Nt
        Nn1 = copy(Nn)                 # shift nonlinear term in time: N^{n-1} <- N^n
        Nn  = G.*fft(real(ifft(u)).^2) # compute Nn = -u u_x

        u = A_inv .* (B .* u + dt32*Nn - dt2*Nn1)

        if mod(n, nsave) == 0
            U[s,:] = real(ifft(u))
            s += 1
        end
    end
    t,U
end
#
# using FileIO, Interpolations, Colors, GPUBenchmarks, GPUArrays, ColorVectorSpace, FixedPointNumbers
# Lx = 64*pi
# Nx = 1024
# dt = 1/16
# nsave = 8
# Nt = 3200
#
# x = Lx*(0:Nx-1)/Nx
# u = cos.(x) + 0.1*sin.(x/8) + 0.01*cos.((2*pi/Lx)*x);
# t,U = ksintegrateNaive(u, Lx, dt, Nt, nsave)
# cn = 100
# cmap = interpolate(colormap("Oranges", cn), BSpline(Linear()), OnCell());
# mini, maxi = extrema(U)
# img_color = map(U) do val
#     val = (val - mini) / (maxi - mini)
#     val = 1 - clamp(val, 0f0, 1f0);
#     idx = (val * (cn - 1)) + 1.0
#     RGB{N0f8}(cmap[idx])
# end
# save(GPUBenchmarks.dir("results", "plots", "pde_result.png"), img_color)

end


# define exceptions for stopping conditions in algorithm
type OptimalPointFound <: Exception end
type UnboundedFunctionException <: Exception end

function get_min(x, d, assts)
    n = length(assts)
    e = zeros(n)
    e[assts] = d
    ratios = []
    for i in 1:n
        if (assts[i]) & (e[i] > 0)
            push!(ratios, (x[i]/e[i], i))
        end
    end

    try
        return minimum(ratios)
    catch
        println(ratios)
    end
end

function get_q(s_N, assts)
    n = length(assts)
    e = zeros(n)
    e[!assts] = s_N

    try
        return indmax(e.<0)
    catch
            print e
    end
end

# a single iteration of the simplex algorithm
function simplex_iteration(assts, x, A, b, c)
    """
    We have a linear program of the form: min c^T x s.t. Ax = b

    arguments:
        assts: true indicates the element belongs to the basic set and false to the nonbasic set
        x: the vector x as given above in the LP
        A: constraint matrix, of the form Ax = b
        b: solutions of constraints
        c: coefficients of the LP function
    """

    B = A[:,assts]
    N = A[:,!assts]

    x_B = x[assts]
    x_N = x[!assts]

    # solve B^T lambda = c_B for lambda
    lambda = B' \ c[assts]

    # compute s_N = c_N - N^\top \lambda
    s_N = c[!assts] - N' * lambda

    # check if all elements of s_N are >= 0
    if all(s_N .>= 0)
        throw(OptimalPointFound())
    end

    # select q \in N with s_q < 0 as entering index
    q = get_q(s_N, assts) # here we always choose the first negative element

    # solve Bd = A_q for d
    d = B \ A[:,q]

    # check d
    if all(s_N .>= 0)
        throw(UnboundedFunctionException())
    end

    # calculate x_q+
    x_q, p = get_min(x, d, assts)

    # update x
    x[assts] = x[assts] - d * x_q
    x[q] = x_q

    # update the assts
    assts[p] = !assts[p]
    assts[q] = !assts[q]

    # returns new assts, x
    return assts, x

end

function run_simplex(assts, x, A, b, c, num_iters, verbose=false)

    for i in 1:num_iters
        #println("Starting iteration: ", i)

        try
            assts, x = simplex_iteration(assts, x, A, b, c)

            if verbose
                println("Resulting x, assts: \n\t", (x,assts))
            end

        catch err
            if isa(err, UnboundedFunctionException)

                println("Unbounded function exception!")
                return assts, x

            elseif isa(err, OptimalPointFound)

                println("Optimal point found!")

                if verbose
                    println("Optimal x, assts: \n\t", (x,assts))
                end

                return assts, x
            else
                println(err)

                throw(err)
            end
        end

    end
    return assts, x
end

function get_starting_values(A, b)
    """
    given a LP min c'x s.t. Ax = b, we want to create the new LP e'z s.t. Ax + Ez = b, where x=0, e=ones(.)

    A: the original constraint matrix
    b: the original b

    returns new A, x, c, assts to pass into simplex algorithm
    """
    nrows, ncols = size(A)
    # init x, c
    x = zeros(ncols + nrows)
    c = zeros(ncols + nrows)

    # add z to x
    x[ncols+1:ncols+nrows,:] = abs(b')
    c[ncols+1:ncols+nrows,:] = 1.

    # assignments are non-zero elements of x
    assts = (x .!= 0)

    E = zeros(nrows, nrows)
    vals = b .> 0

    for i in 1:nrows
        if vals[i]
            E[i,i] = 1
        else
            E[i,i] = -1
        end
    end

    return [A E], x, c, assts

end

function run_two_stage_simplex(A, b, c, num_iters)
    time1 = time()
    A0, x0, c0, assts0 = get_starting_values(A, b)

    nrows, ncols = size(A)
    l = length(x0)

    println("\nRunning Phase I of simplex algorithm\n")
    # run phase I
    assts_start, x_start = run_simplex(assts0,x0,A0,b,c0,num_iters)

    # run phase II
    try
        @assert x_start[l] == 0
    catch err
        println("No feasible solution for m=", size(A)[2])
    end

    # discard z
    xnew = x_start[1:l-nrows]
    assts_new = assts_start[1:l-nrows]

    println("\nRunning Phase II of simplex algorithm \n")

    x, assts = run_simplex(assts_new,xnew,A,b,c,num_iters)

    time2 = time()

    @printf("\nTime: %.5f (s)", time2-time1)

    return x, assts, (time2-time1)

end

#run_two_stage_simplex(A, b, c, 10)

function solve_subproblem(assts, x_k, G, c, A, b)
    g_k = G * x_k + c

    ## if all assignments are false
    if all(assts .== false)
        return G \ -g_k

    else
        dimx = length(x_k)
        A_w = A[assts,:]
        k,k = size(G)
        m,n = size(A_w)

        # construct matrix
        mat = [G A_w'; A_w zeros(k+m - n,k+m - n)]

        # pad with 0's
        d, = size(g_k)
        answ = [-g_k; zeros(k+m - d)]

        # compute solution and get x
        sol = mat \ answ
        x = sol[1:dimx,:]

        return x
    end
end

function compute_multipliers(assts, x_k, G, c, A)
    g = G * x_k + c
    lambda = A[assts,:]' \ g

    n = length(assts)
    return lambda, (1:n)[assts]
end

function compute_step_length(A, b, x_k, p_k, assts)
    """
    Returns step length alpha_k and blocking constraint of choice -- if the latter is 0, then alpha_k = 1
    """
    n = length(b)

    # by default, return 1 with no blocking constraints
    min = 1
    blocking = 0

    for i in 1:n
        # if i is in the working set, continue
        if assts[i]
            continue
        end

        a_i = A[i,:] ## this is a row vector so no need to transpose
        prod = a_i * p_k
        @assert length(prod) == 1

        # if a_i^ p_k >= 0, continue
        if (prod)[1] >= 0
            continue
        end

        val = (b[i] - a_i * x_k) / (prod)
        @assert length(val) == 1
        if val[1] < min
            min = val[1]
            blocking = i
        end
    end

    return min, blocking
end

function active_set_iteration(assts, x_k, G, c, A, b)
    """
    assts: assignments of constraint indices to the working set -- 1's are in the working set, 0's are not
    x_k: the current value of x_k obtained from the previous iteration
    G: the matrix in the quadratic function, i.e., x^T G x + x^T c
    c: the c from the quadratic function above
    A: matrix, where each row is the a_i from the constraint
    b: vector where each index is the b_i corresponding to constraint i
    """
    # solve subproblem to find p_k
    p_k = solve_subproblem(assts, x_k, G, c, A, b)

    #if all(p_k == 0) sufficient close to 0
    if all(abs(p_k-0) .<= 1e-6)

        if all(assts .== false)
            throw(OptimalPointFound())
        else

            # compute lagrange multipliers lambda_i that satisfy
            lambda, inds = compute_multipliers(assts, x_k, G, c, A)
        end

        # if lambda_i >= 0 for all i in W_k \cap I
        if all(lambda .>= 0)
            # stop with solution x* = x_k
            throw(OptimalPointFound())

        # (not all lambda_i >= 0)
        else
            # index of most negative multiplier lambda_j
            j = inds[indmin(lambda)]

            # remove j from working set
            assts[j] = false
            x_new = x_k
        end

    # (p_k != 0)
    else
        # compute step length alpha_k
        alpha_k, blocking = compute_step_length(A, b, x_k, p_k, assts)

        # update x_{k} += alpha_k * p_k
        x_new = x_k + alpha_k * p_k

        # if there are blocking constraints, update W_k (assts) by adding one of them to W_k
        if blocking != 0
            assts[blocking] = true
        end
    end

    return assts, x_new

end

function run_active_set(num_iters, assts, x_0, G, c, A, b, verbose=true)
    x_k = x_0
    for i in 1:num_iters
        if verbose
            println("Iteration:\t", i)
        end
        try
            assts, x_k = active_set_iteration(assts, x_k, G, c, A, b)
            if verbose
                println("Resulting x, assts: \n\t", (x_k,assts))
            end
        catch err
            if isa(err, OptimalPointFound)

                println("Optimal point found!")

                if verbose
                    println("Optimal x, assts: \n\t", (x_k,assts))
                end

                return assts, x_k
            else
                println(err)
                throw(err)
            end
        end

    end
    return assts, x_k
end

function run_active_set_full(num_iters, assts, x_0, G, c, A, b, verbose=true)
    x_k = x_0
    vals = []
    for i in 1:num_iters
        if verbose
            println("Iteration:\t", i)
        end
        try
            assts, x_k = active_set_iteration(assts, x_k, G, c, A, b)

            val = 1/2 * x_k' * G * x_k + c' * x_k
            push!(vals, val)

            if verbose
                println("Resulting x, assts: \n\t", (x_k,assts))
            end
        catch err
            if isa(err, OptimalPointFound)

                println("Optimal point found!")

                if verbose
                    println("Optimal x, assts: \n\t", (x_k,assts))
                end

                return assts, x_k, vals
            else
                println(err)
                throw(err)
            end
        end

    end
    return assts, x_k, vals
end