#
"""
 FWD Pass: NonLin layer -> Lin Layer
 BWD Pass: GD on NonLin layer, Lin layer

"""

using Revise
using diffMesh
import Zygote
using NNlib, Flux, LinearAlgebra, Plots
#--------------------------------------#
np  = 2001 # data points
ng  = 31   # FEM nodes

function udata(x)
    u   = 2 .+ zero(x)
    u .+= @. sin(1*pi*x) * (-1 <= x <= 0)
    u .+= @. sin(4*pi*x) * ( 0 <  x <= 1)
    return u
end
#--------------------------------------#
function addEndPts(xe::AbstractArray
                  ,x0::Number,x1::Number)

    n = length(xe) + 2
    A,R = Zygote.ignore() do
        A = zeros(n,2)
        A[1,1] = 1
        A[n,2] = 1
        R = restrict(n)
        return A,R
    end

    xg = A * [x0,x1] + R'*xe

    return xg
end
#--------------------------------------#
function model(xe,ug,x)

    xg = addEndPts(xe,-1.0,1.0)

    return reluHat(xg,ug,x)
end
#--------------------------------------#
function llsq(xg)

    NN = reluHatFunc(xg)
    A  = NN(xp')'
    linOp(u) = reluHat(NN,u,xp)

#   ug = udata(xg) # wrong   least sq. for mse loss minimization
    ug = A \ up    # correct least sq. for mse loss minimization

    return ug
end
#--------------------------------------#
function loss()
    fp   = model(xe,ug,xp)
    loss = Flux.Losses.mse(fp,up)
    return loss
end
#--------------------------------------#
function makeplot()
    fp = model(xe,ug,xp)

    xg = addEndPts(xe,-1,1)
    NN = reluHatFunc(xg)
#   ug;= llsq(xg) # defined in global scope

    plt = plot()
    plt = plot!(title="Gradient Descent",xlabel="x",ylabel="y")
    plt = plot!(ylims=(-1,4))

    plt = plot!(xp,up,width=3,label="data")
    plt = plot!(xp,fp,width=3,label="prediction")
#   plt = scatter!(xg,ug,width=4,color="red",label=false)

    # hat functions
    NNp = NN(xp')
    for i=1:ng
        scale = ug[i]
#       scale = 1.0
        global plt = plot!(xp,scale .* NNp[i,:]
                          ,width=2,color="black",label=false)
    end

    #savefig(plt,"diffMesh.png")
    display(plt)
    return plt
end
#--------------------------------------#
function callback(;doplot=true)

    l2 = loss()
    fp = model(xe,ug,xp)
    er = norm(up .- fp,Inf)
    println("Error ∞ norm: $er, loss: $l2")

    if(doplot) makeplot() end
    return false
end
#--------------------------------------#

# data
xp = Array(range(-1,stop=1,length=np))
up = udata(xp)

# initialize
xg = sort(2*rand(ng).-1)
ug = rand(length(xg))
#xg = Array(range(-1,stop=1,length=ng))
#ug = udata(xg)

xe = xg[2:end-1] # w/o end points
fp = model(xe,ug,up)

callback(doplot=true)

ps = Flux.params(xe,ug)
data = Iterators.repeated((),100)
opt = ADAM(1e-2)

gs = gradient(()->loss(),ps)

#anim = Animation()
for d in data
    global gs .= gradient(()->loss(),ps)
    Flux.Optimise.update!(opt,ps,gs)
    callback(doplot=true)
    #frame(anim)
end
#gif(anim,"examples/GD.gif",fps=30)
#--------------------------------------#
nothing


 # at the moment these are just Float64, so most of the precision below is lost
 # would be better to make these "Irrational" but that can wait a little as there
 # are worse pieces of the code
 const stieltjes_n = [γ,
                      -0.0728158454836767248605863758749013191377363383, 	# A082633
                      -0.0096903631928723184845303860352125293590658061, 	# A086279
                      +0.0020538344203033458661600465427533842857158044, 	# A086280
                      +0.0023253700654673000574681701775260680009044694, 	# A086281
                      +0.0007933238173010627017533348774444448307315394, 	# A086282
                      -0.0002387693454301996098724218419080042777837151, 	# A183141
                      -0.0005272895670577510460740975054788582819962534, 	# A183167
                      -0.0003521233538030395096020521650012087417291805, 	# A183206
                      -0.0000343947744180880481779146237982273906207895, 	# A184853
                      +0.0002053328149090647946837222892370653029598537, 	# A184854
                      ]

# we should probably implement some code to actually calculate these at least for checking,
# but crude sum is very very slow, and a lookup table is all we really need
# here are some more values if needed http://fredrikj.net/math/stieltjes100k20d.txt

# calculation
# https://hal.archives-ouvertes.fr/hal-01026562/document
# https://mathoverflow.net/questions/105535/how-to-check-numerical-precision-of-my-computation-of-stieltjes-constants
# https://arxiv.org/pdf/1309.2877.pdf
#
#
# Lambert-W for calculations https://github.com/jlapeyre/LambertW.jl

# old values from Ainsworth and Howell
# gamma = [0.57721566490153
#          -0.07281584548368,
#          -0.00969036319287,
#          0.00205383442030,
#          0.00232537006546,
#          0.00079332381728,
#          -0.0002387693455,
#          -0.0005272895671,
#          -0.0003521233539,
#          -0.0000343947747,
#          ]

"""
    stieltjes(n)

 Provides the first 10 Stieltjes (generalized Euler-Mascheroni) constants (see
 Abramowitz and Stegunm, 23.2.5) or [https://en.wikipedia.org/wiki/Stieltjes_constants](https://en.wikipedia.org/wiki/Stieltjes_constants).

 There is a table at "The Generalized Euler-Mascheroni Constants", O.R. Ainsworth and L.W.Howell
 NASA Technical Paper 2264, Jan 1984
[https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19840007812.pdf](https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19840007812.pdf)
 but the OEIS has more accurate values, which will be useful when I get around to
 bit float versions of the code.

 Note that stieltjes(0) = γ, the Euler–Mascheroni constant, also called just Euler's constant.
 [https://en.wikipedia.org/wiki/Euler-Mascheroni_constant](https://en.wikipedia.org/wiki/Euler%E2%80%93Mascheroni_constant)

## Arguments
* `n::Integer`: the number of elements to compute.

## Examples
```jldoctest; setup = :(using Polylogarithms)
julia> stieltjes(0)
0.5772156649015329
```
"""
function stieltjes(n::Integer)
    if n<0
        throw(DomainError(n))
    elseif n>10
        throw(DomainError(n, "Only the first 11 Stieltjes numbers are defined so far."))
    end
    return stieltjes_n[n+1]
end


@testset "4.2 Cosine" begin
    include("4.2.0 (a cos)^m (b trg)^n.jl")
    include("4.2.1.1 (a+b cos)^n.jl")
    include("4.2.1.2 (g sin)^p (a+b cos)^m.jl")
    include("4.2.1.3 (g tan)^p (a+b cos)^m.jl")
    include("4.2.10 (c+d x)^m (a+b cos)^n.jl")
    include("4.2.12 (e x)^m (a+b cos(c+d x^n))^p.jl")
    include("4.2.13 (d+e x)^m cos(a+b x+c x^2)^n.jl")
    include("4.2.2.1 (a+b cos)^m (c+d cos)^n.jl")
    include("4.2.2.2 (g sin)^p (a+b cos)^m (c+d cos)^n.jl")
    include("4.2.2.3 (g cos)^p (a+b cos)^m (c+d cos)^n.jl")
    include("4.2.3.1 (a+b cos)^m (c+d cos)^n (A+B cos).jl")
    include("4.2.4.1 (a+b cos)^m (A+B cos+C cos^2).jl")
    include("4.2.4.2 (a+b cos)^m (c+d cos)^n (A+B cos+C cos^2).jl")
    include("4.2.7 (d trig)^m (a+b (c cos)^n)^p.jl")
    include("4.2.8 (a+b cos)^m (c+d trig)^n.jl")
    include("4.2.9 trig^m (a+b cos^n+c cos^(2 n))^p.jl")
end


#################################
# Markov chains for game theory #
#################################

"""
    game2markov(game::Dict{String,<:Array}, s::Vector{Vector{T}}) where T<:Real

Returns Markov chain of a given game and given vector of action probabilities

**Input parameters**
* `game::Dict{String,<:Array}` - dictionary of players and their payoff matrices
* `s::Vector{Vector{T}}` - collection of actions probabilities for each player
"""
function game2markov(game::Dict{String,<:Array},
                    s::Vector{Vector{T}}) where T<:Real
    # problem: 2 players
    permuts = permutations( vcat(1, fill(0, size(s[1])[1]-1 )) ) |> collect |> unique
    grid_exp = Iterators.product(collect(permuts),collect(permuts)) |> collect
    to_states = Dict(string(i) => collect(grid_exp[i]) for i in 1:length(grid_exp))
    to_states_inv = Dict(collect(grid_exp[i]) => i for i in 1:length(grid_exp))

    trans = Matrix(0I, length(to_states), length(to_states))
    for i in 1:length(to_states)
        indx = Nash._next_state(game, to_states[string(i)], to_states_inv)
        trans[i, indx] = 1/length(indx) # distributed equally when more than 1 best reply
    end

    init = Array(1:length(to_states))
    mc = MarkovChain(trans, init)
    return mc
end

"""
    plot_markov(no_steps::Int, mc::MarkovChain{Int,Array{Int,2},Array{Int,1}})

Plots a path of Markov chain simulation of a given time length and all initial values

**Input parameters**
* `no_steps::Int` - scalar of time length (number of iterations)
* `mc::MarkovChain{Int,Array{Int,2},Array{Int,1}}` - Markov chain of a game (game2markov output function)
"""
function plot_markov(no_steps::Int,
        mc::MarkovChain{Int,Array{Int,2},Array{Int,1}})
    no_states = stationary_distributions(mc)[1] |> length
    for i in 1:no_states
        line = simulate(mc, no_steps, init = i)
        i == 1 ? display(plot(1:no_steps, line, label = string(i), lw = 2)) : display(plot!(line, label = string(i), lw = 2))
    end
end


## General  weight function
## Wheeler algorithm, https://authors.library.caltech.edu/86868/1/1.4822929.pdf
## algorithm not considered, just text

abstract type AbstractWeightFunction{T,N} <: AbstractContinuousOrthogonalPolynomial{T} end
# parent types for `@registerX` constructor
abstract type WeightFunction{T,N} <: AbstractWeightFunction{T,N} end
abstract type WeightFunction1{α,T,N} <: AbstractWeightFunction{T,N} end
export WeightFunction, WeightFunction1

"""
    WeightFunction{T}

A type for orthogonal polynomials relative to some weight
function. The Wheeler or modified Chebyshev algorithm
([Gautschi](https://www.cs.purdue.edu/homes/wxg/Madrid.pdf),
[Press and Teukolsky](https://doi.org/10.1063/1.4822929))
is used to generate the three-term recurrence relation.

If the second order differential equation, `σ⋅p'' + τ⋅p' + λ⋅p=-` is
known, using that to define the polynomial type would be preferred, as
then several additional properties follow for free.

The key computation is the modified moment, `∫πⱼ dw` where `πⱼ` is the
`j`th basis vector for an associated *monic* system, `P`, and `w` is the
weight function.  These values are registered through the
`@register_weight_function(Type, P, w)` macro, as
illustrated in the examples.

#  Example.

Toy example with `ChebyshevU` being derived using the  `Chebyshev` system.

```jldoctest
julia> using Polynomials, SpecialPolynomials

julia> const SP = SpecialPolynomials
SpecialPolynomials

julia> SP.@register0 Toy SP.WeightFunction   # register a  Toy  example

julia> SP.@register_weight_function Toy MonicChebyshev SP.weight_function(ChebyshevU)

julia> [SP.Cn.(Toy, 1:5) SP.Cn.(MonicChebyshevU, 1:5)]
5×2 Array{Float64,2}:
 0.25  0.25
 0.25  0.25
 0.25  0.25
 0.25  0.25
 0.25  0.25
```

Elliptic orthogonal polynomials on  `[-1,1]`. Demo 2 of Gautschi.

```jldoctest WeightFunction
julia> using Polynomials, SpecialPolynomials

julia> const SP=SpecialPolynomials
SpecialPolynomials

julia> N,  ω² = 40, 0.999
(40, 0.999)

julia> w(t) = ((1-ω²*t^2)*(1-t^2))^(-1/2)
w (generic function with 1 method)

julia> SP.@register0 WF SP.WeightFunction

julia> SP.@register_weight_function WF MonicChebyshev w

julia> αs, βs = -SP.Bn.(WF, 0:5), SP.Cn.(WF, 0:5);

julia> [αs βs]
6×2 Array{Float64,2}:
 -1.87309e-15  9.68226
  2.35136e-15  0.793782
 -8.51176e-16  0.119868
 -3.06017e-15  0.22704
 -3.09826e-16  0.241061
  2.37296e-15  0.245429
```

The main computation involved in this is the modified moment, `νⱼ =
∫πⱼ dw`, computed with `QuadGK.quadgk`. For some examples, this
computation can be completed directly and the `modified_moment` method
may be overloaded for the type. This example is from Press and
Teukolsky, where the modified moments are given through the function
`v(j)` defined below.

```jldoctest
julia> using Polynomials, SpecialPolynomials, SpecialFunctions

julia> const SP=SpecialPolynomials
SpecialPolynomials

julia> w(t) = -log(t)
w (generic function with 1 method)

julia> SP.@register0 WF1 SP.WeightFunction   #  register  type WF1 as a weight function

julia> SP.@register_weight_function WF1  MonicShiftedLegendre w

julia> ν(j) = iszero(j) ? 1 : (-1)^j * gamma(j+1)^2 / (j*(j+1)*gamma(2j+1)) # help  out
ν (generic function with 1 method)

julia> SP.modified_moment(::Type{WF1},  j::Int) = ν(j)

julia> αs, βs = -SP.Bn.(WF1, 0:5), SP.Cn.(WF1, 0:5);

julia> [αs βs]
6×2 Array{Float64,2}:
 -0.75      1.0
 -0.381148  0.423611
 -0.504058  0.172646
 -0.516236  0.203166
 -0.517168  0.222419
 -0.513854  0.239667
```

"""
WeightFunction

## We need w, π
## w a function; π a *monic* system (ismonic(P) == true)
weight_function(::Type{<:AbstractWeightFunction}) = throw(ArgumentError("No default method"))
ϟ(P::Type{<:AbstractWeightFunction}) = throw(ArgumentError("No default method"))

Polynomials.domain(W::Type{<:AbstractWeightFunction}) = (domain∘ϟ)(W)
basis_symbol(::Type{<:AbstractWeightFunction}) = "W"

## An,Bn,Cn for the monic orthogonal polynomials for the given weight function
## We  have An=1;  α(n) =  -Bn(n); β(n) = Cn(n)
An(::Type{W}, n) where {W <: AbstractWeightFunction} = one(eltype(W))

function Bn(::Type{W}, k::Int) where {W <:AbstractWeightFunction}

    P = ϟ(W)

    if k == 0
        ν₀, ν₁ =  modified_moment(W, 0), modified_moment(W, 1)
        out = Bn(P, 0) + ν₁ / ν₀
    else
        out = Bn(P, k) - sigma(W, k-1, k)/sigma(W, k-1, k-1)  + sigma(W, k, k+1)/sigma(W, k, k)
    end
    -out

end

function Cn(::Type{W}, k::Int) where  {W <: AbstractWeightFunction}
    iszero(k) && return innerproduct(W, one, one)
    (sigma(W, k, k)/sigma(W, k-1, k-1))
end

@memoize function sigma(W::Type{<:AbstractWeightFunction}, k, l)

    P = ϟ(W)

    T = eltype(W)
    k == -1 && return zero(T)/1
    k == 0  && return modified_moment(W, l) # ν_l = ∫π_l dw
    k > l   && return zero(T)/1

    # otherwise get through recursion (this uses Gautschi's formula, P&T have apparent error
    sigma(W, k-1, l+1) - (Bn(W, k-1) - Bn(P, l)) * sigma(W, k-1, l) - (Cn(W, k-1))*sigma(W, k-2, l) + Cn(P,l)*sigma(W, k-1, l-1)

end

## Compute the modified moment
## v_j =  ∫ π_j ω(x) dx
## (The moment is ∫ x^j ω(x) dx)
## This can  be overridden when an exact calculuation is known, e.g.:
function modified_moment(::Type{W},  j::Int) where {W <: AbstractWeightFunction}
    P = ϟ(W)
    @assert ismonic(P)
    modified_moment(basis(P,j), W)
end

@memoize function modified_moment(πⱼ::AbstractOrthogonalPolynomial, W)
    P = ϟ(W)
    ω = weight_function(W)
    πⱼdω = x -> πⱼ(x) * ω(x)
    return ∫(πⱼdω, P, atol=sqrt(eps(float(eltype(W)))))
end

# integrate fdw over region specified by domain(P)
function ∫(fdw, P; kwargs...)
    dom = domain(P)
    a, b = first(dom), last(dom)
    if first(bounds_types(dom)) == Open
        a += eps(float(one(a)))
    end
    if last(bounds_types(dom)) == Open
        b -= eps(float(one(b)))
    end

    a,err = quadgk(fdw, a, b;  kwargs...)
    a
end


using Test, ARFIMA, Statistics, Random, StatsBase

N = 1000
σ = 2.3

@testset "Size Validation" begin
    x = arfima(N, σ, nothing) # white noise
    @test abs(std(x) - σ) < 1
    @test length(x) == N

    x = arfima(N, σ, nothing, SVector(0.2, 0.2))
    @test length(x) == N
    @test all(!isinf, x)
    x = arfima(N, σ, nothing, SVector(0.2,0.2), SVector(0.1))
    @test length(x) == N
    @test all(!isinf, x)
    x = arfima(N, σ, 1, SVector(0.02,0.02), SVector(0.01))
    @test length(x) == N
    @test all(!isinf, x)
    x = arfima(N, σ, 0.25, SVector(0.02,0.02), SVector(0.01))
    @test length(x) == N
    @test all(!isinf, x)
end

@testset "ARMA" begin
    # white noise
    rng = Random.MersenneTwister(1)
    noise = arma(rng, N, σ, nothing, nothing);
    @test std(noise) ≈ σ atol=0.01

    # AR
    @testset "AR Noise" for φ in [SVector(zeros(1)...), SVector(zeros(3)...), SVector(zeros(5)...)]
        rng = Random.MersenneTwister(1)
        x = arma(rng, N, σ, φ, nothing); # produce white noise
        @test autocor(noise, 1:2) ≈ autocor(x, 1:2) atol=0.05
        @test mean(noise - x) ≈ 0.0 atol=0.05
    end

    rng = Random.MersenneTwister(1)
    φ = 0.8
    ar = arma(rng, 100*N, σ, SVector(φ), nothing);
    @test [φ, φ^2] ≈ autocor(ar, 1:2) atol=0.01

    # MA
    @testset "MA Noise" for θ in [SVector(zeros(1)...), SVector(zeros(3)...), SVector(zeros(5)...)]
        rng = Random.MersenneTwister(1)
        x = arma(rng, N, σ, nothing, θ); # produce white noise
        @test autocor(noise, 1:2) ≈ autocor(x, 1:2) atol=0.05
        @test mean(noise - x) ≈ 0.0 atol=0.05
    end

    rng = Random.MersenneTwister(1)
    θ = 0.96
    ma = arma(rng, 100*N, σ, nothing, SVector(θ));
    @test [-θ/(1+θ^2),0] ≈ autocor(ma, 1:2) atol=0.01

    # ARMA
    @testset "ARMA Noise" for θ in [SVector(zeros(1)...), SVector(zeros(2)...)],
                              φ in [SVector(zeros(1)...), SVector(zeros(3)...), SVector(zeros(5)...)]
        rng = Random.MersenneTwister(1)
        x = arma(rng, N, σ, φ, θ); # produce white noise
        @test autocor(noise, 1:2) ≈ autocor(x, 1:2) atol=0.05
        @test mean(noise - x) ≈ 0.0 atol=0.05
    end

    rng = Random.MersenneTwister(1)
    φ = 0.7
    θ = 0.2
    x = arma(rng, 100*N, σ, SVector(φ), SVector(θ));
    ρ(k) = k == 0 ? (1+φ*θ)*(φ+θ)/(1+2φ*θ+θ^2) : φ*ρ(k-1)
    @test ρ.(1:3) ≈ autocor(x, 1:3) atol=0.05
end


using GeometryExperiments
using Test

const P2 = Point{2,Float64}
const P3 = Point{3,Float64}

@testset "GeometryExperiments.jl" begin
    @testset "Transforms" begin
        @testset "Translations" begin
            tr = Translation(1., 3.)
            tr_inv = inv(tr)
            @test tr ∘ tr_inv == Translation(0., 0.)
            @test tr(Point(1., 2.)) == Point(2., 5.)
        end

        @testset "Scalings" begin
            sc = Scaling(1., 2.)
            sc_inv = inv(sc)
            @test sc ∘ sc_inv == Scaling(1., 1.)
            @test sc(Point(1., 2.)) == Point(1., 4.)

            us = UniformScaling(2.)
            @test us ∘ inv(us) == UniformScaling(1.)
            @test us(Point(1., 2.)) == Point(2., 4.)
        end

        @testset "Rotations" begin
        end

        @testset "Composition" begin
            @test Translation(2., 3.) ∘ Translation(1., 2.) == Translation(3., 5.)
            @test Scaling(2., 3.) ∘ Scaling(1., 2.) == Scaling(2., 6.)

            tr = Translation(1., 2.) ∘ Scaling(1., 2.) ∘ Translation(-1., -2.)
            @test all(transforms(tr) .== [Translation(1., 2.), Scaling(1., 2.), Translation(-1., -2.)])

            tr = Scaling(2., 3.) ∘ Translation(1., 2.)
            @test tr(Point(0., 0.)) == Point(2., 6.)

            p = Point(1., 2.)
            @test (inv(tr) ∘ tr)(p) == p
        end
    end

    @testset "Geometry" begin
        eval_sph(radius, p::Point) = hypot(p...) - radius

        p = Point(0., 1., 0.)

        hc = HyperCube(0.2)
        @test p ∉ hc
        @test Point(0., 0., 0.) ∈ hc
        @test Translated(hc, Translation(0., 0., 0.))(p) == hc(p) == 0.8
        @test Translated(hc, Translation(0.05, 1., 0.))(p) ≈ -0.15
        @test Transformed(hc, Scaling(1., 2., 3.)) === Box(0.2, Scaling(1., 2., 3.))

        sph = HyperSphere(0.2)
        @test p ∉ sph
        @test Point(0., 0., 0.) ∈ sph
        @test sph(Point(0.3, 0.4, 0.5)) == eval_sph(0.2, Point(0.3, 0.4, 0.5))
        @test sph(Point(0.5, 0.2)) == eval_sph(0.2, Point(0.5, 0.2))
        @test Translated(sph, Translation(0., 0., 0.))(p) == sph(p) == 0.8
        @test Translated(sph, Translation(0.05, 1., 0.))(p) ≈ -0.15
        @test p ∈ Translated(sph, Translation(0.05, 1., 0.))

        elps = Ellipsoid(0.2, Scaling(1., 2., 3.))
        @test Scaled(sph, Scaling(1., 2., 3.)) === elps
        @test elps ≈ Ellipsoid(Point(0.2, 0.4, 0.6))
        @test p ∉ elps

        @testset "Projections" begin
            p = Projection{2}(HyperSphere(eval_sph(0., Point(0.2, 0.2))))
            @test Point(0.1,0.1) ∈ p
            @test Point(0.1,2.) ∉ p
            @test Point(0.1,2.,0.) ∉ p
            @test Point(0.1,0.1,0.) ∈ p
            @test Point(0.1,0.1,0.1) ∉ p
            @test p(Point(0.2,0.2)) == 0.
            @test p(Point(0.2,0.2,0.)) == 0.
            @test p(Point(0.2,0.2,0.5)) == 0.5
            @test p(Point(0.2,0.2,0.5,0.3)) == eval_sph(0., Point(0.5,0.3))
        end
    end

    @testset "Point sets" begin
        set = PointSet([Point(0., 0.), Point(1., 0.), Point(0., 1.), Point(1., 1.)])
        set2 = PointSet([Point(0., 0.5), Point(0.5, 0.), Point(0.5, 1.), Point(1., 0.5)])
        @test boundingelement(set) == Translated(Scaled(HyperCube(1.), Scaling(0.5, 0.5)), Translation(0.5, 0.5))
        @test boundingelement(set) == boundingelement(set2)

        set = PointSet([Point(-1., -1.), Point(1., -1.), Point(-1., 1.), Point(1., 1.)])
        @test boundingelement(set) == Translated(Scaled(HyperCube(1.), Scaling(1., 1.)), Translation(0., 0.))
        @test Scaling(1., 1.)(set) == set
        @test Translation(0., 0.)(set) == set
        (Scaling(1., 1.) ∘ Translation(0., 0.))(set) == set

        set = PointSet([Point(0., 0., 0.), Point(1., 0., 0.), Point(0., 1., 0.), Point(0., 0., 1.), Point(1., 1., 0.), Point(1., 0., 1.), Point(0., 1., 1.), Point(1., 1., 1.)])
        @test boundingelement(set) == Translated(Scaled(HyperCube(1.), Scaling(0.5, 0.5, 0.5)), Translation(0.5, 0.5, 0.5))

        @test PointSet(HyperCube, P2) == PointSet(P2[(-1,-1),(1,-1),(-1,1),(1,1)])
        @test PointSet(HyperCube(0.5), P2) == PointSet(P2[(-0.5,-0.5),(0.5,-0.5),(-0.5,0.5),(0.5,0.5)])
        @test PointSet(HyperCube, P3) == PointSet(P3[(-1,-1,-1),(1,-1,-1),(-1,1,-1),(1,1,-1),(-1,-1,1),(1,-1,1),(-1,1,1),(1,1,1)])

        @testset "Nearest" begin
            set = PointSet([Point(0., 1.), Point(0.5, 0.5), Point(1., 0)])
            @test sort_nearest(set, Point(1., 0.75)) == set.points[[2, 3, 1]]
        end
    end

    @testset "Curves" begin
        @testset "Bezier curves" begin
            T = Float64
            b = BezierCurve()
            points = P2[(0,0),(0.5,1),(1,0)]
            @test b(T(0), points) == P2(0,0)
            @test b(T(1), points) == P2(1,0)
            @test b(T(0.5), points) == P2(0.5,0.5)
            @test b(T(0.5), points) == P2(0.5,0.5)
            @test_throws DomainError(T(-0.1), "b(t) is not defined for t outside [0, 1].") b(T(-0.1), points)
            @test_throws DomainError(T(1.2), "b(t) is not defined for t outside [0, 1].") b(T(1.2), points)
        end

        @testset "Patches" begin
            p = Patch(BezierCurve(), 3)
            points = P2[(0,0),(0.5,1),(1,0), (1.5,-1),(2,0)]
            @test p(0, points) == P2(0,0)
            @test p(0.25, points) == P2(0.5, 0.5)
            @test p(0.5, points) == P2(1,0)
            @test p(0.75, points) == P2(1.5, -0.5)
            @test p(1, points) == P2(2,0)
            @test split(points, p) == [@view(points[1:3]), @view(points[3:5])]
            @test curve_points(p, points) == @view points[1:3]
            @test curve_points(p, points, 1) == @view points[3:5]
        end
    end

    @testset "Mesh encodings" begin
        strip = TriangleStrip(1:5)
        list = TriangleList([(1, 2, 3), (2, 4, 3), (3, 4, 5)])
        @test TriangleList(strip) == list

        fan = TriangleFan(1:5)
        list = TriangleList([(1, 2, 3), (1, 3, 4), (1, 4, 5)])
        @test GeometryExperiments.topology_class(typeof(fan)) == Triangle
        @test TriangleList(fan) == list

        @test MeshVertexEncoding(P2[(1.2,1.4),(0.1,0.2),(0.3,0.4),(0.5,0.2)], Triangle) isa MeshVertexEncoding{<:TriangleStrip,P2}
        @test MeshVertexEncoding(fan, P2[(1.2,1.4),(0.1,0.2),(0.3,0.4),(0.5,0.2)]) isa MeshVertexEncoding{<:TriangleFan,P2}

        strip = LineStrip(1:5)
        @test GeometryExperiments.topology_class(typeof(strip)) == Line
        @test LineList(strip) == LineList([(1, 2), (2, 3), (3, 4), (4, 5)])
    end
end


### Linear Interpolation
struct LinearInterpolation{uType,tType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  LinearInterpolation{FT}(u,t) where FT = new{typeof(u),typeof(t),FT,eltype(u)}(u,t)
end
LinearInterpolation(u,t) = LinearInterpolation{true}(u,t)

### Quadratic Interpolation
struct QuadraticInterpolation{uType,tType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  QuadraticInterpolation{FT}(u,t) where FT = new{typeof(u),typeof(t),FT,eltype(u)}(u,t)
end
QuadraticInterpolation(u,t) = QuadraticInterpolation{true}(u,t)

### Lagrange Interpolation
struct LagrangeInterpolation{uType,tType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  n::Int
  LagrangeInterpolation{FT}(u,t,n) where FT = new{typeof(u),typeof(t),FT,eltype(u)}(u,t,n)
end

function LagrangeInterpolation(u,t,n=nothing)
  if n == nothing
    n = length(t) - 1 # degree
  end
  if n != length(t) - 1
    error("Currently only n=length(t) - 1 is supported")
  end
  LagrangeInterpolation{true}(u,t,n)
end

### ZeroSpline Interpolation
struct ZeroSpline{uType,tType,dirType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  dir::Symbol
  ZeroSpline{FT}(u,t,dir) where FT = new{typeof(u),typeof(t),typeof(dir),FT,eltype(u)}(u,t,dir)
end
 ZeroSpline(u,t;dir=:left) = ZeroSpline{true}(u,t,dir)

### QuadraticSpline Interpolation
struct QuadraticSpline{uType,tType,tAType,dType,zType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  tA::tAType
  d::dType
  z::zType
  QuadraticSpline{FT}(u,t,tA,d,z) where FT = new{typeof(u),typeof(t),typeof(tA),
                                                  typeof(d),typeof(z),FT,eltype(u)}(u,t,tA,d,z)
end

function QuadraticSpline(u,t)
  s = length(t)
  dl = ones(eltype(t),s-1)
  d = ones(eltype(t),s)
  du = zeros(eltype(t),s-1)
  tA = Tridiagonal(dl,d,du)
  d = zero(t)
  for i = 2:length(d)
    d[i] = 2//1 * (u[i] - u[i-1])/(t[i] - t[i-1])
  end
  z = tA\d
  QuadraticSpline{true}(u,t,tA,d,z)
end

# Cubic Spline Interpolation
struct CubicSpline{uType,tType,hType,zType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  h::hType
  z::zType
  CubicSpline{FT}(u,t,h,z) where FT = new{typeof(u),typeof(t),typeof(h),typeof(z),FT,eltype(u)}(u,t,h,z)
end

function CubicSpline(u,t)
  n = length(t) - 1
  h = vcat(0, diff(t), 0)
  dl = h[2:n+1]
  d = 2 .* (h[1:n+1] .+ h[2:n+2])
  du = h[2:n+1]
  tA = LinearAlgebra.Tridiagonal(dl,d,du)
  d = zero(t)
  for i = 2:n
    d[i] = 6(u[i+1] - u[i]) / h[i+1] - 6(u[i] - u[i-1]) / h[i]
  end
  z = tA\d
  CubicSpline{true}(u,t,h[1:n+1],z)
end

### BSpline Curve Interpolation
struct BSplineInterpolation{uType,tType,pType,kType,cType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  d::Int    # degree
  p::pType  # params vector
  k::kType  # knot vector
  c::cType  # control points
  pVecType::Symbol
  knotVecType::Symbol
  BSplineInterpolation{FT}(u,t,d,p,k,c,pVecType,knotVecType) where FT =  new{typeof(u),typeof(t),typeof(p),typeof(k),typeof(c),FT,eltype(u)}(u,t,d,p,k,c,pVecType,knotVecType)
end

function BSplineInterpolation(u,t,d,pVecType,knotVecType)
  n = length(t)
  s = zero(eltype(u))
  p = zero(t)
  k = zeros(eltype(t),n+d+1)
  l = zeros(eltype(u),n-1)
  p[1] = zero(eltype(t))
  p[end] = one(eltype(t))

  for i = 2:n
    s += √((t[i] - t[i-1])^2 + (u[i] - u[i-1])^2)
    l[i-1] = s
  end
  if pVecType == :Uniform
    for i = 2:(n-1)
      p[i] = p[1] + (i-1)*(p[end]-p[1])/(n-1)
    end
  elseif pVecType == :ArcLen
    for i = 2:(n-1)
      p[i] = p[1] + l[i-1]/s * (p[end]-p[1])
    end
  end

  lidx = 1
  ridx = length(k)
  while lidx <= (d+1) && ridx >= (length(k)-d)
    k[lidx] = p[1]
    k[ridx] = p[end]
    lidx += 1
    ridx -= 1
  end

  ps = zeros(eltype(t),n-2)
  s = zero(eltype(t))
  for i = 2:n-1
    s += p[i]
    ps[i-1] = s
  end

  if knotVecType == :Uniform
    # uniformly spaced knot vector
    # this method is not recommended because, if it is used with the chord length method for global interpolation,
    # the system of linear equations would be singular.
    for i = (d+2):n
      k[i] = k[1] + (i-d-1)//(n-d) * (k[end]-k[1])
    end
  elseif knotVecType == :Average
    # average spaced knot vector
    idx = 1
    if d+2 <= n
      k[d+2] = 1//d * ps[d]
    end
    for i = (d+3):n
      k[i] = 1//d * (ps[idx+d] - ps[idx])
      idx += 1
    end
  end
  # control points
  N = spline_coefficients(n,d,k,p)
  c = vec(N\u[:,:])
  BSplineInterpolation{true}(u,t,d,p,k,c,pVecType,knotVecType)
end

### BSpline Curve Approx
struct BSplineApprox{uType,tType,pType,kType,cType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  d::Int    # degree
  h::Int    # number of control points (n => h >= d >= 1)
  p::pType  # params vector
  k::kType  # knot vector
  c::cType  # control points
  pVecType::Symbol
  knotVecType::Symbol
  BSplineApprox{FT}(u,t,d,h,p,k,c,pVecType,knotVecType) where FT =  new{typeof(u),typeof(t),typeof(p),typeof(k),typeof(c),FT,eltype(u)}(u,t,d,h,p,k,c,pVecType,knotVecType)
end

function BSplineApprox(u,t,d,h,pVecType,knotVecType)
  n = length(t)
  s = zero(eltype(u))
  p = zero(t)
  k = zeros(eltype(t),h+d+1)
  l = zeros(eltype(u),n-1)
  p[1] = zero(eltype(t))
  p[end] = one(eltype(t))

  for i = 2:n
    s += √((t[i] - t[i-1])^2 + (u[i] - u[i-1])^2)
    l[i-1] = s
  end
  if pVecType == :Uniform
    for i = 2:(n-1)
      p[i] = p[1] + (i-1)*(p[end]-p[1])/(n-1)
    end
  elseif pVecType == :ArcLen
    for i = 2:(n-1)
      p[i] = p[1] + l[i-1]/s * (p[end]-p[1])
    end
  end

  lidx = 1
  ridx = length(k)
  while lidx <= (d+1) && ridx >= (length(k)-d)
    k[lidx] = p[1]
    k[ridx] = p[end]
    lidx += 1
    ridx -= 1
  end

  ps = zeros(eltype(t),n-2)
  s = zero(eltype(t))
  for i = 2:n-1
    s += p[i]
    ps[i-1] = s
  end

  if knotVecType == :Uniform
    # uniformly spaced knot vector
    # this method is not recommended because, if it is used with the chord length method for global interpolation,
    # the system of linear equations would be singular.
    for i = (d+2):h
      k[i] = k[1] + (i-d-1)//(h-d) * (k[end]-k[1])
    end
  elseif knotVecType == :Average
    # NOTE: verify that average method can be applied when size of k is less than size of p
    # average spaced knot vector
    idx = 1
    if d+2 <= h
      k[d+2] = 1//d * ps[d]
    end
    for i = (d+3):h
      k[i] = 1//d * (ps[idx+d] - ps[idx])
      idx += 1
    end
  end
  # control points
  c = zeros(eltype(u), h)
  c[1] = u[1]
  c[end] = u[end]
  q = zeros(eltype(u), n)
  N = zeros(eltype(t), n, h)
  for i = 1:n
    N[i, :] .= spline_coefficients(h,d,k,p[i])
  end
  for k = 2:n-1
    q[k] = u[k] - N[k, 1]*u[1] - N[k, h]*u[end]
  end
  Q = Matrix{eltype(u)}(undef, h-2, 1)
  for i = 2:h-1
    s = 0.0
    for k = 2:n-1
      s += N[k, i]*q[k]
    end
    Q[i-1] = s
  end
  N = N[2:end-1,2:h-1]
  M = transpose(N) * N
  P = M\Q
  c[2:end-1] .= vec(P)
  BSplineApprox{true}(u,t,d,h,p,k,c,pVecType,knotVecType)
end

### Loess
struct Loess{uType,tType,αType,xType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  d::Int
  α::αType
  q::Int
  x::xType
  Loess{FT}(u,t,d,α,q,x) where FT = new{typeof(u),typeof(t),typeof(α),typeof(x),FT,eltype(u)}(u,t,d,α,q,x)
end

function Loess(u,t,d,α)
  n = length(t)
  q = floor(Int,n*α)
  x = Matrix{eltype(t)}(undef,n,d+1)
  x[:,1] .= one(t[1])
  for i = 2:(d+1)
    x[:,i] = t .^ (i-1)
  end
  Loess{true}(u,t,d,α,q,x)
end

### GaussianProcesses
struct GPInterpolation{uType,tType,gpType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  gp::gpType
  GPInterpolation{FT}(u,t,gp) where FT = new{typeof(u),typeof(t),typeof(gp),FT,eltype(u)}(u,t,gp)
end

function GPInterpolation(u,t,m,k,n=-2.0)
  gp = GP(t,u,m,k,n)
  GPInterpolation{true}(u,t,gp)
end

### Curvefit
struct Curvefit{uType,tType,mType,cfType,FT,T} <: AbstractInterpolation{FT,T}
  u::uType
  t::tType
  m::mType
  c_f::cfType
  Curvefit{FT}(u,t,m,c_f) where FT = new{typeof(u),typeof(t),typeof(m),typeof(c_f),FT,eltype(u)}(u,t,m,c_f)
end

function Curvefit(u,t,m,p)
  c_f = curve_fit(m,t,u,p)
  Curvefit{true}(u,t,m,c_f)
end


"""
Solve the nonnegative least-squares problem.

   [X,INFORM] = AS_NNLS(A,B) solves the problem

     minimize_x  1/2 ||Ax-B||_2^2        subject to  x >= 0.

   AS_NNLS(A,B,C) additionally includes a linear term in the objective:

     minimize_x  1/2 ||Ax-B||_2^2 + C'x  subject to  x >= 0.

   AS_NNLS(A,B,C,OPTS) specifies options that can be set using
   AS_SETPARMS.

   AS_NNLS(A,B,C,OPTS,INFORM) uses information stored in INFORM
   (from a previous call to AS_NNLS) to warm-start the
   algorithm. Note that the previous call to AS_NNLS must have been
   to a problem with the same A and C.

   In all cases, the INFORM output argument is optional, and contains
   statistics on the solution process.

   Inputs
   A       is an m-by-n matrix, explicit or an operator.
   B       is an m-vector.
   C       is an n-vector.
   OPTS    is an options structure created using AS_SETPARMS.
   INFORM  is an information structure from a previous call to AS_NNLS.

   Example
   m = 600; n = 2560; k = 20;    % No. of rows, columns, and nonzeros
   p = randperm(n); p = p(1:k);  % Position of nonzeros in x
   x = zeros(n,1);               % Generate sparse nonnegative solution
   x(p) = max(randn(k,1),0);
   A = randn(m,n);               % Gaussian m-by-n ensemble
   b = A*x;                      % Compute the RHS vector
   x = as_nnls(A,b);             % Solve the nonnegative LS problem

   See also `bpdual`.
"""
function as_nnls(A::AbstractMatrix, b::Vector, kwargs...)

    m, n = size(A)
    bl = -Inf*ones(n)
    bu =     zeros(n)
    λ = 1.0

    (xx, r, inform) = bpdual(A, b, λ, bl, bu; kwargs...)

    # BPdual's solution xx only contains nonzero elements.
    # Make it full length.
    x = zeros(n)
    x[inform.active] = xx

    return (x, r, inform)
end # function as_nnls


### A Pluto.jl notebook ###
# v0.12.18

using Markdown
using InteractiveUtils

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

# ╔═╡ 2940cb92-4d76-11eb-3628-493a998f3033
using BenchmarkTools

# ╔═╡ bdb2792e-4d71-11eb-1c5b-03e16bad7067
md"""
# Analysis of Algorithms
Theoretical Study of Program Performance and Resources Usage

**What is more important that Performance ?**
"""

# ╔═╡ 0a158d10-4d72-11eb-3bf5-0ff33f2b9b9d
begin
	hint(text) = Markdown.MD(Markdown.Admonition("hint", "Hint", [text]))
	hint(md"""
	- Correctness
	- Simplicity
	- Maintainability: Modularity,
	- Rapid Prototyping (Programming Time)
	- Robustness and Stability of the software
	- Wider Collection of features and Functionality
	- Security
	- User Friendliness
	- Scalability => related to performance
	""")
end

# ╔═╡ dc8d89be-4d72-11eb-13e8-718f21bc1362
md"""
**Why Study Analysis of Algorithms and Performance though its in bottom of the above list ?**
- Performance related to User Friendliness (too long lag makes it less user friendly), also related to Security
- Performance draws the line between feasible and not feasible. (If its not fast enough or uses too much memory it is not useable) => bring you to cutting edge of entrepreneurship
- Algorithms give language to talk about program behaviour and Quantify the resource measure of the algorithm
- Efficiency is a concern for large scale problems (scalability): Solving Large Problems with Large Inputs
- Cases where the solution might not be really clear on how to do (or even to state clearly) such as Ml, CV, NLP, Playing Games

Examples
- Shortage Path between two points given map of all highways
- Best Assignment of Resources to People or Jobs to machines, Scheduling
- Measuring Similarity of two Documents
- Human Genome
- Social Network in Facebook
"""

# ╔═╡ f3972612-4d73-11eb-3d7a-ff39ee463047
md"""
#### Two functions might produce the same output but have different efficiency?
"""

# ╔═╡ e8a90d58-4d73-11eb-3d34-b93907fef7bc
begin
	function f1(n)
	    for i in 1:2
	        n += 100
		end
	    return n
	end

	function f2(n)
	    for i in 1:100
	        n += 2
		end
	    return n
	end

	f1(10),f2(10)
end

# ╔═╡ 9a9926ca-4d73-11eb-35fb-8529f7e050d4
md"""
## How should we measure the running time of the algorithms?

Experimental Approach
1. Write a program that implements the program
2. Run the program with the datasets of varying size and composition
3. Measure system time to get an accurate measure of the actual running time.

we can use `@time` macro to measure runtime.

System time might not be accurate enough.
- There are multiple process running in the background which leads to random noise in runtime measurements => Take Measurement Multiple Times.
- Compilation time and Runtime might need to be separated.

Popular tool for Julia [BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl) creates a `@benchmarkable` object and uses `tune!` to figure out the params (such as number of samples to evaluate) and even set params by oneself before executing the benchmark trials and collecting samples. One can also run GC before starting a benchmark with `gctrial=true` and before taking samples with `gcsample=true`. Also set time to run with `seconds=300` to prolong the  benchmark time.
"""

# ╔═╡ 9a1431b8-4d73-11eb-3381-8bb9213a52cd
b = @benchmark sum(sin.(cos.(rand(100_000))))

# ╔═╡ 99e369c0-4d73-11eb-1d56-53a3da9bedda
begin
	using Plots
	histogram(b.times)
end

# ╔═╡ f201e03c-4d73-11eb-26d4-3150337daec6
md"""
#### Limitations of experimental study
- need to implement the algorithm before we can test the running time.
- we can only perform experiment on a limited set of inputs, this may not be indicative of running time on different set of data
- for comparison of two algorithms, the same hardware and software environment as well as operating environment needs to be maintained.

Benchmarking on different systems will result in different speeds need a more generalized method to analyze and communicate the performance of the algorithm.
- use high-level description of algorithm (pseudocodes) instead of testing implemented program
- take into account all possible inputs
- evaluate efficiency independent of hardware and software environment as well as tiny implementation details.

"""

# ╔═╡ c32d52c6-4d7a-11eb-3a20-019cdb3c889b
md"""
Pseudocode Convention for low-level Primitive operations.
- Data assignment: use = for assignment
- Arthimetic and logical operations: == for equality, (=,-,*,/) use standard mathematical symbols
- Programming Constructs
    - Conditionals: if ... then ... [else if ... ] [else ...]
    - Loops
        - While ... do ...
        - repeat ... until ...
        - for ... do ...
    - array indexing: A[i], A[i,j]
    - Methods
        - Declaration: **Algorithm** methodname(arg1, arg2)
        - calls: methodname(args)
        - returns: return value

Julia Code is very similar to Pseudocode
"""

# ╔═╡ eb203a78-4d7a-11eb-2e5a-13c68c97a58c
md"""
## Asymptotic Analysis: How does runtime scales with input size ?
Ignore Machine dependent constants, look at the growth of the running $T(n)$ as $n -> \infty$ rather than actually running time.

Three Types of Analysis
- Worst-case (usually): $T(n)$ = max time on any input of size n
- Average-case (sometimes): $T(n)$ = expected time over all inputs of size n. (Expectation needs assumption of statistical distribution of time)
- Best-case (Not useful: eg sorting a sorted array)

"""

# ╔═╡ a3d6e466-4d80-11eb-1d4e-933988883a5b
md"""
## Notations
Bounded From Above

> Big-O : $f(n) = O(g(n)) \text{ or } f \preceq g, \quad \exists N,c \ : \ \forall n \geq N, f(n) \leq c \cdot g(n)$
- eg: $3n^2+5n+2 \leq 3n^2+5n^2+2n^2$ both are $O(n^2)$ as they have the same growth rate. Smaller items are general omited
- eg: $O(\log(n))$ is same for $\log_2(n)$, $\log_{10}(n)$, $\log_x(n)$  base doesn't matter as they only differ by a constant multiple
- since $n^a \prec n^b \quad \forall 0 <a<b : n=O(n^2),\  \sqrt(n) = O(n)$ O(n^2) grows faster
- also $n^a \prec b^n \quad \forall a> 0 b>0 : n^5=O((\sqrt2)^n),\  n^{100} = O(1.1^n)$
- also $(\log n)^a \prec n^b \quad \forall (a,b>0): (\log n)^3 = O(\sqrt(n)), n \log n = O(n^2)$

"""

# ╔═╡ f273f432-4d8d-11eb-0d4b-e330161da74b
md"""

log $(@bind plotlog html"<input type=checkbox >")
sqrt $(@bind plotsqrt html"<input type=checkbox >")
n $(@bind plotn html"<input type=checkbox >")
n logn $(@bind plotnlogn html"<input type=checkbox >")
n² $(@bind plotnsq html"<input type=checkbox >")
2ⁿ $(@bind plot2n html"<input type=checkbox >")

`plotrange = ` $(@bind plotrange html"<input type=range min=2 max=50 step=1 value=10>")
"""

# ╔═╡ 1a95c1d8-4d7b-11eb-2c23-abfd4e673b3c
begin
	#using LaTeXStrings
	plot([n->log(n), n->sqrt(n),n->n, n->n*log(n),n->n^2,n->2^n], 1:0.1:plotrange,label=(["log(n)" "sqrt(n)" "n" "n*log(n)" "n^2" "2^n"]))
end

# ╔═╡ 82abcbc4-4d84-11eb-05e7-b7fdd509052b
md"""
$$\log{n} \prec \sqrt{n} \prec n \prec n \log{n} \prec n^2 \prec 2^n$$
"""

# ╔═╡ 399e9e2a-4d8e-11eb-315a-319497f05e8b
md"""
For function f,g : $\mathbb N \rightarrow \mathbb R^+$ we say that
- Big-Ω: $f(n) = \Omega(g(n)) \text{ or } f \succeq g \text{ if for some }c, f(n) \geq c \cdot g(n)$ f grows slower than g
- Big-Θ:$f(n) = \Theta(g(n)) \text{ or } f \asymp g \text{ if }, f = O(g) \text{ and } f= \Omega(g)$ f grows at the same rate as g
- little-o : $ f(n) = o(g(n)) \text{ or } f \prec g \text{ if } f(n)/g(n) \rightarrow 0 \text{ as } n \rightarrow \infty $ f grows slower than g
"""

# ╔═╡ 0522e1fe-4d90-11eb-32fe-dd3a37e7d498
md"""
Big-O looks only asymptotic detail (useful for very large data) and loses important information about constant multiples. But change by a factor of 100 or 2 is still a big deal. Once we have a good asymptotic run time we can then look into other details such as benchmarking, interaction with memory hierarchy,...
"""

# ╔═╡ 256fd75e-4d73-11eb-0ba3-cd5276a8640c
md"""
## References
- [MIT 6.006 Introduction to Algorithms](https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-006-introduction-to-algorithms-fall-2011/)
- [MIT 6.046 Design and Analysis of Algorithms](https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-046j-design-and-analysis-of-algorithms-spring-2015/)
- [UCSandiegoX DSA](https://www.edx.org/micromasters/ucsandiegox-algorithms-and-data-structures)
- [GTX DSA](https://www.edx.org/professional-certificate/gtx-data-structures-and-algorithms)
- [Algorithms](https://en.wikipedia.org/wiki/Algorithm)

"""

# ╔═╡ Cell order:
# ╟─bdb2792e-4d71-11eb-1c5b-03e16bad7067
# ╟─0a158d10-4d72-11eb-3bf5-0ff33f2b9b9d
# ╟─dc8d89be-4d72-11eb-13e8-718f21bc1362
# ╟─f3972612-4d73-11eb-3d7a-ff39ee463047
# ╠═e8a90d58-4d73-11eb-3d34-b93907fef7bc
# ╟─9a9926ca-4d73-11eb-35fb-8529f7e050d4
# ╠═2940cb92-4d76-11eb-3628-493a998f3033
# ╠═9a1431b8-4d73-11eb-3381-8bb9213a52cd
# ╠═99e369c0-4d73-11eb-1d56-53a3da9bedda
# ╟─f201e03c-4d73-11eb-26d4-3150337daec6
# ╟─c32d52c6-4d7a-11eb-3a20-019cdb3c889b
# ╟─eb203a78-4d7a-11eb-2e5a-13c68c97a58c
# ╠═a3d6e466-4d80-11eb-1d4e-933988883a5b
# ╟─f273f432-4d8d-11eb-0d4b-e330161da74b
# ╟─1a95c1d8-4d7b-11eb-2c23-abfd4e673b3c
# ╟─82abcbc4-4d84-11eb-05e7-b7fdd509052b
# ╠═399e9e2a-4d8e-11eb-315a-319497f05e8b
# ╟─0522e1fe-4d90-11eb-32fe-dd3a37e7d498
# ╟─256fd75e-4d73-11eb-0ba3-cd5276a8640c


######################## Relevance Matching Pursuit ###########################
# equivalent to consecutive runs of forward and backward regression
# δ is the largest marginal increase/decrease in residual norm before
# rmp with outer iterations
function rmp(A::AbstractMatOrFac, b::AbstractVector, δ::Real,
                                    maxiter::Int = 1, x = spzeros(size(A, 2)))
    n = size(A, 1)
    xt = copy(x) # to keep track of changes in x
    P = StepwiseRegression(A, b, x.nzind)
    for i in 1:maxiter
        # foward stage
        for _ in 1:n
            forward_step!(P, x, 0, δ) || break # breaks if no change occured
        end
        !(xt ≈ x) || break # break outer loop if x is not changing anymore
        xt .= x
        # backward stage
        for _ in nnz(x):-1:1
            backward_step!(P, x, Inf, δ) || break # breaks if no change occured
        end
        !(xt ≈ x) || break # break outer loop if x is not changing anymore
        xt .= x
    end
    return x
end

# max_ε is the residual norm tolerance
# δ is the largest marginal increase/decrease in residual norm before
# either forward or backward algorithm terminates
# k is the desired sparsity of the solution
function rmp(A::AbstractMatOrFac, b::AbstractVector,
            k::Int, x = spzeros(size(A, 2)))
    n = size(A, 1)
    P = StepwiseRegression(A, b, x.nzind)
    for _ in 1:n
        forward_step!(P, x, 0, 0) || break
    end
    for _ in nnz(x):-1:k+1
        backward_step!(P, x, Inf, Inf) || break
    end
    return x
end

# see Adaptive Forward-Backward Greedy Algorithm for Sparse Learning with Linear Models
# terminates if algorithm can't decrease residual norm by more than δ
function foba(A::AbstractMatOrFac, b::AbstractVector, δ::Real, x = spzeros(size(A, 2));
                                                            isfast::Val = Val(true))
    n = size(A, 1)
    P = StepwiseRegression(A, b, x.nzind)
    for i in 1:n
        forward_step!(P, x, 0, δ) || break
        max_δ = sqrt(maximum(P.δ²)) # this is the change in residual norm of last forward step
        while backward_step!(P, x, Inf, max_δ/2, isfast) end # FoBa only takes backward steps if they increase the norm by half of decrease of forward algorithm
    end
    return x
end


using Test

using MathOptInterface
const MOI = MathOptInterface
const MOIT = MathOptInterface.Test
const MOIU = MathOptInterface.Utilities
const MOIB = MathOptInterface.Bridges

include("../utilities.jl")

mock = MOIU.MockOptimizer(MOIU.Model{Float64}())
config = MOIT.TestConfig()

@testset "QuadtoSOC" begin
    bridged_mock = MOIB.Constraint.QuadtoSOC{Float64}(mock)
    @testset "Error for non-convex quadratic constraints" begin
        x = MOI.add_variable(bridged_mock)
        @test_throws ErrorException begin
            MOI.add_constraint(bridged_mock,
                               MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Float64}[],
                                                           [MOI.ScalarQuadraticTerm(1.0, x, x)],
                                                           0.0),
                               MOI.GreaterThan(0.0))
        end
        @test_throws ErrorException begin
            MOI.add_constraint(bridged_mock,
                               MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{Float64}[],
                                                           [MOI.ScalarQuadraticTerm(-1.0, x, x)],
                                                           0.0),
                               MOI.LessThan(0.0))
        end
    end
    @testset "Quadratic constraints with 2 variables" begin
        MOIU.set_mock_optimize!(mock,
            (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
                MOI.OPTIMAL,
                (MOI.FEASIBLE_POINT, [0.5, 0.5])
            ),
            (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock,
                MOI.OPTIMAL,
                (MOI.FEASIBLE_POINT, [0.5, (√13 - 1)/4])
            )
        )
        MOIT.solve_qcp_edge_cases(bridged_mock, config)
        ci = first(MOI.get(mock,
                           MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64},
                                                       MOI.RotatedSecondOrderCone}()))
        x, y = MOI.get(mock, MOI.ListOfVariableIndices())
        # The matrix is
        # 2 1
        # 1 2
        # for which the cholesky factorization is U' * U with U =
        # √2 √2/2
        #  . √3/√2
        expected = MOI.VectorAffineFunction{Float64}(MOI.VectorAffineTerm.([3, 3, 4],
                                                                           MOI.ScalarAffineTerm.([√2, √2/2, √3/√2],
                                                                                [x, y, y])),
                                                     [1.0, 1.0, 0.0, 0.0])
        @test MOI.get(mock, MOI.ConstraintFunction(), ci) ≈ expected
    end
    @testset "QCP tests" begin
        MOIU.set_mock_optimize!(mock,
            (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/2, 7/4],
                (MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives)  => [zeros(2)],
                (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone)  => [[0.25, 1.0, -1/√2]]))
        MOIT.qcp1test(bridged_mock, config)
        MOIU.set_mock_optimize!(mock,
            (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [√2],
                (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone)  => [[1/√2, 1/(2 * √2), -1/√2]]))
        MOIT.qcp2test(bridged_mock, config)
        MOIT.qcp3test(bridged_mock, config)
        @testset "Bridge deletion" begin
            ci = first(MOI.get(bridged_mock,
                               MOI.ListOfConstraintIndices{MOI.ScalarQuadraticFunction{Float64},
                                                           MOI.LessThan{Float64}}()))
            test_delete_bridge(bridged_mock, ci, 1, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0),))
        end
        MOIU.set_mock_optimize!(mock,
            (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0],
                (MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone)  => [[1.0, 1/3, -1/√2, -1/√6]]))
        MOIT.qcp4test(bridged_mock, config)
        MOIT.qcp5test(bridged_mock, config)
    end
end


mutable struct ElementaryPathLPSolver <: ElementaryPathSolver
  graph::SimpleDiGraph
  source::Int
  destination::Int

  solver

  # Optimisation model (reused by solve_linear).
  model # ::Model
  x # ::Vector{Variable}: index is edge

  function ElementaryPathLPSolver(solver)
    return new(DiGraph(0), -1, -1, solver, nothing, nothing)
  end
end

function _elementary_path_lazy_callback(solver::ElementaryPathLPSolver, cb_data)
  # Based on the hypothesis that the strengthening constraints have been added beforehand!
  # I.e. at most one edge incoming and at most one outgoing (except for source/destination).
  x = Dict(e => callback_value(cb_data, solver.x[e]) for e in edges(solver.graph))

  # Helper methods.
  inedges(g, v) = (edgetype(g)(x, v) for x in inneighbors(g, v))
  outedges(g, v) = (edgetype(g)(v, x) for x in outneighbors(g, v))

  # Find a subtour.
  current_node = solver.source # TODO: factor this loop out? Also useful for sorting edges.
  while current_node != solver.destination
    # Only one outgoing edge from the current node, as per the constraints.
    for e in outedges(solver.graph, current_node)
      if x[e] >= .5
        x[e] = 0.0
        current_node = dst(e)
        break
      end
    end
  end

  # Find the edges that remain after the path is removed.
  lhs_edges = Edge[]
  for e in edges(solver.graph)
    # Don't consider edges adjacent to the source or the destination
    if src(e) == solver.source || dst(e) == solver.destination
      continue
    end

    if x[e] >= .5
      push!(lhs_edges, e)
    end
  end

  # Nothing left? No subtour!
  if length(lhs_edges) == 0
    return
  end

  # Add lazy constraints (subtour elimination). Separate all subtours to generate constraints
  # as tight as possible.
  while length(lhs_edges) > 0
    # Find one subtour among these edges.
    con_edges = Edge[lhs_edges[1]]
    cur_node = dst(lhs_edges[1])
    while cur_node != src(lhs_edges[1])
      for (i, e) in enumerate(lhs_edges)
        if src(e) == cur_node
          cur_node = dst(e)
          push!(con_edges, e)
          deleteat!(lhs_edges, i)
        end
      end
    end

    # Build the corresponding constraint.
    con = @build_constraint(sum(x[e] for e in con_edges) <= length(con_edges) - 1)
    MOI.submit(solver.model, MOI.LazyConstraint(cb_data), con)
  end
end

function build!(solver::ElementaryPathLPSolver, graph::SimpleDiGraph, source::Int, destination::Int)
  n = nv(graph)
  solver.graph = graph
  solver.source = source
  solver.destination = destination

  # Helper methods.
  inedges(g, v) = (edgetype(g)(x, v) for x in inneighbors(g, v))
  outedges(g, v) = (edgetype(g)(v, x) for x in outneighbors(g, v))

  other_nodes = collect(filter(vertices(solver.graph)) do v; v != source && v != destination; end)

  # Build the optimisation model behind solve_linear.
  solver.model = Model(solver.solver)
  solver.x = @variable(solver.model, [e in edges(solver.graph)], binary=true)
  # Normally, with this formulation, explicitly having binary variables is not necessary,
  # but this is only valid if minimising a linear function. In all other cases, there is
  # no guarantee to have an integer-feasible solution.

  for e in edges(solver.graph)
    set_name(solver.x[e], "x_$(src(e))_$(dst(e))")
  end

  function edge_incidence(v)
    if length(inedges(solver.graph, v)) > 0
      ins = sum(solver.x[e] for e in inedges(solver.graph, v))
    else
      ins = 0.0
    end

    if length(outedges(solver.graph, v)) > 0
      outs = sum(solver.x[e] for e in outedges(solver.graph, v))
    else
      outs = 0.0
    end

    return ins - outs
  end

  @constraint(solver.model, edge_incidence(source) == -1)
  @constraint(solver.model, edge_incidence(destination) == 1)
  @constraint(solver.model, [v in other_nodes], edge_incidence(v) == 0)

  # # Eliminate a large number of subtours.
  @constraint(solver.model, sum(solver.x[e] for e in inedges(solver.graph, source)) == 0)
  @constraint(solver.model, sum(solver.x[e] for e in outedges(solver.graph, source)) == 1)
  @constraint(solver.model, sum(solver.x[e] for e in inedges(solver.graph, destination)) == 1)
  @constraint(solver.model, sum(solver.x[e] for e in outedges(solver.graph, destination)) == 0)
  for v in other_nodes
    @constraint(solver.model, sum(solver.x[e] for e in inedges(solver.graph, v)) <= 1)
    @constraint(solver.model, sum(solver.x[e] for e in outedges(solver.graph, v)) <= 1)
  end

  # Set the lazy-constraint callback to ensure the solution is always an elementary path.
  set_parameter(solver.model, "LazyConstraints", 1) # TODO: only works with Gurobi.
  MOI.set(solver.model, MOI.LazyConstraintCallback(), cb_data -> _elementary_path_lazy_callback(solver, cb_data))
end

has_lp_formulation(::ElementaryPathLPSolver) = true

function get_lp_formulation(solver::ElementaryPathLPSolver, reward::Dict{Tuple{Int, Int}, Float64})
  return solver.model,
    sum(reward[(i, j)] * solver.x[Edge(i, j)] for (i, j) in keys(reward)),
    Dict{Tuple{Int, Int}, JuMP.VariableRef}((i, j) => solver.x[Edge(i, j)] for (i, j) in keys(reward))
end

function _sort_path(path::Vector{Tuple{Int, Int}}, source::Int, destination::Int, n_vertices::Int)
  sorted = Tuple{Int, Int}[]
  current_node = source
  edges = copy(path)

  i = 0
  while length(edges) > 0
    # Find the corresponding edge (or the first one that matches).
    for i in 1:length(edges)
      e = edges[i]
      if e[1] == current_node
        push!(sorted, e)
        current_node = e[2]
        deleteat!(edges, i)
        break
      end
    end

    if current_node == destination
      break
    end

    # Safety: ensure this loop does not make too many iterations.
    i += 1
    if i > n_vertices
      error("Assertion failed: infinite loop when sorting the edges of the path $path")
    end
  end

  if length(edges) > 0
    error("Edges remaining after sorting the edges: the solution is likely to contain at least a subtour, $edges")
  end

  return sorted
end

function solve_linear(solver::ElementaryPathLPSolver, reward::Dict{Tuple{Int, Int}, Float64})
  m, obj, vars = get_lp_formulation(solver, reward)
  @objective(m, Max, obj)

  set_silent(m)
  optimize!(m)

  if termination_status(m) != MOI.OPTIMAL
    return Tuple{Int, Int}[]
  end
  sol = Tuple{Int, Int}[(i, j) for (i, j) in keys(reward) if value(vars[i, j]) > 0.5]
  return _sort_path(sol, solver.source, solver.destination, nv(solver.graph))
end

function solve_budgeted_linear(solver::ElementaryPathLPSolver,
                               reward::Dict{Tuple{Int, Int}, Float64},
                               weight::Dict{Tuple{Int, Int}, T},
                               budget::Int) where {T<:Number} # Handle both Int and Float64
  m, obj, vars = get_lp_formulation(solver, reward)
  @objective(m, Max, obj)

  # Add the budget constraint (or change the existing constraint).
  if :ElementaryPathLP in keys(m.ext)
    budget_constraint = m.ext[:ElementaryPathLP][:budget_constraint]
    set_normalized_rhs(budget_constraint, budget)
  else
    budget_constraint = @constraint(m, sum(weight[i] * vars[i] for i in keys(reward)) >= budget)
    m.ext[:ElementaryPathLP] = Dict(:budget_constraint => budget_constraint)
  end

  set_silent(m)
  optimize!(m)

  if termination_status(m) != MOI.OPTIMAL
    return Tuple{Int, Int}[]
  end

  sol = Tuple{Int, Int}[(i, j) for (i, j) in keys(reward) if value(vars[i, j]) > 0.5]
  return _sort_path(sol, solver.source, solver.destination, nv(solver.graph))
end


"""
Liu S., Yang Y., Forrest J. (2017) Grey Numbers and Their Operations. In: Grey Data Analysis.
Computational Risk Management. Springer, Singapore. https://doi.org/10.1007/978-981-10-1841-1_3
"""
module GreyNumbers

struct GreyNumber{T<:Real}
    a::T
    b::T
    GreyNumber{T}(x::T, y::T) where {T <: Real} = new{T}(min(x, y), max(x, y))
end

GreyNumber(x::T, y::T) where {T<:Real} = GreyNumber{T}(x,y)

GreyNumber()::GreyNumber{Float64} = GreyNumber(0.0, 0.0)

GreyNumber(x::T) where {T <: Real} = GreyNumber{T}(x, x)

GreyNumber{T}(x::GreyNumber{T}) where {T <: Real} = GreyNumber{T}(x.a, x.b)

GreyNumber{T}(x::Bool)  where {T <: Real}= GreyNumber(Float64(x), Float64(x))

Base.transpose(x::GreyNumber{T}) where {T <: Real} = x

Base.adjoint(x::GreyNumber{Float64}) where {T <: Real} = x

Base.copy(x::GreyNumber{T}) where {T <: Real} = GreyNumber{T}(x.a, x.b)

Base.length(g::GreyNumber) = 1

function whitenizate(g::GreyNumber; t::Float64 = 0.5)::Float64
    @assert 0.0 <= t <= 1.0
    return g.a * t + g.b * (1.0 - t)
end

function Base.:+(g1::GreyNumber, g2::GreyNumber)::GreyNumber
    return GreyNumber(g1.a + g2.a, g1.b + g2.b)
end

function Base.:+(g::GreyNumber{T}, k::T)::GreyNumber{T} where T <: Real
    return GreyNumber(g.a + k, g.b + k)
end

function Base.:+(k::T, g::GreyNumber{T})::GreyNumber{T} where T <: Real
    return g + k
end

function Base.:-(g::GreyNumber)::GreyNumber
    minusone = -1 * one(g.a)
    return GreyNumber(minusone * g.b, minusone * g.a)
end

function Base.:-(g1::GreyNumber, g2::GreyNumber)::GreyNumber
    return GreyNumber(g1.a - g2.b, g1.b - g2.a)
end

function Base.:-(g::GreyNumber{T}, k::T)::GreyNumber{T} where T <: Real
    return g - GreyNumber(k, k)
end

function Base.:-(k::T, g::GreyNumber{T})::GreyNumber{T} where T <: Real
    return GreyNumber(k, k) - g
end

function Base.:*(g1::GreyNumber, g2::GreyNumber)::GreyNumber
    v = [g1.a * g2.a, g1.a * g2.b, g1.b * g2.a, g1.b * g2.b]
    return GreyNumber(
        minimum(v),
        maximum(v),
    )
end

function Base.:*(k::T where {T <: Number}, g1::GreyNumber)::GreyNumber
    a = g1.a * k
    b = g1.b * k
    return GreyNumber(min(a, b), max(a,b))
end

function Base.:*(g1::GreyNumber, k::T where {T <: Number})::GreyNumber
    return k * g1
end

function Base.inv(g::GreyNumber)::GreyNumber
    newa = min(inv(g.a), inv(g.b))
    newb = max(inv(g.a), inv(g.b))
    return GreyNumber(newa, newb)
end

function Base.:/(g1::GreyNumber, g2::GreyNumber)::GreyNumber
    #@assert g2.a != 0
    #@assert g2.b != 0
    #@assert g2.a * g2.b > 0
    v = [g1.a / g2.a, g1.a / g2.b, g1.b / g2.a, g1.b / g2.b]
    return GreyNumber(
        minimum(v),
        maximum(v)
    )
end

function Base.:/(k::T where {T <: Real}, g1::GreyNumber)::GreyNumber
    return g1 / GreyNumber(k, k)
end

function Base.:/(g1::GreyNumber, k::T where {T <: Real})::GreyNumber
    return g1 / GreyNumber(k, k)
end

function Base.:^(g::GreyNumber, k::T where {T <: Real})::GreyNumber
    @assert k > 0
    @assert g.a <= g.b
    anew = g.a^k
    bnew = g.b^k
    return GreyNumber(min(anew, bnew), max(anew, bnew))
end

function Base.isless(g1::GreyNumber, g2::GreyNumber)::Bool
    if g1.a < g2.a
        return true
    elseif g1.a == g2.a
        return g1.b < g2.b
    else
        return false
    end
end

function Base.:≤(g1::GreyNumber, g2::GreyNumber)::Bool
    if g1.a <= g2.a
        return true
    elseif g1.a == g2.a
        return g1.b <= g2.b
    else
        return false
    end
end

function Base.isgreater(g1::GreyNumber, g2::GreyNumber)::Bool
    if g1.a > g2.a
        return true
    elseif g1.a == g2.a
        return g1.b > g2.b
    else
        return false
    end
end

function Base.:≥(g1::GreyNumber, g2::GreyNumber)::Bool
    if g1.a >= g2.a
        return true
    elseif g1.a == g2.a
        return g1.b >= g2.b
    else
        return false
    end
end

function Base.isequal(g1::GreyNumber, g2::GreyNumber)::Bool
    return (g1.a == g2.a) && (g1.b == g2.b)
end

function Base.isreal(g::GreyNumber)::Bool
    return isreal(g.a)
end

function Base.isinteger(g::GreyNumber)::Bool
    return isinteger(g.a)
end

function Base.isinf(g::GreyNumber)::Bool
    return isinf(g.a) || isinf(g.b)
end

function Base.iszero(g::GreyNumber)::Bool
    return iszero(g.a) && iszero(g.b)
end

function Base.isone(g::GreyNumber)::Bool
    return isone(g.a) && isone(g.b)
end

function Base.iseven(g::GreyNumber)::Bool
    @assert eltype(g.a) <: Int
    return iseven(g.a) && iseven(g.b)
end

function Base.isodd(g::GreyNumber)::Bool
    @assert eltype(g.a) <: Int
    return isodd(g.a) && isodd(g.b)
end

function Base.sum(g::GreyNumber{T})::T where {T <: Number}
    return g.a + g.b
end

function Base.eltype(g::GreyNumber)::Type
    return eltype(g.a)
end

function Base.iterate(g::GreyNumber, state=1)
    if state == 1
        (g.a, state + 1)
    elseif  state == 2
        (g.b, state + 1)
    else
        nothing
    end
end

function Base.sqrt(g::GreyNumber)::GreyNumber
    return g^0.5
end

function Base.cbrt(g::GreyNumber)::GreyNumber
    return GreyNumber(cbrt(g.a), cbrt(g.b))
end

function Base.abs(g::GreyNumber)::GreyNumber
    return GreyNumber(abs(g.a), abs(g.b))
end

function Base.getindex(g::GreyNumber{T}, index::Int64)::T where T
    @assert index == 1 || index == 2
    if index == 1
        g.a
    else
        g.b
    end
end

function Base.last(g::GreyNumber{T})::T where T
    return g.b
end

function kernel(g::GreyNumber)::Float64
    return (g.a + g.b) / 2.0
end

end # end of module GreyNumbers


import Optim

"""
Compute highest density interval from inverse cdf
icdf is a function handle
Find minimum width interval that contains specified mass

Based on p630 "Doing Bayesian Data Analysis" Kruschke 2010
See also hdiFromSamples
"""
function hdiFromIcdf(icdf::Function, mass::Real=.95)
    widthFn(lower::Real) = icdf(lower+mass)-icdf(lower)
    results = Optim.optimize(widthFn, 0, 1-mass)
    @assert Optim.converged(results)
    lower = results.minimum
    [icdf(lower), icdf(lower+mass)]
end


function x()
	a = 12320657
	b = 9659666
    m = 20201227
    e = 1
    l = 0
    while e != a
        e *= 7
        e %= m
        l += 1
    end
    k = 1
    for i=1:l
        k *= b
        k %= m
    end
    println(k)
end


"""
    capacitor(freq, C)
Create a `CircuitData` for a capacitor of capacitance `C` at frequencies
specified by `freq`. It uses the formula Z(ω) = 1/(jωC) where ω=2πf.
"""
capacitor(freq::Real, C::Number) = Impedance(1/(im * (2π * freq) * C))
capacitor(freq::AbstractVector{T}, C::Number) where {T<:Real} =
    CircuitData(freq, [capacitor(f, C) for f in freq])

"""
    inductor(freq, C)
Create a `CircuitData` for an inductor of inductance `L` at frequencies
specified by `freq`. It uses the formula Z(ω) = jωL where ω=2πf.
"""
inductor(freq::Real, L::Number) = Impedance(im * (2π * freq) * L)
inductor(freq::AbstractVector{T}, L::Number) where {T<:Real} =
    CircuitData(freq, [inductor(f, L) for f in freq])

"""
    resistor(freq, R)
Create a `CircuitData` for a resistor of resistance `R` at frequencies
specified by `freq`. It uses the formula Z(ω) = R where ω=2πf.
"""
resistor(freq::Real, R::Number) = Impedance(R)
resistor(freq::AbstractVector{T}, R::Number) where {T<:Real} =
    CircuitData(freq, [Impedance(R) for f in freq])


include("kepler.jl")

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

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


include("../src/DelaunayTessellationFieldEstimator.jl")
using DelaunayTessellationFieldEstimator
using Plots

Coords = rand(0.0:500.0,10,2)

tess = DelaunayTessellation()

C = Coordinates(Coords)

P = BuildDelaunay!(tess,C.GeometryCoordinates)
x,y = VoronoiDelaunay.getplotxy(delaunayedges(tess))
plot(x,y)
scatter!(C.GeometryCoordinates[:,1],C.GeometryCoordinates[:,2])

t = FindTriangles(tess,P[1])
A = ContiguousVoronoiCellArea(t)

# yet to get a triangle counter
(A*GeomToDataAreaScalingFactor(C))

u=UniquePoints(t)
DelaunayVertexDensity(tess,P[1])

length(u)/(A*GeomToDataAreaScalingFactor(C))

DelaunayVertexDensity(tess,C)


using FiniteDiff
using Dolo

@testset "Test the G derivative w.r.t. x and μ" begin

    model_rbc_mc = yaml_import("examples/models/rbc_mc.yaml")
    model_rbc = yaml_import("examples/models/rbc.yaml")
    model_rbc_iid = yaml_import("examples/models/rbc_iid.yaml")
    for model in [model_rbc_mc, model_rbc, model_rbc_iid]

        sol = Dolo.improved_time_iteration(model)

        G = Dolo.distG(model, sol)

        x0_not_flat = G.x0
        x0_flat = cat(G.x0.data...; dims = 1)

        μ0 = G.μ0

        for x0 in [x0_not_flat, x0_flat]

            μ1, ∂G_∂μ, ∂G_∂x = G(μ0, x0, diff = true)

            Jμ_exact = convert(Matrix, ∂G_∂μ)
            Jμ_num = FiniteDiff.finite_difference_jacobian(mu -> G(mu, x0), μ0)

            Jx_exact = convert(Matrix, ∂G_∂x)
            Jx_num = FiniteDiff.finite_difference_jacobian(x -> G(μ0, x), x0)

            @assert maximum(abs, Jμ_num - Jμ_exact) < 1e-8

            @assert maximum(abs, Jx_num - Jx_exact) < 1e-8
        end

    end

end


using IonSim: _alaguerre
export two_ion_ms, rabi_flop

"""
    two_ion_ms(tspan, Ω::Real, ν::Real, δ::Real, η::Real, n̄::Real)
[ref](https://doi.org/10.1103/PhysRevA.62.022311)

Assumes vibrational mode starts in a thermal state with: ``\\langle a^\\dagger a\\rangle = n̄``
and ions start in doubly ground state. Returns `(ρgg, ρee)`, the population in the doubly
ground and doubly excited state, respectively. ``[Ω], [ν], [δ] = Hz``
"""
function two_ion_ms(tspan, Ω::Real, ν::Real, δ::Real, η::Real, n̄::Real)
    ρgg = Float64[]; ρee = Float64[]
    n̄ *= 1.0; Ω *= 2π; ν *= 2π; δ *= 2π
    for t in tspan
        F = -√2 * η * Ω * sin((ν - δ) * t) / (ν - δ)
        G =  -√2 * η * Ω * (1 - cos((ν - δ) * t)) / (ν - δ)
        A = -((η * Ω)^2 / (ν - δ)) * (t - (1/(2 * (ν - δ))) * sin(2 * (ν - δ) * t))
        term1 = (1/2) * exp(-(F^2 + G^2)/4)
        term2 = (1/8) * exp(-(F^2 + G^2))
        ρee_t = 0.0
        ρgg_t  = 0.0
        for n in 0:200
            pn = _Pn(n̄, n)
            isnan(pn) && continue
            t1_1 = _laguerre((F^2 + G^2) / 2, n) * cos(A + (1/2) * F * G) * term1
            t2_2 = _laguerre(2 * (F^2 + G^2), n) * term2
            ρgg_t += pn * (t1_1 + t2_2)
            ρee_t += pn * (-t1_1 + t2_2)
        end
        push!(ρgg, ρgg_t)
        push!(ρee, ρee_t)
    end
    3/8 .+ ρgg, 3/8 .+ ρee
end

"""
    rabi_flop(tspan, Ω::Real, η::Real, n̄::Real; s::Int=0)

Single ion rabi flop. Returns:
``\\sum_{n=0}^∞ p_n sin^2(\\Omega_n t)``
 with
``\\Omega_n = Ωe^{-η^2/2}η^s\\sqrt{\\frac{n!}{(n+s)!}}L_{n}^{s}(η^2)``

where ``s`` is the order of the (blue) sideband that we are driving and ``L_{n}^{s}`` is the
associated Laguerre polynomial. [ref](https://doi.org/10.1103/RevModPhys.75.281)

"""
function rabi_flop(tspan, Ω::Real, η::Real, n̄::Real; s::Int=0)
    n̄ *= 1.0; Ω *= 2π
    p = Vector{Float64}(undef, 0)
    for t in tspan
        pi = 0.0
        for n in 0:300
            pi += _Pn(n̄, n) * sin(Ω/2 * exp(-η^2/2) * η^s * sqrt(1/prod(n+1:n+s)) * _alaguerre(η^2, n, s) * t)^2
        end
        push!(p, pi)
    end
    p
end

function _laguerre(x, n)
    L = 1.0, -x + 1
    if n < 2
        return L[n+1]
    end
    for i in 2:n
        L = L[2], ((2i - 1 - x) * L[2] - (i - 1) * L[1]) / i
    end
    L[2]
end

_Pn(n̄::Real, n::Int) = (n̄ / (n̄ + 1))^n / (n̄ + 1)


module Points

export Point

using ..Elements
import ..Motions: induce_velocity, mutually_induce_velocity!, self_induce_velocity!, advect,
                  allocate_velocity

#== Type definition ==#

"""
    Point{T,R} <: Elements.Element

An immutable structure representing a point source/vortex

## Fields

- `z::ComplexF64`: position
- `S::T`: strength/circulation
"""
struct Point{S <: Number,R <: Real} <: Element
    z::Complex{R}
    S::Union{R,Complex{R}}
    Point{S}(z::Complex{R}, s::T)           where {S <: Complex, T <: Real, R <: Real} = (U = promote_type(R,T); new{Complex{U},U}(z, im*convert(U,s)))
    Point{S}(z::Complex{R}, s::Complex{T})  where {S <: Complex, T <: Real, R <: Real} = (U = promote_type(R,T); new{Complex{U},U}(z, convert(Complex{U},s)))
    Point{S}(z::Complex{R}, s::T)           where {S <: Real, T <: Real, R <: Real}    = (U = promote_type(R,T); new{U,U}(z, convert(U,s)))
end
# struct Point{T <: Number, R <: Number} <: Element
#     z::Complex{R}
#     S::T
#     Point{T,R}(z, s::Real) where {T <: Complex,R <: Number} = new(z, im*s)
#     Point{T,R}(z, s)       where {T <: Complex,R <: Number} = new(z, s)
#     Point{T,R}(z, s::Real) where {T <: Real, R <: Number} = new(z, s)
# end

#Point(z::Complex{R},S::T) where {T,R} = Point{T,R}(z,S)

Elements.kind(::Point) = Singleton
Elements.kind(::Type{Point{T,R}}) where {T,R} = Singleton

Elements.property_type(::Point{T,R}) where {T,R} = R
Elements.property_type(::Type{Point{T,R}}) where {T,R} = R

#Elements.promote_property_type(::Point{T,R}) where {T,R} = promote_type(T,R)
#Elements.promote_property_type(::Type{Point{T,R}}) where {T,R} = promote_type(T,R)

#== Methods to be extended ==#

Elements.position(p::Point) = p.z
Elements.blobradius(p::Point) = 0.0

Elements.streamfunction(z::Complex{T}, p::Point) where {T} = real(-0.5p.S*log(z - p.z)/π)

Elements.complexpotential(z::Complex{T}, p::Point) where {T} = -0.5im*p.S*log(z - p.z)/π

cauchy_kernel(z) = z != zero(z) ? 0.5im/(π*conj(z)) : zero(z)

# ensures that the velocity is of same type as position
allocate_velocity(v::Vector{Point{T,R}}) where {T,R} = zeros(Complex{R},length(v))

function induce_velocity(z::Complex{T}, p::Point, t) where {T}
    p.S'*cauchy_kernel(z - p.z)
end

function mutually_induce_velocity!(ws₁, ws₂,
                                   points₁::Vector{Point{T₁}},
                                   points₂::Vector{Point{T₂}}, t) where {T₁, T₂}
    for (s, source) in enumerate(points₁)
        for (t, target) in enumerate(points₂)
            K = cauchy_kernel(target.z - source.z)
            ws₂[t] += source.S'*K
            ws₁[s] -= target.S'*K
        end
    end
    nothing
end

function self_induce_velocity!(ws, points::Vector{Point{T,R}}, t) where {T,R}
    N = length(points)

    for s in 1:N, t in s+1:N
        K = cauchy_kernel(points[t].z - points[s].z)
        ws[t] += points[s].S'*K
        ws[s] -= points[t].S'*K
    end
    ws
end

function advect(p::Point{T}, w::ComplexF64, Δt::Float64) where {T}
    Point{T}(p.z + w*Δt, p.S)
end

#function Base.show(io::IO, p::Point)
#    print(io, "Vortex.Point(z = $(round(p.z, digits=3)), Γ = $(round(p.Γ, digits=3)))")
#end

end


hurst_indes(Ra)

pain_index(Ra)

smoothing_index(Ra) 0.9825174 # R's PerformanceAnalytics result

ulcer_index(Ra) => 1.128081 # R's PerformanceAnalytics result


using MultivariateStats
using Base.Test

if !isdefined(Base, :normalize)
    normalize(x) = x/norm(x)
end

#Test equivalence of eigenvectors/singular vectors taking into account possible
#phase (sign) differences
#This may end up in Base.Test; see JuliaLang/julia#10651
function test_approx_eq_vecs{S<:Real,T<:Real}(a::StridedVecOrMat{S},
    b::StridedVecOrMat{T}, error=nothing)
    n = size(a, 2)
    @test n==size(b, 2) && size(a, 1)==size(b, 1)
    error==nothing && (error=n^3*(eps(S)+eps(T)))
    for i=1:n
        ev1, ev2 = a[:, i], b[:, i]
        deviation = min(abs(norm(ev1-ev2)), abs(norm(ev1+ev2)))
        if !isnan(deviation)
            @test_approx_eq_eps deviation 0.0 error
        end
    end
end

srand(34568)

## prepare data

d = 5
ns = [10, 15, 20]
nc = length(ns)
n = sum(ns)
Xs = Matrix{Float64}[]
ys = Vector{Int}[]
Ss = Matrix{Float64}[]
cmeans = zeros(d, nc)

for k = 1:nc
	R = qr(randn(d, d))[1]
	nk = ns[k]

	Xk = R * Diagonal(2 * rand(d) + 0.5) * randn(d, nk) .+ randn(d)
	yk = fill(k, nk)
	uk = vec(mean(Xk, 2))
	Zk = Xk .- uk
	Sk = Zk * Zk'

	push!(Xs, Xk)
	push!(ys, yk)
	push!(Ss, Sk)
	cmeans[:,k] = uk
end

X = hcat(Xs...)
y = vcat(ys...)
mv = vec(mean(X, 2))
Sw = zeros(d, d)
for k = 1:nc
	Sw += Ss[k]
end

Sb = zeros(d, d)
for k = 1:nc
	dv = cmeans[:,k] - mv
	Sb += ns[k] * (dv * dv')
end

Z = X .- mv
Sa = Z * Z'
@test_approx_eq Sw + Sb Sa

@assert size(X) == (d, n)
@assert size(y) == (n,)

## Stats

S = multiclass_lda_stats(nc, X, y)

@test S.dim == d
@test S.nclasses == nc

@test classweights(S) == ns
@test_approx_eq classmeans(S) cmeans
@test_approx_eq mean(S) mv

@test_approx_eq withclass_scatter(S) Sw
@test_approx_eq betweenclass_scatter(S) Sb

## Solve

emax = maximum(eigvals(Sw))
Sw_c = copy(Sw)
Sb_c = copy(Sb)

lambda = 1.0e-3
Sw_r = Sw + (lambda * emax) * eye(d)

P1 = mclda_solve(Sb, Sw, :gevd, nc-1, lambda)
@test_approx_eq P1' * Sw_r * P1 eye(nc-1)
U = Sb * P1
V = Sw_r * P1
# test whether U is proportional to V,
# which indicates P is the generalized eigenvectors
@test_approx_eq U V*Diagonal(vec(mean(U./V, 1)))

P2 = mclda_solve(Sb, Sw, :whiten, nc-1, lambda)
@test_approx_eq P2' * Sw_r * P2 eye(nc-1)

test_approx_eq_vecs(P1, P2)

## LDA

M = fit(MulticlassLDA, nc, X, y; method=:gevd, regcoef=lambda)
@test indim(M) == d
@test outdim(M) == nc - 1
@test_approx_eq projection(M) P1
@test_approx_eq M.pmeans M.proj'cmeans
@test_approx_eq transform(M, X) M.proj'X

M = fit(MulticlassLDA, nc, X, y; method=:whiten, regcoef=lambda)
@test indim(M) == d
@test outdim(M) == nc - 1
# @test_approx_eq projection(M) P2  # signs may change
@test_approx_eq M.pmeans M.proj'cmeans
@test_approx_eq transform(M, X) M.proj'X

## High-dimensional LDA (subspace LDA)

# Low-dimensional case (no singularities)

dX = randn(5,1500);
# 3 groups of 500 with zero mean
for i = 0:500:1000
    dX[:,(1:500)+i] = dX[:,(1:500)+i] .- mean(dX[:,(1:500)+i],2)
end
centers = [zeros(5) [10.0;zeros(4)] [0.0;10.0;zeros(3)]]
X = [dX[:,1:500].+centers[:,1] dX[:,501:1000].+centers[:,2] dX[:,1001:1500].+centers[:,3]]
label = [fill(1,500); fill(2,500); fill(3,500)]
M = fit(SubspaceLDA, X, label)
@test indim(M) == 5
@test outdim(M) == 2
@test_approx_eq mean(M) vec(mean(centers,2))
@test_approx_eq classmeans(M) centers
@test classweights(M) == [500,500,500]
x = rand(5)
@test_approx_eq transform(M, x) projection(M)'*x
dcenters = centers .- mean(centers,2)
Hb = dcenters*sqrt(500)
Sb = Hb*Hb'
Sw = dX*dX'
# When there are no singularities, we need to solve the "regular" LDA eigenvalue equation
proj = projection(M)
@test_approx_eq Sb*proj Sw*proj*Diagonal(M.λ)
@test_approx_eq proj'*Sw*proj eye(2,2)

# also check that this is consistent with the conventional algorithm
Mld = fit(MulticlassLDA, 3, X, label)
for i = 1:2
    @test_approx_eq abs(dot(normalize(proj[:,i]), normalize(Mld.proj[:,i]))) 1
end

# High-dimensional case (undersampled => singularities)
X = randn(10^6, 9)
label = rand(1:3, 9)
M = fit(SubspaceLDA, X, label)
centers = M.cmeans
for i = 1:3
    flag = label.==i
    @test_approx_eq centers[:,i] mean(X[:,flag], 2)
    @test M.cweights[i] == sum(flag)
end
dcenters = centers .- mean(X, 2)
Hb = dcenters*Diagonal(sqrt(M.cweights))
Hw = X - centers[:,label]
@test_approx_eq (M.projw'*Hb)*(Hb'*M.projw)*M.projLDA (M.projw'*Hw)*(Hw'*M.projw)*M.projLDA*Diagonal(M.λ)
Gw = projection(M)'*Hw
@test_approx_eq Gw*Gw' eye(2,2)

# Test that nothing breaks if one class has no members
label[1:4] = 1
label[5:9] = 3  # no data points have label == 2
M = fit(SubspaceLDA, X, label)
centers = M.cmeans
dcenters = centers .- mean(X, 2)
Hb = dcenters*Diagonal(sqrt(M.cweights))
Hw = X - centers[:,label]
@test_approx_eq (M.projw'*Hb)*(Hb'*M.projw)*M.projLDA (M.projw'*Hw)*(Hw'*M.projw)*M.projLDA*Diagonal(M.λ)


# By replacing the 1st digit of the 2-digit number *3, it turns out that six of
# the nine possible values: 13, 23, 43, 53, 73, and 83, are all prime.
#
# By replacing the 3rd and 4th digits of 56**3 with the same digit, this
# 5-digit number is the first example having seven primes among the ten
# generated numbers, yielding the family: 56003, 56113, 56333, 56443, 56663,
# 56773, and 56993. Consequently 56003, being the first member of this family,
# is the smallest prime with this property.
#
# Find the smallest prime which, by replacing part of the number (not
# necessarily adjacent digits) with the same digit, is part of an eight prime
# value family.

using ProjectEulerSolutions

# Use Seive to get set of primes, then make a list of possible indexes to
# substitute (we only go up to 6 digits).  Sustitute with new digits and cycle
# through testing for primality until a set of 8 occurs.
function p051solution(n::Integer=100)::Integer

    primes = sieve_eratosthenes(n)
    primeset = Set(primes)
    maxnumprimes = 0
    minprime = n
    checked = Set{Integer}()
    for p in primes
        # Don't need to check primes that have already been tested
        if p in checked
            continue
        end
        numdigs = max(2, ndigits(p))
        mult = (10 .^ (0:numdigs-1))
        # Can't change an even number of digits
        if numdigs == 2
            ss = [[2]]
        elseif numdigs == 3
            ss = [[2], [3]]
        elseif numdigs == 4
            ss = [[2], [3], [4], [2,3,4]]
        elseif numdigs == 5
            ss = [[2], [3], [4], [5], [2,3,4], [2,3,5], [2,4,5]]
        elseif numdigs == 6
            ss = [[2], [3], [4], [5], [6], [2,3,4], [2,3,5], [2,3,6], [2,4,5], [2,4,6], [2,5,6], [2,3,4,5,6]]
        end
        if p > 10
            # Don't use the "last" digit since it would have to include even
            # numbers, which aren't prime
            for s in ss
                d = digits(p)
                numprimes = 0
                minp = 0
                for j = 0:9
                    d[s] .= j
                    newp = sum(d .* mult)
                    if isprime(newp)
                        push!(checked, newp)
                        numprimes += 1
                        if minp == 0
                            minp = newp
                        end
                    end
                end
                if numprimes == 8
                    return minp
                end
            end
        end
    end
    return -1
end

p051 = Problems.Problem(p051solution)

Problems.benchmark(p051, 1_000_000)


using Test
using Revise
using LinearAlgebra
using MFGnet
using Printf

d = 3
m1 = 4
m2 = 6
m3 = 8
nex = 10;
L = SingleLayer()
N = NN([L;L;L])

Rs = [Float64,Float32]

for k = 1:length(Rs)
    R = Rs[k]
    s = randn(R,d,nex)
    K1 = randn(R,m1,d)
    b1 = randn(R,m1)
    Θ1 = (K1,b1)
    K2 = randn(R,m2,m1)
    b2 = randn(R,m2)
    Θ2 = (K2,b2)
    K3 = randn(R,m3,m2)
    b3 = randn(R,m3)
    Θ3 = (K3,b3)
    w = randn(R,m3)
    Θ = (Θ1,Θ2,Θ3)

    @testset "derivative check, mult. dispatch test $(Rs[k]), and trace" begin
    z = N(s,Θ)
    @test eltype(z) == R
    Φ = w'*z
    ds = randn(R,size(s))
    dΦ,d2Φ = getGradAndHessian(N,w,s,Θ)
    @test eltype(dΦ) == R
    dΦds = sum(ds.*dΦ,dims=1)

    d2Φ2 = MFGnet.getJSJSTmv(N,w,s,Θ)
    curv = zeros(R,1,nex)
    for j=1:nex
        curv[1,j] = ds[:,j]'*d2Φ[:,:,j]*ds[:,j]
    end
    MFGnet.getJSTmv(N,w,s,Θ)
    d2Φ2dt = MFGnet.getHessmv(N,ds,s,Θ)
    curvt = sum(ds.*d2Φ2dt,dims=1)

    trH = MFGnet.getTraceHess(N,s,Θ)
    trHt, = MFGnet.getTraceHessAndGrad(N,s,Θ)
    @test norm(trH-trHt)/norm(trH) < sqrt(eps(R))
    for j=1:nex
        @test abs(trH[j] - tr(d2Φ[:,:,j]))/abs(tr(d2Φ[:,:,j])) < sqrt(eps(R))
    end

    for j=1:15
        h = R(2.0^(-j))
        Φt = w'*N(s + h .* ds,Θ)

        E0 = norm(Φ-Φt)
        E1 = norm(Φ+h*dΦds - Φt)
        E2 = norm(Φ+h*dΦds+0.5*h^2*curv - Φt)

        @printf("h=%1.3e\t\tE0=%1.3e\tE1=%1.3e\tE1=%1.3e\n",h,E0,E1,E2)
    end

    d2 = d
    W = randn(R,m3,d2,nex)
    JTW = MFGnet.getJSTmv(N,W,s,Θ)
    @test eltype(JTW) == R

    # test JSJSTmv
    dZ = w*ones(R,1,nex)
    @test eltype(d2Φ[1])==R
    @test eltype(d2Φ2[1])==R
    @test norm(vec(d2Φ) - vec(d2Φ2[1])) <= sqrt(eps(R))

end
end


#### Problem generation ####

m=[2,3,4,5,6]; # Problem size
I=10.*m; #number of costumers
J=m+1; # number of service facilities
K=3(m+1) #number of potential locations for defence facilities

function Ecualidian_distanse(x1::Float64,x2::Float64,y1::Float64,y2::Float64)
    dist=((x1-x2)^2+(y1-y2)^2)^(1/2)
    return dist
end

# customer locations
customer_location=zeros(I[1],2)
for i=1:I[1]
    customer_location[i,1]=1000*rand()
    customer_location[i,2]=1000*rand()
end

#service locations
service_location=zeros(J[1],2)
for j=1:J[1]
    service_location[j,1]=1000*rand()
    service_location[j,2]=1000*rand()
end

#potential defence positions
pdef_location=zeros(K[1],2)
for k=1:K[1]
    pdef_location[k,1]=1000*rand()
    pdef_location[k,2]=1000*rand()
end

distance_cus_ser=zeros(I[1],J[1])# distance between customer i and service facility j

for i=1:I[1]
    for j=1:J[1]
        dist=Ecualidian_distanse(customer_location[i,1],service_location[j,1],customer_location[i,2],service_location[j,2])
        distance_cus_ser[i,j]=dist
    end
end

distance_ser_pdef=zeros(J[1],K[1]) # distance between service facility j and potential position for defence facilities
for j=1:J[1]
    for k=1:K[1]
        dist=Ecualidian_distanse(service_location[j,1],pdef_location[k,1],service_location[j,2],pdef_location[k,2])
        distance_ser_pdef[j,k]=dist
    end
end

customer_demand=zeros(1,I[1]) # defining customer demand
for i=1:I[1]
    customer_demand[i]=rand(collect(5:5:100))
end

def_pos_cost=zeros(1,K[1]) # cost of stablishing a defence facility at position k
for k=1:K[1]
    def_pos_cost[k]=rand(collect(1000:1000:20000))
end

attacker_buddet=2; # attacker budget ### Must change based on  the paper
 r=60  #range of defence facilities

 #service facilities capacity
 ser_cap=zeros(1,J[1]);
 q_p_vec=zeros(1,J[1]);
for j=1:J[1]
    q_p_vec[j]=rand([1,m[1]+1])
end
su=(1+rand())*sum(customer_demand)
for j=1:J[1]
    ser_cap[j]=(su*q_p_vec[j])/sum(q_p_vec)
end
for j=1:J[1]
    a=rem(ser_cap[j],20)
    ser_cap[j]=ser_cap[j]-a
end

#defining probablity of defence
prob_matrix=zeros(size(distance_ser_pdef))
for j=1:J[1]
    p_l=0
    p_u=0
    for k=1:K[1]
        if 0<distance_ser_pdef[j,k]<=r
            p_u=0.95
        else
            p_u=0.15
        end
        if distance_ser_pdef[j,k]>r
            p_l=0.15
        else
            p_l=0.95
        end
        prob_matrix[j,k]=p_l+(((p_u-p_l)*(r-distance_ser_pdef[j,k]))/r)
    end
end

CS=2 #unit shipment cost of demand per unit distance
CO=500 # unit outsourcing cost of demand (independent of distance)


"""
- x0: [gene, P2_gene, RNA, P, P2]
- k1: repression binding rate
- k1r: reverse repression binding rate
- k2: transcription rate
- k3: translation rate
- k4: dimerization rate
- k4r: dissociation rate
- k5: RNA degradation
- k6: protein degradation rate
"""
function neg_autoreg(;x0 = [10, 0, 0, 0, 0], k1=1.0, k1r=10.0, k2=0.01, k3=10.0, k4=1.0, k4r=1.0, k5=0.1, k6=0.01)
  m = Network("auto-regulation")

  m <= Species("gene",    x0[1])
  m <= Species("P2_gene", x0[2])
  m <= Species("RNA",     x0[3])
  m <= Species("P",       x0[4])
  m <= Species("P2",      x0[5])

  m <= Reaction("repression binding",         k1,  "gene + P2 --> P2_gene")
  m <= Reaction("reverse repression binding", k1r, "P2_gene --> gene + P2")
  m <= Reaction("transcription",              k2,  "gene --> gene + RNA")
  m <= Reaction("translation",                k3,  "RNA --> RNA + P")
  m <= Reaction("dimerization",               k4,  "P + P --> P2")
  m <= Reaction("dissociation",               k4r, "P2 --> P + P")
  m <= Reaction("RNA degradation",            k5,  "RNA --> 0")
  m <= Reaction("protein degradation",        k6,  "P --> 0")

  return m
end


using HorizonSideRobots
include("../basics.jl")

md"""
            На прямоугольном поле произвольных размеров расставить маркеры в виде "шахматных" клеток, начиная с юго-западного угла поля, когда каждая
            отдельная "шахматная" клетка имеет размер n x n клеток поля (n - это параметр функции). Начальное положение Робота - произвольное,
            конечное - совпадает с начальным. Клетки на севере и востоке могут получаться "обрезанными" - зависит от соотношения размеров поля
            и "шахматных" клеток. (Подсказка: здесь могут быть полезными две глобальных переменных, в которых будут содержаться текущие декартовы
            координаты Робота относительно начала координат в левом нижнем углу поля, например)
"""

 """Решение задачи 12"""
 function solve_12!(r, n)
    (M, N), (I, J) = get_field_size_and_position!(r)
    cells = Set()
    visited = Set()
    count = 0

    # Отбирает клетки поля подходящие для раскраски и пушит в {cells}
    for t in 1:M
        i = 1 + M - t
        if (M-i) % (2*n) < n
            count = 0
        else
            count = n
        end
        for j in 1:N
            if count < n
                push!(cells, (i, j))
            elseif count == 2*n
                push!(cells, (i, j))
                count = 0
            end
            count += 1
        end
    end

    mark_some_cells_with_dfs!(r, visited, (I, J), false, cells)

    show(r)
end


# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

function _add_ramp_eqs!(
    model::JuMP.Model,
    g::Unit,
    formulation_prod_vars::Gar1962.ProdVars,
    formulation_ramping::PanGua2016.Ramping,
    formulation_status_vars::Gar1962.StatusVars,
)::Nothing
    # TODO: Move upper case constants to model[:instance]
    RESERVES_WHEN_SHUT_DOWN = true
    gn = g.name
    reserve = model[:reserve]
    eq_str_prod_limit = _init(model, :eq_str_prod_limit)
    eq_prod_limit_ramp_up_extra_period =
        _init(model, :eq_prod_limit_ramp_up_extra_period)
    eq_prod_limit_shutdown_trajectory =
        _init(model, :eq_prod_limit_shutdown_trajectory)
    UT = g.min_uptime
    SU = g.startup_limit   # startup rate, i.e., max production right after startup
    SD = g.shutdown_limit  # shutdown rate, i.e., max production right before shutdown
    RU = g.ramp_up_limit   # ramp up rate
    RD = g.ramp_down_limit # ramp down rate
    T = model[:instance].time

    # Gar1962.ProdVars
    prod_above = model[:prod_above]

    # Gar1962.StatusVars
    is_on = model[:is_on]
    switch_off = model[:switch_off]
    switch_on = model[:switch_on]

    for t in 1:T
        Pbar = g.max_power[t]
        if Pbar < 1e-7
            # Skip this time period if max power = 0
            continue
        end

        #TRD = floor((Pbar - SU) / RD) # ramp down time
        # TODO check amk changed TRD wrt Kneuven et al.
        TRD = ceil((Pbar - SD) / RD)  # ramp down time
        TRU = floor((Pbar - SU) / RU) # ramp up time, can be negative if Pbar < SU

        # TODO check initial time periods: what if generator has been running for x periods?
        # But maybe ok as long as (35) and (36) are also used...
        if UT > 1
            # Equation (38) in Kneuven et al. (2020)
            # Generalization of (20)
            # Necessary that if any of the switch_on = 1 in the sum,
            # then switch_off[gn, t+1] = 0
            eq_str_prod_limit[gn, t] = @constraint(
                model,
                prod_above[gn, t] +
                g.min_power[t] * is_on[gn, t] +
                reserve[gn, t] <=
                Pbar * is_on[gn, t] -
                (t < T ? (Pbar - SD) * switch_off[gn, t+1] : 0.0) - sum(
                    (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for
                    i in 0:min(UT - 2, TRU, t - 1)
                )
            )

            if UT - 2 < TRU
                # Equation (40) in Kneuven et al. (2020)
                # Covers an additional time period of the ramp-up trajectory, compared to (38)
                eq_prod_limit_ramp_up_extra_period[gn, t] = @constraint(
                    model,
                    prod_above[gn, t] +
                    g.min_power[t] * is_on[gn, t] +
                    reserve[gn, t] <=
                    Pbar * is_on[gn, t] - sum(
                        (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for
                        i in 0:min(UT - 1, TRU, t - 1)
                    )
                )
            end

            # Add in shutdown trajectory if KSD >= 0 (else this is dominated by (38))
            KSD = min(TRD, UT - 1, T - t - 1)
            if KSD > 0
                KSU = min(TRU, UT - 2 - KSD, t - 1)
                # Equation (41) in Kneuven et al. (2020)
                eq_prod_limit_shutdown_trajectory[gn, t] = @constraint(
                    model,
                    prod_above[gn, t] +
                    g.min_power[t] * is_on[gn, t] +
                    (RESERVES_WHEN_SHUT_DOWN ? reserve[gn, t] : 0.0) <=
                    Pbar * is_on[gn, t] - sum(
                        (Pbar - (SD + i * RD)) * switch_off[gn, t+1+i] for
                        i in 0:KSD
                    ) - sum(
                        (Pbar - (SU + i * RU)) * switch_on[gn, t-i] for
                        i in 0:KSU
                    ) - (
                        (KSU >= TRU || KSU > t - 2) ? 0.0 :
                        max(0, (SU + (KSU + 1) * RU) - (SD + TRD * RD)) *
                        switch_on[gn, t-(KSU+1)]
                    )
                )
            end
        end
    end
end