"""
    kmeans(X::AbstractMatrix{<:Real}, r::AbstractVector{Int},
        c::AbstractVector{Int}, μ::AbstractMatrix{<:Real}; <keyword arguments>)

Cluster the data `X` with the ``k``-means algorithm.

# Keyword arguments
- `maxiter::Int=256`: the number of maximum iterations.
- `dist::SemiMetric=SqEuclidean()`: the distance function.
- `ϵ::AbstractFloat=1.0e-6`: the absolute tolerance for convergence.
"""
function kmeans(X::AbstractMatrix{<:Real}, r::AbstractVector{Int},
                c::AbstractVector{Int}, μ::AbstractMatrix{<:Real};
                maxiter::Int=256, dist::SemiMetric=SqEuclidean(),
                ϵ::AbstractFloat=1.0e-6)
    mx, n = size(X)
    mμ, k = size(μ)

    mx == mμ || throw(DimensionMismatch("number of data features must match"))
    k > 1 || throw(ArgumentError("number of clusters must at least be 2"))
    k <= n || throw(ArgumentError("more clusters than data instances are not allowed"))

    C = Matrix{Int}(undef, 0, 0)
    W = Matrix{Float64}(undef, 0, 0)
    Y = zeros(Float64, k, n)
    μ2 = convert(Matrix{Float64}, μ)

    pcs = Vector{PartitionalClustering}(undef, 0)

    distances = pairwise(dist, μ2, X, dims=2)
    pre_objcosts = 0
    objcosts = 0
    for i = 1:maxiter
        # Update cluster assignments, objective function costs, and cluster
        # centers (part 1/2)
        @inbounds for j = 1:n
            cost, y = findmin(view(distances, :, j))
            Y[y, j] = 1
            objcosts += cost
            μ2[:, y] += X[:, j]
        end

        # Update cluster centers (part 2/2)
        @inbounds for j = 1:k
            sz = sum(Y[j, :])
            μ2[:, j] = iszero(sz) ? μ[:, j] : μ2[:, j] / sz
        end

        pairwise!(distances, dist, μ2, X, dims=2)

        pc = PartitionalClustering(copy(r), copy(c), copy(C), copy(W), copy(Y),
                (μ=copy(μ2),))
        push!(pcs, pc)

        # Check for convergence
        isapprox(objcosts, pre_objcosts, atol=ϵ) && break

        fill!(Y, zero(eltype(Y)))
        fill!(μ2, zero(eltype(μ2)))

        pre_objcosts = objcosts
        objcosts = 0
    end

    return pcs
end

"""
    kmeans(X::AbstractMatrix{<:Real}, μ::AbstractMatrix{<:Real}; <keyword arguments>)

Like [`kmeans`](@ref), but automatically generate `r` and `c` according to the
size of `X`.
"""
function kmeans(X::AbstractMatrix{<:Real}, μ::AbstractMatrix{<:Real};
                maxiter::Int=256, dist::SemiMetric=SqEuclidean(),
                ϵ::AbstractFloat=1.0e-6)
    r = collect(1:size(X, 1))
    c = collect(1:size(X, 2))
    return kmeans(X, r, c, μ, maxiter=maxiter, dist=dist, ϵ=ϵ)
end


const PISpins = Matrix{Int16}

"""
generate a random spin configuration.
"""
rand_pispins(nsite::Int, ntau::Int) = rand([-Int16(1), Int16(1)], nsite, ntau)

"""
number of imaginary time slice.
"""
ntau(spins::PISpins) = size(spins, 2)

nsite(spins::PISpins) = size(spins, 1)

"""
    flip!(spins::PISpins, i::Int, j::Int) -> PISpins

flip a spin at specific position inplace.
"""
flip!(spins::PISpins, i::Int, j::Int) = (spins[i, j] = -spins[i, j]; spins)

"""
    PathIntegralIter{MT<:AbstractModel}
    PathIntegralIter(model::MT, spins::PISpins) where {MT<:AbstractModel} -> PathIntegralIter{MT}

An iterator for sweeps in Path Integral MonteCarlo.
"""
struct PathIntegralIter{MT<:AbstractModel}
    model::MT
    spins::PISpins
    PathIntegralIter(model::MT, spins::PISpins) where {MT<:AbstractModel} = new{MT}(model, spins)
end

function Base.iterate(si::PathIntegralIter, state::Int=1)
    model = si.model
    spins = si.spins
    lt = model.lattice
    NN = nsite(lt)
    NTAU = ntau(spins)

    beta = 1/model.temp
    dtau = beta/NTAU
    J_spatial = model.J0/NTAU
    J_Trotter = - log(tanh(dtau*model.Γ)) * model.temp / 2

    # visit each spin on the space-time lattice in sequential order
    for itau = 1:NTAU
        for ir = 1:NN
            # interaction energy with the neighbouring spins in real space and
            # the neighbouring spins in imaginary time
            exchange_field = J_spatial * sum(view(spins, neighbors(lt, ir), itau))
            # periodic boundary conditions in imaginary time
            itau_up = mod(itau, NTAU) + 1
            itau_down = mod(itau-2, NTAU) + 1
            exchange_field += J_Trotter * (spins[ir,itau_up] + spins[ir,itau_down])

            energy_diff =  2 * spins[ir,itau] * exchange_field   # Note: J_spatial > 0 corresponds fo FM interactions
            # Metropolis-Hastings update: flip the spin with probability min(1, exp(-beta*energy_diff))
            (energy_diff < 0 || rand() < exp(-beta*energy_diff)) && flip!(spins, ir, itau)
        end
    end
    ((state, model, spins), state+1)
end

"""
    measure(model::AbstractModel, spins::PISpins) -> Tuple

Measure observables, returns a tuple of (E, Mz, Mz², Mz⁴).
"""
function measure(model::AbstractModel, spins::PISpins)
    lt = model.lattice
    NN = nsite(lt)
    NTAU = ntau(spins)
    beta = 1/model.temp
    dtau = beta/NTAU

    J_spatial = model.J0/NTAU
    J_Trotter = - log(tanh(dtau*model.Γ)) * model.temp / 2
    # Measure the total energy
    energy_tot = 0.0
    magnz = 0.0
    for itau = 1:NTAU
        for ir = 1:NN
            magnz += spins[ir,itau]
            energy_tot -= J_spatial * spins[ir,itau] * sum(view(spins, neighbors(lt, ir), itau)) / 2  # compenstate for double counting of bonds
            # periodic boundary conditions in imaginary time
            itau_up = mod(itau, NTAU) + 1
            energy_tot -= J_Trotter * spins[ir,itau] * spins[ir,itau_up]
        end
    end
    magnz /= (NTAU * NN)
    energy_tot / (NTAU * NN), magnz, magnz^2, magnz^4
end

"""
    runsse(sseiter, ntherm::Int, nmeas::Int; binsize::Int=200, seed::Int=2) -> DynamicBin

Run an PathIntegralIter application for calculating mean energy and magnetization, where
    * sseiter: PathIntegralIter instance.
    * ntherm: number of steps for heat bath.
    * nmeas: number of measures.
    * binsize: size of bin.
"""
function runsse(sseiter, ntherm::Int, nmeas::Int; binsize::Int=200)
    println("thermalizing ...")
    for (k, model, spins) in sseiter
        k == ntherm && break
    end

    println("measuring ...")
    println("      E          Mz         Mz²        Mz⁴")
    bin = DynamicBin(binsize, Float64, Float64, Float64, Float64)

    for (k, model, spins) in sseiter
        res = measure(model, spins)
        push!(bin, res)

        if fitted(bin)
            print_lastbin(bin); println()
        end
        k == nmeas && break
    end
    bin
end


using LinearAlgebra
using Zygote
using ForwardDiff
using ReverseDiff
using StaticArrays
using Base.Iterators

## Finite differencing
central_2nd(φp, φ, φm)     = φm - 2φ + φp
Δ²_central(φp, φ, φm, Δx)  = central_2nd(φp, φ, φm) / Δx^2
Δ_central(φp, φm, Δx)      = (φp - φm) / 2Δx
Δ_forward(φ1, φ2, Δt)      = (φ2 - φ1) / Δt

## Stencil generation
neighbours(CI, deg) = CI .+ CartesianIndices(ntuple(x -> -deg:deg, length(CI)))

stencil_depth(CI, depth) = [ CI + CartesianIndex(ntuple(x -> ifelse(x == i, depth, 0), length(CI))) for i in 1:length(CI) ]

stencils(CI, deg, poke = false) = ifelse(poke, [ stencil_depth(CI, -(deg + 1)); neighbours(CI, deg)[:]; stencil_depth(CI, deg + 1) ], neighbours(CI, deg)[:])

stencil_1(CI) = stencils(CI, 0, true)

## FTCS scheme
ftcs_stencil(φ, index) = @view φ[stencil_1(index)[2:end]]

function substitute(φ, index, val)
    if index ∈ CartesianIndices(φ)
        φ[index]
    else
        val
    end
end

function ftcs_stencil(φ, index, φ_boundaries)
    if any(index.I .<= 1) || any(index.I .>= size(φ)) # There have to be more conditions...
        φ_vec = substitute.(Ref(φ), stencil_1(index), φ_boundaries)
    else
        φ_vec = @view φ[stencil_1(index)[2:end]]
    end
end

## Difference operators specific to FTCS stencil

# First derivatives
Δt(φ, dt) = Δ_forward(φ[4], φ[3], dt)
Δx(φ, dx) = Δ_central(φ[5], φ[1], dx)
Δy(φ, dy) = Δ_central(φ[6], φ[2], dy)

# Second derivatives
Δ²x(φ, dx) = Δ²_central(φ[5], φ[3], φ[1], dx)
Δ²y(φ, dy) = Δ²_central(φ[6], φ[3], φ[2], dy)

# Governing equations in residual form
vorticity_residual(ω, ψ, ν, dt, dx, dy) = Δt(ω, dt) + Δy(ψ, dy) * Δx(ω, dx) - Δx(ψ, dx) * Δy(ω, dy) - ν * (Δ²x(ω, dx) + Δ²y(ω, dy))
streamfunction_residual(ω, ψ, dx, dy)   = Δ²x(ψ, dx) + Δ²y(ψ, dy) + ω[3]

# Compute residual equations
function compute_residuals!(R, p, sizer, ω_bound, ψ_bound, ν, dt, dx, dy)
    ωs = @view p[1:prod(sizer)]
    ψs = @view p[prod(sizer)+1:end]

    ωs = reshape(ωs, sizer)
    ψs = reshape(ψs, sizer)

    # Get indices and bounds
    inds = CartesianIndices(ωs)
    R    = reshape(R, (sizer..., 2))
    # ∂R∂p = reshape(∂R∂p, ((sizer..., sizer...)..., 4))

    # Iterate over grid
    for ind in inds
        # Get neighbours of a cell
        ω = ftcs_stencil(ωs, ind, ω_bound)
        ψ = ftcs_stencil(ψs, ind, ψ_bound)

        # Evaluate residuals
        R[ind,1] = vorticity_residual(ω, ψ, ν, dt, dx, dy) # Vorticity equation
        R[ind,2] = streamfunction_residual(ω, ψ, dx, dy)   # Streamfunction equation
    end

    R[:]
    # , reshape(∂R∂p, (2 .* (prod(size(ωs)), prod((reverse ∘ size)(ωs)))...))
end

newton_update(p, δp, α = 1.0) = p - α * δp

# Pre-allocated version
newton_update!(p, δp, α = 1.0) = @. p = p - α * δp

## Initial setup
dt, dx, dy  = 0.1, 0.2, 0.1
ts, xs, ys  = 0:dt:1, -1:dx:1, -1:dy:1
grid        = product(xs, ys, ts)
α           = 1.0
ν           = 0.05
U           = 2.0
ω_bound     = 1.0
ψ_bound     = 0.1

ωs = ω_bound * ones(size(grid))
ψs = ψ_bound * ones(size(grid))

p = [ ωs[:]; ψs[:] ]

R    = similar(p)
∂R∂p = zeros(2 .* (prod(size(ωs)), prod((reverse ∘ size)(ωs)))...)

num_iters = 3
compute_residuals!(R, p) = compute_residuals!(R, p, size(ωs), ω_bound, ψ_bound, ν, dt, dx, dy)
compute_grad_fwd(R, p) = ForwardDiff.jacobian(compute_residuals!, R, p)

##
# using NLsolve
# prob = nlsolve(compute_residuals!, p)

## NEWTON ITERATIONS
#=================================================================#

function newton_solver!(R, p, num_iters = 3, α = 1.0)
    # Array to store errors
    ε = zeros(num_iters);

    # Newton iteration loop
    for i in 1:num_iters
        # Compute residuals
        R    = compute_residuals!(R, p)

        # Compute Jacobian
        ∂R∂p = compute_grad_fwd(R, p)

        # Compute Newton step
        Δp   = ∂R∂p \ -R

        # Update state with relaxation factor
        p   .= newton_update!(p, Δp, α)

        # Error processing
        ε[i] = maximum(abs.(Δp))
        println("Newton step error L² norm: $(ε[i])")
    end
    R, p, ε
end

##
R, p, ε = newton_solver!(R, p, num_iters, α)

## Plotting
using GLMakie

# function grad_vorticity_residual(ω, ψ, ν, dt, dx, dy)

# end

# grad_streamfunc_residual(ω, ψ, dx, dy) = gradient(streamfunction_residual, ω, ψ, dx, dy)

# function grad_residual_update!(∂R∂p, grad, inds)
#     j, k, l = inds.I
#     a, b, c, d, e, f = size(∂R∂p)
#              ∂R∂p[inds, j  , k  , l  ] = grad[3]
#     if k > 1 ∂R∂p[inds, j  , k-1, l  ] = grad[1] end
#     if l > 1 ∂R∂p[inds, j  , k  , l-1] = grad[2] end
#     if j < a ∂R∂p[inds, j+1, k  , l  ] = grad[4] end
#     if k < b ∂R∂p[inds, j  , k+1, l  ] = grad[5] end
#     if l < c ∂R∂p[inds, j  , k  , l+1] = grad[6] end
# end

# function update_jacobian!(∂R∂p, ind, ω, ψ, ν, dt, dx, dy)
#     # Evaluate Jacobian
#     grad_ω = grad_vorticity_residual(ω, ψ, ν, dt, dx, dy)
#     grad_ψ = grad_streamfunc_residual(ω, ψ, dx, dy)

#     grad_residual_update!(∂R∂p[:,:,:,:,:,:,1], grad_ω[1], ind)
#     grad_residual_update!(∂R∂p[:,:,:,:,:,:,2], grad_ω[2], ind)
#     grad_residual_update!(∂R∂p[:,:,:,:,:,:,3], grad_ψ[1], ind)
#     grad_residual_update!(∂R∂p[:,:,:,:,:,:,4], grad_ψ[2], ind)

#     nothing
# end


@testset "Variable type" begin
    d = Degree(2,3)

    z = MPC.Variable("z", Complex)
    x = MPC.Variable("x", Real)
    b = MPC.Variable("b", Bool)

    @test iscomplex(z) == true
    @test isreal(x) == true
    @test isbool(b) == true

    @test hash(Degree(1,3)) == hash(Degree(1,3))
    @test hash(MPC.Variable("a",Complex)) == hash(MPC.Variable("a",Complex))
end

@testset "Exponent type" begin
    z = MPC.Variable("z", Complex)
    x = MPC.Variable("x", Real)

    one = Exponent()
    zexpo = Exponent(z)
    expo = Exponent(SortedDict(z=>Degree(3,2),
                               x=>Degree(2,0)))

    @test zexpo == Exponent(SortedDict(z=>Degree(1,0)))
    @test Exponent(SortedDict(z=>Degree(3,2),
                              x=>Degree(0,0))) == Exponent(SortedDict(z=>Degree(3,2)))
end

@testset "Polynomial type" begin
    emptypoly = Polynomial()

    a = MPC.Variable("a", Complex)
    b = MPC.Variable("b", Real)

    one = Exponent()
    expoa2 = Exponent(SortedDict(a=>Degree(1,1)))
    expob2 = Exponent(SortedDict(b=>Degree(1,0)))
    expoa = Exponent(SortedDict(a=>Degree(1,0)))
    expob = Exponent(SortedDict(b=>Degree(1,0)))

    p_obj = Polynomial(SortedDict{Exponent, Number}(expoa2=>1.0,
                                                    expob2=>3.5,
                                                    expoa=>5+3im,
                                                    expob=>0,
                                                    one=>-1))

    io = IOBuffer()
    print(io, p_obj)
    @test String(take!(io)) == "-1 + (5 + 3im)*a + conj(a) * a"
end

@testset "Point type" begin

    pt = Point()

    z = MPC.Variable("z", Complex)
    x1 = MPC.Variable("x1", Real)
    x2 = MPC.Variable("x2", Real)
    b = MPC.Variable("b", Bool)
    b1 = MPC.Variable("b1", Bool)

    pointdict = SortedDict(z =>1+2im,
                          x1 => 3.5,
                          x2 => 0,
                          b => 1,
                          b1 => 3.5)

    pt = Point(pointdict)
    @test pt == Point([z x1 x2 b b1], [1+2im 3.5 0 1 3.5])
    @test length(pt) == 4

    io = IOBuffer()
    print(io, pt)
    @test String(take!(io)) == "b  1\nb1 1\nx1 3.5\nz  1 + 2im\n"
end

@testset "Constraint type" begin
    a = MPC.Variable("a", Complex)
    b = MPC.Variable("b", Real)

    one = Exponent()
    expoa2 = Exponent(SortedDict(a=>Degree(1,1)))
    expob2 = Exponent(SortedDict(b=>Degree(1,0)))
    expoa = Exponent(SortedDict(a=>Degree(1,0)))
    expob = Exponent(SortedDict(b=>Degree(1,0)))

    p1 = Polynomial(SortedDict{Exponent, Number}(expoa2=>1.0,
                                                 expob2=>3.5,
                                                 expoa=>5+3im,
                                                 expob=>0,
                                                 one=>-1))

    ub_c = 3+5im
    lb_c = -3-2im

    C1 = p1 << ub_c
    C2 = ub_c >> p1
    @test C1 == Constraint(p1, -Inf-Inf*im, ub_c)
    @test C2 == Constraint(p1, -Inf-Inf*im, ub_c)

    @test (lb_c << C1) == Constraint(p1, lb_c, ub_c)
    @test (C2 >> lb_c) == Constraint(p1, lb_c, ub_c)

    C1 = lb_c << p1
    C2 = p1 >> lb_c
    @test C1 == Constraint(p1, lb_c, +Inf+Inf*im)
    @test C2 == Constraint(p1, lb_c, +Inf+Inf*im)

    @test (C1 << ub_c) == Constraint(p1, lb_c, ub_c)
    @test (ub_c >> C2) == Constraint(p1, lb_c, ub_c)

    @test (p1 == ub_c) == Constraint(p1, ub_c, ub_c)
    @test (p1 == ub_c) != (p1 << ub_c)

    io = IOBuffer()
    print(io, Constraint(p1, lb_c, ub_c))
    @test String(take!(io)) == "-3 - 2im < -1 + (5 + 3im)*a + conj(a) * a < 3 + 5im"

    print(io, p1 << ub_c)
    @test String(take!(io)) == "-1 + (5 + 3im)*a + conj(a) * a < 3 + 5im"
    print(io, lb_c << p1)
    @test String(take!(io)) == "-3 - 2im < -1 + (5 + 3im)*a + conj(a) * a"

    print(io, Constraint(p1, ub_c, ub_c))
    @test String(take!(io)) == "-1 + (5 + 3im)*a + conj(a) * a = 3 + 5im"
end

@testset "Problem type" begin
    a = MPC.Variable("a", Complex)
    b = MPC.Variable("b", Real)

    one = Exponent()
    expoa2 = Exponent(SortedDict(a=>Degree(1,1)))
    expob2 = Exponent(SortedDict(b=>Degree(1,0)))
    expoa = Exponent(SortedDict(a=>Degree(1,0)))
    expob = Exponent(SortedDict(b=>Degree(1,0)))

    p1 = Polynomial(SortedDict{Exponent, Number}(expoa2=>1.0,
                                                expob2=>3.5,
                                                expoa=>5+3im,
                                                expob=>0,
                                                one=>-1))

    vars = SortedDict{String, Type}("a" => Complex, "b" => Real)
    ctrs = SortedDict{String, Constraint}("ctr1" => ((-3+5im) << p1),
                                          "ctr2" => (p1 == 2.5))
    pb = Problem(p1, ctrs, vars)
    Problem()

    io = IOBuffer()
    print(io, pb)
    @test String(take!(io)) == "▶ variables: a b \n▶ objective: -1 + (5 + 3im)*a + conj(a) * a\n▶ constraints: \n →       ctr1: -3 + 5im < -1 + (5 + 3im)*a + conj(a) * a\n →       ctr2: -1 + (5 + 3im)*a + conj(a) * a = 2.5\n"

end


function sqarea_triangle(a::T,b::T,c::T) where {T <: Real}
# How to compute (twice) area squared of triangle with
# high precision (Goldberg 1991):
a,b,c=reverse(sort([a,b,c]))
area = (a+(b+c))*(c-(a-b))*(c+(a-b))*(a+(b-c))
return area
end

# Shows the overlap area accuracy for the two formulae:

# 1). Mandel & Agol:

function circle_overlap(r,b)
kap1 = acos((1-r^2+b^2)/(2*b))
kap0 = acos((r^2+b^2-1)/(2*b*r))
akite = sqrt((4*b^2-(1+b^2-r^2)^2)/4)
area = r^2*kap0+kap1-akite

# 2). Luger & Agol:

kite_area2 = sqrt(sqarea_triangle(one(r),b,r))
# Angle of section for occultor:
kap0  = atan(kite_area2,(r-1)*(r+1)+b^2)
# Angle of section for source:
kap1 = atan(kite_area2,-((r-1)*(r+1)-b^2))
# Flux of visible uniform disk:
sn = kap1 + r^2*kap0 - .5*kite_area2

# Now at high precision:
r_big = big(r); b_big=big(b)
kite_area2_big = sqrt(sqarea_triangle(big(1.0),b_big,r_big))
# Angle of section for occultor:
kap0_big  = atan(kite_area2,(r_big-1)*(r_big+1)+b_big^2)
# Angle of section for source:
kap1_big = atan(kite_area2_big,-((r_big-1)*(r_big+1)-b_big^2))
# Flux of visible uniform disk:
sn_big = kap1_big + r_big^2*kap0_big - 0.5*kite_area2_big

return area,sn,sn_big
end

using PyPlot
fig,axes = subplots(2,2,figsize=(12, 7))
fig[:subplots_adjust](wspace=0.25)

r = 0.1
nb = 1000
db = 10 .^ range(-15, stop=-2, length=nb)
b = 1 - r .+ db
area=zeros(nb); sn=zeros(nb); sn_big=zeros(BigFloat,nb)
for i=1:nb
  area[i],sn[i],sn_big[i] = circle_overlap(r,b[i])
end

ax = axes[1]
ax[:plot](db,abs.(pi*r^2 .- area),label=L"$r=0.1$, MA (2002)",lw=2)
ax[:plot](db,abs.(pi*r^2 .- sn),label=L"$r=0.1$, ALFM (2019)",".")
ax[:plot](db,abs.(convert(Array{Float64,1},pi*big(r)^2 .- sn_big)),label=L"$r=0.1$, BigFloat",linestyle="--",lw=2)
ax[:set_ylabel](L"$\pi r^2 - A_\mathrm{overlap}$", fontsize=16)
ax[:legend](loc="lower right",fontsize=11)
ax[:set_xscale]("log")
ax[:set_yscale]("log")
ax[:axis]([1e-15,1e-2,1e-25,1e-2])
ax[:plot]([1e-16,1e-2],[1,1]*(pi*0.1^2/2^53),c="grey",linestyle="-.")

ax = fig[:add_axes]((0.25, 0.925, 0.1, 0.1))
t1 = range(0, stop=2pi, length=100)
x1 = cos.(t1); y1 = sin.(t1)
x2 = 0.8 .+ 0.2 * cos.(t1); y2 = 0.2 * sin.(t1)
ax[:plot](x1,y1,"k-",lw=1.5)
ax[:fill_between](x2,0,y2,facecolor="k")
ax[:set_aspect](1)
ax[:axis]("off")

ax = fig[:add_axes]((0.685, 0.925, 0.1, 0.1))
x1 = cos.(t1); y1 = sin.(t1)
x2 = 1.2 .+ 0.2 * cos.(t1); y2 = 0.2 * sin.(t1)
ax[:plot](x1,y1,"k-",lw=1.5)
ax[:fill_between](x2,0,y2,facecolor="k")
ax[:set_aspect](1)
ax[:axis]("off")

ax = axes[2]
ax[:plot](db,abs.(area-convert(Array{Float64,1},sn_big)),label=L"$r=0.1$, MA (2002)",lw=2)
ax[:plot](db,abs.(sn-convert(Array{Float64,1},sn_big)),label=L"$r=0.1$, ALFM (2019)",".")
ax[:set_xlabel](L"$b-(1-r)$", fontsize=14)
ax[:set_ylabel]("error", fontsize=14)
ax[:legend](loc="upper right",fontsize=11)
ax[:set_xscale]("log")
ax[:set_yscale]("log")
ax[:axis]([1e-15,1e-2,1e-25,1e-2])
ax[:plot]([1e-16,1e-2],[1,1]*(pi*0.1^2/2^53),c="grey",linestyle="-.")

b = 1 + r .- db
area=zeros(nb); sn=zeros(nb); sn_big=zeros(BigFloat,nb)
for i=1:nb
  area[i],sn[i],sn_big[i] = circle_overlap(r,b[i])
end

ax = axes[3]
mask = area .> 0.0
ax[:plot](db[mask],area[mask],label=L"$r=0.1$, MA (2002)",lw=2)
ax[:plot](db,sn,label=L"$r=0.1$, ALFM (2019)",".")
ax[:plot](db,convert(Array{Float64,1},sn_big),label=L"$r=0.1$, BigFloat",linestyle="--",lw=2)
ax[:set_ylabel](L"$A_\mathrm{overlap}$", fontsize=16)
ax[:legend](loc="lower right",fontsize=11)
ax[:set_xscale]("log")
ax[:set_yscale]("log")
ax[:axis]([1e-15,1e-2,1e-25,1e-2])

ax = axes[4]
ax[:plot](db,abs.(area-convert(Array{Float64,1},sn_big)),label=L"$r=0.1$, MA (2002)",lw=2)
ax[:plot](db,abs.(sn-convert(Array{Float64,1},sn_big)),label=L"$r=0.1$, ALFM (2019)",".")
ax[:set_ylabel]("error", fontsize=14)
ax[:set_xscale]("log")
ax[:set_yscale]("log")
ax[:set_xlabel](L"$1+r-b$", fontsize=14)
ax[:legend](loc="upper right",fontsize=11)
ax[:axis]([1e-15,1e-2,1e-25,1e-2])
savefig("area_of_overlap_r01.pdf", bbox_inches="tight")


module testMoments
using Test
using LinearAlgebra

srcDir = dirname(dirname(@__FILE__))*"\\src\\Moments\\"
include(srcDir*"Moments.jl")
import .Moments

const la = LinearAlgebra
const mom = Moments

@testset "eᵢ" begin
    n =7
    for k in 1:n
        e = mom.eᵢ(n,k)
        @test length(e) == n
        @test maximum(e) == 1
        @test minimum(e) == 0
        @test sum(e) == 1
    end
end

@testset "var_kron" begin
    A = repeat([[1,0,1]],2,2)
    B = repeat([[0,1,0]],2,2)
    A_tens_B =  mom.var_kron(A,B)
    @test A_tens_B == repeat([[1,1,1]],4,4)

    n = rand(2:4)
    m = rand(2:5)
    k = n*m
    A = [rand(0:2,5) for i in 1:n, j in 1:n]
    B = [rand(0:2,5) for i in 1:m, j in 1:m]
    A_tens_B =  mom.var_kron(A,B)
    @test size(A_tens_B) == (k,k)

    A =  mom.make_mon_expo(4,(1,1))
    B =  mom.make_mon_expo(4,(1,1);isle = false)
    C = mom.var_kron(A,B)
    for i in 1:4, j in 1:4
        rc(k) = (1+(k-1)*4):(4+(k-1)*4)
        @assert C[rc(i),rc(j)] == [A[i,j]] .+ B
    end

    # Need more
end

@testset "make_mom_expo_mat" begin
    mon_expo_mat =  mom.make_mon_expo(3,(1,1);isle = true)
    @test la.diag(mon_expo_mat) == [ [0, 0, 0],
                                  [2, 0, 0],
                                  [0, 2, 0],
                                  [0, 0, 2]]

    n = 4
    t = 2

    M_vec =  mom.make_mon_expo(n,t)
    M_mat =  mom.make_mon_expo(n,(t,t))

    ## Complex moments

    t = 2
    d = (2,2)
    n = sum(2 .* d)
    M_vec =  mom.make_mon_expo(d,t)
    @assert length(M_vec) == binomial(n+t,t)
    M_mat =  mom.make_mon_expo(d,(t,t))
    @assert M_vec == M_mat[1,:]

end

@testset "expo_conj" begin
    @test [mom.expo_conj((2,2), 1:8)] == [[3, 4, 1, 2, 7, 8, 5, 6]]
    @test [mom.expo_conj((3,3), 1:12)] == [[4, 5, 6, 1, 2, 3, 10, 11, 12, 7, 8, 9]]
end

@testset "make_mom_expo_keys" begin
    n = 3
    t = (1,1)
    mek = mom.make_mon_expo(n,2)
    @test mek == [[0, 0, 0],
                            [1, 0, 0],
                            [0, 1, 0],
                            [0, 0, 1],
                            [2, 0, 0],
                            [1, 1, 0],
                            [1, 0, 1],
                            [0, 2, 0],
                            [0, 1, 1],
                            [0, 0, 2]]

    for i in 1:3
        n = rand(3:6,1)[1]
        t = rand(2:4,1)[1]
        mek = mom.make_mon_expo(n,t*2)
        @test size(unique(mek))[1]  == size(mek)[1]
    end
end

## Real

@testset "make_xxᵀ_tens_yyᵀ" begin
    d = (3,3)
    xxᵀtensyyᵀ = mom.make_xxᵀ_tens_yyᵀ(d)
    @test size(xxᵀtensyyᵀ) == (prod(d),prod(d))

    @test diag(xxᵀtensyyᵀ) ==  [[2, 0, 0, 2, 0, 0],
                                [2, 0, 0, 0, 2, 0],
                                [2, 0, 0, 0, 0, 2],
                                [0, 2, 0, 2, 0, 0],
                                [0, 2, 0, 0, 2, 0],
                                [0, 2, 0, 0, 0, 2],
                                [0, 0, 2, 2, 0, 0],
                                [0, 0, 2, 0, 2, 0],
                                [0, 0, 2, 0, 0, 2]]

    @test sum.(xxᵀtensyyᵀ) == repeat([4],prod(d),prod(d))

    for i in 1:5
    d = rand(2:5,2)
    xxᵀtensyyᵀ = mom.make_xxᵀ_tens_yyᵀ(d)
    halfsum(arr) = [sum(arr[1:d[1]]),sum(arr[d[1]+1:end])]
    @test halfsum.(xxᵀtensyyᵀ) == repeat([[2,2]],prod(d),prod(d))
    end
end

@testset "get_ℝ_block_diagᵀ" begin
    d = (3,3)
    t = (4,4)
    ℝ_block_diag = mom.get_ℝ_block_diag(d,t)

    @test sum([size(ℝ_block_diag[b])[1] for b ∈ keys(ℝ_block_diag)]) == binomial(sum(d)+t[1],t[1])
end

## Complex

@testset "make_xx̄ᵀ_tens_yȳᵀ" begin
    d = rand(2:5,2)
    xx̄ᵀ_tens_yȳᵀ = mom.make_xx̄ᵀ_tens_yȳᵀ(d)
end

@testset "get_ℂ_block_diagᵀ" begin

for b in keys(xx̄yȳMM_blocks)
    B = xx̄yȳMM_blocks[b]

    split_expo(ααᶥββᶥ,d) =     (ααᶥββᶥ[1:d[1]],
                                ααᶥββᶥ[d[1]+1:2*d[1]],
                                ααᶥββᶥ[1+2*d[1]:2*d[1]+d[2]],
                                ααᶥββᶥ[1+2*d[1]+d[2]:end])

    Iᵗ_temp = map(x ->  sum.(split_expo(x,d)),B)
    map(x ->  split_expo(x,d),B)
    r_list  = map(v -> v[1]-v[2],Iᵗ_temp) ; s_list  = map(v -> v[3]-v[4],Iᵗ_temp)
    @test (unique([r_list...])...,unique([s_list...])...) == b .* 2
end

pf(x) = prod(factorial.(x))
@test split_expo([1,1,1,2,2,2,3,3,3,4,4,4],(3,3)) == ([1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4])
γ,γᶥ,δ,δᶥ = split_expo([2,0,0,0,0,0,1,0,0,0,0,0],(3,3))
η,ηᶥ,ζ,ζᶥ = split_expo([0,0,0,0,0,0,2,0,0,0,0,0],(3,3))
a = ((-1)^sum(δᶥ + ζᶥ) * (im)^sum(δ+δᶥ+ζ+ζᶥ))* pf(γ + δ) * pf(γᶥ + δᶥ) * pf(η + ζ) * pf(ηᶥ + ζᶥ)
b = pf(γ)*pf(γᶥ)*pf(δ)*pf(δᶥ)*pf(η)*pf(ηᶥ)*pf(ζ)*pf(ζᶥ)

t = (2,2)
d = (2,2)
xx̄yȳMM_blocks = Moments.get_xx̄yȳMM_blocks(d,t)
MM = Moments.make_mon_expo(d,t)
size(MM)[1] == sum([size(xx̄yȳMM_blocks[b])[1] for b in [keys(xx̄yȳMM_blocks)...]])

γγᶥδδᶥζζᶥηηᶥ = Moments.get_γγᶥδδᶥζζᶥηηᶥ(d,t)
@assert size([keys(γγᶥδδᶥζζᶥηηᶥ)...])[1] == size(Moments.make_mon_expo(d,2*t[1]))[1]
for b in [keys(γγᶥδδᶥζζᶥηηᶥ)...]
    @assert [b] == unique(γγᶥδδᶥζζᶥηηᶥ[b][1] .+ γγᶥδδᶥζζᶥηηᶥ[b][2])
end

end

end


# We probably won't use this. Prefer Distances.jl

function hellinger_distance!(p_counts::AbstractDict{T,V}, q_counts::AbstractDict{T,V}) where {T, V}
    p_sum = sum(values(p_counts))
    q_sum = sum(values(q_counts))
    total = float(zero(V))
    for (key, val) in p_counts
        if haskey(q_counts, key)
            total += (sqrt(val / p_sum) - sqrt(q_counts[key] / q_sum))^2
            delete!(q_counts, key)
        else
            total += val / p_sum
        end
    end
    total += sum(x -> x / q_sum, values(q_counts))
    dist = sqrt(total / 2)
    return dist
end

hellinger_distance(p_counts::AbstractDict, q_counts::AbstractDict) =
    hellinger_distance!(copy(p_counts), copy(q_counts))


#=
VecE2: a 2d euclidean vector
=#

struct VecE2{R<:Real} <: VecE{2,R}
    x::R
    y::R
end
VecE2() = VecE2(0.0,0.0)
VecE2(x::Integer, y::Integer) = VecE2(float(x), float(y))
VecE2(t::Tuple) = VecE2(promote(t...)...)

polar(r::Real, θ::Real) = VecE2(r*cos(θ), r*sin(θ))

Base.show(io::IO, a::VecE2) = @printf(io, "VecE2(%.3f, %.3f)", a.x, a.y)

Base.atan2(a::VecE2) = atan2(a.y, a.x)

dist(a::VecE2, b::VecE2) = hypot(a.x-b.x, a.y-b.y)
function dist2(a::VecE2, b::VecE2)
    Δx = a.x-b.x
    Δy = a.y-b.y
    Δx*Δx + Δy*Δy
end

proj(a::VecE2, b::VecE2, ::Type{Float64}) = (a.x*b.x + a.y*b.y) / hypot(b.x, b.y) # dot(a,b) / |b|
function proj(a::VecE2, b::VecE2, ::Type{VecE2})
    # dot(a,b) / dot(b,b) ⋅ b
    s = (a.x*b.x + a.y*b.y) / (b.x*b.x + b.y*b.y)
    VecE2(s*b.x, s*b.y)
end

lerp(a::VecE2, b::VecE2, t::Real) = VecE2(a.x + (b.x-a.x)*t, a.y + (b.y-a.y)*t)

Base.rot180(a::VecE2) = VecE2(-a.x, -a.y)
Base.rotl90(a::VecE2) = VecE2(-a.y,  a.x)
Base.rotr90(a::VecE2) = VecE2( a.y, -a.x)
function rot(a::VecE2, θ::Float64)

    #=
    Rotates counter-clockwise about origin
    =#

    c = cos(θ)
    s = sin(θ)

    VecE2(a.x*c - a.y*s, a.x*s+a.y*c)
end


###Expressions
ex=:x+:y
@test isa(ex,Expression)
@test has(ex,:x)
ex*=ex
@test has(ex,:x)

#addparse
ex=:x+:y
ap=terms(ex)
@test length(ap)==2
@test expression(ap)==ex #maybe have isequivalent instead to give addparse some room, maybe have == = isequivalent, equivalent=isequivalent?
i=1
for term in ex
	@test term==ap[i]
	i+=1
end

#componify
ex=:x+1-(:x+1)
ex=componify(ex)
@test has(ex,:x)

#simplify
ex=(:x+:x)/2
@test simplify(ex)==:x
@test simplify(:x+:x)==2*:x

#evaluate

#original unsorted tests
ex1=simplify(1+:x*2-1)
ex2=push!(2,:x)
@test ex1==ex2

ex1=:x*:y
ex2=Equations.sumsym(-:x+:x*:y+:x)
@test ex1==ex2

@test Equations.sumsym(1-:x+:x)==1
@test Equations.sumnum(1+:x-1)==:x

@test evaluate(:x+:z,Dict(:x=>3,:z=>4))==7

#Units
l=Physical(:l,:m);t=Physical(:t,:s);v=l/t;
@test v==Physical(:l/:t,:m/:s)


using SeisIO, SeisNoise, DTWDT, PlotlyJS, JLD2
import LinearAlgebra.pinv

# This is a script to test the functionality of the DTW functions of DTWDT.jl
# This assumes synthetic data generated via genSynth.jl

function testDTW(finame::String, foname::String, InputDict::Dict, type::String; plot::Bool=false)
    valData = jldopen(finame)
    dvVlist = valData["info/dvVlist"]
    noiselist = valData["info/noiselist"]

    f_err = jldopen(foname, "a+")
    if type=="realData"
        f_err["info/dvV"] = dvVlist
        f_err["info/noise"] = noiselist
    end

    maxLag = InputDict["maxLag"]
    b = InputDict["b"]
    st = InputDict["st"]
    dt = InputDict["dt"]
    direction = InputDict["direction"]
    mintime = InputDict["mintime"]

    for dvV in dvVlist
        println("dv/v: $dvV")
        for noiselvl in noiselist
            signals = valData["$type/$dvV.$noiselvl"]
            (u1, u2) = signals

            npts = length(u1)
            tvec = (collect(0:npts-1) .* dt) .+ mintime

            (stbarTime, stbar, dist, error) = dtwdt(u1, u2, dt, maxLag=maxLag, b=b, direction=direction)

            # linear regression for dv/V
            A = ones(npts,2)
            A[:,2] = tvec
            coef = pinv(A) * stbarTime
            (a, m) = coef

            if plot==true
                plot_dtt(tvec, stbarTime, m, dvV)
            end

            f_err["$type/$dvV.$noiselvl"] = [m, error]
        end
    end
    close(f_err)
    close(valData)
end

function plot_dtt(tvec::Array{Float64,1}, stbarTime::Array{Float64,1}, m::Float64, dvV::Float64)
    trace1 = PlotlyJS.scatter(;x=tvec, y=stbarTime, name="Predicted dt")
    trace2 = PlotlyJS.scatter(;x=tvec, y=tvec .* m, name="Regression")
    trace3 = PlotlyJS.scatter(;x=tvec, y=tvec .* dvV, name="True dt")
    p=PlotlyJS.plot([trace1, trace2, trace3])
    display(p)
    readline()
end

# MWCS input parameters
InputDict_real = Dict( "maxLag"    => 200,
                       "b"         => 1,
                       "st"        => 0,
                       "dt"        => 0.05,
                       "direction" => 0,
                       "mintime"   => -100.0)

InputDict_synth = Dict( "maxLag"    => 200,
                        "b"         => 1,
                        "st"        => 0,
                        "dt"        => 0.05,
                        "direction" => 0,
                        "mintime"   => 0.0)

finame = "verificationData.jld2"
foname = "verificationDataError_DTW.jld2"
type = "rickerConv"

for type in ["rickerConv", "dampedSinusoid", "realData"]
    if type == "realData"
        InputDict = InputDict_real
    else
        InputDict = InputDict_synth
    end
    testDTW(finame, foname, InputDict, type)
end


using CombinatorialSpaces
using StaticArrays: SVector
using GLMakie
const Point3D = SVector{3,Float64}

GLMakie.AbstractPlotting.inline!(false)

cat = EmbeddedDeltaSet2D("cat.obj")

sd = EmbeddedDeltaDualComplex2D{Bool,Float64,Point3D}(cat);
subdivide_duals!(sd, Barycenter())

fig, ax, ob = wireframe(sd;  color=(:blue, 1), transparency=true, linewidth=3)
wireframe!(cat; color=(:red, 1), transparency=true, linewidth=6)
display(fig)
println("Press ENTER to end display server")
readline()


using SymbolicUtils: istree

function nterms(t)
    if istree(t)
        return reduce(+, map(nterms, arguments(t)), init=0)
    else
        return 1
    end
end
# Soft pivoted
# Note: we call this function with a matrix of Union{SymbolicUtils.Symbolic, Any}
# It should work as-is with Operation type too.
function sym_lu(A)
    m, n = size(A)
    L = fill!(Array{Any}(undef, size(A)),0) # TODO: make sparse?
    for i=1:min(m, n)
        L[i,i] = 1
    end
    U = copy!(Array{Any}(undef, size(A)),A)
    p = BlasInt[1:m;]
    for k = 1:m-1
        _, i = findmin(map(ii->iszero(U[ii, k]) ? Inf : nterms(U[ii,k]), k:n))
        i += k - 1
        # swap
        U[k, k:end], U[i, k:end] = U[i, k:end], U[k, k:end]
        L[k, 1:k-1], L[i, 1:k-1] = L[i, 1:k-1], L[k, 1:k-1]
        p[k] = i

        for j = k+1:m
            L[j,k] = U[j, k] / U[k, k]
            U[j,k:m] .= U[j,k:m] .- L[j,k] .* U[k,k:m]
        end
    end
    for j=1:m
        for i=j+1:n
            U[i,j] = 0
        end
    end

    (L, U, LinearAlgebra.ipiv2perm(p, m))
end

# Given a vector of equations and a
# list of independent variables,
# return the coefficient matrix `A` and a
# vector of constants (possibly symbolic) `b` such that
# A \ b will solve the equations for the vars
function A_b(eqs, vars)
    exprs = rhss(eqs) .- lhss(eqs)
    for ex in exprs
        @assert islinear(ex, vars)
    end
    A = jacobian(exprs, vars)
    b = A * vars - exprs
    A, b
end

"""
    solve_for(eqs::Vector, vars::Vector)

Solve the vector of equations `eqs` for a set of variables `vars`.

Assumes `length(eqs) == length(vars)`

Currently only works if all equations are linear.
"""
function solve_for(eqs, vars)
    A, b = A_b(eqs, vars)
    _solve(A, b)
end

function _solve(A, b)
    A = SymbolicUtils.simplify.(to_symbolic.(A), polynorm=true)
    b = SymbolicUtils.simplify.(to_symbolic.(b), polynorm=true)
    SymbolicUtils.simplify.(ldiv(sym_lu(A), b))
end

# ldiv below

_iszero(x::Number) = iszero(x)
_isone(x::Number) = isone(x)
_iszero(::Term) = false
_isone(::Term) = false

function simplifying_dot(x,y)
    isempty(x) && return 0
    muls = map(x,y) do xi,yi
        _isone(xi) ? yi : _isone(yi) ? xi : _iszero(xi) ? 0 : _iszero(yi) ? 0 : xi * yi
    end

    reduce(muls) do acc, x
        _iszero(acc) ? x : _iszero(x) ? acc : acc + x
    end
end

function ldiv((L,U,p), b)
    m, n = size(L)
    x = Vector{Any}(undef, length(b))
    b = b[p]

    for i=n:-1:1
        sub = simplifying_dot(x[i+1:end], U[i,i+1:end])
        den = U[i,i]
        x[i] = _iszero(sub) ? b[i] : b[i] - sub
        x[i] = _isone(den) ? x[i] : _isone(-den) ? -x[i] : x[i] / den
    end

    # unit lower triangular solve first:
    for i=1:n
        sub = simplifying_dot(b[1:i-1], L[i, 1:i-1]) # this should be `b` not x
        x[i] = _iszero(sub) ? x[i] : x[i] - sub
    end
    x
end


# linearfitxy.jl

struct stfitxy
    X   :: Vector{Float64}
    Y   :: Vector{Float64}
    σX  :: Union{Float64, Vector{Float64}}
    σY  :: Union{Float64, Vector{Float64}}
    r   :: Union{Float64, Vector{Float64}}
    a   :: Float64
    b   :: Float64
    σa  :: Float64
    σb  :: Float64
    σa95:: Float64
    σb95:: Float64
    S   :: Float64
    ρ   :: Float64
    bᵢ  :: Vector{Float64}
    i   :: Int64
  end

"""
linearfitxy(X, Y; σX=0, σY=0, r=0, isplot=false, ratio=1)

Performs 1D linear fitting of experimental data with uncertainties in  X and Y:
- Linear fit:             `Y = a + b*X`                               [1]
- Errors:                 ``X ± σX;  Y ± σY``                         [2]
- Errors' correlation:    ``r =  = cov(σX, σY) / (σX * σY)``          [3]

# Arguments:
- `X` and `Y` are input data vectors with length ≥ 3
- Optional standard deviation errors ``σX`` and ``σY`` are vectors or scalars
- Optional `r` is the correlation between the ``σX`` and ``σY`` errors
`r` can be a vector or scalar

``σX`` and ``σY`` errors (error ellipses) with bivariate Gaussian distribution assumed.
If no errors, or if only ``σX`` or ``σY`` are provided, then the results are equivalent
to those from the [LsqFit.jl](https://github.com/JuliaNLSolvers/LsqFit.jl) package.

Based on York et al. (2004) with extensions (confidence intervals, diluted corr. coeff.).

# Examples:
```julia-repl
st = linearfitxy(X, Y)    # no errors in X and Y, no plot displayed

st = linearfitxy(X, Y; σX, σY, isplot=true) # XY errors not correlated (r=0); plot ratio=1

linearfitxy(X, Y; σX,σY,r=0,isplot=true,ratio=:auto) # XY errors not correlated (r=0); plot
```

The results are in the fields of the returned st::stfitxy structure:
- The intercept `a`, the slope `b` and their uncertainties `σa` and `σb`
- ``σa95`` and ``σb95``: 95%-confidence interval using two-tailed t-Student distribution,
    e.g.: ``b ± σb95 = b ± t(0.975,N-2)*σb``
- Goodness of fit `S` (reduced ``Χ²`` test): quantity with ``Χ²`` N-2 degrees of freedom
  `S ~ 1`: fit consistent with errors, `S > 1`: poor fit, `S >> 1`: errors underestimated,
  `S < 1`: overfitting or errors overestimated
- Pearson's correlation coefficient ``ρ`` that accounts for data errors
- Optional display of fit results with error ellipses and confidence intervals

The default argument `isplot=false` can be turned on to plot the results.
Currently Plots.jl's gr() is used

"""
function linearfitxy(X, Y; σX=0, σY=0, r=0, isplot=false, ratio=1)

    N =   length(X)
    d = TDist(N-2)     # t-Student distribution with N-2 degrees of freedom
    cf = quantile(d, 0.975)  # correction factor for 95% confidence intervals (two-tailed distribution)

    if (σX == 0) && (σY == 0)
        σX = σY = r = zeros(N)

        M = [ones(N) X]
        (a, b) = M \ Y
        X̄ = mean(X)    # Ȳ = mean(Y)

        # standard error * Ŝ factor defined in Cantrell (2008)
        Ŝ = sqrt(sum((Y - b*X .- a).^2)/(N-2))  # goodness of fit
        σb = sqrt(1/sum((X .- X̄ ).^2))
        σa = Ŝ * sqrt(1/N + X̄^2 * σb^2)
        σb *= Ŝ

        # Pearson's correlation coefficient:
        ρ = cov(X,Y)/sqrt(var(X) * var(Y))

        st = stfitxy(X, Y, σX, σY, r, a, b, σa, σb, cf*σa, cf*σb, Ŝ, ρ, [b], 1)

    else
        Nmax = 50;                  # maximum number of iterations
        tol = 1e-15;                #relative tolerance to stop at

        σX == 0 && (σX = 1e-16)
        σY == 0 && (σY = 1e-16)
        length(σX) == 1 &&  (σX = σX*ones(N))
        length(σY) == 1 &&  (σY = σY*ones(N))
        length(r) == 1  &&  (r = r*ones(N))

        M = [ones(N) X]
        (a, b) = M \ Y              # initial guess for b
        bᵢ = zeros(Nmax+1);         #vector to save b iterations in
        bᵢ[1] = b
        X̄ = 0; Ȳ = 0;
        β = zeros(N)
        W = zeros(N)
        local i
        for outer i = 1:Nmax        # KEY TRICK
            ωX = 1 ./σX.^2
            ωY = 1 ./σY.^2
            α = sqrt.(ωX .* ωY)
            W .= ωX .* ωY ./(ωX + b^2 * ωY - 2*b * r .* α)
            X̄ = sum(W .* X)/sum(W)
            Ȳ = sum(W .* Y)/sum(W)
            U = X .- X̄
            V = Y .- Ȳ
            β .= W .*(U ./ ωY + b * V ./ ωX - (b * U + V) .* r ./ α)
            b = sum(W .* β .* V)/sum(W .* β .* U)
            bᵢ[i+1] = b
            abs((bᵢ[i+1] - bᵢ[i])/bᵢ[i+1]) < tol && break
        end
        a = Ȳ - b*X̄
        x = X̄ .+ β                  # y = Ȳ + b*β
        X̄ = sum(W .* x)/sum(W)      # Ȳ = sum(W .* y)/sum(W)

        # compare to: Ŝ = sqrt(sum((Y - b*X .- a).^2)/(N-2))
        Ŝ = sqrt(sum(W .* (Y - b*X .- a).^2) /(N-2))  # goodness of fit (York, 2004), (Cantrell, 2008)

        σb = sqrt(1/sum(W .* (x .- X̄).^2))
        σa = Ŝ * sqrt(1/sum(W) + X̄^2 * σb^2)
        σb *= Ŝ             # standard error * Ŝ factor (Cantrell, 2008)

        # See Wikipedia Regression dilution (Pearson's correlation coefficient with errors in variables)
        vX = var(X); vY = var(Y)
        ρ = cov(X,Y)/sqrt(vX*vY) * sqrt(vX/(vX + var(σX))) * sqrt(vY/(vY + var(σY)))

        st = stfitxy(X, Y, σX, σY, r, a, b, σa, σb, cf*σa, cf*σb, Ŝ, ρ, bᵢ, i)
    end

    if isplot
        plot_linfitxy(st; ratio=ratio)
    end

    @printf("\n>>> [± σ]  Y = (%.4f ± %.4f) + (%.4f ± %.4f)*X \n", a,  σa, b, σb)
    @printf(">>> [± 95%% CI]  Y = (%.4f ± %.4f) + (%.4f ± %.4f)*X \n", a, cf*σa, b, cf*σb)
    @printf(">>> Pearson ρ = %.3f;  Goodness of fit = %.3f \n\n", ρ, Ŝ)

    return st
end

function plot_covariance_ellipses!(X, Y, a,  b, c; lc=:lightgrey, lw=0.3)
    # https://cookierobotics.com/007/
    # covariance matrix = [a b; b c]
    t = LinRange(0, 2π, 72)
    st = sin.(t)
    ct = cos.(t)
    for (X, Y, a, b, c) in zip(X, Y, a, b, c)
        r = sqrt(((a - c)/2)^2 + b^2)
        λ₁ = sqrt(abs((a + c)/2 + r))
        λ₂ = sqrt(abs((a + c)/2 - r))

        (b == 0 && a >= c) ? (θ = 0.) :
        (b == 0 && a < c)  ? (θ = π/2) :
        (θ = atan(λ₁^2 - a, b))

        xₜ = @. X + λ₁*cos(θ)*ct - λ₂*sin(θ)*st
        yₜ = @. Y + λ₁*sin(θ)*ct + λ₂*cos(θ)*st
        plot!(xₜ, yₜ, lc=lc, lw=0.3, label=false)
    end
    display(plot!())
end

function  plot_linfitxy(st::stfitxy; ratio=1)
# set plotting defaults
    plot_font = "Computer Modern";
    default(fontfamily=plot_font,framestyle=:axes,yminorgrid=true, legendtitlefontsize=6,fg_color_legend=nothing,
            legendfontsize=6, guidefont=(7,:black),tickfont=(6,:black),size=(600,400), dpi=300,
            titlefont = (6, plot_font))

    X = st.X;  Y = st.Y;  σX = st.σX;  σY = st.σY;  r = st.r;
    covXY = σX .* σY .* r
    # sort data because of ribbon:
    I = sortperm(X);
    X = X[I];  Y = Y[I]; σX = σX[I];  σY = σY[I]; covXY = covXY[I];

    str1 = @sprintf("[± σ]  Y = (%.4f ± %.4f) + (%.4f ± %.4f)*X", st.a, st.σa, st.b, st.σb)
    str2 = @sprintf("\n[± 95%% CI]  Y = (%.4f ± %.4f) + (%.4f ± %.4f)*X", st.a, st.σa95, st.b, st.σb95)
    str3 = @sprintf("\nPearson ρ = %.2f; Goodness of fit = %.2f", st.ρ, st.S)

    dX = diff([extrema(X)...])[1]/7    # extends plot x-axis by 1/7 each side
    x1, x2 = (-dX, dX) .+ extrema(X)
    xx = [x1; X; x2]
    tl, bl = (st.a - st.σa) .+ (st.b + st.σb)*xx,  (st.a + st.σa) .+ (st.b - st.σb)*xx
    σp, σm = maximum([tl bl], dims=2) .-  (st.a .+ st.b*xx),  (st.a .+ st.b*xx) .- minimum([tl bl], dims=2)

    #TODO: 95%-confidence interval computation for X-Y errors
    N = length(X)
    d = TDist(N-2)     # t-Student distribution with N-2 degrees of freedom
    cf = quantile(d, 0.975)  # correction factor for 95% confidence intervals (two-tailed distribution)
    σx95 = cf * st.S * sqrt.(1/N .+ (X .- mean(X)).^2 / var(X) /(N-1))

    p = plot(xlims=(x1,x2), title=str1*str2*str3, ratio=ratio, legend=:outerbottomright)
    scatter!([x1-dX],[Y[1]], mc=:lightblue, label="Y=(a±σa)+(b±σb)X", marker=:rect)

    #TODO: 95%-confidence interval computation for X-Y errors
    if all(≈(0), σX) && all(≈(0), σY)
        scatter!([x1-dX],[Y[1]], mc=:reds, label="95% confidence interval", marker=:rect)
        plot!(X, st.a .+ st.b*X, color=:reds, ribbon=(σx95,σx95), label=false)
        plot!(xlims = (-dX, dX)./5 .+ extrema(X))
    end
    plot!([x1-dX],[Y[1]], lc=:white, label=" ")   # adds space in legend
    plot!(xx, st.a .+ st.b*xx, color=:lightblue, ribbon=(σp,σm), label=false)
    plot!(xx, st.a .+ st.b*xx, color=:blue, lw=0.5, xlabel="X", ylabel="Y", label="LinearFitXY")

    scatter!(X, Y, msw=0.1, ms=1., msc=:lightgrey, xerror= σX, yerror= σY, label=false)
    scatter!(X, Y, msw=0.1, ms=1.5, mc=:blue, label=false)

    if !all(≈(0), σX) || !all(≈(0), σY)
        plot_covariance_ellipses!(X, Y, σX.^2, covXY, σY.^2; lc=:grey)
    end
    display(p)
end


function line_search(f, x0, g, h, opt_type="steepest", max_iter=50, tol=1e-6)
    """
    f: objective function
    x0: starting value of iterate
    grad_f: gradient of the objective
    hess_f: Hessian of the objective, not required for all methods
    opt_type: type of step direction
        "steepest"
        "newton"
        "newton_CG"
    """
    println("Using method ", opt_type)
    n = length(x0)
    xvals = Vector{Vector{Float64}}(); push!(xvals, x0)
    ncalls = 0; Bk = nothing

    for i in 1:max_iter
        i % 100 == 0 && println(i)
        xcurr = xvals[end]
        xprev = i > 1 ? xvals[end-1] : nothing

        p, alpha, nc, Bk = compute_steps(xcurr, f, g, h, opt_type, Bk, xprev)
        xnew = xcurr + alpha * p
        push!(xvals, xnew)
        ncalls += nc

        if abs(mean(xnew - xcurr)) <= tol
            println("Number of indefinite fixes ", ncalls)
            println("Number of iterations ", i)
            return(xvals)
        end
    end

    println("Finished algorithm without converging.")
    return(xvals)
end

function compute_steps(xcurr, f, g, h, opt_type, Bk, xprev)
    """
    Computes the step size and step direction depending on the type of method

    xcurr: current iterate
    f: objective function
    grad_f: gradient of the objective
    hess_f: Hessian of the objective, not required for all methods
    opt_type: type of step direction
    """

    fk = f(xcurr)
    gk = g(xcurr)
    ncalls = 0

    if opt_type == "steepest"
        p = steepest_descent(gk)
        alpha = get_step_size(3, 0.9, xcurr, f, g, p)
    elseif opt_type == "newton"
        Bk = h(xcurr)
        if !check_posdef(Bk)
            ncalls += 1
            Bk = cholesky_mod(1e-3, Bk)
        end
        p = newton(gk, Bk)
        alpha = get_step_size(1, 0.9, xcurr, f, g, p)

    elseif opt_type == "newton_CG"
        Bk = h(xcurr)
        if !check_posdef(Bk)
            ncalls += 1
            Bk = cholesky_mod(1e-3, Bk)
        end
        p = newton_CG(gk, Bk)
        alpha = get_step_size(1, 0.9, xcurr, f, g, p)
    elseif opt_type == "BFGS"
        # here Bk is actually the approx inverse Hessian
        if Bk == nothing
            Hk = h(xcurr)
            if !check_posdef(Hk)
                ncalls += 1
                Hk = cholesky_mod(1e-3, Hk)
            end
            Bk = inv(Hk)
            p = -Bk * gk
        else
        #if xprev != nothing
            sk = xcurr - xprev
            yk = gk - g(xprev)
            Bk = BFGS(sk, yk, Bk)
            p = -Bk * gk
        end

        alpha = get_step_size(1, 0.9, xcurr, f, g, p)
    else
        println(1)
    end

    return p, alpha, ncalls, Bk
end

function get_step_size(alpha_init, rho, xcurr, f, grad_f, p_k, c=1e-4)

    alpha = alpha_init
    fk = f(xcurr)
    gradfk = grad_f(xcurr)

    while f(xcurr + alpha*p_k) > fk + c * alpha * sum(gradfk'*p_k)
        alpha = rho * alpha
    end
    return(alpha)
end

function steepest_descent(gk)
    return(-gk)
end

function newton(gk, Bk)
    return(-Bk \ gk)
end

function newton_CG(gk, Bk)
    gradnorm = norm(gk, 2)[1]
    tol = min(0.5, sqrt(gradnorm)) * gradnorm

    z = zeros(gk); r = gk; d = -gk

    j = 1
    while true
        if (d' * Bk * d)[1] <= 0
            if j == 1
                return -fk
            else
                return z
            end
        end
        a = (r'*r ./ (d' * Bk * d))[1]
        z += a * d
        rold = r
        r += a * Bk * d
        if norm(r, 2) <= tol
            return z
        end
        beta = ((r' * r) ./ (rold' * rold))[1]
        d = -r + beta * d
        j += 1
    end
end

function BFGS(sk, yk, Bk)
"""
    Performs BFGS update
    returns search direction approx **inverse Hessian** Bk

    sk: xnew - xcurr
    yk: gradnew - gradcurr
    Bk: approx to inverse Hessian
"""
    rho = (1 ./ (yk' * sk))[1]
    return (eye(Bk) - rho * sk * yk') * Bk * (eye(Bk) - rho * yk * sk') + rho * sk * sk'
end

function cholesky_mod(beta, H)
    @assert beta > 0
    if minimum(diag(H)) > 0
        tau = 0
    else
        tau = -minimum(diag(H)) + beta
    end
    while true
        candidate = H + tau * eye(H)
        check_posdef(candidate) && return candidate
        tau = max(2*tau, beta)
    end
end

function check_posdef(A)
    try
        L = chol(A)
        return true
    catch
        return false
    end
end


Crops = [:wheat,:corn,:beets]
Purchased = [:wheat,:corn]
Sold = [:wheat,:corn,:beets_quota,:beets_extra]

Cost = Dict(:wheat=>150,:corn=>230,:beets=>260)
Required = Dict(:wheat=>200,:corn=>240,:beets=>0)
PurchasePrice = Dict(:wheat=>238,:corn=>210)
SellPrice = Dict(:wheat=>170,:corn=>150,:beets_quota=>36,:beets_extra=>10)
Budget = 500

@everywhere begin
    struct FarmerScenario <: StochasticPrograms.AbstractScenarioData
        Yield::Dict{Symbol,Float64}
    end
    StochasticPrograms.probability(::FarmerScenario) = 1/3
    function StochasticPrograms.expected(scenarios::Vector{FarmerScenario})
        sd = FarmerScenario(Dict(:wheat=>sum([probability(s)*s.Yield[:wheat] for s in scenarios]),
                                 :corn=>sum([probability(s)*s.Yield[:corn] for s in scenarios]),
                                 :beets=>sum([probability(s)*s.Yield[:beets] for s in scenarios])))
    end
end

s1 = FarmerScenario(Dict(:wheat=>3.0,:corn=>3.6,:beets=>24.0))
s2 = FarmerScenario(Dict(:wheat=>2.5,:corn=>3.0,:beets=>20.0))
s3 = FarmerScenario(Dict(:wheat=>2.0,:corn=>2.4,:beets=>16.0))

sp = StochasticProgram((Crops,Cost,Budget),(Required,PurchasePrice,SellPrice),[s1,s2,s3])

@first_stage sp = begin
    (Crops,Cost,Budget) = stage
    @variable(model, x[c = Crops] >= 0)
    @objective(model, Min, sum(Cost[c]*x[c] for c in Crops))
    @constraint(model, sum(x[c] for c in Crops) <= Budget)
end

@second_stage sp = begin
    @decision x
    (Required,PurchasePrice,SellPrice) = stage
    s = scenario
    @variable(model, y[p = Purchased] >= 0)
    @variable(model, w[s = Sold] >= 0)
    @objective(model, Min, sum( PurchasePrice[p] * y[p] for p = Purchased) - sum( SellPrice[s] * w[s] for s in Sold))

    @constraint(model, const_minreq[p=Purchased],
                   s.Yield[p] * x[p] + y[p] - w[p] >= Required[p])
    @constraint(model, const_minreq_beets,
                   s.Yield[:beets] * x[:beets] - w[:beets_quota] - w[:beets_extra] >= Required[:beets])
    @constraint(model, const_aux, w[:beets_quota] <= 6000)
end

push!(problems,(sp,"Farmer"))


#exprGenerator.jl

using Calculus, SymbolicUtils, SymbolicUtils.Code
import Base.*, Base.show

@syms x f(x) df(x)

struct tabularFunction
	vals::Matrix{Int64}
	x0::Int64
	baseSym::Char
	tabularFunction(x,y)=new(rand(UnitRange(-10,10),(4,3)),x,y)
end

function Base.println(t::tabularFunction)
	println("x  $(t.baseSym)(x) $(t.baseSym)'(x) $(t.baseSym)''(x)")
	for i in 1:4
		println("$((t.x0)+i)  $(t.vals[i,:])")
	end
end

function charVector(s::String)
	v = Char[]
	for c in s
		push!(v,c)
	end
	v
end

function *(v::Vector{Char})
	s = ""
	for i in v
		s=s*i
	end
	s
end

function compose(s::String,f::String)
	v,w = charVector(s),charVector(f)
	final = Char[]

	index = 1

	for c in v
		if c == 'x'
			push!(final,'(')
			for d in w
				push!(final,d)
			end
			push!(final,')')
		else
		push!(final,c)
		end
		index += 1
	end
	*(final)
end


#############################################################################
# transpose.jl
# Returns the transpose of a matrix
# All expressions and atoms are subtpyes of AbstractExpr.
# Please read expressions.jl first.
#############################################################################

import Base.transpose, Base.adjoint

struct TransposeAtom <: AbstractExpr
    head::Symbol
    id_hash::UInt64
    children::Tuple{AbstractExpr}
    size::Tuple{Int,Int}

    function TransposeAtom(x::AbstractExpr)
        children = (x,)
        return new(:transpose, hash(children), children, (x.size[2], x.size[1]))
    end
end

function sign(x::TransposeAtom)
    return sign(x.children[1])
end

function monotonicity(x::TransposeAtom)
    return (Nondecreasing(),)
end

function curvature(x::TransposeAtom)
    return ConstVexity()
end

function evaluate(x::TransposeAtom)
    return transpose(evaluate(x.children[1]))
end

# Since everything is vectorized, we simply need to multiply x by a permutation
# matrix such that coeff * vectorized(x) - vectorized(x') = 0
function conic_form!(x::TransposeAtom, unique_conic_forms::UniqueConicForms)
    if !has_conic_form(unique_conic_forms, x)
        objective = conic_form!(x.children[1], unique_conic_forms)

        sz = length(x)

        num_rows = x.size[1]
        num_cols = x.size[2]

        I = Array{Int}(undef, sz)
        J = Array{Int}(undef, sz)

        k = 1
        for r in 1:num_rows
            for c in 1:num_cols
                I[k] = (c - 1) * num_rows + r
                J[k] = (r - 1) * num_cols + c
                k += 1
            end
        end

        transpose_matrix = sparse(I, J, 1.0)

        objective = transpose_matrix * objective
        cache_conic_form!(unique_conic_forms, x, objective)
    end
    return get_conic_form(unique_conic_forms, x)
end

transpose(x::AbstractExpr) = TransposeAtom(x)

struct AdjointAtom <: AbstractExpr
    head::Symbol
    id_hash::UInt64
    children::Tuple{AbstractExpr}
    size::Tuple{Int,Int}

    function AdjointAtom(x::AbstractExpr)
        children = (x,)
        return new(:adjoint, hash(children), children, (x.size[2], x.size[1]))
    end
end

function sign(x::AdjointAtom)
    return sign(x.children[1])
end

function monotonicity(x::AdjointAtom)
    return (Nondecreasing(),)
end

function curvature(x::AdjointAtom)
    return ConstVexity()
end

function evaluate(x::AdjointAtom)
    return evaluate(x.children[1])'
end

# Since everything is vectorized, we simply need to multiply x by a permutation
# matrix such that coeff * vectorized(x) - vectorized(x') = 0
function conic_form!(x::AdjointAtom, unique_conic_forms::UniqueConicForms)
    if !has_conic_form(unique_conic_forms, x)
        objective = conic_form!(x.children[1], unique_conic_forms)

        sz = length(x)

        num_rows = x.size[1]
        num_cols = x.size[2]

        I = Array{Int}(undef, sz)
        J = Array{Int}(undef, sz)

        k = 1
        for r in 1:num_rows
            for c in 1:num_cols
                I[k] = (c - 1) * num_rows + r
                J[k] = (r - 1) * num_cols + c
                k += 1
            end
        end

        transpose_matrix = sparse(I, J, 1.0)
        objective = transpose_matrix * objective

        for var in keys(objective)
            x1 = conj(objective[var][1])
            x2 = conj(objective[var][2])
            objective[var] = (x1, x2)
        end
        cache_conic_form!(unique_conic_forms, x, objective)
    end
    return get_conic_form(unique_conic_forms, x)
end

adjoint(x::AbstractExpr) = AdjointAtom(x)
adjoint(x::Constant) = Constant(x.value')


function doctest(lib::PolyhedraLibrary)
    A = [1 1;1 -1;-1 0]
    b = [1,0,0]
    V = [1//2 1//2; 0 1; 0 0]
    hr = hrep(A, b)
    p = polyhedron(hr, lib)
    @test p ⊆ HalfSpace([1, -1], 0)
    @test !(p ⊆ HalfSpace([1, 1], 0))
    @test !(p ⊆ HyperPlane([-1, 0], 0))
    inequality_fulltest(p, A, b, BitSet())
    generator_fulltest(p, V)
    @test hrepiscomputed(p)
    @test vrepiscomputed(p) # computation triggered in generator_fulltest
    @test [1//4, 1//2] in p
    @test ininterior([1//4, 1//2], p)
    @test inrelativeinterior([1//4, 1//2], p)
    @test !([1, 1//2] in p)
    @test !ininterior([1, 1//2], p)
    @test !inrelativeinterior([1, 1//2], p)
    @test [0, 1] in p
    @test !ininterior([0, 1], p)
    @test !inrelativeinterior([0, 1], p)
    @test !(Ray([0, 1]) in p)
    @test !ininterior(Ray([0, 1]), p)
    @test !inrelativeinterior(Ray([0, 1]), p)
    Ap = reshape([1, -1], 2, 1)
    bp = [1, 0]
    inequality_fulltest(eliminate(p, [1]), Ap, bp, BitSet())
    inequality_fulltest(project(p, [2]), Ap, bp, BitSet())
    inequality_fulltest(eliminate(p, [1], ProjectGenerators()), Ap, bp, BitSet())
    inequality_fulltest(project(p, [2], ProjectGenerators()), Ap, bp, BitSet())
    if supportselimination(p, BlockElimination())
        inequality_fulltest(eliminate(p, [1], BlockElimination()), Ap, bp, BitSet())
        inequality_fulltest(project(p, [2], BlockElimination()), Ap, bp, BitSet())
    end
    @test_throws DimensionMismatch project(p, ones(1, 1))
    @test_throws DimensionMismatch project(p, ones(2, 3))
    inequality_fulltest(project(p, reshape([0, 1], 2, 1)), Ap, bp, BitSet())
    inequality_fulltest(fixandeliminate(p, 2, eltype(p) <: AbstractFloat ? 1/4 : 1//4), reshape([1, -1], 2, 1), [1/4, 0], BitSet())
    # tries to create CDDPolyhedron of BigFloat, needs to let CDD decide of the promotion
    #inequality_fulltest(fixandeliminate(p, 2, 1/4, reshape([1, -1], 2, 1), [1/4, 0], BitSet())
    inequality_fulltest(translate(p, -[0, 1]), [1 1; 1 -1;-1 0], [0, 1, 0], BitSet())
    # Testing 2D plotting
    RecipesBase.apply_recipe(Dict{Symbol, Any}(), p)[1].args == ([0, 0.5, 0.5, 0, 0], [1, 0.5, 0.5, 0, 1])
end


module EllipseBounds
using CurveProximityQueries
using ConvexBodyProximityQueries
using IntervalArithmetic
using LinearAlgebra
using RecipesBase
using StaticArrays

export Ellipse

struct Ellipse{A, B, C}
    center::A
    rotate::C
    radii::B
end

import ConvexBodyProximityQueries: support
function support(el::Ellipse, dir::SVector)
    rotdir = el.rotate'*dir
    sp = el.radii .* normalize(el.radii .* rotdir)
    el.rotate*sp .+ el.center
end

function Ellipse(c::Curve, θ::Interval)
    focusA = c(θ.lo)
    focusB = c(θ.hi)
    center = (focusB + focusA)/2.0
    BA = focusB - focusA
    focaldistance = norm(BA)
    dir = BA/focaldistance
    rotate = rotationmatrix(dir)
    majradius = arclength(c, θ)/2
    minradius = majradius > focaldistance/2 ? sqrt(majradius^2 - (focaldistance/2)^2) : 0.0
    radii = SVector{2}(majradius, minradius)
    Ellipse(center, rotate, radii)
end

function rotationmatrix(dir::SVector{2})
    SMatrix{2,2}(dir[1],dir[2],-dir[2],dir[1])
end

@recipe function f(b::Ellipse)
    grid := false
    axis := false
    ticks := false
    legend := false
    aspect_ratio := :equal
    linewidth --> 0.2
    linecolor --> :black
    fillcolor --> :black
    fillalpha --> 0.5
    fillrange := 0.0
    @series begin
        t = 0:0.1:2π
        bds = b.rotate*[b.radii[1]*cos.(t)'; b.radii[2]*sin.(t)'] .+ b.center
        bds = hcat(bds, bds[:, 1])
        bds[1,:], bds[2,:]
    end
end

end


include("DM1_1.jl")
include("DM2_1.jl")
include("DM1_2_corrige.jl")
#=========================================#
function ReactiveGRASP( #Utilisation du Reactive GRASP
    matrix, #Un matrice de taille n*m qui représente les conditions
    cost, #Une liste des couts de chaque variable
    n, #nombre de variables
    m, #nombre de contraintes
    ite, #on tierera ce nombre avant de recalculer des probabilités de chaque alpha
    coupe, #Le nombre de coupe du segment [0,1], nous permet de construire nos alpha
    alphaset,
    temps #La ressource en temps en seconde alloué
    )

    #initialisation
    (p,nb_iteration,z_cumul,zBest,zWorst) = intialiser(matrix,cost, n, m, ite, coupe, alphaset)

    evol_p=Float64[] #liste qui stokera l'évolution de nos probabilités au fur et à mesure des itérations.
    t=time()
    cpt = 1

    while (time()-t <= temps) #Condtion sur les ressources en temps
        append!(evol_p,p)
        while (cpt <= ite)

            for i in 1:length(p) #On s'assure que chaque probabilité se soit exprimé au moins une fois
                (SOL,z, crts) = GRASP(cost, matrix, n, m, p[i])
                z_cumul[i] += z
            end

            prob=rand(Float64)
            #prob = rand(1:length(p))

            alpha_choisit = choix_alpha(p,prob)
            (SOL, z, crts) = GRASP(cost, matrix, n, m, p[alpha_choisit])
            #(SOL, z, crts) = GRASP(cost, matrix, n, m, p[prob])

            #Amelioration
            (SOL, z) = exchange1_1(SOL,n,m,cost,crts,matrix)

            nb_iteration[alpha_choisit] += 1
            #nb_iteration[prob] += 1
            z_cumul[alpha_choisit] += z
            #z_cumul[prob] += z

            if z > zBest
                zBest = z
            end

            if z < zWorst
                zWorst = z
            end

            cpt = cpt+1 #Compteur du nombre d'itération
        end

        recalcul_p!(p,z_cumul,zBest,zWorst,nb_iteration,evol_p)
        nb_iteration = ones(Int64, length(p))
        z_cumul = zeros(Int64, length(p))

    end
    println("zBest: ", zBest, "    zWorst: ", zWorst)
    println(evol_p) #A commenter si on ne souhaite pas voir la liste d'évolution des probabilités
    return(evol_p)
end

#=======================================#
function intialiser(matrix,cost, n, m, ite, coupe, alphaset) #initialisationd des variables

    p = Float64[]
    if alphaset == 0
        for i in 1:coupe
            push!(p,i/coupe)
        end
    else
        p = alphaset
    end

    nb_iteration = ones(Int64, length(p))

    z_cumul = zeros(Int64, length(p))

    (SOL,z) =Glouton(cost, matrix, n, m)

    zBest = z

    zWorst = z

    return(p,nb_iteration,z_cumul,zBest,zWorst)
end

#=======================================#

function recalcul_p!(p,z_cumul,zBest,zWorst,nb_iteration,evol_p)
    q=zeros(Float64,length(p))

    somme_q=0

    for i in 1:length(p)

        moyenne=(z_cumul[i]/nb_iteration[i])
        q[i]=(moyenne-zWorst)/(zBest-zWorst)
        somme_q+=q[i]

    end

    for i in 1:length(q)
        p[i]=q[i]/somme_q
    end

    for i in 2:length(p)-1
        p[i]+=p[i-1]
    end
    p[length(p)]=1

end

#=======================================#

function choix_alpha(p,prob)
    for i in 1:length(p)
        if prob < p[i]
            return(i)
        elseif i==length(p)
            return(i)
        end
    end
end


function sample(range::UnitRange{Int64}, n; nc::Bool = false)
    N = range.stop - range.start + 2
    result = Array{Int64}(undef, n)
    m = 1
    ncm = 1
    t = 1
    if nc
        nc_result = Array{Int64}(undef, N-n-1)
        while m <= n
            if (N-t) * rand() >= (n - m+1)
                nc_result[ncm] = t
                ncm+=1
            else
                result[m] = t
                m+=1
            end
            t+=1
        end
        nc_result[ncm:end] = collect(t:range.stop)
        return result, nc_result
    else
        while m <= n
            if (N-t) * rand() < (n - m+1)
                result[m] = t
                m+=1
            end
            t+=1
        end

        return result
    end
end


using Distributions, PDMats
using Test, LinearAlgebra

# RealInterval
r = RealInterval(1.5, 4.0)
@test minimum(r) == 1.5
@test maximum(r) == 4.0
@test extrema(r) == (1.5, 4.0)

@test partype(Gamma(1, 2)) == Float64
@test partype(Gamma(1.1, 2)) == Float64
@test partype(Normal(1//1, 2//1)) == Rational{Int}
@test partype(MvNormal(rand(Float32, 5), Matrix{Float32}(I, 5, 5))) == Float32

# special cases
@test partype(Kolmogorov()) == Float64
@test partype(Hypergeometric(2, 2, 2)) == Float64
@test partype(DiscreteUniform(0, 4)) == Float64

A = rand(1:10, 5, 5)
B = rand(Float32, 4)
C = 1//2
L = rand(Float32, 4, 4)
D = PDMats.PDMat(L * L')

# Ensure that utilities functions works with abstract arrays

@test isprobvec(GenericArray([1, 1, 1])) == false
@test isprobvec(GenericArray([1/3, 1/3, 1/3]))

# Positive definite matrix
M = GenericArray([1.0 0.0; 0.0 1.0])
# Non-invertible matrix
N = GenericArray([1.0 0.0; 1.0 0.0])

@test Distributions.isApproxSymmmetric(N) == false
@test Distributions.isApproxSymmmetric(M)


function solution()
    values = readlines("smalltest.txt")
    values = parse.(Int, values)
    windowindices = [1, 2, 3]
    prevsum = -Inf
    count = 0
    while windowindices[3] <= length(values)
        currsum = values[windowindices[1]] + values[windowindices[2]] + values[windowindices[3]]
        if prevsum > 0 && currsum > prevsum
            count += 1
        end
        windowindices .+= 1
        prevsum = currsum
    end
    println(count)
end

solution()


##########################
# FunctionCell Interface #
##########################
"""
	kmeans(k, d=Distances.Euclidean() [;kwargs])

Constructs an untrained cell that when piped data inside, clusters the data using the
k-means algorithm where `k` is the desired number of clusters and `d` is the distance
that will be used to calculate distances from new samples to the clustering generated
from the untrained function cell.

# Keyword arguments (same as in `Clustering`)
  * `init` initialization algorithm; can be `:rand` for random, `:kmpp` for K-medoids++ or `:kmcen` for choosing samples with highest centrality
  or a vector of size 'k' providing the indexes of the initial seeds (default `:kmpp`)
  * `maxiter` maximum number of iterations (default `100`)
  * `tol` tolerable change in objective at convergence (default `1.0e-6`)
  * `weights` can be `nothing` for unit weight or a vector of length `n` for individual sample weights (default `nothing`)
  * `display` Level of information; can be `:none`, `:final`, `:iter` (default `:none`)

Read the `Clustering.jl` documentation for more information.
"""
kmeans(k::Int, d::Distances.PreMetric=Distances.Euclidean(); kwargs...) = FunctionCell(kmeans, (k,d), ModelProperties(), kwtitle("K-means ($k clusters)", kwargs); kwargs...)

############################
# DataCell/Array Interface #
############################
"""
	kmeans(x, k, d=Distances.Euclidean() [;kwargs])

Performs k-means clustering on the dataset `x` using `k` number of clusters and returns the clustering; when piping
data to the resulting clustering (e.g. trained cell), the distances from input data to the center clusters are
returned, using `d` as distance.
"""
# Training
kmeans(x::T where T<:CellData, k::Int, d::Distances.PreMetric=Distances.Euclidean(); kwargs...) = kmeans(getx!(x), k, d; kwargs...)
kmeans(x::T where T<:AbstractVector, k::Int, d::Distances.PreMetric=Distances.Euclidean(); kwargs...) = kmeans(mat(x, LearnBase.ObsDim.Constant{2}()), k, d; kwargs...)
kmeans(x::T where T<:AbstractMatrix, k::Int, d::Distances.PreMetric=Distances.Euclidean(); kwargs...) = begin

	# Perform clustering
	kmeansdata = Clustering.kmeans(x, k; kwargs...)

	@assert size(kmeansdata.centers,2) == k "[kmeans] The number of resulting clusters differes from the desired one k=$k."

	# Build model properties
	modelprops = ModelProperties(size(kmeansdata.centers,1),k) # output dimension is 'k' i.e. the number of cluster centers is 'k'

	FunctionCell(kmeans, Model((d,kmeansdata), modelprops), kwtitle("K-means ($k clusters)", kwargs) );
end

# Execution
kmeans(x::T where T<:CellData, model::Model{<:Tuple{<:Distances.PreMetric, <:Clustering.KmeansResult}}) =
	datacell(kmeans(getx!(x), model), gety(x))
kmeans(x::T where T<:AbstractVector, model::Model{<:Tuple{<:Distances.PreMetric, <:Clustering.KmeansResult}}) =
	kmeans(mat(x, LearnBase.ObsDim.Constant{2}()), model)
kmeans(x::T where T<:AbstractMatrix, model::Model{<:Tuple{<:Distances.PreMetric, <:Clustering.KmeansResult}}) =
	# Return distances from each input sample to clustering centers
	Distances.pairwise(model.data[1], model.data[2].centers, x) # d(centers,x)

##########################
# FunctionCell Interface #
##########################
"""
	kmeans!(centers, d=Distances.Euclidean() [;kwargs])

Constructs an untrained cell that when piped data inside, clusters the data using the
k-means algorithm where `centers` contains the initial cluster centers (will be updated
inplace) and `d` is the distance that will be used to calculate distances from new samples
to the clustering generated from the untrained function cell.

# Keyword arguments (same as in `Clustering`)
  * `maxiter` maximum number of iterations (default `100`)
  * `tol` tolerable change in objective at convergence (default `1.0e-6`)
  * `weights` can be `nothing` for unit weight or a vector of length `n` for individual sample weights (default `nothing`)
  * `display` Level of information; can be `:none`, `:final`, `:iter` (default `:none`)

Read the `Clustering.jl` documentation for more information.
"""
kmeans!(centers::T where T<:AbstractMatrix, d::Distances.PreMetric=Distances.Euclidean(); kwargs...) =
	FunctionCell(kmeans!, (centers,d), ModelProperties(), kwtitle("K-means! ($(size(centers,2)) clusters)", kwargs); kwargs...)

############################
# DataCell/Array Interface #
############################
"""
	kmeans!(x, centers, d=Distances.Euclidean() [;kwargs])

Performs k-means clustering on the dataset `x` using `centers` as initial cluster centers
and returns the clustering; when piping data to the resulting clustering (e.g. trained cell),
the distances from input data to the new cluster centers are returned, using `d` as distance.
"""
# Training
kmeans!(x::T where T<:CellData, centers::S where S<:AbstractMatrix, d::Distances.PreMetric=Distances.Euclidean(); kwargs...) = kmeans!(getx!(x), centers, d; kwargs...)
kmeans!(x::T where T<:AbstractVector, centers::S where S<:AbstractMatrix, d::Distances.PreMetric=Distances.Euclidean(); kwargs...) = kmeans!(mat(x, LearnBase.ObsDim.Constant{2}()), centers, d; kwargs...)
kmeans!(x::T where T<:AbstractMatrix, centers::S where S<:AbstractMatrix, d::Distances.PreMetric=Distances.Euclidean(); kwargs...) = begin

	# Perform clustering
	kmeansdata = Clustering.kmeans!(x, centers; kwargs...)

	# Build model properties
	modelprops = ModelProperties(size(kmeansdata.centers,1), size(kmeansdata.centers,2), nothing, Dict("eval_distance" => d))

	# Return a trained cell that uses `kmeans` and not `kmeans!` as execution function
	# since the only difference is in training (e.g. clustering) and not in
	# assigning new data to clusters.
	FunctionCell(kmeans, Model((d,kmeansdata), modelprops), kwtitle("K-means ($(size(kmeansdata.centers,2)) clusters)", kwargs) );
end


using LinearAlgebra, Statistics
using BenchmarkTools
using Plots, DataFrames

using FRAC

Plots.theme(:ggplot2) # Change Plots.jl theme

# In this example, we simulate demand for `T` markets, and assume the researcher
# wants to allow for preferences to differ in `B` separate time periods/geographic
# regions.

# This file performs the following:
# -- Simulate demand data for many markets with varying numbers of products
# -- Calculate IVs
# -- Estimate FRAC models and calculate various price elasticities
# -- Extract and plot results of all models

B = 100; # number of separate models to estimate
T = 20000; # number of markets/2

J1 = 2; # number of products in half of markets
J2 = 4;

# Simulate demand with two characteristics, both with random coefficients
# Run benchmarks for multiple FRAC models
s,p,z,x = simulate_logit(J1,T,[-0.4 0.1], [0.5 0.5], 0.3);
s2,p2,z2,x2 = simulate_logit(J2,T,[-0.4 0.1], [0.5 0.5], 0.3);

# Reshape data into desired DataFrame, add necessary IVs
df = toDataFrame(s,p,z,x);
df = reshape_pyblp(df);
df2 = reshape_pyblp(toDataFrame(s2,p2,z2,x2));
df2[!,"product_ids"] = df2.product_ids .+ 2;
df2[!,"market_ids"] = df2.market_ids .+ T .+1;
df = [df;df2]
df[!,"by_example"] = mod.(1:size(df,1),B); # Generate variable indicating separate geographies
df[!,"demand_instruments1"] = df.demand_instruments0.^2;
df[!,"demand_instruments2"] = df.x .^2;

# Add simple differentiation-style IVs: difference from market-level sum
gdf = groupby(df, :market_ids);
cdf = combine(gdf, names(df) .=> sum);
cdf = select(cdf, Not(:market_ids_sum));
sums = innerjoin(select(df, :market_ids), cdf, on = :market_ids);
df[!,"demand_instruments3"] = (df.demand_instruments0 - sums.demand_instruments0_sum).^2;
df[!,"demand_instruments4"] = (df.x - sums.x_sum).^2;

df[!,"dummy_FE"] .= rand();
df[!,"dummy_FE"] = (df.dummy_FE .> 0.5);

# Estimate FRAC and calculate
#   (1) all own-price elasticities and
#   (2) all elasticities w.r.t. the good with product_ids == 1

# All models include two dimensions of random coefficients and two fixed effects
@time results = estimateFRAC(data = df, linear= "prices + x", nonlinear = "prices + x",
    by_var = "by_example", fes = "product_ids + dummy_FE",
    se_type = "robust", constrained = false)
@time own_elast = price_elasticities(frac_results = results, data = df,
    linear = "prices + x", nonlinear = "prices + x", which = "own",
    by_var="by_example")
@time cross_elast = price_elasticities(frac_results = results, data = df,
    linear = "prices + x", nonlinear = "prices + x", which = "cross",
    by_var="by_example", product=2)

# Extraneous helper functions can be used to plot FRAC results with standard
# errors (when unconstrained). Here we show, for all estimated models,
#   (1) Mean price coefficient
#   (2) Variance of price coefficient
#   (3) Variance of x coefficient
# (Horizontal lines indicate true value)

plot1 = plotFRACResults(df, frac_results = results, var = "prices", plot = "scatter", by_var = "by_example")
plot!(plot1, ylims = (-1, 0), leg=false)
plot!([-0.4], linetype = :hline, linewidth = 2)

plot2 = plotFRACResults(df, frac_results = results, var = "prices", param = "sd", plot = "scatter", by_var = "by_example")
plot!(plot2, ylims = (0, 0.5))
plot!([0.25], linetype = :hline, linewidth = 2)

plotFRACResults(df,frac_results = results, var = "x", param = "sd", plot = "scatter", by_var = "by_example")
plot!([0.25], linetype = :hline, linewidth = 2)

# Can also extract estimates, SEs, and groups (values of by_var) in the same order
alphas, alpha_se, byvals = extractEstimates(df, frac_results = results, param = "mean", by_var = "by_example")
sigmas, sigma_se, byvals = extractEstimates(df, frac_results = results, param = "sigmas", by_var = "by_example")
xsigmas, xsigma_se, byvals = extractEstimates(df, frac_results = results, var = "x", param = "sigmas", by_var = "by_example")


include("kern_proc.jl")

d = 10
n = [50, 100, 500]

λ = rand(d)

cov_procs = Array(Proc, 0)
push!(cov_procs, KernelTest(SEArd(λ, 1.0), d, GaussianProcesses.cov))
push!(cov_procs, KernelTest(Mat12Ard(λ, 1.0), d, GaussianProcesses.cov))
push!(cov_procs, KernelTest(Mat32Ard(λ, 1.0), d, GaussianProcesses.cov))
push!(cov_procs, KernelTest(Mat52Ard(λ, 1.0), d, GaussianProcesses.cov))
push!(cov_procs, KernelTest(RQArd(λ, 1.0, 1.0), d, GaussianProcesses.cov))
cov_table = run(cov_procs, n)

grad_stack_procs = Array(Proc, 0)
push!(grad_stack_procs, KernelTest(SEArd(λ, 1.0), d, GaussianProcesses.grad_stack))
push!(grad_stack_procs, KernelTest(Mat12Ard(λ, 1.0), d, GaussianProcesses.grad_stack))
push!(grad_stack_procs, KernelTest(Mat32Ard(λ, 1.0), d, GaussianProcesses.grad_stack))
push!(grad_stack_procs, KernelTest(Mat52Ard(λ, 1.0), d, GaussianProcesses.grad_stack))
push!(grad_stack_procs, KernelTest(RQArd(λ, 1.0, 1.0), d, GaussianProcesses.grad_stack))
grad_stack_table = run(grad_stack_procs, n)

println("\ncov calculations")
show(cov_table)

println("\grad_stack calculations")
show(grad_stack_table)


# using Revise, Plots, Test
using PseudoArcLengthContinuation, LinearAlgebra, Setfield, SparseArrays, ForwardDiff, Parameters
const PALC = PseudoArcLengthContinuation
norminf = x -> norm(x, Inf)

function Fbp(x, p)
	return [x[1] * (3.23 .* p.μ - 0.12 * x[1] + 0.234 * x[1]^2) + x[2], x[2]]
end

par = (μ = -0.2, ν = 0)
####################################################################################################
opt_newton = PALC.NewtonPar(tol = 1e-8, verbose = false, maxIter = 20)
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = 0.01, pMax = 0.1, pMin = -0.3, detectBifurcation = 2, nev = 2, newtonOptions = opt_newton, maxSteps = 100)

	br, _ = @time PALC.continuation(
		Fbp, [0.1, 0.1], par, (@lens _.μ),
		printSolution = (x, p) -> norminf(x),
		opts_br; plot = false, verbosity = 0, normC = norminf)

####################################################################################################
# normal form computation
D(f, x, p, dx) = ForwardDiff.derivative(t->f(x .+ t .* dx, p), 0.)

d1F(x,p,dx1)         = D((z, p0) -> Fbp(z, p0), x, p, dx1)
d2F(x,p,dx1,dx2)     = D((z, p0) -> d1F(z, p0, dx1), x, p, dx2)
d3F(x,p,dx1,dx2,dx3) = D((z, p0) -> d2F(z, p0, dx1, dx2), x, p, dx3)
jet = (Fbp,
	(x, p) -> PALC.finiteDifferences(z -> Fbp(z, p), x),
	(x, p, dx1, dx2) -> d2F(x, p, dx1, dx2),
	(x, p, dx1, dx2, dx3) -> d3F(x, p, dx1, dx2, dx3))

bp = PALC.computeNormalForm(jet..., br, 1; verbose=true)

# normal form
nf = bp.nf

@test norm(nf[1]) < 1e-10
	@test norm(nf[2] - 3.23) < 1e-10
	@test norm(nf[3]/2 - -0.12) < 1e-10
	@test norm(nf[4]/6 - 0.234) < 1e-10

####################################################################################################
# Automatic branch switching
br, _ = continuation(jet..., br, 1, opts_br; verbosity = 0)

####################################################################################################
function Fbp2d(x, p)
	return [ x[1] * (3.23 .* p.μ - 0.123 * x[1]^2 - 0.234 * x[2]^2),
			 x[2] * (3.23 .* p.μ - 0.456 * x[1]^2 - 0.123 * x[2]^2),
			 x[3]]
end

d1F2d(x,p,dx1) = D((z, p0) -> Fbp2d(z, p0), x, p, dx1)
	d2F2d(x,p,dx1,dx2)     = D((z, p0) -> d1F2d(z, p0, dx1), x, p, dx2)
	d3F2d(x,p,dx1,dx2,dx3) = D((z, p0) -> d2F2d(z, p0, dx1, dx2), x, p, dx3)

jet = (Fbp2d, (x, p) -> ForwardDiff.jacobian(z -> Fbp2d(z, p), x), d2F2d, d3F2d)

par = (μ = -0.2, ν = 0)

br, _ = @time PALC.continuation(
	Fbp2d, [0.01, 0.01, 0.01], par, (@lens _.μ),
	printSolution = (x, p) -> norminf(x),
	setproperties(opts_br; nInversion = 2); plot = false, verbosity = 0, normC = norminf)

# we have to be careful to have the same basis as for Fbp2d or the NF will not match Fbp2d
bp2d = @time PALC.computeNormalForm(jet..., br, 1; ζs = [[1, 0, 0.], [0, 1, 0.]]);

PALC.nf(bp2d)
bp2d(rand(2), 0.2)
bp2d(Val(:reducedForm), rand(2), 0.2)

@test abs(bp2d.nf.b3[1,1,1,1] / 6 - -0.123) < 1e-10
@test abs(bp2d.nf.b3[1,1,2,2] / 2 - -0.234) < 1e-10
@test abs(bp2d.nf.b3[1,1,1,2] / 2 - -0.0)   < 1e-10
@test abs(bp2d.nf.b3[2,1,1,2] / 2 - -0.456) < 1e-10
@test norm(bp2d.nf.b2, Inf) < 3e-6
@test norm(bp2d.nf.b1 - 3.23 * I, Inf) < 1e-10
@test norm(bp2d.nf.a, Inf) < 1e-6
####################################################################################################
# test of the Hopf normal form
function Fsl2!(f, u, p, t)
	@unpack r, μ, ν, c3, c5 = p
	u1 = u[1]
	u2 = u[2]
	ua = u1^2 + u2^2

	f[1] = r * u1 - ν * u2 - ua * (c3 * u1 - μ * u2) - c5 * ua^2 * u1
	f[2] = r * u2 + ν * u1 - ua * (c3 * u2 + μ * u1) - c5 * ua^2 * u2

	return f
end

Fsl2(x, p) = Fsl2!(similar(x), x, p, 0.)
par_sl = (r = 0.5, μ = 0.132, ν = 1.0, c3 = 1.123, c5 = 0.2)

d1Fsl(x,p,dx1)         = D((z, p0) -> Fsl2(z, p0), x, p, dx1)
d2Fsl(x,p,dx1,dx2)     = D((z, p0) -> d1Fsl(z, p0, dx1), x, p, dx2)
d3Fsl(x,p,dx1,dx2,dx3) = D((z, p0) -> d2Fsl(z, p0, dx1, dx2), x, p, dx3)

# detect hopf bifurcation
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.02, ds = 0.01, pMax = 0.1, pMin = -0.3, detectBifurcation = 2, nev = 2, newtonOptions = (@set opt_newton.verbose = true), maxSteps = 100)

br, _ = @time PALC.continuation(
	Fsl2, [0.0, 0.0], (@set par_sl.r = -0.1), (@lens _.r),
	printSolution = (x, p) -> norminf(x),
	opts_br; plot = false, verbosity = 3, normC = norminf)

hp = PALC.computeNormalForm(
	(x, p) -> Fsl2(x, p),
	(x, p) -> ForwardDiff.jacobian(z -> Fsl2(z, p), x),
	(x, p, dx1, dx2) -> 	 d2Fsl(x, p, dx1, dx2),
	(x, p, dx1, dx2, dx3) -> d3Fsl(x, p, dx1, dx2, dx3),
	br, 1)

nf = hp.nf

@test abs(nf.a - 1) < 1e-10
@test abs(nf.b/2 - (-par_sl.c3 + im*par_sl.μ)) < 1e-10


function circleShape(x, y, r)
	θ = LinRange(0, 2*π, 500)
	x .+ r*sin.(θ), y .+ r*cos.(θ)
end


@auto_hash_equals struct Point{I,C,F}
    ec::C   # the curve containing this point
    x::F
    y::F
end

const ConcretePoint{C,F} = Point{false,C,F}
const IdealPoint{C,F}    = Point{true, C,F}

point(ec::Curve{F}, x, y) where {F} = point(ec, convert(F, x), convert(F, y))
point(ec::Curve{F}, x::F, y::F, valid = false) where {F} = begin
    valid || contains(ec, x, y) || error("($(x), $(y)) is not a valid point on $(ec)")
    ConcretePoint{typeof(ec),F}(ec, x, y)
end

point(ec::Curve{F}) where {F} = (z = zero(ring(ec)); IdealPoint{typeof(ec),F}(ec, z, z))
ideal(ec::Curve) = point(ec)

# http://sagenb.org/src/schemes/elliptic_curves/ell_generic.py: is_x_coord(self, x)
# http://sagenb.org/src/schemes/elliptic_curves/ell_generic.py: lift_x(self, x, all=False)
point(ec::WNFCurve{T}, x::T) where {T} = begin
    y = x
    try
        r = sqrt(x^3 + ec.a * x + ec.b)
        y = convert(T, r)
    catch
    end

    !contains(ec, x, y) && error("no point with x-coordinate $(x) on $(ec)")
    point(ec, x, y, true)
end

rand(ec::Curve{T}) where {T<:FinFieldElem} = begin
    R = field(ec)
    q = order(R)

    # the following allows the ideal point to be picked
    rand() <= 1 / convert(BigInt, q + 1) && return ideal(ec)

    for _ in 1:100
        x = rand(R)
        try
            p = point(ec, x)
            return p
        catch e
            !isa(e, ErrorException) && rethrow()
        end
    end

    error("no random point on $(ec) could be found")
end

show(io::IO, p::ConcretePoint) = print(io, "($(p.x), $(p.y)) on elliptic curve $(curve(p))")
show(io::IO, p::IdealPoint) = print(io, "𝒪 on elliptic curve $(curve(p))")

curve(p::Point) = p.ec
in(p::Point, ec::Curve) = (ec == curve(p)) && contains(ec, p.x, p.y)

samecurve(p::Point{I,C,F}, q::Point{J,C,F}) where {I,J,C,F} = curve(p) == curve(q)
samecurve(p::Point, q::Point) = false

isideal(::IdealPoint) = true
isideal(::ConcretePoint) = false

-(p::IdealPoint) = p
-(p::ConcretePoint{C}) where {C<:WNFCurve} = point(curve(p), p.x, -p.y, true)
-(p::ConcretePoint{C}) where {C<:GWNFCurve} = point(curve(p), p.x, -p.y - curve(p).a₁ * p.x - curve(p).a₃, true)
-(p::Point, q::Point) = p + (-q)

+(p::Point, q::IdealPoint) = (samecurve(p, q) || error("$(p) and $(q) have different curves"); p)
+(p::IdealPoint, q::ConcretePoint) = (samecurve(p, q) || error("$(p) and $(q) have different curves"); q)
+(p::ConcretePoint{C}, q::ConcretePoint{C}) where {C<:WNFCurve} = begin
    samecurve(p, q) || error("$(p) and $(q) have different curves")

    ec = curve(p)
    if p == q
        p.y == 0 && return ideal(ec)        # vertical line
        m = divexact(3*p.x^2 + ec.a, 2*p.y) # slope of the tangent line
    else
        p.x == q.x && return ideal(ec)      # vertical line
        m = divexact(q.y - p.y, q.x - p.x)  # slope of the secant line
    end

    x = m^2 - q.x - p.x                     # using Vieta's formula for the sum of the roots
    y = m * (x - p.x) + p.y
    point(ec, x, -y, true)                  # do the reflection to get the sum of the two points
end
+(p::ConcretePoint{C}, q::ConcretePoint{C}) where {C<:GWNFCurve} = begin
    samecurve(p, q) || error("$(p) and $(q) have different curves")

    ec = curve(p)
    if p.x == q.x
        p.y + q.y + ec.a₁*p.x + ec.a₃ == 0 && return ideal(ec)

        t = 2*p.y + ec.a₁*p.x + ec.a₃
        c = divexact(3*p.x^2  + 2*ec.a₂*p.x +   ec.a₄ - ec.a₁*p.y, t)
        d = divexact(-(p.x^3) +   ec.a₄*p.x + 2*ec.a₆ - ec.a₃*p.y, t)
     else
        c = divexact(q.y - p.y,         q.x - p.x)
        d = divexact(p.y*q.x - q.y*p.x, q.x - p.x)
    end

    Σx = c^2 + ec.a₁*c - ec.a₂ - p.x - q.x
    Σy = -(c + ec.a₁) * Σx - d - ec.a₃
    point(ec, Σx, Σy, true)
end

*(n::Integer, p::IdealPoint) = p
*(p::IdealPoint, n::Integer) = p
*(n::Integer, p::ConcretePoint) = p * n
*(p::ConcretePoint, n::Integer) = begin
    n == 0 && return ideal(curve(p))
    n < 0 && return -p * -n

    q = p
    r = (n & 1 == 1) ? p : ideal(curve(p))

    i = oftype(n, 2)
    while i <= n
      q = q + q
      if n & i == i
        r = q + r
      end
      i = i << 1
    end

    r
end


export Ball

struct Ball{K} <: AbstractSimpleShape{K}
    c::SFloat{K}  # center of ball
    r::Float  # radius
    Ball{K}(c,r) where {K} = new(c,r)  # suppress default outer constructor
end

Ball(c::AbsVecReal, r::Real) = Ball{length(c)}(c,r)

Base.:(==)(s1::Ball, s2::Ball) = s1.c==s2.c && s1.r==s2.r
Base.isapprox(s1::Ball, s2::Ball) = s1.c≈s2.c && s1.r≈s2.r
Base.hash(s::Ball, h::UInt) = hash(s.c, hash(s.r, hash(:Ball, h)))

level(x::SReal{K}, s::Ball{K}) where {K} = norm(x-s.c) / s.r - 1.0
center(s::Ball) = s.c
bounds(s::Ball) = (s.c .- s.r, s.c .+ s.r)


export shift, shift!

"""
    shift!(arr, shifts; kwargs...)

Shifts an array in-place. For real arrays it is based on `rfft`.
For complex arrays based on `fft`.
`shifts` can be non-integer, for integer shifts one should prefer
`circshift` or `ShiftedArrays.circshift` because a FFT-based methods
introduces numerical errors.

## kwargs...
* `fix_nyquist_frequency=false`: Fourier shifting of even-sized arrays is not revertible. However, if you did
    `shift(x, δ)` you can it revert by `shift(x, δ, fix_nyquist_frequency=true)`. This only works if `δ` is the same.
* `take_real=true`: For even-sized arrays we take by default the `real` part of the exponential phase at the Nyquist frequency.
    This satisfies the property of real valuedness and the aliasing of the Nyquist term.

## Memory Usage
Note that for complex arrays we can avoid any large memory allocations
because of `fft!`. For `rfft` there does not exist a usable implementation
yet, so for real arrays there might be a temporary larger memory usage.

# Examples
```jldoctest
julia> x = [1.0 2.0 3.0; 4.0 5.0 6.0]
2×3 Matrix{Float64}:
 1.0  2.0  3.0
 4.0  5.0  6.0

julia> shift!(x, (1, 2))
2×3 Matrix{Float64}:
 5.0  6.0  4.0
 2.0  3.0  1.0

julia> x = [0, 1.0, 0.0, 1.0]
4-element Vector{Float64}:
 0.0
 1.0
 0.0
 1.0

julia> shift!(x, 0.5)
4-element Vector{Float64}:
 0.49999999999999994
 0.5
 0.49999999999999994
 0.5
```
"""
function shift!(arr::AbstractArray{<:Complex, N}, shifts; soft_fraction=0, fix_nyquist_frequency=false, take_real=true) where {N}
    return shift_by_1D_FT!(arr, shifts; soft_fraction=soft_fraction, fix_nyquist_frequency=fix_nyquist_frequency, take_real=take_real)
end

function shift!(arr::AbstractArray{<:Real, N}, shifts; soft_fraction=0, fix_nyquist_frequency=false, take_real=true) where {N}
    return shift_by_1D_RFT!(arr, shifts; soft_fraction=soft_fraction, fix_nyquist_frequency=fix_nyquist_frequency, take_real=take_real)
end

"""
    shift(arr, shifts)

Returning a shifted array.
See [`shift!`](@ref shift!) for more details
"""
function shift(arr, shifts; soft_fraction=0, fix_nyquist_frequency=false, take_real=true)
    return shift!(copy(arr), shifts; soft_fraction=soft_fraction, fix_nyquist_frequency=fix_nyquist_frequency, take_real=take_real)
end

function soft_shift(freqs, shift, fraction=eltype(freqs)(0.1); corner=false)
    rounded_shift = round.(shift);
    if corner
        w = window_half_cos(size(freqs),border_in=2.0-2*fraction, border_out=2.0, offset=CtrCorner)
    else
        w = ifftshift_view(window_half_cos(size(freqs),border_in=1.0-fraction, border_out=1.0))
    end
    return cispi.(-freqs .* 2 .* (w .* shift + (1.0 .-w).* rounded_shift))
end

function shift_by_1D_FT!(arr::AbstractArray{<:Complex, N}, shifts; soft_fraction=0, take_real=false, fix_nyquist_frequency=false) where {N}
    for (d, shift) in pairs(shifts) # iterates of the dimension d using the corresponding shift
        if iszero(shift)
            continue
        end
        # better use reorient from NDTools here?
        freqs = reshape(fftfreq(size(arr, d)), ntuple(i -> 1, Val(d-1))..., size(arr,d))
        # allocates a 1D slice of exp values
        if iszero(soft_fraction)
            ϕ = cispi.(- freqs .* 2 .* shift)
        else
            ϕ = soft_shift(freqs, shift, soft_fraction)
        end
        # in even case, set one value to real
        if iseven(size(arr, d))
            s = size(arr, d) ÷ 2 + 1
            ϕ[s] = take_real ? real(ϕ[s]) : ϕ[s]
            invr = 1 / ϕ[s]
            invr = isinf(invr) ? 0 : invr
            ϕ[s] = fix_nyquist_frequency ? invr : ϕ[s]
        end
        # go to fourier space and apply ϕ
        fft!(arr, d)
        arr .*= ϕ
    end

    # do one single multi dimensional fft to go back to real space
    # but only over those dimensions where shift != 0
    dims = Int[]
    for (i, s) in pairs(shifts)
        if !iszero(s)
            push!(dims, i)
        end
    end
    ifft!(arr, dims)

    return arr
end

# the idea is the following:
# rfft(x, 1) -> exp shift -> fft(x, 2) -> exp shift ->  fft(x, 3) -> exp shift -> ifft(x, [2,3]) -> irfft(x, 1)
# So once we did a rft to shift something we can call the routine for complex arrays to shift
function shift_by_1D_RFT!(arr::AbstractArray{<:Real, N}, shifts; soft_fraction=0, fix_nyquist_frequency=false, take_real=true) where {T, N}
    for (d, shift) in pairs(shifts)
        if iszero(shift)
            continue
        end

        s = size(arr, d) ÷ 2 + 1
        freqs = reshape(fftfreq(size(arr, d))[1:s], ntuple(i -> 1, d-1)..., s)
        if iszero(soft_fraction)
            ϕ = cispi.(-freqs .* 2 .* shift)
        else
            ϕ = soft_shift(freqs, shift, soft_fraction, corner=true)
        end
        if iseven(size(arr, d))
            # take real and maybe fix nyquist frequency
            ϕ[s] = take_real ? real(ϕ[s]) : ϕ[s]
            invr = 1 / ϕ[s]
            invr = isinf(invr) ? 0 : invr
            ϕ[s] = fix_nyquist_frequency ? invr : ϕ[s]
        end
        p = plan_rfft(arr, d)

        arr_ft = p * arr
        arr_ft .*= ϕ
        # since we now did a single rfft dim, we can switch to the complex routine
        new_shifts = ntuple(i -> i ≤ d ? 0 : shifts[i], N)
         # workaround to mimic in-place rfft
        shift_by_1D_FT!(arr_ft, new_shifts; soft_fraction=soft_fraction, take_real=take_real, fix_nyquist_frequency=fix_nyquist_frequency)
        # go back to real space now and return because shift_by_1D_FT processed
        # the other dimensions already
        mul!(arr, inv(p), arr_ft)
        return arr # this breaks the for loop and finishes the algorithm
    end
    return arr
end

 #"""
 #    shift_by_ND(arr, shift_vector)
 #
 #Shifts an array by the shift_vector using sinc-interpolation via FFTs
 #`shift_vector`.
 #The vector by which to shift
 #```
 #"""
 #function shift_by_ND(arr::AbstractArray{T, N}, shift_vector) where {T, N}
 #    # for complex arrays we need a full FFT
 #    if T <: Complex
 #        arr_out = shift_by_FFT(arr, Tuple(shift_vector))
 #    else
 #        arr_out = shift_by_RFFT(arr, Tuple(shift_vector))
 #    end
 #    return arr_out
 #end
 #
 #
 #"""
 #    shift_by_RFFT(mat, new_size)
 #
 #Does shifting based on `rfft`. This function is called by `shift`.
 #"""
 #function shift_by_ND_RFT(mat, shift_vec) where {T}
 #    old_size=size(mat)
 #    irft(rft(mat).*exp.(
 #        dot.([shift_vec], idx(rft_size(mat),scale=ScaRFT,offset=CtrRFT)
 #        ).*(2im*pi)), size(mat,1))
 #end
 #
 #
 #function shift_by_ND_FT(mat, shift_vec) where {T}
 #    old_size=size(mat)
 #    ift(ft(mat).*exp.(
 #        dot.([shift_vec], idx(size(mat),scale=ScaFT,offset=CtrFT)
 #        ).*(2im*pi)))
 #end
 #


# A delay eigenvalue problem
function dep0(n::Int=5)
    msws_rng = MSWS_RNG()
    A0 = gen_rng_mat(msws_rng,n,n)
    A1 = gen_rng_mat(msws_rng,n,n)
    tau = 1.0
    nep = DEP([A0,A1],[0,tau])
    return nep
end

# A delay eigenvalue problem with sparse matrices
function dep0_sparse(n::Integer=100, p::Real=0.25)
    msws_rng = MSWS_RNG()
    A0 = sparse(1:n,1:n,vec(gen_rng_mat(msws_rng,n,1)))+gen_rng_spmat(msws_rng,n,n,p)
    A1 = sparse(1:n,1:n,vec(gen_rng_mat(msws_rng,n,1)))+gen_rng_spmat(msws_rng,n,n,p)
    tau = 1.0
    nep = DEP([A0,A1],[0,tau])
    return nep
end

# A delay eigenvalue problem with sparse tridiagonal matrices
function dep0_tridiag(n::Integer=100)
    msws_rng = MSWS_RNG()
    K = [1:n;2:n;1:n-1]; J=[1:n;1:n-1;2:n] # sparsity pattern of tridiag matrix
    A0 = sparse(K, J, vec(gen_rng_mat(msws_rng,3*n-2,1)))
    A1 = sparse(K, J, vec(gen_rng_mat(msws_rng,3*n-2,1)))
    tau = 1.0
    nep = DEP([A0,A1],[0,tau])
    return nep
end

# A polynomial eigenvalue problem
function pep0(n::Integer=200)
    msws_rng = MSWS_RNG()
    A0=gen_rng_mat(msws_rng,n,n)
    A1=gen_rng_mat(msws_rng,n,n)
    A2=gen_rng_mat(msws_rng,n,n)
    A=[A0,A1,A2]
    nep=PEP(A)
    return nep
end

# A polynomial eigenvalue problem with symmetric matrices
function pep0_sym(n::Integer=200)
    msws_rng = MSWS_RNG()
    A0 = Symmetric(gen_rng_mat(msws_rng,n,n))
    A1 = Symmetric(gen_rng_mat(msws_rng,n,n))
    A2 = Symmetric(gen_rng_mat(msws_rng,n,n))
    A = [A0, A1, A2]
    nep = PEP(A)
    return nep
end

# A sparse polynomial eigenvalue problem
function pep0_sparse(n::Integer=200, p::Real=0.03)
    msws_rng = MSWS_RNG()
    A0=gen_rng_spmat(msws_rng,n,n,p)
    A1=gen_rng_spmat(msws_rng,n,n,p)
    A2=gen_rng_spmat(msws_rng,n,n,p)
    A=[A0,A1,A2]
    nep=PEP(A)
    return nep
end

# Helper for random matrices, stable over releases
# B. Widynski, Middle Square Weyl Sequence RNG, arXiv 1704.00358
mutable struct MSWS_RNG
    x::UInt128
    w::UInt128
    s::UInt128
    function MSWS_RNG(seed::UInt128 = zero(UInt128))
        base = 0x9ef09a97ac0f9ecaef01c4f2db0958c9
        s = (seed << 1) + base
        x = 0x1de568e1a1ca1b593cbf13f7407cf43e
        w = 0xd4ac5c288559e14a5fafc1b7df9f9e0e
        return new(x, w, s)
    end
end

function gen_rng_int(rng::MSWS_RNG)
    rng.x *= rng.x
    rng.x += (rng.w += rng.s)
    rng.x = (rng.x>>64) | (rng.x<<64)
    return UInt64(rng.x & typemax(UInt64))
end

function gen_rng_float(rng::MSWS_RNG)
    return Float64(gen_rng_int(rng)/typemax(UInt64))
end

function gen_rng_mat(rng::MSWS_RNG, n::Integer, m::Integer)
    A = zeros(Float64,n,m)
    for c = 1:m
        for r = 1:n
            A[r,c] = 1-2*gen_rng_float(rng)
        end
    end
    return A
end

function gen_rng_spmat(rng::MSWS_RNG, n::Integer, m::Integer, p::Real)
    n=Int64(n)
    m=Int64(m)
    nonzeros = round(p*m*n)
    dict = Dict{Tuple{Int64,Int64},Float64}()
    for i = 1:nonzeros
        r = mod(gen_rng_int(rng),n) + 1
        c = mod(gen_rng_int(rng),m) + 1
        dict[r,c] = 1-2*gen_rng_float(rng)
    end
    idxes = collect(keys(dict))
    nnzs = length(idxes)
    r = zeros(Int64,nnzs)
    c = zeros(Int64,nnzs)
    vals = zeros(Float64,nnzs)
    for i = 1:nnzs
        r[i] = idxes[i][1]
        c[i] = idxes[i][2]
        vals[i] = dict[idxes[i]]
    end
    return sparse(r,c,vals,n,m)
end


#!/usr/bin/env julia
# -*- coding: utf-8 -*-
#
# Force field transform
#
# Aaron LI
# 2015/07/14
#

using FITSIO;
#include("../julia/ndgrid.jl");

@everywhere function meshgrid(vx, vy)
    m, n = length(vy), length(vx)
    vx = reshape(vx, 1, n)
    vy = reshape(vy, m, 1)
    (repmat(vx, m, 1), repmat(vy, 1, n))
end

# Calculate the forces between the specified point with respect to the image.
@everywhere function force(p0, img)
    img = copy(img);
    x0, y0 = p0;
    v0 = img[y0, x0];
    img[y0, x0] = 0.0;
    rows, cols = size(img);
    x, y = meshgrid(1:cols, 1:rows);
    x[y0, x0] = -1;
    y[y0, x0] = -1;
    f_x = v0 .* img .* (x-x0) ./ ((x-x0).^2 + (y-y0).^2).^1.5;
    f_y = v0 .* img .* (y-y0) ./ ((x-x0).^2 + (y-y0).^2).^1.5;
    #return (f_x, f_y);
    return (sum(f_x), sum(f_y));
end

# Perform the "force field transform" for the input image.
#
# Return:
#   (amplitudes, angles)
#   amplitudes: the amplitudes of the resulting forces of each pixel
#   angles: the directions of the resulting forces of each pixel,
#           in unit radian.
@everywhere function force_field_transform_serial(img, rowstart=1, rowend="end")
    rows, cols = size(img)
    if rowend == "end"
        rowend = rows
    end
    amplitudes = zeros(rows, cols)
    angles = zeros(rows, cols)
    t0 = time()
    t_p = t0 + 30  # in 30 seconds
    for y = rowstart:rowend
        for x = 1:cols
            t1 = time()
            if (t1 >= t_p)
                percent = 100*((y-rowstart)*cols + x+1) / ((rowend-rowstart+1)*cols)
                @printf("Worker #%d: progress: %.3f%%; %.1f min\n",
                        myid(), percent, (t1-t0)/60.0)
                t_p += 30  # in 30 seconds
            end
            F_x, F_y = force((x, y), img)
            #@printf("F_x, F_y = (%f, %f)\n", F_x, F_y);
            amplitudes[y, x] = sqrt(F_x^2 + F_y^2)
            angles[y, x] = atan2(F_y, F_x)
        end
    end
    t1 = time()
    @printf("Worker #%d: finished in %.1f min!\n", myid(), (t1-t0)/60.0)
    return (amplitudes, angles)
end

# parallel-capable
function force_field_transform(img)
    t0 = time()
    rows, cols = size(img)
    np = nprocs()
    amplitudes = cell(np)
    angles = cell(np)
    # split rows for each process
    rows_chunk = div(rows, np)
    rowstart = cell(np)
    rowend = cell(np)
    @sync begin
        for p = 1:np
            rowstart[p] = 1 + rows_chunk * (p-1)
            if p == np
                rowend[p] = rows
            else
                rowend[p] = rowstart[p] + rows_chunk - 1
            end
            # perform transform
            @async begin
                amplitudes[p], angles[p] = remotecall_fetch(p,
                        force_field_transform_serial,
                        img, rowstart[p], rowend[p])
            end
        end
    end
    t1 = time()
    @printf("Finished in %.1f min!\n", (t1-t0)/60.0)
    return (sum(amplitudes), sum(angles))
end

# arguments
#println(ARGS);
if length(ARGS) != 3
    println("Usage: PROG <input_fits_img> <out_fits_amplitudes> <out_fits_angles>");
    exit(1);
end

infile = ARGS[1];
outfile_ampl = ARGS[2];
outfile_angles = ARGS[3];

fits_img = FITS(infile);
img = read(fits_img[1]);
header = read_header(fits_img[1]);

# perform force field transform
ampl, angles = force_field_transform(img);

outfits_ampl = FITS(outfile_ampl, "w");
outfits_angles = FITS(outfile_angles, "w");
write(outfits_ampl, ampl; header=header);
write(outfits_angles, angles; header=header);

close(fits_img);
close(outfits_ampl);
close(outfits_angles);

#= vim: set ts=8 sw=4 tw=0 fenc=utf-8 ft=julia: =#


using HPAT

@acc hpat function movingavg_test(file_name)
    df = DataSource(DataTable{:id=Int64,:x=Float64,:y=Float64}, HDF5, file_name)
    avg = stencil(x->(x[-1]+x[0]+x[1])/3.0, df[:x])
    return sum(avg)
end

movingavg_test(ENV["HOME"]*"/tmp/cumsum.hdf5")


# simple program to test the new k-means (not ready yet)

using Base.Test
using Clustering
using Distances

import Distances.pairwise!

srand(34568)

m = 3
n = 1000
k = 10

x = rand(m, n)

# non-weighted
r = kmeans(x, k; maxiter=50)
@test isa(r, KmeansResult{Float64})
@test size(r.centers) == (m, k)
@test length(r.assignments) == n
@test all(r.assignments .>= 1) && all(r.assignments .<= k)
@test length(r.costs) == n
@test length(r.counts) == k
@test sum(r.counts) == n
@test r.cweights == map(Float64, r.counts)
@test isapprox(sum(r.costs), r.totalcost)

# non-weighted (float32)
r = kmeans(map(Float32, x), k; maxiter=50)
@test isa(r, KmeansResult{Float32})
@test size(r.centers) == (m, k)
@test length(r.assignments) == n
@test all(r.assignments .>= 1) && all(r.assignments .<= k)
@test length(r.costs) == n
@test length(r.counts) == k
@test sum(r.counts) == n
@test r.cweights == map(Float64, r.counts)
@test isapprox(sum(r.costs), r.totalcost)

# weighted
w = rand(n)
r = kmeans(x, k; maxiter=50, weights=w)
@test isa(r, KmeansResult{Float64})
@test size(r.centers) == (m, k)
@test length(r.assignments) == n
@test all(r.assignments .>= 1) && all(r.assignments .<= k)
@test length(r.costs) == n
@test length(r.counts) == k
@test sum(r.counts) == n

cw = zeros(k)
for i = 1:n
    cw[r.assignments[i]] += w[i]
end
@test isapprox(r.cweights, cw)

@test isapprox(dot(r.costs, w), r.totalcost)

# custom distance metric
type MySqEuclidean <: SemiMetric end

# redefinition of Distances.pairwise! for MySqEuclidean type
function pairwise!(r::AbstractMatrix, dist::MySqEuclidean, a::AbstractMatrix, b::AbstractMatrix)
    At_mul_B!(r, a, b)
    sa2 = sum(abs2, a, 1)
    sb2 = sum(abs2, b, 1)
    for j = 1 : size(r,2)
        sb = sb2[j]
        @simd for i = 1 : size(r,1)
            @inbounds r[i,j] = sa2[i] + sb - 2 * r[i,j]
        end
    end
    r
end

r = kmeans(x, k; maxiter=50, init=:kmcen, distance=MySqEuclidean())
r2 = kmeans(x, k; maxiter=50, init=:kmcen)
@test isa(r, KmeansResult{Float64})
@test size(r.centers) == (m, k)
@test length(r.assignments) == n
@test all(r.assignments .>= 1) && all(r.assignments .<= k)
@test length(r.costs) == n
@test length(r.counts) == k
@test sum(r.counts) == n
@test r.cweights == map(Float64, r.counts)
@test isapprox(sum(r.costs), r.totalcost)
for fn in fieldnames(r)
    @test getfield(r, fn) == getfield(r2, fn)
end