using DataFrames
using Gadfly
using Colors
using ColorSchemes
using GLM
using Compose

using Distributed
addprocs()
@everywhere using StatsBase
@everywhere using Distributions

function bc_upd(ϵ::Float64, α::Float64, τ::Float64, n_agents::Int, n_steps::Int)
    bc_ar = Array{Float64,2}(undef, n_agents, n_steps + 1)
    bc_ar[:, 1] = sort(rand(n_agents), rev=true)
    @views for j in 1:n_steps, i in 1:n_agents
        @inbounds bc_ar[i, j + 1] = (1 - α)*mean(bc_ar[:, j][findall(abs.(bc_ar[:, j] .- bc_ar[:, j][i]) .< ϵ)]) + α*τ
    end
    return bc_ar
end

# simplest, most straightforward version, for illustration
function bc_upd(ϵ, α, τ, n_agents, n_steps)
    bc_ar = zeros(n_agents, n_steps + 1)
    bc_ar[:, 1] = rand(n_agents)
    for j in 1:n_steps
        for i in 1:n_agents
            bc_ar[i, j + 1] = (1 - α)*mean(bc_ar[:, j][findall(abs.(bc_ar[:, j] .- bc_ar[:, j][i]) .< ϵ)]) + α*τ
        end
    end
    return bc_ar
end

res = bc_upd(.1, .2, .7, 50, 30)

df₀ = res |> rotr90 |> DataFrame
rename!(df₀, [Symbol("$i") for i in 1:size(df₀, 2)])
df = df₀ |> stack
df[!, :steps] = repeat(0:size(df₀, 1) - 1, outer=size(df₀, 2))

palette = [ get(ColorSchemes.viridis, i) for i in range(0, length=size(df₀, 2), stop=1) ]

plot(df, x=:steps, y=:value, color=:variable, Geom.point, Geom.line,
    Coord.cartesian(xmax=size(df₀, 1)),
    Guide.xlabel("Time"),
    Scale.color_discrete_manual(palette...),
    Guide.ylabel("Opinion"),
    Guide.title("ϵ = .1 / α = .2 / τ = .7"),
    Theme(key_position=:none, point_size=1.5pt))

# different approach

@everywhere abstract type Agent end

@everywhere mutable struct TruthSeeker <: Agent
    ϵ::Float64
    α::Float64
    op::Float64
end

eps_vals(pop::Array{TruthSeeker,1}) = Float64[ pop[i].ϵ for i in eachindex(pop) ]
alp_vals(pop::Array{TruthSeeker,1}) = Float64[ pop[i].α for i in eachindex(pop) ]
opinions(pop::Array{TruthSeeker,1}) = Float64[ pop[i].op for i in eachindex(pop) ]

peers(pop::Array{TruthSeeker,1}) = Bool[ abs(pop[j].op - pop[i].op) ≤ pop[j].ϵ for i in eachindex(pop), j in eachindex(pop) ] # the peers for agent j are in the j-th column

function update!(pop::Array{TruthSeeker,1}, τ::Float64)
    prs = peers(pop)
    ops = opinions(pop)
    soc = sum(ops .* prs, dims=1) ./ sum(prs, dims=1)
    @inbounds for i in eachindex(pop)
        pop[i].op = pop[i].α*τ + (1 - pop[i].α)*soc[i]
    end
end

function run_model(ϵ::Float64, α::Float64, τ::Float64, numb_agents::Int, numb_updates::Int)
    pp = [ TruthSeeker(ϵ, α, rand()) for _ in 1:numb_agents ]
    res = zeros(numb_agents, numb_updates + 1)
    res[:, 1] = opinions(pp)
    @inbounds for i in 1:numb_updates
        update!(pp, τ)
        res[:, i + 1] = opinions(pp)
    end
    return res
end

function run_model(ϵ::Float64, α::Float64, τ::Float64, numb_agents::Int)
    pp = [ TruthSeeker(ϵ, α, rand()) for _ in 1:numb_agents ]
    res = Array{Float64,1}[]
    push!(res, opinions(pp))
    while true
        update!(pp, τ)
        push!(res, opinions(pp))
        if all(abs.(res[end] .- res[end - 1]) .< 10^-5)
            break
        end
    end
    return reduce(hcat, res)
end

res = run_model(.1, .2, .7, 50, 30)
res = run_model(.1, .2, .7, 50)

# NB `run_model(.1, .2, .7, 50, 30)` is the equivalent of `bc_upd(.1, .2, .7, 50, 30)`; the first has fewer allocations and is much faster (relatively speaking, given that both are very fast):
using BenchmarkTools
@btime run_model(.1, .2, .7, 50, 30);
@btime bc_upd(.1, .2, .7, 50, 30);

df₀ = res |> rotr90 |> DataFrame
srt = sortperm(Vector(df₀[1, :]))
permutecols!(df₀, srt)
rename!(df₀, [Symbol("$i") for i in 1:size(df₀, 2)])
df = df₀ |> stack
df[!, :steps] = repeat(0:size(df₀, 1) - 1, outer=size(df₀, 2))

palette = [ get(ColorSchemes.viridis, i) for i in range(0, length=size(df₀, 2), stop=1) ]

plot(df, x=:steps, y=:value, color=:variable, Geom.point, Geom.line,
    Coord.cartesian(xmax=size(df₀, 1)),
    Guide.xlabel("Time"),
    Scale.color_discrete_manual(palette...),
    Guide.ylabel("Opinion"),
    Guide.title("ϵ = .1 / α = .2 / τ = .7"),
    Theme(key_position=:none, point_size=1.5pt))

# first extension: random-per-agent ϵ and α (but we can still specify upper and lower bounds for both)

function run_model(ϵ_inf::Float64, ϵ_sup::Float64, α_inf::Float64, α_sup::Float64, τ::Float64, numb_agents::Int, numb_updates::Int)
    pp = [ TruthSeeker(ϵ_inf < ϵ_sup ? rand(Uniform(ϵ_inf, ϵ_sup)) : ϵ_inf, α_inf < α_sup ? rand(Uniform(α_inf, α_sup)) : α_inf, rand()) for _ in 1:numb_agents ]
    res = zeros(numb_agents, numb_updates + 1)
    res[:, 1] = opinions(pp)
    @inbounds for i in 1:numb_updates
        update!(pp, τ)
        res[:, i + 1] = opinions(pp)
    end
    return res
end

function run_model(ϵ_inf::Float64, ϵ_sup::Float64, α_inf::Float64, α_sup::Float64, τ::Float64, numb_agents::Int)
    pp = [ TruthSeeker(ϵ_inf < ϵ_sup ? rand(Uniform(ϵ_inf, ϵ_sup)) : ϵ_inf, α_inf < α_sup ? rand(Uniform(α_inf, α_sup)) : α_inf, rand()) for _ in 1:numb_agents ]
    res = Array{Float64,1}[]
    push!(res, opinions(pp))
    while true
        update!(pp, τ)
        push!(res, opinions(pp))
        if all(abs.(res[end] .- res[end - 1]) .< 10^-5)
            break
        end
    end
    return reduce(hcat, res)
end

res = run_model(.0, .25, .0, .5, .7, 50, 75)
res = run_model(.0, .25, .0, .5, .7, 50)

df₀ = res |> rotr90 |> DataFrame
srt = sortperm(Vector(df₀[1, :]))
permutecols!(df₀, srt)
rename!(df₀, [Symbol("$i") for i in 1:size(df₀, 2)])
df = df₀ |> stack
df[!, :steps] = repeat(0:size(df₀, 1) - 1, outer=size(df₀, 2))

plot(df, x=:steps, y=:value, color=:variable, Geom.point, Geom.line,
    Coord.cartesian(xmax=size(df₀, 1)),
    Guide.xlabel("Time"),
    Scale.color_discrete_manual(palette...),
    Guide.ylabel("Opinion"),
    Guide.title("ϵᵢ = U(0, .25)/ αᵢ = U(0, .5) / τ = .7"),
    Theme(key_position=:none, point_size=1.5pt))

# we can already do some interesting statistics: record the agents' ϵ and α values, and measure their accuracy, and look whether the former are good predictors of the latter

function conc(ar::AbstractArray, numb_simulations::Int)
    x = ar[:, :, 1]
    for i in 2:numb_simulations
        w = vcat(x, ar[:, :, i])
        x = w
    end
    return x
end

sse(ar::Array{Float64,2}, τ::Float64) = sum((ar .- τ).^2, dims=2)

function run_model_stats(ϵ_inf::Float64, ϵ_sup::Float64, α_inf::Float64, α_sup::Float64, τ::Float64, numb_agents::Int)
    pp = [ TruthSeeker(ϵ_inf < ϵ_sup ? rand(Uniform(ϵ_inf, ϵ_sup)) : ϵ_inf, α_inf < α_sup ? rand(Uniform(α_inf, α_sup)) : α_inf, rand()) for _ in 1:numb_agents ]
    res = Array{Float64,1}[]
    push!(res, opinions(pp))
    while true
        update!(pp, τ)
        push!(res, opinions(pp))
        if all(abs.(res[end] .- res[end - 1]) .< 10^-5)
            break
        end
    end
    out = reduce(hcat, res)
    return hcat(sse(out, τ), eps_vals(pp), alp_vals(pp))
end

function run_sims(ϵ_inf::Float64, ϵ_sup::Float64, α_inf::Float64, α_sup::Float64, numb_agents::Int, numb_simulations::Int)
    v = Array{Float64,3}(undef, numb_agents, 4, numb_simulations)
    @inbounds for i in 1:numb_simulations
        t = rand()
        out = run_model_stats(ϵ_inf, ϵ_sup, α_inf, α_sup, t, numb_agents)
        v[:, :, i] = hcat(out, fill(t, numb_agents))
    end
    return conc(v, numb_simulations)
end

res = run_sims(.0, .25, .0, .5, 50, 1000)
res = DataFrame(SSE = res[:, 1], ϵ = res[:, 2], α = res[:, 3], τ = res[:, 4])
res[!, :ϵ] = standardize(ZScoreTransform, res[!, :ϵ], dims=1)
res[!, :α] = standardize(ZScoreTransform, res[!, :α], dims=1)

lm(@formula(SSE ~ α + ϵ + τ), res)

# further types of agents

@everywhere mutable struct FreeRider <: Agent
    ϵ::Float64
    α::Float64
    op::Float64
end

@everywhere mutable struct Campaigner <: Agent
    ϵ::Float64
    α::Float64
    op::Float64
end

@everywhere function ppl(ϵ_tr::Float64, α_tr::Float64, ϵ_fr::Float64, α_fr::Float64, ϵ_ca::Float64, α_ca::Float64, numb_ts::Int, numb_fr::Int, numb_ca::Int, ρ::Float64)
    ts = [ TruthSeeker(ϵ_tr , α_tr, rand()) for _ in 1:numb_ts ]
    fr = [ FreeRider(ϵ_fr, α_fr, rand()) for _ in 1:numb_fr ]
    ca = [ Campaigner(ϵ_ca, α_ca, ρ) for _ in 1:numb_ca ]
    un = Array{Union{TruthSeeker,FreeRider,Campaigner},1}[ts, fr, ca]
    return reduce(vcat, un)
end

@everywhere opinions(pop::Array{Union{TruthSeeker,FreeRider,Campaigner},1}) = Float64[ pop[i].op for i in eachindex(pop) ]

@everywhere function peers(pop::Array{Union{TruthSeeker,FreeRider,Campaigner},1})
    numb_ts = count(x->typeof(x) == TruthSeeker, pop)
    numb_fr = count(x->typeof(x) == FreeRider, pop)
    return Bool[ abs(pop[j].op - pop[i].op) ≤ pop[j].ϵ for i in eachindex(pop), j in 1:(numb_ts + numb_fr) ]
end

@everywhere function update!(pop::Array{Union{TruthSeeker,FreeRider,Campaigner},1}, τ::Float64)
    prs = peers(pop)
    ops = opinions(pop)
    soc = sum(ops .* prs, dims=1) ./ sum(prs, dims=1)
    @inbounds for i in eachindex(pop)
        if typeof(pop[i]) == TruthSeeker
            pop[i].op = pop[i].α*τ + (1 - pop[i].α)*soc[i]
        elseif typeof(pop[i]) == FreeRider
            pop[i].op = soc[i]
        end
    end
end

function run_model(ϵ_tr::Float64, α_tr::Float64, ϵ_fr::Float64, α_fr::Float64, ϵ_ca::Float64, α_ca::Float64, numb_ts::Int, numb_fr::Int, numb_ca::Int, τ::Float64, ρ::Float64, numb_updates::Int)
    numb_agents = numb_ts + numb_fr + numb_ca
    pp = ppl(ϵ_tr, α_tr, ϵ_fr, α_fr, ϵ_ca, α_ca, numb_ts, numb_fr, numb_ca, ρ)
    res = zeros(numb_agents, numb_updates + 1)
    res[:, 1] = opinions(pp)
    @inbounds for i in 1:numb_updates
        update!(pp, τ)
        res[:, i + 1] = opinions(pp)
    end
    return res
end

@everywhere function run_model(ϵ_tr::Float64, α_tr::Float64, ϵ_fr::Float64, α_fr::Float64, ϵ_ca::Float64, α_ca::Float64, numb_ts::Int, numb_fr::Int, numb_ca::Int, τ::Float64, ρ::Float64)
    pp = ppl(ϵ_tr, α_tr, ϵ_fr, α_fr, ϵ_ca, α_ca, numb_ts, numb_fr, numb_ca, ρ)
    res = Array{Float64,1}[]
    push!(res, opinions(pp))
    while true
        update!(pp, τ)
        push!(res, opinions(pp))
        if all(abs.(res[end] .- res[end - 1]) .< 10^-5)
            break
        end
    end
    return reduce(hcat, res)
end

const numb_ts = 50
const numb_fr = 10
const numb_ca = 5

res = run_model(.1, .2, .1, .0, .0, .0, numb_ts, numb_fr, numb_ca, .7, .9)

df₀ = res' |> DataFrame
rename!(df₀, [Symbol("$i") for i in 1:size(df₀, 2)])
df = df₀ |> stack
df[!, :steps] = repeat(0:size(df₀, 1) - 1, outer=size(df₀, 2))
df[!, :agent] = vcat(fill("Truth seeker", size(df₀, 1)*numb_ts), fill("Free rider", size(df₀, 1)*numb_fr), fill("Campaigner", size(df₀, 1)*numb_ca))

palette = [colorant"darksalmon", colorant"gold", colorant"darkseagreen"]

Gadfly.plot(df, x=:steps, y=:value, group=:variable, color=:agent,
        Geom.point, Geom.line, Scale.color_discrete_manual(palette..., order=[3, 2, 1]),
        Coord.cartesian(xmax=maximum(df.steps)),
        Guide.xlabel("Update"), Guide.ylabel("Opinion"),
        Guide.colorkey(title="Group", labels=["Campaigners", "Free riders", "Truth seekers"]),
        yintercept=[.7], Geom.hline(style=:dot, color=colorant"grey"),
        Guide.annotation(compose(context(), Compose.text(maximum(df.steps) - 1, 0.735, "τ"), fontsize(11pt))))

# conversion

@everywhere begin
    const ϵ_fr = .0
    const ϵ_ca = .0
    const α_tr = .0
    const α_fr = .0
    const α_ca = .0
    const τ    = .0
    const ρ    = .8

    const numb_ts = 50
    const numb_fr = 0
end

@everywhere function perc_conv(eps::Float64, nc::Int)
    res = run_model(eps, α_tr, ϵ_fr, α_fr, ϵ_ca, α_ca, numb_ts, numb_fr, nc, τ, ρ)
    return sum(≈(ρ, atol=1e-3), res[1:numb_ts, end])
end

@everywhere function perc_sim(eps::Float64, nc::Int)
    return Float64[ perc_conv(eps, nc) for _ in 1:100 ] |> mean
end

function conv_sims()
    perc_mat = Matrix{Float64}(undef, 50, 50)
    @inbounds for j in 1:50
        perc_mat[:, j] = pmap(i->perc_sim(i/100, j), 1:50)
    end
    return perc_mat
end

@time res = conv_sims()

function xlabelname(x)
    n = x/100
    return "$n"
end

function ylabelname(x)
    n = abs(x - 50)
    return "$n"
end

ticks = collect(0:10:50)

Gadfly.spy(rotl90(res),
    Guide.ColorKey(title="Converted\ntruth seekers"),
    Guide.xlabel("ϵ"), Guide.ylabel("Campaigners"),
    Guide.title("ρ = $ρ"), # given that alpha is zero, the value of tau doesn't matter here
    Guide.xticks(ticks=ticks), Scale.x_continuous(labels=xlabelname),
    Guide.yticks(ticks=ticks), Scale.y_continuous(labels=ylabelname),
    Scale.ContinuousColorScale(p->get(ColorSchemes.viridis, p), minvalue=0, maxvalue=50),
    Theme(grid_color=colorant"white", panel_fill="white"))


using ApproxFun, Base.Test
    import ApproxFun: ChebyshevDirichlet,Ultraspherical,space

@test_approx_eq Fun(exp,ChebyshevDirichlet{1,1})(.1) exp(.1)
@test_approx_eq Fun(Fun(exp,ChebyshevDirichlet{1,1}),Ultraspherical(1))(.1) exp(.1)

d=Interval()
sp=ChebyshevDirichlet{1,1}(d)
B=dirichlet(sp)
D=Derivative(d)
L=D^2+I

@test norm(([B;L]\[1.])-([dirichlet(d);L]\[1.])) <10eps()

f=Fun(t->cos(t)+cos(3t),CosSpace)

@test (f.*f-Fun(t->(cos(t)+cos(3t))^2,CosSpace)).coefficients|>norm <100eps()

f=Fun(exp,Taylor(Circle()))
g=Fun(z->1./(z-.1),Hardy{false}(Circle()))
@test_approx_eq (f(1.)+g(1.)) (exp(1.) + 1./(1-.1))

## Periodic
f=Fun(x->exp(-10sin((x-.1)/2)^2),Laurent)
@test_approx_eq f(.5) (Conversion(space(f),Fourier(domain(f)))*f)(.5)
@test_approx_eq f(.5) Fun(f,Fourier)(.5)

## PiecewiseSPace

x=Fun(identity,[-1.,0.,1.])
sp=space(x)
D=Derivative(sp)

u=[dirichlet(sp);
    D^2]\[1];
u2=[dirichlet();Derivative(Chebyshev())^2]\[1.]
@test_approx_eq u(0.) u2(0.)

x=Fun(identity,[-10.,0.,1.,15.])
sp=space(x)
D=Derivative(sp)

u=[dirichlet(sp);
    D^2-x]\[airyai(-10.)];

@test_approx_eq u(0.) airyai(0.)

s=Fun(sin,[-2.,2.])|>abs
c=Fun(cos,[-2.,2.])|>abs
sc=Fun(x->abs(sin(x))+abs(cos(x)),[-2,-π/2,0,π/2,2])
@test norm(sc-(c+s))<100eps()

# max/min creates breakpoints

x=Fun()
g=4*(x-.2)
f=max(-1,g)
f2=min(f,1)
f3=Fun(x->x<-0.05?-1.0:(x<0.45?4*(x-.2):1),[-1.0;-0.05;0.45;1.0])
@test norm(f2(collect(linspace(-1,1,10)))-f3(collect(linspace(-1,1,10)))) < 2eps()

x=Fun(identity,[im,0.,1.])
@test_approx_eq sqrt(1-x)(0.2im) sqrt(1-0.2im)
@test_approx_eq sqrt(1-x)(0.2) sqrt(1-0.2)

w=2/(sqrt(1-x)*sqrt(1+im*x))
@test_approx_eq w(0.2im) 2/(sqrt(1-0.2im)*sqrt(1+im*(0.2im)))
@test_approx_eq w(0.2) 2/(sqrt(1-0.2)*sqrt(1+im*(0.2)))

## Equivalent spaces

@test norm(Fun(cos,Chebyshev)-Fun(cos,Jacobi(-0.5,-0.5)))<100eps()
@test norm(Fun(cos,Chebyshev)-Fun(cos,JacobiWeight(0,0)))<100eps()
@test norm(Fun(cos,Jacobi(-0.5,-0.5))-Fun(cos,JacobiWeight(0,0))) < 100eps()
@test norm(Fun(cos,Chebyshev)-Fun(cos,JacobiWeight(0,0,Jacobi(-0.5,-0.5))))<100eps()
@test norm(Fun(cos,Jacobi(-0.5,-0.5))-Fun(cos,JacobiWeight(0,0,Jacobi(-0.5,-0.5))))<100eps()

## ContinuousSpace

import ApproxFun: PiecewiseInterval,ContinuousSpace

d=PiecewiseInterval(1.,2.,3.,4.)
S=ContinuousSpace(d)
D=Derivative(S)
u=[ldirichlet(S),D-I]\[exp(1.)]

@test_approx_eq u(1.1) exp(1.1)
@test_approx_eq u(3.4) exp(3.4)
@test_approx_eq last(u) exp(4)

d=PiecewiseInterval(0,1.,1.+im,im,0.)
@test_approx_eq Fun(exp,d)(.1) exp(.1)

## Triple SumSpace

x=Fun()
w=log(1-x)+sqrt(1-x^2)
f=w+x
@test_approx_eq f(0.1) (w(0.1)+0.1)
@test_approx_eq (w+1)(0.1) (w(0.1)+1)
@test_approx_eq (w+x+1)(0.1) (w(0.1)+1.1)
@test_approx_eq ((w+x)+1)(0.1) (w(0.1)+1.1)

## SumSpace bug

dsp=JacobiWeight(1.,0.,Jacobi(0.,1.,[0.,1.]))⊕JacobiWeight(0.5,0.,Jacobi(-0.5,0.5,[0.,1.]))
rsp=Legendre([0.,1.])⊕JacobiWeight(0.5,0.,Jacobi(0.5,0.5,[0.,1.]))
C=Conversion(dsp,rsp)
f=Fun([1.,2.,3.,4.,5.],dsp)
@test_approx_eq f(0.1) (C*f)(0.1)

## Piecewise + Cosntant

Γ=Circle()∪Circle(0.0,0.4)
G=Fun(z->in(z,Γ[2])?[1 0; -1/z 1]:[z 0; 0 1/z],Γ)   # Before the 80 wasn’t specified causing inconsistency
@test_approx_eq (G-I)(1.) (G(1.)-I)

## Previoius seffdault

x=Fun(identity,[-1.,1.])
f=x+sin(2x)*sqrt(1-x^2)
@test_approx_eq f(0.1) 0.1+sin(2*0.1)*sqrt(1-0.1^2)

## Check multiple piecewisesapce

x=Fun(identity,[-3,-2])+Fun(identity,[2,3])
w=sqrt(9-x^2)
f=w+Fun()
@test_approx_eq (f+w)(2.5) 2w(2.5)
@test_approx_eq (f+w)(.5) f(.5)

## Check Jacobi recurrence bug

S=Jacobi(-.5,.5)
f=Fun(exp,S)
@test_approx_eq f(0.1) exp(0.1)

## Check cancel conversion works
x=Fun([0.,1.])
f=exp(x)-1
Fun(f,JacobiWeight(1.,0.,[0.,1.]))

## Hermite
f=Fun(x->x+x^2,Hermite())
@test_approx_eq f(1.) 2.

## Arc exp

z=Fun(identity,Arc(0.,.1,0.,π/2))
@test_approx_eq exp(z)(0.1exp(0.2im)) exp(0.1exp(0.2im))


using OrdinaryDiffEq, DiffEqBase, Base.Test
srand(100)
setprecision(400)

f = (t,u) -> (2u)
analytic = (t,u0) -> u0*exp(t)
prob = ODETestProblem(f,1/2,analytic)

sol3 =solve(prob,RK4(),dt=1/2^(6),abstol=1,reltol=0)

prob = ODETestProblem(f,BigInt(1)//BigInt(2),analytic,(BigInt(0)//BigInt(1),BigInt(1)//BigInt(1)))

integrator = init(prob,RK4(),dt=BigInt(1)//BigInt(2)^(6),abstol=1,reltol=0)

sol =solve(prob,RK4(),dt=BigInt(1)//BigInt(2)^(6),abstol=1,reltol=0)
sol2 =solve(prob,RK4(),dt=BigInt(1)/BigInt(2)^(6),abstol=1,reltol=0)

sol.u
sol2.u
sol3.u
@test eltype(sol.u) == Rational{BigInt}
@test eltype(sol2.u) == Rational{BigInt}
@test eltype(sol3.u) == Float64

sol4 =solve(prob,DP5(),dt=BigInt(1)//BigInt(2)^(3),adaptive=false)

@test eltype(sol4.u) == Rational{BigInt}

tabalg = ExplicitRK(tableau = constructDormandPrince(Rational{BigInt}))

integrator = init(prob,tabalg,dt=BigInt(1)//BigInt(2)^(3),abstol=1,reltol=0,adaptive=false)
sol5 =solve(prob,tabalg,dt=BigInt(1)//BigInt(2)^(3),abstol=1,reltol=0,adaptive=false)

@test typeof(sol5.u[end]) == Rational{BigInt}

prob = ODETestProblem(f,1/2+1/2im,analytic)


using ViewerGL, LinearAlgebra
GL = ViewerGL
using LinearAlgebraicRepresentation
Lar = LinearAlgebraicRepresentation

#include("")
store = [];
scaling = 1.05;
V,(VV,EV,FV,CV) = Lar.cuboid([0.25,0.25,0.25],true,[-0.25,-0.25,-0.25]);
mybox = (V,CV,FV,EV);
for k=1:5
	size = rand()*scaling
	scale = Lar.s(size,size,size)
	transl = Lar.t(rand(3)...)
	alpha = 2*pi*rand()
	rx = Lar.r(alpha,0,0); ry = Lar.r(0,alpha,0); rz = Lar.r(0,0,alpha)
	rot = rx * ry * rz
	str = Lar.Struct([ transl, scale, rot, mybox ])
	obj = Lar.struct2lar(str)
	vs = obj[1]
	diag = LinearAlgebra.norm(vs[:,8]-vs[:,1])
	if diag > 1/5
		push!(store, obj)
	end
end

str = Lar.Struct(store);
V,CV,FV,EV = Lar.struct2lar(str);
# V = Plasm.normalize3D(V) TODO:  solve MethodError bug

open("/tmp/lar.txt", "w") do f
	write(f, "V = $V\n\n")
	write(f, "CV = $CV\n\n")
	write(f, "FV = $FV\n\n")
	write(f, "EV = $EV\n\n")
	close(f)
end
GL.VIEW([ GL.GLPol(V,CV, GL.COLORS[1]) ]);

# @show EV
# @show size(EV)
cop_EV = Lar.coboundary_0(EV::Lar.Cells);
# @show cop_EV
# @show size(cop_EV)

# @show size(V)
# @show size(FV)
# @show size(EV)
cop_FE = Lar.coboundary_1(V, FV::Lar.Cells, EV::Lar.Cells);
# @show cop_FE
# @show size(cop_FE)

W = convert(Lar.Points, V');#cobordo 1

V, copEV, copFE, copCF = Lar.Arrangement.spatial_arrangement(
	W::Lar.Points, cop_EV::Lar.ChainOp, cop_FE::Lar.ChainOp);
# println("||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||")
# @show V
# @show copEV
# @show copFE
# @show copCF
# println("||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||")
triangulated_faces = Lar.triangulate2D(V, [copEV, copFE]);
FVs = convert(Array{Lar.Cells}, triangulated_faces);
V = convert(Lar.Points, V');
#GL.VIEW( GL.GLExplode(V,FVs,1.5,1.5,1.5,99,1) );

EVs = Lar.FV2EVs(copEV, copFE); # polygonal face fragments
#GL.VIEW( GL.GLExplode(V,EVs,1.5,1.5,1.5,99,1) );


# ---
# title: 260. Single Number III
# id: problem260
# author: Indigo
# date: 2021-06-05
# difficulty: Medium
# categories: Bit Manipulation
# link: <https://leetcode.com/problems/single-number-iii/description/>
# hidden: true
# ---
#
# Given an integer array `nums`, in which exactly two elements appear only once
# and all the other elements appear exactly twice. Find the two elements that
# appear only once. You can return the answer in **any order**.
#
# **Follow up:  **Your algorithm should run in linear runtime complexity. Could
# you implement it using only constant space complexity?
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [1,2,1,3,2,5]
#     Output: [3,5]
#     Explanation: [5, 3] is also a valid answer.
#
#
# **Example 2:**
#
#
#
#     Input: nums = [-1,0]
#     Output: [-1,0]
#
#
# **Example 3:**
#
#
#
#     Input: nums = [0,1]
#     Output: [1,0]
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 30000`
#   *  Each integer in `nums` will appear twice, only two integers will appear once.
#
#
## @lc code=start
using LeetCode

function single_number_iii(nums::Vector{T}) where T <: Integer
    res = reduce(⊻, nums; init = zero(T))
    div = one(T)
    while (div & res) == 0
        div <<= one(T)
    end
    a = b = 0
    for n in nums
        (div & n == 0) ? a ⊻= n : b ⊻= n
    end
    return a, b
end
## @lc code=end


#=
# MNIST

We begin by importing the required packages.
We load MNIST via the MLDatasets.jl package.
=#

import Makie
import CairoMakie
using Random: bitrand
using Statistics: mean
import MLDatasets
import Flux
import RestrictedBoltzmannMachines as RBMs
using ValueHistories: MVHistory
import CenteredRBMs
using RestrictedBoltzmannMachines: BinaryRBM
nothing #hide

#=
Useful function to plot MNIST digits.
=#

"""
    imggrid(A)

Given a four dimensional tensor `A` of size `(width, height, ncols, nrows)`
containing `width x height` images in a grid of `nrows x ncols`, this returns
a matrix of size `(width * ncols, height * nrows)`, that can be plotted in a heatmap
to display all images.
"""
function imggrid(A::AbstractArray{<:Any,4})
    reshape(permutedims(A, (1,3,2,4)), size(A,1)*size(A,3), size(A,2)*size(A,4))
end

#=
Load the MNIST dataset. We only work with 0s and 1s for speed.
=#

Float = Float32
train_x, train_y = MLDatasets.MNIST.traindata()
train_x = Array{Float}(train_x[:, :, train_y .∈ Ref((0,1))] .> 0.5)
println(size(train_x, 3), " train samples, with ", count(train_y .== 0), " zeros and ", count(train_y .== 1), " ones.")
nothing #hide

#=
Initialize and train a centered RBM
=#

rbm_c = CenteredRBMs.center(BinaryRBM(Float, (28,28), 400))
RBMs.initialize!(rbm_c, train_x) # centers from data
batchsize = 256
optim = Flux.ADAM()
vm = bitrand(28, 28, batchsize) # fantasy chains
history_c = MVHistory()
push!(history_c, :lpl, mean(RBMs.log_pseudolikelihood(rbm_c, train_x)))
@time for epoch in 1:100 # track pseudolikelihood every 5 epochs
    RBMs.pcd!(rbm_c, train_x; epochs=5, vm, history=history_c, batchsize, optim)
    push!(history_c, :lpl, mean(RBMs.log_pseudolikelihood(rbm_c, train_x)))
end
rbm_c = CenteredRBMs.uncenter(rbm_c) # convert to equivalent RBM (without offsets)
nothing #hide

#=
For comparison, we also train a normal (uncentered) RBM.
=#

rbm_u = BinaryRBM(Float, (28,28), 400)
RBMs.initialize!(rbm_u, train_x)
vm = bitrand(28, 28, batchsize)
history_u = MVHistory()
push!(history_u, :lpl, mean(RBMs.log_pseudolikelihood(rbm_u, train_x)))
@time for epoch in 1:100 # track pseudolikelihood every 5 epochs
    RBMs.pcd!(rbm_u, train_x; epochs=5, vm, history=history_u, batchsize, optim)
    push!(history_u, :lpl, mean(RBMs.log_pseudolikelihood(rbm_u, train_x)))
end
nothing #hide

# Plot log-pseudolikelihood of train data during learning.

fig = Makie.Figure(resolution=(600, 300))
ax = Makie.Axis(fig[1,1], xlabel="epochs", ylabel="pseudolikelihood")
Makie.lines!(ax, get(history_u, :lpl)..., label="normal")
Makie.lines!(ax, get(history_c, :lpl)..., label="centered")
Makie.axislegend(ax, position=:rb)
fig

# Seconds per epoch.

fig = Makie.Figure(resolution=(600, 300))
ax = Makie.Axis(fig[1,1], xlabel="epoch", ylabel="seconds")
Makie.lines!(ax, get(history_u, :Δt)..., label="normal")
Makie.lines!(ax, get(history_c, :Δt)..., label="centered")
Makie.axislegend(ax, position=:rt)
fig

# Log-pseudolikelihood vs. computation time instead of epoch count.

fig = Makie.Figure(resolution=(600, 300))
ax = Makie.Axis(fig[1,1], xlabel="seconds", ylabel="pseudolikelihood")
Makie.lines!(ax, cumsum([0; get(history_u, :Δt)[2]])[1:5:end], get(history_u, :lpl)[2], label="normal")
Makie.lines!(ax, cumsum([0; get(history_c, :Δt)[2]])[1:5:end], get(history_c, :lpl)[2], label="centered")
Makie.axislegend(ax, position=:rb)
fig

# Now we do the Gibbs sampling to generate RBM digits.

nrows, ncols = 10, 15
@time fantasy_x_c = RBMs.sample_v_from_v(rbm_c, bitrand(28,28,nrows*ncols); steps=10000)
@time fantasy_x_u = RBMs.sample_v_from_v(rbm_u, bitrand(28,28,nrows*ncols); steps=10000)
nothing #hide

# Plot the resulting samples.

# Normal RBM.

fig = Makie.Figure(resolution=(40ncols, 40nrows))
ax = Makie.Axis(fig[1,1], yreversed=true)
Makie.image!(ax, imggrid(reshape(fantasy_x_u, 28, 28, ncols, nrows)), colorrange=(1,0))
Makie.hidedecorations!(ax)
Makie.hidespines!(ax)
fig

# Centered RBM.

fig = Makie.Figure(resolution=(40ncols, 40nrows))
ax = Makie.Axis(fig[1,1], yreversed=true)
Makie.image!(ax, imggrid(reshape(fantasy_x_c, 28, 28, ncols, nrows)), colorrange=(1,0))
Makie.hidedecorations!(ax)
Makie.hidespines!(ax)
fig


# Packages used in this chapter
# To check for installed packages in V1.0 we now need to use the PKG API

using Pkg
ipks = Pkg.installed()
pkgs = ["BenchmarkTools","MacroTools"]
for p in pkgs
    !haskey(ipks,p) && Pkg.add(p)
end

# We can define these here or as required
using BenchmarkTools, MacroTools, PyPlot

# We need these as some functions have moved from Base to Stdlib
#
using Printf, Statistics

recip(x::Number) =
(x == zero(typeof(x))) ? error("Invalid reciprocal") : one(typeof(x)) / x

recip(2)

recip(recip(2))  # recip does not ALWAYS return the same type

recip(11//17)

recip(11 + 17im)

aa = rand(3)
recip(aa)

## recip(a::Array) = [a[i] = recip(a[i]) for i = 1:length(a)]
## OR
recip(a::Array) = map(recip,a)
recip(aa)

map(sin,recip(aa)) # and can map functions to the array

bb = [2.1 3.2 4.3; 9.8 8.7 7.6]

recip(bb) # our definition works but returns a 1-D array

cc = recip(aa)'.*recip(bb)  # because they are 1-D arrays we can do this.

incr(x) = x + 1

@code_native incr(2)

@code_native incr(2.7)

@code_native incr(2//7)

@code_native incr(2.0 + 7.0im)

# Look at the intermediat stages

dump(:(incr(2.7)))

@code_lowered incr(2.7)

@code_typed incr(2.7)

@code_llvm(incr(2.7))

ex1 = :((x^2 + y^2 - 2*x*y)^0.5)

ex2 = quote
    (x^2 + y^2 - 2*x*y)^0.5
end

# If we instantiate the variables x,y,z then we can evaluate the expression
# This is NOT a function call, so values need to be known before hand
#
x = 1.1; y = 2.5;
eval(ex1)

# Note that the @eval macro does NOT behave as the function call
@eval ex1

# Unless the expression is prefixed with '$'
@eval $ex1

# i.e.  @eval $(ex) === eval(ex)

# Although ex1 and ex2 are equivalent the AST of the expression
# is slightly different
# Here it is for ex1, we leave ex2 to the reader
#
dump(ex1)

# Dump is very verbose, so using show_expr creates an S-expression version
#
Meta.show_sexpr(ex1)

# Traversing a tree
#
function traverse!(ex, symbols) end

function traverse!(ex::Symbol, symbols)
    push!(symbols, ex)
end

function traverse!(ex::Expr, symbols)
    if ex.head == :call  # function call
        for arg in ex.args[2:end]
            traverse!(arg, symbols)  # recursive
        end
    else
        for arg in ex.args
            traverse!(arg, symbols)  # recursive
        end
    end
end

function traverse(ex::Expr)
    symbols = Symbol[]
    traverse!(ex, symbols)
    return unique(symbols)  # Don't output duplicate
end

traverse(ex1)

macro pout(n)
    if typeof(n) == Expr
       println(n.args)
    end
    return n
end

@pout x

@pout (x^2 + y^2 - 2*x*y)^0.5

macro dotimes(n, body)
    quote
        for i = 1:$(esc(n))
            $(esc(body))
        end
    end
end

@dotimes 3 print("Hi")

i = 0; @dotimes 3 [global i += 1; println(i*i)]

import Base.macroexpand
macroexpand(ex::Expr) = macroexpand(Main,ex)

macroexpand(:(@assert n > 0))

# Expand our @dotimes function, whic is somewhat simpler
#
macroexpand(Main,:(@dotimes 3 [global i += 1; println(i*i)]))

# Not all macro expansions produce short boiler plate
#
using Printf, Statistics
aa = [rand() for i = 1:100000]
@printf "The average value is %f.3 over %d trials"  mean(aa) length(aa)

macroexpand(:(@printf "The average value is %f.3 over %d trials"  mean(aa) length(aa)))

# A more useful version of dotimes macro is until
# Creates a loop and breaks out when a condition fails
#
macro until(condition, block)
    quote
        while true
            $(esc(block))
            if $(esc(condition))
                break
            end
        end
    end
end

i = 0; @until (i >= 3) [global i += 1; println(i*i)]

# The until macro can be used to implement a simple if-then-else
#
macro trigger(cond, body1, body2)
    :(if !$cond
        $(esc(body1))
    else
        $(esc(body2))
    end)
end

fac(n::Integer) = (n == 1) ? 1 : n*fac(n-1)

n = 10
@trigger (n < 1) fac(n) ArgumentError("$n not positive")

n = -1; @trigger (n < 1) fac(n) ArgumentError("$n not positive")

# Evaluate a polynomial with a function
# This is NOT a macro version

function poly_native(x, a...)
  p=zero(x)
  for i = 1:length(a)
    p = p + a[i] *  x^(i-1)
  end
  return p
end

f_native(x) = poly_native(x,1,2,3,4,5)
f_native(2.1)

# Neither IS this!

function poly_horner(x, a...)
  b=zero(x)
  for i = length(a):-1:1
    b = a[i] + b * x
  end
  return b
end

# f -> (((5*x + 4)*x + 3)*x + 2)*x + 1
#
f_horner(x) = poly_horner(x,1,2,3,4,5)
f_horner(2.1)

# for the macro version we need a help function
# Define mad(x,a,b)
# [Julia has this function too :- muladd(x,a,b)]

mad(x,a,b) = a*x + b
mad(2.1,5,4)

# And NOW use horner's method in a macro
# [ p... is a variable list of arguments passed as an array]

macro horner(x, p...)
    ex = esc(p[end])
    for i = length(p)-1:-1:1
        ex = :(mad(t, $ex, $(esc(p[i]))))
    end
    Expr(:block, :(t = $(esc(x))), ex)
end

@horner 2.1 1 2 3 4 5

# The saving of eliminating the loop for a 5th order polynomial is not great
# However for larger arrays and complex calculations this can be substantial
#
macroexpand(Main,:(@horner 2.1 1 2 3 4 5))

# Mike Innes' Macrotools has a useful set of macros and utility functions
# We will discuss the package in more detail later

using MacroTools:postwalk

# The postwalk function splits an expression into symbols and then
# reconstructs it, so we can apply different operations to each symbol

ex = :(1 + (2 + 3) + 4)
p = postwalk(ex) do x
    x isa Integer ? fac(x) : x
end

# Evaluate the expression
eval(p)

map(x -> @show(x), [1,2,3,4]);

postwalk(ex) do x
         @show x
end

@capture(ex, a_ + b_ + c_)

b

a*eval(b) + c  #=> 1*5 + 4

reduce(+, 1:10)

plus(a, b) = :($a + $b)

p = reduce(plus, 1:10)

eval(p)

# Do something useful
# This is the series expansion for the SINE function
# We need the factorial function and will use the version from the STDLIB
#
using SpecialFunctions
k = 2
pp = [:($((-1)^k) * x^$(1+2k) / $(factorial(1+2k))) for k = 0:5]

# We can reduce this to a single expression
reduce(plus,pp)

# ... and evaluate it for a specific value of x
x = 2.1; eval(reduce(plus,pp))

# A simple generated function to execute a multiply + add
# The resukt returned is a symbol
#
@generated function mad(a,b,c)
    Core.println("Calculating: a*b + c")
    return :(a * b + c)
end

# Call the function
mad(2.3,1.7,1.1)

# And again with the same arguments
# This time it is not reevaluated
#
mad(2.3,1.7,1.1)

# With different arguments the function is reevaluated
mad(2.3,1.7,1)

# To illustrate the use consider the following function ...
# ... which multiplies the size of dimensions of a n-D array
#
# First a 'normal' function definition

function pdims(x::Array{T,N}) where {T,N}
  s = 1
  for i = 1:N
    s = s * size(x, i)
  end
  return s
end

# And then a 'generated function' version
@generated function gpdims(x::Array{T,N}) where {T,N}
  ex = :(1)
  for i = 1:N
     ex = :(size(x, $i) * $ex)
  end
  return ex
end

# We need an array to test the function
aa = reshape([rand() for i = 1:1000],10,5,5,4); size(aa)

# Both functions provide the same result
#
pdims(aa) == gpdims(aa)

# But if we look at the lowered code
@code_lowered pdims(aa)

@code_lowered gpdims(aa)

# Which results in very different generated native code
@code_native pdims(aa)

@code_native gpdims(aa)

# Previous versions of Julia had an example section, absent in version v1.0
# An interesting one was modular integer, which required a little re-editing
#
module ModInts
export ModInt

import Base: +, -, *, /, inv

struct ModInt{n} <: Integer
  k::Int
  ModInt{n}(k) where n = new(mod(k,n))
end

Base.show(io::IO, k::ModInt{n}) where n =
    print(io, get(io, :compact, false) ? k.k : "$(k.k) mod $n")

(a::ModInt{n} + b::ModInt{n}) where n = ModInt{n}(a.k+b.k)
(a::ModInt{n} - b::ModInt{n}) where n = ModInt{n}(a.k-b.k)
(a::ModInt{n} * b::ModInt{n}) where n = ModInt{n}(a.k*b.k)
-(a::ModInt{n}) where n = ModInt{n}(-a.k)

inv(a::ModInt{n}) where n = ModInt{n}(invmod(a.k, n))
(a::ModInt{n} / b::ModInt{n}) where n = a*inv(b)

Base.promote_rule(::Type{ModInt{n}}, ::Type{Int}) where n = ModInt{n}
Base.convert(::Type{ModInt{n}}, i::Int) where n = ModInt{n}(i)

end

# Test the module
#
using Main.ModInts
m1 = ModInt{11}(2)
m2 = ModInt{11}(7)
m3 = 3*m1 + m2  # => 13 mod 11

# Because of multiple dispatch we can do the following
#
mm = reshape([ModInt{11}(rand(0:10)) for i = 1:100],10,10)
ma = [ModInt{11}(rand(0:10)) for i = 1:10]

mm.*ma'

## An Ordered Pair Module
#
module OrdPairs

import Base: +,-,*,/,==,!=,>,<,>=,<=
import Base: abs,conj,inv,zero,one,show
import LinearAlgebra: transpose,adjoint,norm

export OrdPair

struct OrdPair{T<:Number}
    a::T
    b::T
end

OrdPair(x::Number) = OrdPair(x, zero(T))

value(u::OrdPair)   = u.a
epsilon(u::OrdPair) = u.b

zero(::Type{OrdPairs.OrdPair}) = OrdPair(zero(T),zero(T))
one(::Type{OrdPairs.OrdPair})  = OrdPair(one(T),zero(T))

abs(u::OrdPair)  = abs(value(u))
norm(u::OrdPair) = norm(value(u))

+(u::OrdPair, v::OrdPair) = OrdPair(value(u) + value(v), epsilon(u) + epsilon(v))
-(u::OrdPair, v::OrdPair) = OrdPair(value(u) - value(v), epsilon(u) - epsilon(v))
*(u::OrdPair, v::OrdPair) = OrdPair(value(u)*value(v), epsilon(u)*value(v) + value(u)*epsilon(v))
/(u::OrdPair, v::OrdPair) = OrdPair(value(u)/value(v),(epsilon(u)*value(v) - value(u)*epsilon(v))/(value(v)*value(v)))

==(u::OrdPair, v::OrdPair) = norm(u) == norm(v)
!=(u::OrdPair, v::OrdPair) = norm(u) != norm(v)
>(u::OrdPair, v::OrdPair)  = norm(u) > norm(v)
>=(u::OrdPair, v::OrdPair) = norm(u) >= norm(v)
<(u::OrdPair, v::OrdPair)  = norm(u) < norm(v)
<=(u::OrdPair, v::OrdPair) = norm(u) <= norm(v)

+(x::Number, u::OrdPair) = OrdPair(value(u) + x, epsilon(u))
+(u::OrdPair, x::Number) = x + u

-(x::Number, u::OrdPair) = OrdPair(x - value(u), epsilon(u))
-(u::OrdPair, x::Number) = OrdPair(value(u) - x, epsilon(u))

*(x::Number, u::OrdPair) = OrdPair(x*value(u), x*epsilon(u))
*(u::OrdPair, x::Number) = x*u

/(u::OrdPair, x::Number) = (1.0/x)*u

conj(u::OrdPair)  = OrdPair(value(u),-epsilon(u))
inv(u::OrdPair)   = one(OrdPair)/u

transpose(u::OrdPair) = u
transpose(uu::Array{OrdPairs.OrdPair,2}) = [uu[j,i] for i=1:size(uu)[1],j=1:size(uu)[2]]
adjoint(u::OrdPair) = u

convert(::Type{OrdPair}, x::Number) = OrdPair(x,zero(x))
promote_rule(::Type{OrdPair}, ::Type{<:Number}) = OrdPair

## show(io::IO,u::OrdPair) = print(io,value(u)," + (",epsilon(u),")ϵ")

function show(io::IO,u::OrdPair)
 op::String = (epsilon(u) < 0.0) ? " - " : " + ";
 print(io,value(u),op,abs(epsilon(u)),"ϵ")
end

end

# Exercise the module
# Notice how the show() routine provides pretty-print output
using Main.OrdPairs
p1 = OrdPair(2.3,-1.7)
p2 = OrdPair(4.4,0.9)
p1 * p2

p2/p1

# Again we can operate on array of Ordered Pairs
using Statistics
pp = [OrdPair(rand(),rand()) for i in 1:100]
mean(pp)

# And promote a rational in a mixed OP
p3 = OrdPair(2.3, 11/7)

# The module is not a full implementation
p4 = OrdPair(2.3, 11.0 + 7.2im)

# Also a number of other arithmetic functions need to be imported from Base
std(p1)


"""
    insertion_sorting(array::Array{Int64,1})

The insertion sorting algorithm builds the final sorted array by inserting elements that are
greater than the key, to one position ahead of their current position step one item at a
time. For more information see: [https://en.wikipedia.org/wiki/Insertion_sort](https://en.wikipedia.org/wiki/Insertion_sort)

# Arguments
- `array::Array{Int64,1}`: Unsorted array of integers

# Examples
```julia-repl
julia> import ClassicAlgorithmsCollections
julia> arr = [64, 34, 25, 12, 22, 11, 90]
julia> ClassicAlgorithmsCollections.insertion_sorting(arr)
[11, 12, 22, 25, 34, 64, 90]
```
"""
function insertion_sorting(array::Array{Int64,1})

    # Traverse through 1 to len(array)
    for i in 2:length(array)
        #
        key = array[i]
        j = i - 1
        while j >= 1 && key < array[j]
            array[j+1] = array[j]
            j -= 1
            array[j+1] = key
        end
    end
    return array
end


# # 1D Hawkes process with delayed alpha kernel

#=
In this example, I simulate either a 1D or a 2D Hawkes process, with a
delayed-alpha interaction kernel

```math
g(t) = H(t-t_{\text{delay}}) \,  \frac{(t-t_{\text{delay}})}{\tau^2} \,
 \exp\left(- \frac{(t-t_{\text{delay}})}{\tau} \right)
```

where $H(x)$ is the Heaviside function: $H(x)=0$ for $x<0$, $H(x)=1$ for $t\geq 0$.

Kernels are always normalized so that their integral is 1.
=#

# ## Initialization
using LinearAlgebra,Statistics,StatsBase,Distributions
using Plots,NamedColors ; theme(:default) #; plotlyjs()
using FFTW

using ProgressMeter
using Random
Random.seed!(0)

using HawkesSimulator; const global H = HawkesSimulator

##
# ## Define and visualize the kernel

mytau = 0.5
mydelay = 2.0
myw = fill(0.85,(1,1)) # 1x1 matrix
myinput = [0.5,]       # 1-dim vector

ker = H.KernelAlphaDelay(mytau,mydelay)

plt = let ts = range(-0.5,8;length=150)
  y = [H.interaction_kernel(_t,ker) for _t in ts]
  plt=plot(ts,y ; linewidth=3,leg=false,xlabel="time (s)",
      ylabel="interaction kernel")
  ymax=ylims()[2]
  plot!(plt,[0,0],[0,ymax];linestyle=:dash,linecolor=:black)
end;
plot(plt)

# Note that the kernel starts after zero, according to the delay indicated.

# As a side note: in order to simulate Hawkes processes, one always
# needs to define a *non-increasing upper limit* to the kernel.
# This is what it looks like for this kernel.

plt = let  ts = range(-0.5,8;length=150)
  y = [H.interaction_kernel(_t,ker) for _t in ts]
  yu = [H.interaction_kernel_upper(_t,ker) for _t in ts]
  plt = plot(ts,y ; linewidth=3,xlabel="time (s)",
      ylabel="interaction kernel", label="true kernel")
  plot!(plt, ts,yu ; linewidth=2, label="upper limit", linestyle=:dash)
  ymax=ylims()[2]
  plot!(plt,[0,0],[0,ymax];linestyle=:dash,linecolor=:black,label="")
end;
plot(plt)

# the closer the upper limit is to the true kernel,
# the more efficient the simulation.

##      #src
# ## Build the network and run it
# I compare the final rate with what I expect from the analytic solution
# (see first example file)

pops = H.PopulationState(ker,1)
ntw = H.RecurrentNetwork(pops,myw,myinput)

function run_simulation!(network,num_spikes,
    t_flush_trigger=300.0,t_flush=100.0)
  t_now = 0.0
  H.reset!(network) # clear spike trains etc
  @showprogress "Running Hawkes process..." for _ in 1:num_spikes
    t_now = H.dynamics_step_singlepopulation!(t_now,network)
    H.flush_trains!(network,t_flush_trigger;Tflush=t_flush)
  end
  H.flush_trains!(network) # flush everything into history
  return t_now
end

n_spikes = 80_000
Tmax = run_simulation!(ntw,n_spikes);

ratenum = H.numerical_rates(pops)[1]
rate_analytic = (I-myw)\myinput
rate_analytic = rate_analytic[1] # from 1-dim vector to scalar

@info "Mean rate -  numerical $(round(ratenum;digits=2)), analytic  $(round(rate_analytic;digits=2))"

##
# ## Covariance density

# First, compute covariance density numerically for a reasonable time step
mytrain = pops.trains_history[1]
mydt = 0.1
myτmax = 60.0
mytaus = H.get_times(mydt,myτmax)
ntaus = length(mytaus)
cov_num = H.covariance_self_numerical(mytrain,mydt,myτmax);

# now compute covariance density
#  analytically (as in Hawkes models), at higher resolution,
# and compare analytic and numeric
#
# Note that the high resolution is not just for a better plot, but also to
# ensure the result is more precise when we move from frequency domain
# (Fourier transforms) to time domain.

function four_high_res(dt::Real,Tmax::Real) # higher time resolution, longer time
  k1,k2 = 2 , 0.01
  myτmax = Tmax * k1
  dt *= k2
  mytaus = H.get_times(dt,myτmax)
  nkeep = div(length(mytaus),k1)
  myfreq = H.get_frequencies_centerzero(dt,myτmax)
  gfou = myw[1,1] .* H.interaction_kernel_fourier.(myfreq,Ref(ker)) |> ifftshift
  ffou = let r=rate_analytic
    covf(g) = r/((1-g)*(1-g'))
    map(covf,gfou)
  end
  retf = real.(ifft(ffou))
  retf[2:end] ./= dt # first element is rate
  return mytaus[1:nkeep],retf[1:nkeep]
end

taush,covfou=four_high_res(mydt,myτmax)

plt= let plt = plot(xlabel="time delay (s)",ylabel="Covariance density")
  plot!(plt,mytaus[2:end], cov_num[2:end] ; linewidth=3, label="numerical" )
  plot!(plt,taush[2:end],covfou[2:end]; label="analytic",linewidth=3,linestyle=:dash)
end;

plot(plt)

# Analytics and numerics match quite well.

# **THE END**

# Literate.markdown("examples/alphadelay.jl","docs/src";documenter=true,repo_root_url="https://github.com/dylanfesta/HawkesSimulator.jl/blob/master") #src


# Load packages
using HomotopyContinuation, LinearAlgebra

# Set HC to don't use compilation since the computations are all small
set_default_compile(:none)

# Built index matrix for catalecticant space Cat(r,s)

sym_to_vec(L,m) = [L[i, j] for i = 1:m for j = i:m]

function vec_to_sym(l::AbstractVector{T},m::Int64) where T
    k = 0
    L = Matrix{T}(undef, m, m)
    for i in 1:m, j in i:m
        k += 1
        L[i, j] = L[j, i] = l[k]
    end
    L
end

function CatalecticantIndices(r,s,m)
    tf = [sum(I) == r for I in Iterators.product(ntuple(i->0:r, s)...)]
    indx = collect.(collect(Iterators.product(ntuple(i->0:r, s)...))[tf])
    M = hcat([indx for _ in 1:m]...)
    return M+permutedims(M)
end

function CatalecticantSpace(M::AbstractMatrix,A::AbstractMatrix,m::Integer)
    eq = Vector{typeof(A[1,1]+A[1,1])}(undef,0)
    count = Vector{typeof(M[1,1])}(undef,0)
    ## Parsing the upper triangular matrix
    for i in 1:m
        for j in i:m
            ## Comparing with the remaining entries
            for i2 in (i+1):m
                for j2 in i2:m
                    if M[i,j] == M[i2,j2]
                       #forcing it's not compared to some entry already used to compare
                       if ([i,j] in count)==false
                         #push!(eq, A[i,j]-A[i2,j2]);
                         push!(eq, A[j,i]-A[j2,i2]);
                         push!(count,[i,j]);
                       end
                    end
                end
            end
        end
    end
return eq
end

function adjugate(M::AbstractMatrix{T}) where T
    nr, nc = size(M)
    out = similar(M)
    rows = BitArray(ones(Int16,nr))
    cols = BitArray(ones(Int16,nc))
    for r in 1:nr
        for c in r:nc
            rows[r] = 0
            cols[c] = 0
            out[c, r] = (-1)^(c+r)*det(M[rows,cols])
            rows[r] = 1
            cols[c] = 1
        end
    end
    return out
end


function cupula_dynamics(τ::Real, Δt::Real)
    τ = 4 # same as Laurens & Angelaki (2017)
    κ₁ = τ / (τ + Δt)
    κ₂ = Δt / (τ + Δt)
    return κ₁, κ₂
end

function velocity(D::Number, A::Number, f::Number, t::Number, start::Number)
    D * A * 1/(2*π*f) * (1-cos.(2*π*f*(t-start)))
end

function acceleration(D::Number, A::Number, f::Number, t::Number, start::Number)
    D * A * sin.(2*π*f*(t-start))
end


using AbstractFFTs
using FFTW

struct FourierTransform{ℳ, 𝒩, RT <: Number, CT <: Number}
    ℳcut::Int
    𝒩cut::Int
    m::Vector{RT}
    n::Vector{RT}
    χ::Matrix{RT}
    μ::Matrix{CT}
    forw_plan::FFTW.rFFTWPlan{RT,-1,false,2}
    back_plan::AbstractFFTs.ScaledPlan{CT, FFTW.rFFTWPlan{CT, 1, false, 2, Tuple{Int,Int}}, RT}
end

function FourierTransform(grid::Grid2d{M,N,RT}; ℳcut::Int=0, 𝒩cut::Int=0) where {M,N,RT}
    ℳ = M
    𝒩 = div(N,2)+1

    CT = typeof(complex(one(RT),0))

    m = circshift(collect(-div(ℳ-1,2):div(ℳ,2)), -div(ℳ-1,2))
    n = collect(1:𝒩) .- 1

    χ   = cutoff_frequencies(RT, ℳ, 𝒩, ℳcut, 𝒩cut)
    μ   = multiplicity(CT, ℳ, 𝒩)

    forw_plan = plan_rfft(zeros(RT, M, N), (2,1))
    back_plan = plan_irfft(zeros(CT, ℳ, 𝒩), N, (2,1))

    # println(size(forw_plan), eltype(forw_plan), typeof(forw_plan))
    # println(size(back_plan), eltype(back_plan), typeof(back_plan))

    FourierTransform{ℳ, 𝒩, RT, CT}(ℳcut, 𝒩cut, m, n, χ, μ, forw_plan, back_plan)
end

function Base.show(io::IO, ft::FourierTransform{ℳ,𝒩,RT,CT}) where {ℳ,𝒩,RT,CT}
    print(io, "Fourier Transform for data types (", RT, ",", CT, ") with (", ℳ, ",", 𝒩, ") frequencies.\n")
    print(io, "   Cut off frequencies: ", ft.χ, "\n")
    print(io, "   Multiplicity:        ", ft.μ, "\n")
end

function get_trans(ft::FourierTransform{ℳ,𝒩,RT,CT}) where {ℳ,𝒩,RT,CT}
    zeros(CT, ℳ, 𝒩)
end

"""
Plain (real) FFT.
"""
function prfft!(ft::FourierTransform{ℳ,𝒩,RT,CT}, u::Matrix{RT}, û::Matrix{CT}) where {ℳ,𝒩,RT,CT}
    mul!(û, ft.forw_plan, u)
end

"""
Filtered (real) FFT.
"""
function frfft!(ft::FourierTransform{ℳ,𝒩,RT,CT}, u::Matrix{RT}, û::Matrix{CT}) where {ℳ,𝒩,RT,CT}
    mul!(û, ft.forw_plan, u)
    û .*= ft.χ
end

"""
Inverse (real) FFT.
"""
function irfft!(ft::FourierTransform{ℳ,𝒩,RT,CT}, û::Matrix{CT}, u::Matrix{RT}) where {ℳ,𝒩,RT,CT}
    mul!(u, ft.back_plan, û)
end

function cutoff_frequencies(T, ℳ, 𝒩, ℳcut, 𝒩cut)
    χ = ones(T, ℳ, 𝒩)

    if ℳcut > 0
        for n in 1:𝒩
            for m in ℳcut+1:div(ℳ+1,2)
                χ[m, n] = 0
            end
            for m in ℳcut-1:div(ℳ,2)
                χ[end-m, n] = 0
            end
        end
    end

    if 𝒩cut > 0
        for n in 𝒩cut+1:𝒩
            for m in 1:ℳ
                χ[m,n] = 0
            end
        end
    end

    χ
end

function multiplicity(T, ℳ, 𝒩)
    μ = 2ones(T, ℳ, 𝒩)
    μ[:,1] .= 1
    μ
end


function pearson(a::AbstractSet, b::AbstractSet)
	sA, sB, sI = 0, length(b), 0
	for el in a
		sA += 1
		if contains(b, el)
			sI += 1
		end
	end
	return sI / sqrt(sA * sB)
end

pearson(a::AbstractVector, b::AbstractVector) = cor(a, b)


using ShipNoiseGenerators
using DSP, FFTW, Statistics, Random, Test

function bandpassfilter(x, cutoff, fs; order=2)
    hp = digitalfilter(Bandpass(cutoff[1], cutoff[2]; fs=fs), Butterworth(order))
    filt(hp, x)
end

@testset "ShipNoiseGenerators.jl" begin

n = 96000
fs = 96000
g1 = PinkNoiseGenerator(n)
x = rand(g1)
p = welch_pgram(x, 1024; fs=fs)
xpts = log10.(freq(p))[2:end]
ypts = 10*log10.(power(p))[2:end]
β = sum((xpts.-mean(xpts)).*(ypts.-mean(ypts)))/sum((xpts.-mean(xpts)).^2)

@test β ≈ (10*log10(1/xpts[end])-10*log10(1/xpts[1]))/(log10(xpts[end])-log10(xpts[1])) atol=0.1

n = 96000
fs = 96000
As = [0.8]
frequencies = [200.0]
ϕs = [0.1]
g2 = ShipNoiseGenerator(n=n, fs=fs, As=As, frequencies=frequencies, ϕs=ϕs)
x = rand(g2)

cutoff = [10.0, 1000.0]
y = bandpassfilter(x, cutoff, fs; order=7)
env = abs.(hilbert(y))
rate = 1//80
fsnew = fs*rate
spec = abs.(rfft(resample(env, rate)))
spec[1] = 0.0
freqindices = FFTW.rfftfreq(n, fs)
@test freqindices[argmax(spec)] == frequencies[1]

end


@testset "Array variables" begin
    lfp = LinearFractionalModel(with_optimizer(Clp.Optimizer))
    a = [-2, 0]
    x = @variable(lfp, [i=1:2], base_name="x", lower_bound=0)
    @constraint(lfp, x[2] <= 6)
    @constraint(lfp, -x[1] + x[2] <= 4)
    @constraint(lfp, 2x[1] + x[2] <= 14)
    numer = @expression(lfp, sum(a[i] * x[i] for i in 1:2) + 2)
    denom = @expression(lfp, x[1] + 3x[2] + 4)
    set_objective(lfp, MOI.MIN_SENSE, numer, denom)
    optimize!(lfp)
    @test termination_status(lfp) == MOI.OPTIMAL
    @test all(value.(x) .≈ [7.0, 0.0])
end

@testset "Array constraints" begin
    lfp = LinearFractionalModel(with_optimizer(Clp.Optimizer))
    x = @variable(lfp, [i=1:2], base_name="x")
    a = [2.0, 1.0]
    upbs = [4.0, 20.0]
    lbs = [-4.0, -20.0]
    @constraint(lfp, [i=1:2], x[i] <= upbs[i])
    @constraint(lfp, [i=1:2], x[i] >= lbs[i])
    numer = @expression(lfp,  sum(a[i] * x[i] for i in 1:2) + 2)
    denom = @expression(lfp,  -x[1] + 20*x[2] + 4)
    set_objective(lfp, MOI.MIN_SENSE, numer, denom)
    optimize!(lfp)
    @test termination_status(lfp) === MOI.OPTIMAL
    @test value.(x) ≈ [-4.0, -0.2]
end

@testset "Match LP" begin
    lfp = LinearFractionalModel(with_optimizer(Clp.Optimizer))
    a = [4, 2]
    upbs = [4.0, 20.0]
    lbs = [-1.0, -10.0]
    x = @variable(lfp, [i=1:2], base_name="x", lower_bound=lbs[i], upper_bound=upbs[i])
    @constraint(lfp, x[1] + x[2] <= 5.0)
    @constraint(lfp, x[1] - 2*x[2] >= 10.0)
    numer = @expression(lfp,  dot(a, x))
    denom = @expression(lfp,  sum(x))
    set_objective(lfp, MOI.MIN_SENSE, numer, denom)
    optimize!(lfp)
    m = Model(with_optimizer(Clp.Optimizer))
    xm = @variable(m, [i=1:2], base_name="x")
    t = @variable(m, lower_bound=0.0)
    a = [4, 2]
    upbs = [4, 20]
    lbs = [-1,-10]
    @constraint(m, xm[1] + xm[2] <= 5.0t)
    @constraint(m, xm[1] - 2*xm[2] >= 10.0t)
    @constraint(m, [i=1:2], xm[i] <= upbs[i] * t)
    @constraint(m, [i=1:2], xm[i] >= lbs[i] * t)
    @constraint(m, sum(xm) == 1)
    @objective(m,  Min, sum(a[i] * xm[i] for i in 1:2))
    optimize!(m)
    @test value.(xm)/value(t) ≈ value.(x)
    @test objective_value(lfp) ≈ objective_value(m)
end

@testset "Constant denominator" begin
    lfp = LinearFractionalModel(with_optimizer(Clp.Optimizer))
    a = [4, 2]
    upbs = [4, 20]
    lbs = [-1, -10]
    x = @variable(lfp, [i=1:2], base_name="x", lower_bound=lbs[i], upper_bound=upbs[i])
    @constraint(lfp, x[1] + x[2] <= 5.0)
    @constraint(lfp, x[1] - 2*x[2] >= 10.0)
    @constraint(lfp, [i=1:2], x[i] <= upbs[i])
    @constraint(lfp, [i=1:2], x[i] >= lbs[i])
    numer = @expression(lfp, sum(a[i] * x[i] for i in 1:2))
    denom = @expression(lfp, 5.0)
    set_objective(lfp, MOI.MIN_SENSE, numer, denom)
    optimize!(lfp)

    m = Model(with_optimizer(Clp.Optimizer))
    a = [4, 2]
    upbs = [4, 20]
    lbs = [-1,-10]
    xm = @variable(m, [i=1:2], base_name="x", lower_bound=lbs[i], upper_bound=upbs[i])
    @constraint(m, xm[1] + xm[2] <= 5.0)
    @constraint(m, xm[1] - 2*xm[2] >= 10.0)
    @objective(m,  Min, sum(a[i] * xm[i] for i in 1:2)/5.0)
    optimize!(m)
    @test value.(xm) ≈ value.(x)
    @test objective_value(lfp) ≈ objective_value(m)
end


# From David Goldberg's blog post
# https://tech.ebayinc.com/engineering/fast-approximate-logarithms-part-iii-the-formulas/
@inline function fastlog2(x::Float32)::Float32
    # (x-1)*(a*(x-1) + b)/((x-1) + c) (line 8 of table 2)
    a =   0.338953f0
    b =   2.198599f0
    c =   1.523692f0
    #
    # Assume IEEE representation, which is sgn(1):exp(8):frac(23)
    # representing (1+frac)*2^(exp-127)  Call 1+frac the significand
    #

    # get exponent
    ux1i = reinterpret(UInt32, x)
    exp = (ux1i & 0x7F800000) >> 23
    # actual exponent is exp-127, will subtract 127 later

    greater = ux1i & 0x00400000  # true if signif > 1.5
    if greater !== 0x00000000
        ux2i = (ux1i & 0x007FFFFF) | 0x3f000000
        signif = reinterpret(Float32, ux2i)
        fexp = exp - 126f0    # 126 instead of 127 compensates for division by 2
        signif = signif - 1.0f0
    else
        ux2i = (ux1i & 0x007FFFFF) | 0x3f800000
        signif = reinterpret(Float32, ux2i)
        fexp = exp - 127f0
        signif = signif - 1.0f0
    end
    lg2 = fexp + signif*(a*signif + b)/(signif + c)
    return lg2
end

# Translated from OpenLibm but less accurate because I forced the tableau to be
# Float32, whereas OpenLibm uses Float64
#
# https://github.com/JuliaMath/openlibm/blob/cca41bc1abd01804afa4862bbd2c79cc9803171a/src/s_exp2f.c
const EXP2FT = (Float32(0x1.6a09e667f3bcdp-1),
                Float32(0x1.7a11473eb0187p-1),
                Float32(0x1.8ace5422aa0dbp-1),
                Float32(0x1.9c49182a3f090p-1),
                Float32(0x1.ae89f995ad3adp-1),
                Float32(0x1.c199bdd85529cp-1),
                Float32(0x1.d5818dcfba487p-1),
                Float32(0x1.ea4afa2a490dap-1),
                Float32(0x1.0000000000000p+0),
                Float32(0x1.0b5586cf9890fp+0),
                Float32(0x1.172b83c7d517bp+0),
                Float32(0x1.2387a6e756238p+0),
                Float32(0x1.306fe0a31b715p+0),
                Float32(0x1.3dea64c123422p+0),
                Float32(0x1.4bfdad5362a27p+0),
                Float32(0x1.5ab07dd485429p+0))
@inline function _exp2(x::Float32)
    TBLBITS = UInt32(4)
    TBLSIZE = UInt32(1 << TBLBITS)

    redux = Float32(0x1.8p23f) / TBLSIZE
    P1    = Float32(0x1.62e430p-1f)
    P2    = Float32(0x1.ebfbe0p-3f)
    P3    = Float32(0x1.c6b348p-5f)
    P4    = Float32(0x1.3b2c9cp-7f)

    # Reduce x, computing z, i0, and k.
    t::Float32 = x + redux
    i0 = reinterpret(UInt32, t)
    i0 += TBLSIZE ÷ UInt32(2)
    k::UInt32 = unsafe_trunc(UInt32, (i0 >> TBLBITS) << 20)
    i0 &= TBLSIZE - UInt32(1)
    t -= redux
    z = x - t
    twopk = Float32(reinterpret(Float64, UInt64(0x3ff00000 + k) << 32))

    # Compute r = exp2(y) = exp2ft[i0] * p(z).
    tv = EXP2FT[i0+UInt32(1)]
    u = tv * z
    tv = tv + u * (P1 + z * P2) + u * (z * z) * (P3 + z * P4)

    # Scale by 2**(k>>20)
    return tv * twopk
end

"""
    fastpow(x::Real, y::Real) -> Float32
"""
@inline function fastpow(x::Real, y::Real)
    if iszero(x)
        return 0f0
    elseif isinf(x) && isinf(y)
        return Float32(Inf)
    else
        return _exp2(convert(Float32,y) * fastlog2(convert(Float32, x)))
    end
end
@inline fastpow(x, y) = x^y


using FromFile
using SymbolicUtils
using SymbolicUtils: Chain, If, RestartedChain, IfElse, Postwalk, Fixpoint, @ordered_acrule, isnotflat, flatten_term, needs_sorting, sort_args, is_literal_number, hasrepeats, merge_repeats, _isone, _iszero, _isinteger, istree, symtype, is_operation, has_trig, polynormalize
@from "Core.jl" import Options
@from "InterfaceSymbolicUtils.jl" import SYMBOLIC_UTILS_TYPES

function multiply_powers(eqn::T)::SYMBOLIC_UTILS_TYPES where {T<:Union{<:Number,SymbolicUtils.Sym{<:Number}}}
	return eqn
end

function multiply_powers(eqn::T, op::F)::SYMBOLIC_UTILS_TYPES where {F,T<:SymbolicUtils.Term{<:Number}}
	args = SymbolicUtils.arguments(eqn)
	nargs = length(args)
	if nargs == 1
		return op(multiply_powers(args[1]))
	elseif op == ^
		l = multiply_powers(args[1])
		n::Int = args[2]
		if n == 1
			return l
		elseif n == -1
			return 1.0 / l
		elseif n > 1
			return reduce(*, [l for i=1:n])
		elseif n < -1
			return reduce(/, vcat([1], [l for i=1:abs(n)]))
		else
			return 1.0
		end
	elseif nargs == 2
		return op(multiply_powers(args[1]), multiply_powers(args[2]))
	else
		return mapreduce(multiply_powers, op, args)
	end
end

function multiply_powers(eqn::T)::SYMBOLIC_UTILS_TYPES where {T<:SymbolicUtils.Term{<:Number}}
	op = SymbolicUtils.operation(eqn)
	return multiply_powers(eqn, op)
end

# Operators required for each rule:
function get_simplifier(binops::A, unaops::B) where {A,B}
    PLUS_RULES = [
       rule for (required_ops, rule) in [
       ((+,), @rule(~x::isnotflat(+) => flatten_term(+, ~x))),
       ((+,), @rule(~x::needs_sorting(+) => sort_args(+, ~x))),
       ((+,), @ordered_acrule(~a::is_literal_number + ~b::is_literal_number => ~a + ~b)),
       ((+,), @acrule(*(~~x) + *(~β, ~~x) => *(1 + ~β, (~~x)...))),
       ((+,), @acrule(*(~α, ~~x) + *(~β, ~~x) => *(~α + ~β, (~~x)...))),
       ((+,), @acrule(*(~~x, ~α) + *(~~x, ~β) => *(~α + ~β, (~~x)...))),
       ((+,), @acrule(~x + *(~β, ~x) => *(1 + ~β, ~x))),
       ((+,), @acrule(*(~α::is_literal_number, ~x) + ~x => *(~α + 1, ~x))),
       ((+,), @rule(+(~~x::hasrepeats) => +(merge_repeats(*, ~~x)...))),
       ((+,), @ordered_acrule((~z::_iszero + ~x) => ~x)),
       ((+,), @rule(+(~x) => ~x))]
       if all([(op in binops || op in unaops) for op in required_ops])
    ]
    TIMES_RULES = [
       rule for (required_ops, rule) in [
       ((*,), @rule(~x::isnotflat(*) => flatten_term(*, ~x))),
       ((*,), @rule(~x::needs_sorting(*) => sort_args(*, ~x))),
       ((*,), @ordered_acrule(~a::is_literal_number * ~b::is_literal_number => ~a * ~b)),
       ((*,), @rule(*(~~x::hasrepeats) => *(merge_repeats(^, ~~x)...))),
       ((*,), @acrule((~y)^(~n) * ~y => (~y)^(~n+1))),
       ((*,), @ordered_acrule((~x)^(~n) * (~x)^(~m) => (~x)^(~n + ~m))),
       ((*,), @ordered_acrule((~z::_isone  * ~x) => ~x)),
       ((*,), @ordered_acrule((~z::_iszero *  ~x) => ~z)),
       ((*,), @rule(*(~x) => ~x))]
       if all([(op in binops || op in unaops) for op in required_ops])
    ]
    POW_RULES =[
       rule for (required_ops, rule) in [
       ((*,), @rule(^(*(~~x), ~y::_isinteger) => *(map(a->SymbolicUtils.pow(a, ~y), ~~x)...))),
       ((*,), @rule((((~x)^(~p::_isinteger))^(~q::_isinteger)) => (~x)^((~p)*(~q)))),
       ((*,), @rule(^(~x, ~z::_iszero) => 1)),
       ((*,), @rule(^(~x, ~z::_isone) => ~x)),
       ((*, /,), @rule(inv(~x) => ~x ^ -1))]
       if all([(op in binops || op in unaops) for op in required_ops])
    ]
    ASSORTED_RULES =[
       rule for (required_ops, rule) in [
       ((), @rule(identity(~x) => ~x)),
       ((*,), @rule(-(~x) => -1*~x)),
       ((*, -,), @rule(-(~x, ~y) => ~x + -1(~y))),
       ((/, *,), @rule(~x / ~y => ~x * SymbolicUtils.pow(~y, -1))),
       ((), @rule(one(~x) => one(symtype(~x)))),
       ((), @rule(zero(~x) => zero(symtype(~x))))]
       if all([(op in binops || op in unaops) for op in required_ops])
    ]
    TRIG_RULES = [
       rule for (required_ops, rule) in [
       ((sin, cos, *, +,), @acrule(sin(~x)^2 + cos(~x)^2 => one(~x))),
       ((sin, cos, *, +,), @acrule(sin(~x)^2 + -1        => cos(~x)^2)),
       ((sin, cos, *, +,), @acrule(cos(~x)^2 + -1        => sin(~x)^2)),
       ((tan, sec, *, +,), @acrule(tan(~x)^2 + -1*sec(~x)^2 => one(~x))),
       ((tan, sec, *, +,), @acrule(tan(~x)^2 +  1 => sec(~x)^2)),
       ((tan, sec, *, +,), @acrule(sec(~x)^2 + -1 => tan(~x)^2)),
       ((cot, csc, *, +,), @acrule(cot(~x)^2 + -1*csc(~x)^2 => one(~x))),
       ((cot, csc, *, +,), @acrule(cot(~x)^2 +  1 => csc(~x)^2)),
       ((cot, csc, *, +,), @acrule(csc(~x)^2 + -1 => cot(~x)^2))]
       if all([(op in binops || op in unaops) for op in required_ops])
    ]
    function number_simplifier()
        rule_tree = [If(istree, Chain(ASSORTED_RULES)),
                     If(is_operation(+),
                        Chain(PLUS_RULES)),
                     If(is_operation(*),
                        Chain(TIMES_RULES)),
                     If(is_operation(^),
                        Chain(POW_RULES))] |> RestartedChain

        rule_tree
    end
    trig_simplifier(;kw...) = Chain(TRIG_RULES)
    function default_simplifier(; kw...)
        IfElse(has_trig,
               Postwalk(Chain((number_simplifier(),
                               trig_simplifier())),
                        ; kw...),
               Postwalk(number_simplifier())
                        ; kw...)
    end
    # reduce overhead of simplify by defining these as constant
    serial_simplifier = If(istree, Fixpoint(default_simplifier()))
    serial_polynormal_simplifier = If(istree,
                                      Fixpoint(Chain((polynormalize,
                                                      Fixpoint(default_simplifier())))))
    return serial_polynormal_simplifier
end

function custom_simplify(init_eqn::T, options::Options)::SYMBOLIC_UTILS_TYPES where {T<:SYMBOLIC_UTILS_TYPES}
    if !istree(init_eqn) #simplifier will return nothing if not a tree.
        return init_eqn
    end
    simplifier = get_simplifier(options.binops, options.unaops)
    eqn = simplifier(init_eqn)::SYMBOLIC_UTILS_TYPES #simplify(eqn, polynorm=true)

	# Remove power laws
    eqn = multiply_powers(eqn::SYMBOLIC_UTILS_TYPES)
	return eqn
end


using NLopt

println("Use NLOpt to fit the parameter")

obj = build_loss_objective(prob1,Tsit5(),L2Loss(t,data),
                           maxiters=10000,verbose=false)

opt = Opt(:LN_COBYLA, 1)
min_objective!(opt, obj)
(minf,minx,ret) = NLopt.optimize(opt,[1.3])
@test minx[1] ≈ 1.5 atol=1e-3

opt = Opt(:GN_ESCH, 1)
min_objective!(opt, obj.cost_function2)
lower_bounds!(opt,[1.0])
upper_bounds!(opt,[3.0])
xtol_rel!(opt,1e-3)
maxeval!(opt, 10000)
(minf,minx,ret) = NLopt.optimize(opt,[1.3])
@test minx[1] ≈ 1.5 atol=1e-1

opt = Opt(:GN_ISRES, 1)
min_objective!(opt, obj.cost_function2)
lower_bounds!(opt,[1.0])
upper_bounds!(opt,[3.0])
xtol_rel!(opt,1e-3)
maxeval!(opt, 100000)
(minf,minx,ret) = NLopt.optimize(opt,[1.2])
@test minx[1] ≈ 1.5 atol=1e-1

# test differentiation

for autodiff in (false, true)
   global obj = build_loss_objective(prob1, Tsit5(), L2Loss(t,data);
                              mpg_autodiff = autodiff, maxiters = 10000)

   global opt = Opt(:LD_MMA, 1)
   min_objective!(opt, obj.cost_function2)
   xtol_rel!(opt,1e-3)
   maxeval!(opt, 10000)
   global minf,minx,ret
   (minf,minx,ret) = NLopt.optimize(opt, [1.3])
   @test minx[1] ≈ 1.5 atol=5e-1 #take a look at this, it behaves weirdly
end


# GMM estimation for a sample from Chi^2(theta)
# compare to two method of moments estimators (see chi2mm.m)
using Distributions, Plots
function main()
n = 30
theta = 3.0
reps = 1000
results = zeros(reps,2)
for i = 1:1000
    y = rand(Chisq(theta), n)
    moments = theta -> [theta.-y  theta.-0.5.*(y .- mean(y)).^2.0]
    thetahat, junk, junk, ms, junk = gmm(moments, [3.0], eye(2))
    results[i,1] = sqrt(n)*(thetahat[1]-theta)
    W = inv(cov(ms))
    thetahat, junk, ms, junk = gmm(moments, thetahat, W)
    results[i,2] = sqrt(n)*(thetahat[1]-theta)
end
p1 = npdensity(results[:,1])
plot!(p1, title="Inefficient")
p2 = npdensity(results[:,2])
plot!(p2, title="Efficient")
plot(p1,p2,layout=(2,1),xlims=(-10,20))
savefig("Efficient.svg")
gui()
println("Monte Carlo covariance: (1,1) is inefficient GMM, (2,2) is efficient")

prettyprint(cov(results))
return
end
main()


getshape(::FiniteElement{dim,shape}) where {dim,shape} = shape

function spatial_nodal_coordinate(mesh::AbstractPolytopalMesh{dim,T}, ci::Int,fe::FiniteElement{dim},n_point::Int) where {dim,T}
    x = getverticescoords(mesh,getcell(mesh,ci))
    n_base_funcs = getngeombasefunctions(fe)
    @assert length(x) == n_base_funcs
    vec = zero(Tensors.Vec{dim,T})
    @inbounds for i in 1:n_base_funcs
        vec += fe.M[i, n_point] * x[i]
    end
    return vec
end

"""
gradient_value(ip::FiniteElement{dim,shape<:Shape,order}, k::Int, ξ::Vec{dim,T}) where {dim,shape,order, T}
Compute value of Finite Element basis `j` derivative at point ξ
on the reference shape `shape`
"""
function gradient_value(ip::FiniteElement{dim}, k::Int, ξ::Tensors.Vec{dim,T}) where {dim, T}
    Tensors.gradient(ξ -> value(ip, k, ξ), ξ)
end


using Revise
using AdFem
using PyPlot
using LinearAlgebra
using Statistics
using MAT

mmesh = Mesh(50, 50, 1/50, degree=2)

left = bcnode((x,y)->x<1e-5, mmesh)
right = bcedge((x1,y1,x2,y2)->(x1>0.049-1e-5) && (x2>0.049-1e-5), mmesh)

t1 = eval_f_on_boundary_edge((x,y)->1.0e-4, right, mmesh)
t2 = eval_f_on_boundary_edge((x,y)->0.0, right, mmesh)
rhs = compute_fem_traction_term(t1, t2, right, mmesh)

E = Variable(1.0)
ν = Variable(0.0)
D = compute_plane_stress_matrix(E*ones(get_ngauss(mmesh)), ν*ones(get_ngauss(mmesh)))
K = compute_fem_stiffness_matrix(D, mmesh)

bdval = [eval_f_on_boundary_node((x,y)->0.0, left, mmesh);
        eval_f_on_boundary_node((x,y)->0.0, left, mmesh)]
DOF = [left;left .+ mmesh.ndof]
K, rhs = impose_Dirichlet_boundary_conditions(K, rhs, DOF, bdval)
u = K\rhs
U = matread("fenics/data1.mat")["u"]

using Random; Random.seed!(233)
idx = rand(1:mmesh.ndof, 100)
idx = [idx; idx .+ mmesh.ndof]

loss = sum((U[idx] - u[idx])^2) * 1e10
sess = Session(); init(sess)

loss_ = BFGS!(sess, loss)
run(sess, [E, ν])


#=
Part of this work is derived from astropy/photutils and kbarbary/sep. The relevant derivations
are considered under a BSD 3-clause license. =#
using LazySets: AbstractHyperrectangle,
                EmptySet,
                HPolygon,
                Hyperrectangle,
                LinearMap,
                area,
                constraints_list,
                intersection,
                linear_map,
                matrix,
                set

using Rotations
using StaticArrays

function circular_overlap_single_subpixel(xmin, ymin, xmax, ymax, r, subpixels)
    frac = 0
    dx = (xmax - xmin) / subpixels
    dy = (ymax - ymin) / subpixels
    r2 = r^2

    x = xmin - 0.5dx
    for i in 1:subpixels
        x += dx
        y = ymin - 0.5dy
        for j in 1:subpixels
            y += dy
            if x^2 + y^2 < r2
                frac += 1
            end
        end
    end
    return frac / subpixels^2
end

"""Area of overlap between a rectangle and a circle"""
function circular_overlap_single_exact(xmin, ymin, xmax, ymax, r)
    R = float(typeof(xmin))
    r ≤ 0 && return zero(R)
    if 0 ≤ xmin
        0 ≤ ymin && return circular_overlap_core(xmin, ymin, xmax, ymax, r)
        0 ≥ ymax && return circular_overlap_core(-ymax, xmin, -ymin, xmax, r)
        return (circular_overlap_single_exact(xmin, ymin, xmax, 0, r) +
                circular_overlap_single_exact(xmin, 0, xmax, ymax, r))
    elseif 0 ≥ xmax
        0 ≤ ymin && return circular_overlap_core(-xmax, ymin, -xmin, ymax, r)
        0 ≥ ymax && return circular_overlap_core(-xmax, -ymax, -xmin, -ymin, r)
        return (circular_overlap_single_exact(xmin, ymin, xmax, 0, r) +
                circular_overlap_single_exact(xmin, 0, xmax, ymax, r))
    else
        0 ≤ ymin && return (circular_overlap_single_exact(xmin, ymin, 0, ymax, r) +
                            circular_overlap_single_exact(0, ymin, xmax, ymax, r))
        0 ≥ ymax && return (circular_overlap_single_exact(xmin, ymin, 0, ymax, r) +
                            circular_overlap_single_exact(0, ymin, xmax, ymax, r))
        return (circular_overlap_single_exact(xmin, ymin, 0, 0, r) +
                circular_overlap_single_exact(0, ymin, xmax, 0, r) +
                circular_overlap_single_exact(xmin, 0, 0, ymax, r) +
                circular_overlap_single_exact(0, 0, xmax, ymax, r))
    end
end

"""Core of circular overlap routine"""
function circular_overlap_core(xmin, ymin, xmax, ymax, r)
    R = float(typeof(xmin))
    xmin^2 + ymin^2 > r^2 && return zero(R)
    xmax^2 + ymax^2 < r^2 && return R((xmax - xmin) * (ymax - ymin))

    d1 = sqrt(xmax^2 + ymin^2)
    d2 = sqrt(xmin^2 + ymax^2)
    if d1 < r && d2 < r
        x1, y1 = sqrt(r^2 - ymax^2), ymax
        x2, y2 = xmax, sqrt(r^2 - xmax^2)
        area = ((xmax - xmin) * (ymax - ymin) -
                area_triangle(x1, y1, x2, y2, xmax, ymax) +
                area_arc(x1, y1, x2, y2, r))
    elseif d1 < r
        x1, y1 = xmin, sqrt(r^2 - xmin^2)
        x2, y2 = xmax, sqrt(r^2 - xmax^2)
        area = (area_arc(x1, y1, x2, y2, r) +
                area_triangle(x1, y1, x1, ymin, xmax, ymin) +
                area_triangle(x1, y1, x2, ymin, x2, y2))
    elseif d2 < r
        x1, y1 = sqrt(r^2 - ymin^2), ymin
        x2, y2 = sqrt(r^2 - ymax^2), ymax
        area = (area_arc(x1, y1, x2, y2, r) +
                area_triangle(x1, y1, xmin, y1, xmin, ymax) +
                area_triangle(x1, y1, xmin, y2, x2, y2))
    else
        x1, y1 = sqrt(r^2 - ymin^2), ymin
        x2, y2 = xmin, sqrt(r^2 - xmin^2)
        area = (area_arc(x1, y1, x2, y2, r) +
                area_triangle(x1, y1, x2, y2, xmin, ymin))
    end

    return R(area)
end

####################################
# Some geometric helpers

"""Area of a triangle defined by three vertices"""
area_triangle(x0, y0, x1, y1, x2, y2) = 0.5abs(x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1))

"""
Area of a circular segment above a chord between two points with circle radius `r`

[Reference](http://mathworld.wolfram.com/CircularSegment.html)
"""
function area_arc(x0, y0, x1, y1, r)
    a = sqrt((x1 - x0)^2 + (y1 - y0)^2)
    θ = 2 * asin(0.5 * a / r)
    return r^2 * (θ - sin(θ)) / 2
end

# is (x, y) contained within triangle specified by three points.
function inside_triangle(x, y, x1, y1, x2, y2, x3, y3)
    c = (y1 > y) ⊻ (y2 > y) && x < (x2 - x1) * (y - y1) / (y2 - y1) + x1
    c += (y2 > y) ⊻ (y3 > y) &&  x < (x3 - x2) * (y - y2) / (y3 - y2) + x2
    c += (y3 > y) ⊻ (y1 > y) && x < (x1 - x3) * (y - y3) / (y1 - y3) + x3

    # true if c is odd
    return c % 2 == 1
end

####################################
# Elliptical routines

"""
    inside_ellipse(x, y, h, k, cxx, cyy, cxy)

x: x coordinate of the test point
y: y coordinate of the test point
h: x coordinate of the center of ellipse
k: y coordinate of the center of ellipse
cxx, cyy, cxy: coefficients of equation of ellipse

Utility function to find whether a point is inside ellipse or not.

If point inside ellipse: Returns true else returns false

General equation of ellipse:
    cxx * (x - h)^2 + cxy * (x - h) * (y - k) + cyy * (y - k)^2 = 1
"""
@inline inside_ellipse(x, y, h, k, cxx, cyy, cxy) = cxx * (x - h)^2 + cxy * (x - h) * (y - k) + cyy * (y - k)^2  - 1 < 0

function elliptical_overlap_single_subpixel(xmin, ymin, xmax, ymax, cxx, cyy, cxy, subpixels)
    frac = 0
    dx = (xmax - xmin) / subpixels
    dy = (ymax - ymin) / subpixels

    x = xmin - 0.5dx
    for i in 1:subpixels
        x += dx
        y = ymin - 0.5dy
        for j in 1:subpixels
            y += dy
            if inside_ellipse(x, y, 0, 0, cxx, cyy, cxy)
                frac += 1
            end
        end
    end
    return frac / subpixels^2
end

function sort_order(d1, d2, d3)
    if d1 < d2
        if d2 < 3
            return SA[1, 2, 3]
        elseif d1 < d3
            return SA[1, 3, 2]
        else
            return SA[3, 1, 2]
        end
    else
        if d1 < d3
            return SA[2, 1, 3]
        elseif d2 < d3
            return SA[2, 3, 1]
        else
            return SA[3, 2, 1]
        end
    end
end

# overlap between triangle and unit circle
function triangle_unitcircle_overlap(x1, y1, x2, y2, x3, y3)
    # distances
    d1 = x1^2 + y1^2
    d2 = x2^2 + y2^2
    d3 = x3^2 + y3^2

    # order by distances
    ds = SA[d1, d2, d3]
    # order = sortperm(ds)
    order = sortperm(ds)
    ds = ds[order]
    x1, x2, x3 = SA[x1, x2, x3][order]
    y1, y2, y3 = SA[y1, y2, y3][order]

    # which are inside circle
    inside = map(d -> d < 1, ds)

    # on circle
    on = map(d -> d ≈ 1, ds)

    # triangle is completely inside circle
    if inside[3] || on[3]
        return area_triangle(x1, y1, x2, y2, x3, y3)

    # if vertex 1 or 2 are on the edge, then dot product with 3 to determine intersection
    elseif inside[2] || on[2]
        intersect13 = !on[1] || x1 * (x3 - x1) + y1 * (y3 - y1) < 0
        intersect23 = !on[2] || x2 * (x3 - x2) + y2 * (y3 - y2) < 0
        if intersect13 && intersect23 && !on[2]
            point1 = circle_segment_single2(x1, y1, x3, y3)
            point2 = circle_segment_single2(x2, y2, x3, y3)

            return (area_triangle(x1, y1, x2, y2, point1...) +
                    area_triangle(x2, y2, point1..., point2...) +
                    area_arc(point1..., point2..., 1))
        elseif intersect13
            point1 = circle_segment_single2(x1, y1, x3, y3)

            return (area_triangle(x1, y1, x2, y2, point1...) +
                    area_arc(x2, y2, point1..., 1))
        elseif intersect23
            point2 = circle_segment_single2(x2, y2, x3, y3)

            return (area_triangle(x1, y1, x2, y2, point2...) +
                    area_arc(x1, y1, point2..., 1))
        else
            return area_arc(x1, y1, x2, y2, 1)
        end
    elseif on[1]
        return 0.0
    elseif inside[1]
        point1, point2 = circle_segment(x2, y2, x3, y3)
        point3 = circle_segment_single2(x1, y1, x2, y2)
        point4 = circle_segment_single2(x1, y1, x3, y3)

        if point1[1] > 1 # no intersection
            # check if (x1, y2) and origin are on opposite sides of segment
            if (((0 - point3[2]) * (point4[1] - point3[1]) > (point4[2] - point3[2]) * (0 - point3[1])) !=
                ((y1 - point3[2]) * (point4[1] - point3[1]) > (point4[2] - point3[2]) * (x1 - point3[1])))
                return (area_triangle(x1, y1, point3..., point4...) + π - area_arc(point3..., point4..., 1))
            else
                return area_triangle(x1, y1, point3..., point4...) + area_arc(point3..., point4..., 1)
            end
        else
            # ensure that point1 is the point closest to (x2, y2)
            if (point2[1] - x2)^2 + (point2[2] - y2)^2 < (point1[1] - x2)^2 + (point1[2] - y2)^2
                point1, point2 = point2, point1
            end

            return (area_triangle(x1, y1, point3..., point1...) +
                    area_triangle(x1, y1, point1..., point2...) +
                    area_triangle(x1, y1, point2..., point4...) +
                    area_arc(point1..., point3..., 1) +
                    area_arc(point2..., point4..., 1))
        end
    else
        point1, point2 = circle_segment(x1, y1, x2, y2)
        point3, point4 = circle_segment(x2, y2, x3, y3)
        point5, point6 = circle_segment(x3, y3, x1, y1)

        if point1[1] ≤ 1
            xp = (point1[1] + point2[1]) / 2
            yp = (point1[2] + point2[2]) / 2
            return (triangle_unitcircle_overlap(x1, y1, x3, y3, xp, yp) +
                    triangle_unitcircle_overlap(x2, y2, x3, y3, xp, yp))
        elseif point3[1] ≤ 1
            xp = (point3[1] + point4[1]) / 2
            yp = (point3[2] + point4[2]) / 2
            return (triangle_unitcircle_overlap(x3, y3, x1, y1, xp, yp) +
                    triangle_unitcircle_overlap(x2, y2, x1, y1, xp, yp))
        elseif point5[1] ≤ 1
            xp = (point5[1] + point6[1]) / 2
            yp = (point5[2] + point6[2]) / 2
            return (triangle_unitcircle_overlap(x1, y1, x2, y2, xp, yp) +
                    triangle_unitcircle_overlap(x3, y3, x2, y2, xp, yp))
        else
            return inside_triangle(0, 0, x1, y1, x2, y2, x3, y3) ? π : 0.0
        end
    end
end

# intersection of a segment with the unit cirlce
function circle_segment(x1, y1, x2, y2)
    point1, point2 = circle_line(x1, y1, x2, y2)

    if ((point1[1] > x1 && point1[1] > x2) || (point1[1] < x1 && point1[1] < x2) ||
        (point1[2] > y1 && point1[2] > y2) || (point1[2] < y1 && point1[2] < y2))
        point1 = (2.0, 2.0)
    end

    if ((point2[1] > x1 && point2[1] > x2) || (point2[1] < x1 && point2[1] < x2) ||
        (point2[2] > y1 && point2[2] > y2) || (point2[2] < y1 && point2[2] < y2))
        point2 = (2.0, 2.0)
    end

    return point1[1] > 1 && point2[1] < 2 ?  (point1, point2) : (point2, point1)
end

# closest intersection of a line with the unit cirlce
function circle_segment_single2(x1, y1, x2, y2)
    point1, point2 = circle_line(x1, y1, x2, y2)
    dx1 = abs(point1[1] - x2)
    dy1 = abs(point1[2] - y2)
    dx2 = abs(point2[1] - x2)
    dy2 = abs(point2[2] - y2)

    if dx1 > dy1
        return dx1 > dx2 ? point2 : point1
    else
        return dy1 > dy2 ? point2 : point1
    end
end

# intersection of a line defined by two points with a unit cirlce
function circle_line(x1, y1, x2, y2)
    dx = x2 - x1
    dy = y2 - y1

    if dx ≈ 0 && dy ≈ 0
        return (2.0, 2.0), (2.0, 2.0)
    elseif abs(dx) > abs(dy)
        # find slope and intercept
        a = dy / dx
        b = y1 - a * x1

        # find quadratic determinant
        δ2 = 1 + a^2 - b^2
        if δ2 > 0 # real solutions
            δ = sqrt(δ2)
            _x1 = (-a * b - δ) / (1 + a^2)
            _y1 = a * _x1 + b
            _x2 = (-a * b + δ) / (1 + a^2)
            _y2 = a * _x2 + b
            return (_x1, _y1), (_x2, _y2)
        else
            return (2.0, 2.0), (2.0, 2.0)
        end
    else
        # find slope and intercept
        a = dx / dy
        b = x1 - a * y1

        # find quadratic determinant
        δ2 = 1 + a^2 - b^2
        if δ2 > 0 # real solutions
            δ = sqrt(δ2)
            _y1 = (-a * b - δ) / (1 + a^2)
            _x1 = a * _y1 + b
            _y2 = (-a * b + δ) / (1 + a^2)
            _x2 = a * _y2 + b
            return (_x1, _y1), (_x2, _y2)
        else
            return (2.0, 2.0), (2.0, 2.0)
        end
    end
end

function elliptical_overlap_exact(xmin, ymin, xmax, ymax, a, b, θ)
    sint, cost = sincosd(-θ)

    scale = a * b

    # reproject ellipse
    x1 = (xmin * cost - ymin * sint) / a
    y1 = (xmin * sint + ymin * cost) / b
    x2 = (xmax * cost - ymin * sint) / a
    y2 = (xmax * sint + ymin * cost) / b
    x3 = (xmax * cost - ymax * sint) / a
    y3 = (xmax * sint + ymax * cost) / b
    x4 = (xmin * cost - ymax * sint) / a
    y4 = (xmin * sint + ymax * cost) / b

    return scale * (triangle_unitcircle_overlap(x1, y1, x2, y2, x3, y3) +
                    triangle_unitcircle_overlap(x1, y1, x4, y4, x3, y3))
end

####################################
# Rectangular routines

function rectangular_overlap_single_subpixel(x0, y0, x1, y1, w, h, θ, subpixels)
    dx = (x1 - x0) / subpixels
    dy = (y1 - y0) / subpixels

    frac = 0
    x = x0 - 0.5dx
    for i in 1:subpixels
        x += dx
        y = y0 - 0.5dy
        for j in 1:subpixels
            y += dy
            if inside_rectangle(x, y, w, h, θ)
                frac += 1
            end
        end
    end

    return frac / subpixels^2

end

# see https://math.stackexchange.com/questions/69099/equation-of-a-rectangle
"""intersection with rectangular using implicit Lamé curve"""
function inside_rectangle(x, y, w, h, θ)
    # transform into frame of rectangle
    u, v = RotMatrix{2}(θ) \ SA[x, y]
    return abs(u) < w / 2 && abs(v) < h / 2
end

function rectangular_overlap_exact(xmin, ymin, xmax, ymax, w, h, θ)
    R = RotMatrix{2}(deg2rad(θ))
    aper = R * Hyperrectangle(zeros(SVector{2}), SA[w / 2, h / 2])
    dy = ymax - ymin
    dx = xmax - xmin
    pix = Hyperrectangle(SA[dx / 2 + xmin, dy / 2 + ymin], SA[dx / 2, dy / 2])
    return intersection_area(pix, aper)
end

# contrib: https://nbviewer.jupyter.org/github/mforets/escritoire/blob/master/2020/Week11/2D_intersection.ipynb
# the idea is to use the intersection for HPolygon, which is faster than the fallback because the
# normal vectors to the half-spaces are sorted in counter--clockwise fashion
function intersection_area(X::AbstractHyperrectangle{N},
    Y::LinearMap{N,<:AbstractHyperrectangle{N}}) where {N}
    X_clist = X |> constraints_list
    X_poly = HPolygon(X_clist, sort_constraints = true, prune = false, check_boundedness = false)

    Y_clist = linear_map(matrix(Y), set(Y)) |> constraints_list
    Y_poly = HPolygon(Y_clist, sort_constraints = true, prune = false, check_boundedness = false)
    inter = intersection(X_poly, Y_poly)

    return inter isa EmptySet ? 0.0 : area(inter)
end


using EffectiveWaves, Plots

pyplot()

Maxtime=100.
T=Float64

spatial_dim = 3
medium = Acoustic(spatial_dim; ρ=1.0, c=1.0)

v = 1.0
s1 = Specie(
    Acoustic(spatial_dim; ρ=10.0, c=10.0), Sphere(1.0);
    volume_fraction = v * 3/4
);
s2 = Specie(
    Acoustic(spatial_dim; ρ=0.1, c=0.1), Sphere(1.0);
    volume_fraction = v * 1/4
);
species = [s1,s2]

k_low = ω ./ effective_medium(medium, species).c

## Swap a specie for the medium

    medium = Acoustic(spatial_dim; ρ=10.0, c=10.0)

    v = 1.0
    s2 = Specie(
        Acoustic(spatial_dim; ρ=0.1, c=0.1), Sphere(1.0);
        volume_fraction = v * 1/4
    );
    species = [s2]

    k_low = ω ./ effective_medium(medium, species).c

ω = 0.04187
basis_order = 1
tol = 1e-7
k = ω/medium.c

opts = Dict(
    :symmetry => PlanarAzimuthalSymmetry(),
    :tol => tol,
    # :num_wavenumbers => 4,
    # :mesh_size => 2.0, :mesh_points => 20,
    :basis_order => basis_order
)

detMM = dispersion_complex(ω, medium, species; opts...)

kps =  wavenumbers(ω, medium,  species; opts...)

k_low = ω ./ effective_medium(medium, species).c

# Explore around all the wavenumbers

inds = 1:1
x_max = maximum(abs.(real.(kps[inds])))
y_max = maximum(abs.(imag.(kps[inds])))

x = LinRange(-x_max,x_max,150)
y = LinRange(0.,y_max,100)

X = repeat(reshape(x, 1, :), length(y), 1)
Y = repeat(y, 1, length(x))

z_max = 2.0
Z0 = map( (x,y) -> abs(detMM(x + y*im)),X,Y);
Z = map( z -> (abs(z)> z_max) ? NaN : z, Z0);
contour(x,y,Z,fill=true, xlab = "Re k*", ylab = "Im k*", title="Roots of secular det M - high ω - low φ")
scatter!(kps[inds])

# Explore around one wavenumber

    k1 = abs(real(kps[1])) - imag(kps[1]) * im

    scale = 0.01
    x = LinRange(real(k1) - abs(real(k1)) * scale, real(k1) + abs(real(k1)) * scale,100)
    y = LinRange(imag(k1) - abs(imag(k1)) * scale, imag(k1) + abs(imag(k1)) * scale,100)

    X = repeat(reshape(x, 1, :), length(y), 1)
    Y = repeat(y, 1, length(x))

    Z0 = map( (x,y) -> abs(detMM(x + y*im)),X,Y);

    z_max = 2 * scale
    Z = map( z -> (abs(z)> z_max) ? NaN : z, Z0);
    contour(x,y,Z,fill=true, xlab = "Re k*", ylab = "Im k*", title="Roots of secular det M - high ω - low φ")
    scatter!([k1])

# Old code below

x = k0 .* LinRange(0.18,0.23,50)
y = k0 .* LinRange(0.01,0.17,50)

x = kx; y = ky;

X = repmat(x',length(y),1)
Y = repmat(y,1,length(x))
Z = map( (x,y) -> (z = detMM2([x,y]); (abs(z)> 0.5) ? NaN: z),X,Y)

contour(x,y,Z,fill=true, xlab = "Re k*", ylab = "Im k*", title="Roots of secular det M")

x = LinRange(3.,6.,40)
y = LinRange(-1.5,14.,40)

X = repmat(x',length(y),1)
Y = repmat(y,1,length(x))
Z = map( (x,y) -> (z = detMM2([x,y]); (abs(z)> 1.) ? NaN: z),X,Y)
# Z = map( (x,y) -> detMM2([x,y]),X,Y)

# contour(x,y,Z,fill=true, xlab = "Re k*", ylab = "Im k*", title="Roots of secular det M")
heatmap(x,y,Z, xlab = "Re k*", ylab = "Im k*", title="det M - high ω and almost dirichlet")


using SumOfSquares
using MultivariateMoments
using MultivariatePolynomials
using PolyJuMP

"""
Finds the initial order of the hierarchy; i.e what degrees are required
to compute: Eμ[p]; M[gμ] ∀g ∈ Sy; and positivstellensatz of Eμ[p] on Sx.
"""
function min_order(
        p::AbstractPolynomial,
        Sx::AbstractSemialgebraicSet,
        Sy::AbstractSemialgebraicSet,
)
        round_up_even(i::Integer) = (i + 1) & ~(1)
        deg(s::AbstractBasicSemialgebraicSet) = maxdegree.(inequalities(s))
        deg(s::AbstractAlgebraicSet) = maxdegree.(equalities(s))
        deg(p::AbstractPolynomial) = maxdegree(p)

        y = variables(Sy)
        x = variables(Sx)
        px = subs(p, y => ones(length(y)))
        py = subs(p, x => ones(length(x)))

        round_up_even(maximum([deg(px); deg(py); deg(Sy); deg(Sx)]))
end

"""
Tries to solve a zero-sum two-player polynomial game on semialgebraic sets,
using an iteration of the moment--sos hierarchy.

The inputs are: a payoff function p, semialgebraic strategy sets Sx and Sy, and
an "iteration", where "minimal order + 2*iteration" defines the actual order.

Returns: the payoff and the optimal strategies of both players.

Lasserre, Jean-Bernard. (2004). Global Optimization With Polynomials And The
Problem Of Moments. SIAM Journal on Optimization. 11. 10.1137/S1052623400366802.
P. A. Parrilo (2006). Polynomial games and sum of squares optimization In
Proceedings of the 45th IEEE Conference on Decision and Control (pp. 2855-2860).
"""
function solve_game(
        p::AbstractPolynomial,
        Sx::AbstractSemialgebraicSet,
        Sy::AbstractSemialgebraicSet,
        optimizer_factory;
        iteration::Unsigned = 0x0,
)
        order = min_order(p, Sx, Sy) + iteration * 2

        m = SOSModel(optimizer_factory)

        @variable m α
        @objective m Min α
        @variable m μ Moments(order, Sy)
        @constraint(m, q, linearize(μ, p) <= α, domain = Sx, maxdegree = order)

        set_silent(m)
        optimize!(m)

        if !available_result(m)
                return JuMP.termination_status(m), nothing, nothing
        end

        xmat = moment_matrix(q)
        ymat = moment_matrix(μ)
        value(α), extractatoms(xmat, 1e-4), extractatoms(ymat, 1e-4)
end


a = BigInt(20)
f = factorial
c = Int(f(2a) / (f(a) * f(a)))
println(c)


module WCvsLHE

using Images, ImageFiltering
using FFTW
import OffsetArrays

const Lift{T} = Array{T,3}
const Kern{T,n} = OffsetArrays.OffsetArray{T,n,Array{T,n}}

include("lift.jl")

export wc, lhe, Params

normalize(x::Array) = typeof(x)(x - ones(size(x))*minimum(x))/(maximum(x)-minimum(x))

function project(F::Lift; normalized::Bool = true, args...)
    x = dropdims(sum(F, dims = 3), dims = 3)
    normalized ? x = normalize(x) : nothing
    return x
end

# Approximation of sigmoid for LHE
# Coefficients for the polynomial approximation of the sigmoid
const c = reverse([-7.7456e+00,
                3.1255e-16,
                1.5836e+01,
                -1.8371e-15,
                -1.1013e+01,
                4.4531e-16,
                3.7891e+00,
                1.2391e-15]);

#Degree of the approximating polynomial
const n = length(c)-1;

gr(i::Int, img::Array, ω::Kern) = imfilter(img.^i,ω)
a(i::Int, img::Array)=sum( [(-1.)^(j-i+1)*c[j+1]*binomial(j,i)*img.^(j-i) for j in i:n] )
R(img::Array, ω::Kern) = sum( [ a(i,img) .* gr(i,img,ω) for i in 0:n ] )

# Weight functions generation
function adapted_gaussian(σ::Real, I::Array)
    # Generates a Gaussian with variance σ and correct
    # dimensions w.r.t. to I
    if ndims(I) == 2
        var = (σ, σ)
    else
        var = (σ, σ, (size(I, 3)/size(I, 1))*σ)
    end

    # var = tuple([σ for _ in 1:ndims(I)]...)
    Kernel.gaussian(var) |> reflect # Convolution not correlation
end

# Local Mean Average
LMA(σμ::Real, I0::Array{T, 2}) where T = imfilter(I0, adapted_gaussian(σμ, I0))

# Type to save the results
struct Result{n}
    res :: Array{Float64, n}
    iter :: Int
    tol :: Float64
end
Base.getindex(R::Result, etc...)  = getindex(R.res, etc...)
iterations(R::Result) = R.iter
tolerance(R::Result) = R.tol
project(R::Result{3}, etc...) = Result{2}(project(R.res, etc...), iterations(R), tolerance(R))
show(R::Result{2}) = Gray.(R[:,:])

# Actual procedure
function gradient_descent(I0::Array, W, λ::Real, lma::Array; Δt::Real = .15, threshold::Real = .01, max_iter::Int = 50, M::Real = 1, args...)
    # threshold : the iterations stops when two successive terms have a weighted L2 difference smaller than this quantity
    # max_iter: maximum number of iterations
    next(Ik) = (1-(1+λ)Δt)Ik + (lma + W(Ik)/(2*M))Δt + λ*Δt*I0
    proj(x) = sum(x, dims = 3) # it does nothing if x has 2 dims
    diff(a,b) = sqrt( sum(( proj(a-b) ).^2)/sum(proj(a).^2) )

    prec = I0
    cur = next(I0)
    i = 1
    while diff(prec, cur) >= threshold && i < max_iter
        (prec, cur) = (cur, next(cur))
        i += 1
    end

    Result{ndims(I0)}(cur, i, diff(prec, cur))
end

function algo(I0::Array, σμ::Real, σw::Real, λ::Real; algo_type = :planar, model = :wc, θs::Int = 30, α::Int = 5, verbose::Bool = false, args...)

	if algo_type == :cortical
		F0 = lift(I0, θs; args...)
		lma = lift(LMA(σμ, I0), θs; args...)
	elseif algo_type == :planar
		F0 = copy(I0)
		lma = LMA(σμ, I0)
	else
		error("Type of evolution must be :cortical or :planar")
	end

	if model == :wc
        function σ(x)
            if x < 1/2 - 1/α
                y = 1
            elseif x > 1/2 + 1/α
                y = -1
            else
                y = (1/2-x)*α
            end
            y
        end

		W(I) = imfilter(σ.(I), adapted_gaussian(σw, F0))
        res = gradient_descent(F0, W, λ, lma; args...)
	elseif model == :lhe
		W2(I) = R(I, adapted_gaussian(σw, F0))
        res = gradient_descent(F0, W2, λ, lma; args...)
    else
		error("Model must be :wc or :lhe")
	end

	if algo_type == :cortical
		res = project(res)
	end

    verbose ? res : show(res)
end

wc(I0, σμ, σw, λ; args...) = algo(I0, σμ, σw, λ, model = :wc; args...)
lhe(I0, σμ, σw, λ; args...) = algo(I0, σμ, σw, λ, model = :lhe; args...)

# A useful shorthand:
struct Params
    σμ :: Float64 # std deviation of local mean average
    σw :: Float64 # std deviation of interaction kernel
    λ :: Float64 # attachment to the data
    M :: Float64 # the interaction weight is ν=1/(2M)
end
σμ(p::Params) = p.σμ
σw(p::Params) = p.σw
λ(p::Params) = p.λ
M(p::Params) = p.M

wc(I, p::Params; args...) = wc(Float64.(I), σμ(p), σw(p), λ(p), M = M(p); args...)
lhe(I, p::Params; args...) = lhe(Float64.(I), σμ(p), σw(p), λ(p), M = M(p); args...)

end


module physics

include("num.jl")
using .num
using Elliptic

export dos3D, dosbethe, baredos, fermi, zeta

function dos3D(t::Float64,ω::Float64)
    z = range(-0.995,length=250,stop=0.995)
    z = convert(Array{Float64,1},z);
    ma = 1. .- ((ω / (2. * t) .+ z) ./ 2.).^2.
    km = zeros(Float64,length(ma))
    for (imax,nma) in enumerate(ma)
        if nma > 0. && nma < 1.
            km[imax] = Elliptic.K.(nma)
        end
    end
    intx = km ./ sqrt.(1. .- z.^2)
    return num.trapz(z,intx) / (π^3. * 2. * t)
end

function dosbethe(t::Float64,ω::Float64)
    if ω^2 < 4*t^2
        return (sqrt(4*t^2 - ω^2) / (2*π*t^2))
    else
        return 0.0
    end
end

function baredos(typedos::String,t::Float64,ω::Float64)
    if typedos == "cubic"
        return dos3D(t,ω)
    elseif typedos == "bethe"
        return dosbethe(t,ω)
    else
        println(error("current typedos is either cubic or bethe"))
    end
end

function fermi(ω::Float64,T::Float64)
    kB = 8.61733034e-5;
    if T==0
        if ω <= 0
            return 1.0
        else
            return 0.0
        end
    else
        β = 1. / kB / T
        return 1. ./ ( exp(ω*β) + 1.)
    end
end

zeta(ω,zeroplus) = ω + im * zeroplus

end


## Julia program for Truncated normal distribution
## author: weiya <szcfweiya@gmail.com>
## date: 2018-08-22

# Truncated normal distribution
function rtrunormal(T, mu, sigma, mu_down)
    x = ones(T)
    z = ones(T+1)
    # set initial value of z
    z[1] = rand()
    if mu < mu_down
        z[1] = z[1] * exp(-0.5 * (mu - mu_down)^2 / sigma^2)
    end
    for t = 1:T
        x[t] = rand() * (mu - mu_down + sqrt(-2*sigma^2*log(z[t]))) + mu_down
        z[t+1] = rand() * exp(-(x[t] - mu)^2/(2*sigma^2))
    end
    return(x)
end

## example
rtrunormal(1000, 1.0, 1.0, 1.2)


"""
*Compute the bounds of classical Bell scenarios.*

The `LocalPolytope.jl` module provides tools for characterizing the convex polyhedral
structure of the correlations attainable using classical resources in a Bell scenario.

Each distinct strategy for a given Bell scenario corresponds to a point in vector
space.
The complete set of attainable strategies for a particular Bell scenario is denoted
``\\mathbf{P}``.
Given the constraints of normalization and non-negativity, the extreme points of
``\\mathbf{P}`` correspond to deterministic strategies (matrices with 0,1 elements).
Furthermore, when shared randomness is used in a Bell scenario, any two strategies
can be mixed together in a convex combination.
Hence, ``\\mathbf{P}`` is a convex polyhedron referred to as the *local polytope*.

A convex polytope has two equivalent descriptions:

1. **V-Description**: The polytope is the convex hull of a set of extreme points
    known as vertices ``\\mathbf{V}``,
    ``\\quad\\mathbf{P} = \\text{conv}(\\mathbf{V})``.
2. **H-Description**: The polytope is the intersection of a set of linear half-spaces
    known as facets ``\\mathbf{F}``,
    ``\\quad\\mathbf{P} = \\cap\\mathbf{F}``.

These two descriptions are equivalent,

```math
\\mathbf{P} = \\text{conv}(\\mathbf{V}) = \\cap\\mathbf{F},
```

and there exists a transformation between the set of vertices and facets
``\\mathbf{V} \\leftrightarrow \\mathbf{F}``.

For a given Bell scenario, the V-Description is typically easier to compute,
however, the H-Description describes the bounds of the local polytope as
as linear inequalities known as Bell inequalities.
Bell inequalities are important because they provide a simple test to verify that
a strategy is not contained by the local polyotpe.
That is, if a Bell inequality is violated, the classical resources considered for
the local polytope are not sufficient to reproduce the strategy.
Hence, a Bell violation witnesses the use of resources with greater operational value.
Bell violations are often used to characterize the advantages of quantum resources,
however, it is important to note that a Bell violation can simply mean that more classical
resources were used than anticipated.

### Module Exports:
* [`vertices`](@ref) - Compute the set of extreme-points for the local polytope.
* [`num_vertices`](@ref) - The number of vertices for the local polytope.
* [`vrep`](@ref) - Construct a [`Polyhedron`](https://juliapolyhedra.github.io/Polyhedra.jl/stable/polyhedron/)
    in the vertex representation.
* [`facets`](@ref) - Compute the linear inequalities which bound the local polytope.
* [`generator_vertex`](@ref) - Provide a canonical form for a vertex.
* [`generator_facet`](@ref) - Provide a canonical form for a facet.
* [`adjacency_decomposition`](@ref) - Efficiently compute the generator facets for the local
    polytope using the adjacency decomposition technique.
"""
module LocalPolytope

using LinearAlgebra, Combinatorics
using XPORTA, Polyhedra

import Polyhedra: vrep

using ..BellScenario

include("./vertices.jl")

"""
    vrep(scenario::Scenario; vertices_kwargs...) :: XPORTA.Polyhedron

Constructs a `Polyhedron` using the vertex representation.
See [Polyhedra.jl](https://github.com/JuliaPolyhedra/Polyhedra.jl)
for more details.
The `vertices_kwargs` keyword arguments are passed through to the `vertices`
function for each `Scenario`.

!!! note "Return Type"
    This function differs from the Polyhedra.jl implementation in that it returns
    a `Polyhedron` type rather than a `V-Representation`.
    This is done to reduce the number of steps required to construct a polyhedron.
"""
function vrep(scenario::Scenario; vertices_kwargs...) :: XPORTA.Polyhedron
    polyhedron(vrep(vertices(scenario; vertices_kwargs...)), XPORTA.Library())
end

include("./facets.jl")
include("./generators.jl")
include("./adjacency_decomposition.jl")
include("./utils.jl")

# legacy code
include("../Legacy/LocalPolytope.jl")

end


using Cbc
using DataFrames
using Distances
using JuMP
using LinearAlgebra

cities = DataFrame(
    city=[ "New York, NY", "Los Angeles, CA", "Chicago, IL", "Houston, TX", "Philadelphia, PA", "Phoenix, AZ", "San Antonio, TX", "San Diego, CA", "Dallas, TX", "San Jose, CA", "Austin, TX", "Indianapolis, IN", "Jacksonville, FL", "San Francisco, CA", "Columbus, OH", "Charlotte, NC", "Fort Worth, TX", "Detroit, MI", "El Paso, TX", "Memphis, TN"],
    population=[8405837,3884307,2718782,2195914,1553165,1513367,1409019,1355896,1257676,998537,885400,843393,842583,837442,822553,792862,792727,688701,674433,653450],
    lat=[40.7127,34.0522,41.8781,29.7604,39.9525,33.4483,29.4241,32.7157,32.7766,37.3382,30.2671,39.7684,30.3321,37.7749,39.9611,35.2270,32.7554,42.3314,31.7775,35.1495],
    lon=[-74.0059,-118.2436,-87.6297,-95.3698,-75.1652,-112.0740,-98.4936,-117.1610,-96.7969,-121.8863,-97.7430,-86.1580,-81.6556,-122.4194,-82.9987,-80.8431,-97.3307,-83.0457,-106.4424,-90.0489]
)

n = size(cities,1)
k = 3
P = sum(cities.population) / k

dm = Distances.pairwise(Haversine(6372.8), Matrix(cities[:, [3,4]])', dims=2)
dm = LowerTriangular(dm)

model = Model(Cbc.Optimizer)

@variable(model, x[1:n, 1:k], Bin)

for i in 1:n
    @constraint(model, sum(x[i,:]) == 1)
end

α = -2_500_000
β = 2_500_000

for i in 1:k
    @constraint(model, (x' * cities.population)[i] - P <= β)
    @constraint(model, (x' * cities.population)[i] - P >= α)
end

@variable(model, z[1:n,1:n], Bin)

for k in 1:k, i in 1:n, j in 1:n
    @constraint(model, z[i,j] >= x[i,k] + x[j,k] - 1)
end

@objective(model, Min, dot(z,dm));

optimize!(model)

cities.group = zeros(n)

for i in 1:n, j in 1:k
    if round(value.(x)[i,j]) == 1.0
        cities.group[i] = j
    end
end

for group in groupby(cities, :group)
    @show group
    println("")
    @show sum(group.population)
    println("")
end