@testset "Fearnhead style mixture model particles" begin

    @testset "Cutoff for automatic propogation" begin

        using Particles: cutoff_ascending

        # a stub to replace the original cutoff using cutoff_ascending
        function cutoff(ws, N)
            ws = sort(ws)
            i_keep, w = cutoff_ascending(ws, N)
            return length(ws) - i_keep + 2, w, sum(ws)
        end

        function cutoff_normalized(ws::Vector{<:Real}, N::Int)
            ws = ws ./ sum(ws)
            total = sum(ws)
            for i in eachindex(ws)
                if total / ws[i] + (i-1) >= N
                    return i, total/(N-i+1)
                end
                total -= ws[i]
            end
        end

        function cutoff_while(ws::Vector{<:Real}, N::Int)
            ws = ws ./ sum(ws)
            total = sum(ws)
            i = 1
            while total / ws[i] + (i-1) < N
                total, i = total - ws[i], i+1
                i > length(ws) && error("infinite loop??")
            end
            i, total / (N-i+1)
        end

        ws = sort([1e10, exp.(randn(19))...], rev=true)
        keep, cut, tot = cutoff(ws, 10)
        # @show cutoff_normalized(ws,10)[2], cut, tot,  cut/tot

        # check correctness of the algorithm:  should be the case that
        # \sum_i=1^M min( c*w_i, 1 ) = N
        @test isapprox(sum(min.(ws ./ cut, 1)), 10, atol=1e-5)

        @test isapprox(cutoff_normalized(ws, 10)[2], cut/tot, atol=1e-10)
        @test isapprox(cutoff_while(ws, 10)[2], cut/tot, atol=1e-10)

        ws = sort(exp.(randn(20)), rev=true)
        keep, cut, tot = cutoff(ws, 10)
        @test isapprox(cutoff_normalized(ws, 10)[2], cut/tot, atol=1e-10)
        @test isapprox(cutoff_while(ws, 10)[2], cut/tot, atol=1e-10)

        # when weights all equal, resample all with weight 1/N
        ws = [1. for _ in 1:20]
        keep, cut, tot = cutoff(ws, 10)
        @test isapprox(cutoff_normalized(ws, 10)[2], cut/tot, atol=1e-10)
        @test isapprox(cutoff_while(ws, 10)[2], cut/tot, atol=1e-10)

        @test keep == 1         # first particle NOT kept
        @test cut/tot ≈ 1/10

        # when weights are all equal or zero, also resample all with weight 1/N
        # ...actually, in THIS case you effectively only HAVE N particles withn
        # non-zero weight so you should keep them all, which is what
        # cutoff-ascending gives you (unlike the old solution).
        ws = [i > 10 ? 0. : 1. for i in 1:20]
        keep, cut, tot = cutoff(ws, 10)
        @test keep == 11

        ws = [i > 10 ? 0. : rand() for i in 1:20]
        keep, cut, tot = cutoff(ws, 10)
        @test keep == 11

    end

    @testset "stratified sampling" begin

        Random.seed!(1999)
        w = exp.(2*rand(100))
        w ./= sum(w)
        x = collect(1:100)

        for n in [1, 8, 64, 90, 99]
            for _ in 1:100
                x_samp = Particles.sample_stratified(x, n, w)
                @test length(x_samp) ≤ n
            end
        end

    end

end


using Test
include("cas_theory.jl")
include("cas_simplify.jl")

@test :(4a)         == @simplify 2a + a + a
@test :(a*b*c)      == @simplify a * c * b
@test :(2x)         == @simplify 1 * x * 2
@test :((a*b)^2)    == @simplify (a*b)^2
@test :((a*b)^6)    == @simplify (a^2*b^2)^3
@test :(a+b+d)      == @simplify a + b + (0*c) + d
@test :(a+b)        == @simplify a + b + (c*0) + d - d
@test :(a)          == @simplify (a + d) - d
@test :(a + b + d)  == @simplify a + b * c^0 + d
@test :(a * b * x ^ (d+y))  == @simplify a * x^y * b * x^d
@test :(a * b * x ^ 74103)  == @simplify a * x^(12 + 3) * b * x^(42^3)

@test 1 == @simplify (x+y)^(a*0) / (y+x)^0
@test 2 == @simplify cos(x)^2 + 1 + sin(x)^2
@test 2 == @simplify cos(y)^2 + 1 + sin(y)^2
@test 2 == @simplify sin(y)^2 + cos(y)^2 + 1

@test :(y + sec(x)^2 ) == @simplify 1 + y + tan(x)^2
@test :(y + csc(x)^2 ) == @simplify 1 + y + cot(x)^2

# @simplify ∂(x^2, x)

@time @simplify ∂(x^(cos(x)), x)

@test :(2x^3) == @simplify x * ∂(x^2, x) * x

# @simplify ∂(y^3, y) * ∂(x^2 + 2, x) / y * x

# @simplify (6 * x * x * y)

# @simplify ∂(y^3, y) / y

# # ex = :( ∂(x^(cos(x)), x) )
# ex = :( (6 * x * x * y) )
# g = EGraph(ex)
# saturate!(g, cas; mod=@__MODULE__)
# g.classes
# extract!(g, simplcost; root=g.root)

# params = SaturationParams(
#     scheduler=BackoffScheduler,
#     eclasslimit=5000,
#     timeout=7,
#     schedulerparams=(1000,5),
#     #stopwhen=stopwhen,
# )

# ex = :((x+y)^(a*0) / (y+x)^0)
# g = EGraph(ex)
# @profview println(saturate!(g, cas, params; mod=@__MODULE__))

# ex = extract!(g, simplcost)
# ex = rewrite(ex, canonical_t; clean=false, m=@__MODULE__)


struct OptimFitResults{T<:Optim.OptimizationResults, V<:Optim.AbstractOptimizerState}
    optres::T
    state::V
    # forwarddiff_hessian_callback::Function
end

# the optimizer interface <: Optimizer should implement
#  - minimize(objective, init, optimizer)
#  - minimizer(minimizationResult)
#  - measurement(minimizationResult)
#  - minimum(minimizationResult)
struct BFGSApproxHesse end

function minimize(func, init, optimizer::BFGSApproxHesse;
        algorithm = BFGS(), autodiff = :forwarddiff, show_trace::Bool = true, kws...)
    obj = OnceDifferentiable(func, init; autodiff)
    #
    options = Optim.Options(; show_trace, kws...)
    state = Optim.initial_state(algorithm, options, obj, init)
    #
    optres = optimize(obj, init, algorithm, options, state)
    #
    return OptimFitResults(optres, state)
end
#
#
minimizer(fr::OptimFitResults) = minimizer(fr.optres)
minimum(fr::OptimFitResults) = minimum(fr.optres)
#
#
invH(fr::OptimFitResults) = fr.state.invH
covmat(fr::OptimFitResults) = invH(fr)
errors(fr::OptimFitResults) = sqrt.(diag(fr.state.invH))
#
hessian_callback(p) = ForwardDiff.hessian(func, p)
invexacthessian(fr::OptimFitResults, p = minimizer(fr)) = inv(fr.forwarddiff_hessian_callback(p))
#
#
function measurements(fr, exacthessian::Bool=false)
    mv = minimizer(fr)
    δv = !(exacthessian) ? errors(fr) : sqrt.(diag(invexacthessian(fr)))
    return [±(m,δ) for (m,δ) in zip(mv, δv)]
end


# Visually inspect ContinuousSpace `nearby_ids`
using Agents
using AgentsPlots
AgentsPlots.Plots.pyplot()

mutable struct Ag <: AbstractAgent
    id::Int
    pos::NTuple{2,Float64}
end

ε = 0.2
s = ContinuousSpace((10, 10), ε)
model = ABM(Ag, s)
for i in 1:800
    add_agent!(model)
end

r = 2
function ac(a)
    nids = nearby_ids(model[agentid], model, r, exact=false)
    if a.id == agentid
        return :red
 elseif a.id in nids
        return :blue
    else
        return :yellow
    end
end

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

# %% --- INEXACT ---
agentid = rand(1:800)
a = model[agentid]
δ = Agents.distance_from_cell_center(a.pos, model)
grid_r = (r+δ)/model.space.spacing
focal_cell = Agents.pos2cell(a.pos, model)
allcells = Agents.grid_space_neighborhood(CartesianIndex(focal_cell), model, grid_r)
search_region = [(a .* s.spacing) .- (s.spacing/2) for a in allcells]

p = plotabm(model, as=4, ac=ac, grid = (:both, :dot, 1, 0.9), xticks=(0:s.spacing:s.extent[1]), yticks=(0:s.spacing:s.extent[2]), size=(1000, 1000))
scatter!(search_region; markershape=:square, markersize=8, markerstrokewidth = 0, markeralpha = 0.2, markercolor=:grey)

plot!(p, circleShape(a.pos[1], a.pos[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
# Add all possible mirrors
plot!(p, circleShape(a.pos[1]-s.extent[1], a.pos[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1]-s.extent[1], a.pos[2]-s.extent[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1], a.pos[2]-s.extent[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1]+s.extent[1], a.pos[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1]+s.extent[1], a.pos[2]+s.extent[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1], a.pos[2]+s.extent[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
# Truncate view
xlims!(0, s.extent[1])
ylims!(0, s.extent[2])
title!(p, "inexact")

# %% --- EXACT ---
function act(a)
    if a.id == agentid
        return :red
    elseif a.id in nearby_ids(model[agentid], model, r, exact=true)
        return :blue
    else
        return :yellow
    end
end
p = plotabm(model, as=4, ac=act, grid = (:both, :dot, 1, 0.9), xticks=(0:s.spacing:s.extent[1]), yticks=(0:s.spacing:s.extent[2]), size=(1000, 1000))

focal_cell = Agents.pos2cell(a.pos, model)
grid_r_max = r < model.space.spacing ? 1.0 : r / model.space.spacing + 1
allcells = Agents.grid_space_neighborhood(CartesianIndex(focal_cell), model, grid_r_max)
search_region = [(a .* s.spacing) .- (s.spacing/2) for a in allcells]
scatter!(p, search_region; markershape=:square, markersize=8, markerstrokewidth = 0, markeralpha = 0.2, markercolor=:grey)

grid_r_certain = grid_r_max - 1.2*sqrt(2)
certain_cells = Agents.grid_space_neighborhood(CartesianIndex(focal_cell), model, grid_r_certain)
search_region = [(a .* s.spacing) .- (s.spacing/2) for a in certain_cells]
scatter!(p, search_region; markershape=:diamond, markersize=8, markerstrokewidth = 0, markeralpha = 0.2, markercolor=:green)

plot!(p, circleShape(a.pos[1], a.pos[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
# Add all possible mirrors
plot!(p, circleShape(a.pos[1]-s.extent[1], a.pos[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1]-s.extent[1], a.pos[2]-s.extent[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1], a.pos[2]-s.extent[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1]+s.extent[1], a.pos[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1]+s.extent[1], a.pos[2]+s.extent[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
plot!(p, circleShape(a.pos[1], a.pos[2]+s.extent[2], r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
# Truncate view
xlims!(0, s.extent[1])
ylims!(0, s.extent[2])
title!(p, "exact")

# 3D

mutable struct Ag3<: AbstractAgent
    id::Int
    pos::NTuple{3,Float64}
end
ε = 1.0
s = ContinuousSpace((10, 10, 10), ε)
model = ABM(Ag3, s)
for i in 1:800
    add_agent!(model)
end

function Sphere(O, r)
N = 32
u = LinRange(0, 2π, N)
v = LinRange(0, π, N)
x = cos.(u) * sin.(v)'
y = sin.(u) * sin.(v)'
z = repeat(cos.(v)',outer=[N, 1])
(x.*r.+O[1],y.*r.+O[2],z.*r.+O[3])
end

r = 2
agentid = rand(1:800)
a = model[agentid]
δ = Agents.distance_from_cell_center(a.pos, model)
grid_r = (r+δ)/model.space.spacing
focal_cell = Agents.pos2cell(a.pos, model)
allcells = Agents.grid_space_neighborhood(CartesianIndex(focal_cell), model, grid_r)
search_region = [(a .* s.spacing) .- (s.spacing/2) for a in allcells]

p = plotabm(model, as=4, ac=act,
            grid = (:both, :dot, 1, 0.9), xticks=(0:s.spacing:s.extent[1]), yticks=(0:s.spacing:s.extent[2]), zticks=(0:s.spacing:s.extent[3]),
            size=(1000, 1000, 1000))
#scatter!(search_region; markershape=:square, markersize=8, markerstrokewidth = 0, markeralpha = 0.2, markercolor=:grey)

plot!(p, Sphere(a.pos, r), seriestype = :shape, lw = 0.5,
      c = :blue, linecolor = :black, legend = false, fillalpha = 0.2, aspect_ratio=1)
# Truncate view
xlims!(0, s.extent[1])
ylims!(0, s.extent[2])
zlims!(0, s.extent[3])

plot!(p, camera=(20,50))

plot!(p, camera=(0,90))

plot!(p, camera=(90,0))

plot!(p, camera=(50,50))


import Tulip

"""
    QuantileRegression

Quantile regression representation

## Fields

* `τ`: the quantile value
* `X`: the model matrix
* `β`: the coefficients
* `y`: the response vector
* `wts`: the weights
* `wrkres`: the working residuals
* `fitdispersion`: if true, the dispersion is estimated otherwise it is kept fixed
* `fitted`: if true, the model was already fitted
"""
mutable struct QuantileRegression{
    T<:AbstractFloat,
    M<:AbstractMatrix{T},
    V<:AbstractVector{T},
} <: AbstractRobustModel{T}
    τ::T
    X::M
    β::V
    y::V
    wts::V
    wrkres::V
    fitdispersion::Bool
    fitted::Bool

    function QuantileRegression(
        τ::T,
        X::M,
        y::V,
        wts::V,
    ) where {V<:AbstractVector{T},M<:AbstractMatrix{T}} where {T<:AbstractFloat}
        n = length(y)
        m, p = size(X)
        ll = length(wts)
        ll == 0 || ll == n || error("length of wts is $ll, must be $n or 0")
        m == n || error("X has $m rows, must be like y, $n")
        new{T,M,V}(τ, X, zeros(T, p), y, wts, similar(y), false, false)
    end
end

function show(io::IO, obj::QuantileRegression)
    println(
        io,
        "Quantile regression for quantile: $(obj.τ)\n\nCoefficients:\n",
        coeftable(obj),
    )
end

function show(io::IO, obj::TableRegressionModel{M,T}) where {T,M<:QuantileRegression}
    println(
        io,
        "Quantile regression for quantile: $(obj.model.τ)\n\n$(obj.mf.f)\n\nCoefficients:\n",
        coeftable(obj),
    )
end

"""
    quantreg(X, y, args...; kwargs...)

An alias for `fit(QuantileRegression, X, y; kwargs...)`.

The arguments `X` and `y` can be a `Matrix` and a `Vector` or a `Formula` and a `DataFrame`.
"""
quantreg(X, y, args...; kwargs...) = fit(QuantileRegression, X, y, args...; kwargs...)

"""
    fit(::Type{M}, X::Union{AbstractMatrix{T},SparseMatrixCSC{T}},
        y::AbstractVector{T}; quantile::AbstractFloat=0.5,
        dofit::Bool          = true,
        wts::FPVector        = similar(y, 0),
        fitdispersion::Bool  = false,
        fitargs...) where {M<:QuantileRegression, T<:AbstractFloat}

Fit a quantile regression model with the model matrix (or formula) X and response vector (or dataframe) y.

It is solved using the exact interior method.

# Arguments

- `X`: the model matrix (it can be dense or sparse) or a formula
- `y`: the response vector or a dataframe.

# Keywords

- `quantile::AbstractFloat=0.5`: the quantile value for the regression, between 0 and 1.
- `dofit::Bool = true`: if `false`, return the model object without fitting;
- `wts::Vector = []`: a weight vector, should be empty if no weights are used;
- `fitdispersion::Bool = false`: reevaluate the dispersion;
- `fitargs...`: other keyword arguments like `verbose` to print iteration details.

# Output

the RobustLinearModel object.

"""
function fit(
    ::Type{M},
    X::Union{AbstractMatrix{T},SparseMatrixCSC{T}},
    y::AbstractVector{T};
    quantile::AbstractFloat=0.5,
    dofit::Bool=true,
    wts::FPVector=similar(y, 0),
    fitdispersion::Bool=false,
    fitargs...,
) where {M<:QuantileRegression,T<:AbstractFloat}

    # Check that X and y have the same number of observations
    if size(X, 1) != size(y, 1)
        mess = "number of rows in X and y must match: $(size(X, 1)) != $(size(y, 1))"
        throw(DimensionMismatch(mess))
    end

    # Check quantile is an allowed value
    (0 < quantile < 1) ||
        error("quantile should be a number between 0 and 1 excluded: $(quantile)")

    m = QuantileRegression(quantile, X, y, wts)
    return dofit ? fit!(m; fitargs...) : m
end

function fit(
    ::Type{M},
    X::Union{AbstractMatrix,SparseMatrixCSC},
    y::AbstractVector;
    kwargs...,
) where {M<:QuantileRegression}
    fit(M, float(X), float(y); kwargs...)
end

"""
    refit!(m::QuantileRegression,
          [y::FPVector ;
           verbose::Bool=false,
           quantile::Union{Nothing,
           AbstractFloat}=nothing,
          ]
    )

Refit the `QuantileRegression` model with the new values for the response,
weights and quantile. This function assumes that `m` was correctly initialized.
"""
function refit!(m::QuantileRegression, y::FPVector; kwargs...)
    # Update y
    # Check that the old and new y have the same number of rows
    if size(m.y, 1) != size(y, 1)
        mess =
            "the new response vector should have the same dimension: " *
            "$(size(m.y, 1)) != $(size(y, 1))"
        throw(DimensionMismatch(mess))
    end
    copyto!(m.y, y)

    refit!(m; kwargs...)
end

function refit!(
    m::QuantileRegression{T};
    quantile::Union{Nothing,AbstractFloat}=nothing,
    wts::Union{Nothing,FPVector}=nothing,
    kwargs...,
) where {T}

    # Update wts
    n = length(m.y)
    if !isnothing(wts) && (length(wts) in (0, n))
        copy!(m.wts, wts)
    end

    # Update quantile
    if !isnothing(quantile)
        (0 < quantile < 1) ||
            error("quantile should be a number between 0 and 1 excluded: $(quantile)")
        m.τ = quantile
    end

    m.fitted = false
    fit!(m; kwargs...)
end

"""
    fit!(m::QuantileRegression;
         verbose::Bool=false,
         quantile::Union{Nothing, AbstractFloat}=nothing,
         correct_leverage::Bool=false,
         kwargs...)

Optimize the objective of a `QuantileRegression`.  When `verbose` is `true` the values
of the objective and the parameters are printed on stdout at each function evaluation.

This function assumes that `m` was correctly initialized.
This function returns early if the model was already fitted, instead call `refit!`.
"""
function fit!(
    m::QuantileRegression{T};
    verbose::Bool=false,
    quantile::Union{Nothing,AbstractFloat}=nothing,
    correct_leverage::Bool=false,
    kwargs...,
) where {T}

    # Return early if model has the fit flag set
    m.fitted && return m

    if correct_leverage
        wts = m.wts
        copy!(wts, leverage_weights(m))
        ## TODO: maybe multiply by the old wts?
    end

    # Update quantile
    if !isnothing(quantile)
        (0 < quantile < 1) || throw(
            DomainError(quantile, "quantile should be a number between 0 and 1 excluded"),
        )
        m.τ = quantile
    end

    interiormethod!(m.β, m.wrkres, m.X, m.y, m.τ; wts=m.wts, verbose=verbose)

    m.fitted = true
    m
end

function interiormethod(X, y, τ; kwargs...)
    n, p = size(X)
    T = eltype(y)
    interiormethod!(zeros(T, p), zeros(T, n), X, y, τ; kwargs...)
end

function interiormethod!(βout, rout, X, y, τ; wts=[], verbose::Bool=false)
    model = Tulip.Model{Float64}()
    pb = model.pbdata  # Internal problem data

    n, p = size(X)

    # Define variables
    β = Vector{Int}(undef, p)
    u = Vector{Int}(undef, n)
    v = Vector{Int}(undef, n)
    for i in 1:p
        β[i] = Tulip.add_variable!(pb, Int[], Float64[], 0.0 , -Inf , Inf, "β$i")
    end
    for i in 1:n
        u[i] = Tulip.add_variable!(pb, Int[], Float64[], τ   ,  0.0 , Inf, "u$i")
    end
    for i in 1:n
        v[i] = Tulip.add_variable!(pb, Int[], Float64[], 1-τ ,  0.0 , Inf, "v$i")
    end
    #    @variable(model, β[1:p])
    #    @variable(model, u[1:n] >= 0)
    #    @variable(model, v[1:n] >= 0)

    # Define objective
    # Nothing to do, already defined with the variables
    #    e = ones(n)
    #    @objective(model, Min, τ*dot(e, u) + (1-τ)*dot(e, v) )

    Wy, WX = if isempty(wts)
        y, X
    else
        (wts .* y), (wts .* X)
    end

    # Define constraints
    resid = Vector{Int}(undef, n)
    for i in 1:n
        ci = vcat(β, [u[i], v[i]])
        # Call Vector to transform to dense vector
        cci = vcat(Vector(WX[i, :]), [1.0, -1.0])
        vi = Wy[i]
        resid[i] = Tulip.add_constraint!(pb, ci, cci, vi, vi, "resid$i")
    end
    #    @constraint(model, resid, Wy .== WX * β + u - v)

    Tulip.optimize!(model)

    copyto!(βout, model.solution.x[β])
    copyto!(rout, model.solution.x[u] - model.solution.x[v])
    #    copyto!(βout, value.(β))
    #    copyto!(rout, value.(u) - value.(v))

    if verbose
        println("coef: ", βout)
        println("res: ", rout)
    end
    βout, rout
end

_objective(r::AbstractFloat, τ::AbstractFloat) = (τ - (r < 0)) * r

"""
    hall_sheather_bandwidth()
Optimal bandwidth for sparsity estimation according to Hall and Sheather (1988)
"""
function hall_sheather_bandwidth(q::Real, n::Int, α::Real=0.05)
    zα = quantile(Normal(), 1 - α / 2)
    ## Estimate of r=s/s" from a Normal distribution
    zq = quantile(Normal(), q)
    f = pdf(Normal(), zq)
    r = f^2 / (2 * zq^2 + 1)
    (zα^2 * 1.5 * r / n)^(1 / 5)
end

"""
    jones_bandwidth()
Optimal bandwidth for kernel sparsity estimation according to Jones (1992)
"""
function jones_bandwidth(q::Real, n::Int; kernel=:epanechnikov)
    ## From the kernel k(u) with domain [-1, 1]:
    ## kk = ∫k²(u)du / ( ∫u²k(u)du )^2

    kk = if kernel == :epanechnikov
        # For Epanechnikov kernel: kk = 3/5 / (1/5)^2
        15
    elseif kernel == :triangle
        # For triangle kernel (k(u) = 1 - |u|): kk = 2/3 / (1/6)^2
        24
    elseif kernel == :window
        # That is the Bofinger estimate of the bandwidth
        # For window kernel (k(u) = 1/2): kk = 1/2 / (1/3)^2
        4.5
    else
        error("kernel $(kernel) is not defined for sparsity estimation.")
    end

    ## Estimate of r=s/s" from a Normal distribution
    zq = quantile(Normal(), q)
    f = pdf(Normal(), zq)
    r = f^2 / (2 * zq^2 + 1)
    (kk * r^2 / n)^(1 / 5)
end

"""
    bofinger_bandwidth()
Optimal bandwidth for sparsity estimation according to Bofinger (1975)
"""
function bofinger_bandwidth(q::Real, n::Int)
    return jones_bandwidth(q, n; kernel=:window)
end

function epanechnikov_kernel(x::Real)
    if abs(x) < 1
        return 3 / 4 * (1 - x^2)
    else
        return zero(typeof(x))
    end
end

function triangle_kernel(x::Real)
    if abs(x) < 1
        return 1 - abs(x)
    else
        return zero(typeof(x))
    end
end

function window_kernel(x::Real)
    if abs(x) < 1
        return one(typeof(x)) / 2
    else
        return zero(typeof(x))
    end
end

"""
    sparcity(m::QuantileRegression, α::Real=0.05)

Compute the sparcity or quantile density ŝ(0)
using formula from Jones (1992) - Estimating densities, quantiles,
quantile densities and density quantiles.

"""
function sparcity(
    m::QuantileRegression;
    bw_method::Symbol=:jones,
    α::Real=0.05,
    kernel::Symbol=:epanechnikov,
)
    u = m.τ
    n = nobs(m)
    ## Select the optimal bandwidth from different methods
    h = if bw_method == :jones
        jones_bandwidth(u, n; kernel=kernel)
    elseif bw_method == :bofinger
        bofinger_bandwidth(u, n)
    elseif bw_method == :hall_sheather
        hall_sheather_bandwidth(u, n, α)
    else
        error(
            "only :jones, :bofinger and :hall_sheather methods for estimating the"*
            " optimal bandwidth are allowed: $(bw_method)",
        )
    end

    ## Select kernel for density estimation
    k = if kernel == :epanechnikov
        epanechnikov_kernel
    elseif kernel == :triangle
        triangle_kernel
    elseif kernel == :window
        window_kernel
    else
        error("only :epanechnikov, :triangle and :window kernels are allowed: $(kernel)")
    end

    r = sort(residuals(m))

    s0 = 0
    for i in eachindex(r)
        if i == 1
            s0 += r[i] / h * k(u / h)
        else
            s0 += (r[i] - r[i-1]) / h * k((u - (i - 1) / n) / h)
            if i == lastindex(r)
                s0 += r[i] / h * k((u - 1) / h)
            end
        end
    end
    s0
end

function location_variance(
    m::QuantileRegression,
    sqr::Bool=false;
    bw_method::Symbol=:jones,
    α::Real=0.05,
    kernel::Symbol=:epanechnikov,
)
    v = sparcity(m; bw_method=bw_method, α=α, kernel=kernel)^2
    v *= m.τ * (1 - m.τ)
    v *= (nobs(m) / dof_residual(m))
    return sqr ? v : sqrt(v)
end

"""
    nobs(m::QuantileRegression)
For linear and generalized linear models, returns the number of elements of the response.
"""
function nobs(m::QuantileRegression)::Int
    if !isempty(m.wts)
        count(!iszero, m.wts)
    else
        size(m.y, 1)
    end
end

coef(m::QuantileRegression) = m.β

dispersion(m::QuantileRegression) = mean(abs.(residuals(m)))

stderror(m::QuantileRegression) = location_variance(m, false) .* sqrt.(diag(vcov(m)))

function weights(m::QuantileRegression{T}) where {T<:AbstractFloat}
    if isempty(m.wts)
        weights(ones(T, length(m.y)))
    else
        weights(m.wts)
    end
end

workingweights(m::QuantileRegression) = m.wrkres

response(m::QuantileRegression) = m.y

isfitted(m::QuantileRegression) = m.fitted

islinear(m::QuantileRegression) = true

fitted(m::QuantileRegression) = m.y - m.wrkres

residuals(m::QuantileRegression) = m.wrkres

predict(m::QuantileRegression, newX::AbstractMatrix) = newX * coef(m)
predict(m::QuantileRegression) = fitted(m)

function nulldeviance(m::QuantileRegression)
    μ = quantile(m.y, m.τ)
    if isempty(m.wts)
        sum(_objective.(m.wrkres, Ref(m.τ)))
    else
        sum(m.wts .* _objective.(m.wrkres, Ref(m.τ)))
    end
end

deviance(m::QuantileRegression) = sum(_objective.(m.wrkres, Ref(m.τ)))

## TODO: define correctly the loglikelihood of the full model
fullloglikelihood(m::QuantileRegression) = 0

loglikelihood(m::QuantileRegression) = fullloglikelihood(m) - deviance(m) / 2

nullloglikelihood(m::QuantileRegression) = fullloglikelihood(m) - nulldeviance(m) / 2

modelmatrix(m::QuantileRegression) = m.X

vcov(m::QuantileRegression) =
    inv(Hermitian(float(Matrix(modelmatrix(m)' * (weights(m) .* modelmatrix(m))))))

projectionmatrix(m::QuantileRegression) =
    Hermitian(modelmatrix(m) * vcov(m) * modelmatrix(m)') .* weights(m)

leverage_weights(m::QuantileRegression) = sqrt.(1 .- leverage(m))


# These reproduce the behaviour of DS9 according to http://ds9.si.edu/doc/ref/how.html

"""
    logstretch(num,a=1000)

A log-stretch as defined by the SAO DS9 application: http://ds9.si.edu/doc/ref/how.html
"""
logstretch(x,a=1000) = log(a*x+1)/log(a)
"""
    powstretch(num, a=1000)

A power-stretch as defined by the SAO DS9 application: http://ds9.si.edu/doc/ref/how.html
"""
powstretch(x,a=1000) = (a^x - 1)/a
"""
    sqrtstretch(num)

A square root stretch (simply defined as Base.sqrt)
"""
sqrtstretch(x) = sqrt(x)
"""
    squarestretch(num)

A squarestretch-stretch as defined by the SAO DS9 application: http://ds9.si.edu/doc/ref/how.html
"""
squarestretch(x) = x^2
"""
    asinhstretch(num)

A hyperbolic arcsin stretch as defined by the SAO DS9 application: http://ds9.si.edu/doc/ref/how.html.
"""
asinhstretch(x) = asinh(10x)/3
"""
    sinhstretch(num)

A hyperbolic sin stretch as defined by the SAO DS9 application: http://ds9.si.edu/doc/ref/how.html
"""
sinhstretch(x) = sinh(3x)/10
# These additional stretches reproduce behaviour from astropy
"""
    logstretch(num,a=1000)

A power distance stretch as defined by astropy.
"""
powerdiststretch(x, a=1000) = (a^x - 1) / (a - 1)

"""
    Percent(99.5)

Returns a callable that calculates display limits that include the given
percent of the image data.
Reproduces the behaviour of the SAO DS9 scale menu.

Example:
```julia
julia> imview(img, clims=Percent(90))
```
This will set the limits to be the 5th percentile to the 95th percentile.
"""
struct Percent
    perc::Float64
    trim::Float64
    Percent(percentage::Number) = new(Float64(percentage), (1 - percentage/100)/2)
end
(p::Percent)(data::AbstractArray) = quantile(vec(data), (p.trim, 1-p.trim))
(p::Percent)(data) = p(collect(data))
Base.broadcastable(p::Percent) = Ref(p)
Base.show(io::IO, p::Percent; kwargs...) = print(io, "Percent($(p.perc))", kwargs...)

"""
    Zscale(options)(data)

Wraps PlotUtils.zscale in a callable with default parameters.
This is a common algorithm for agressively stretching astronomical data
to see faint structure that originated in IRAF: https://iraf.net/forum/viewtopic.php?showtopic=134139
but is now seen in many other applications/libraries (DS9, Astropy, etc.)

Usage:
```
imview(img, clims=Zscale())
implot(img, clims=Zscale(contrast=0.1))
```

Default parameters:
```
nsamples::Int=1000
contrast::Float64=0.25
max_reject::Float64=0.5
min_npixels::Float64=5
k_rej::Float64=2.5
max_iterations::Int=5
```
"""
Base.@kwdef struct Zscale
    nsamples::Int=1000
    contrast::Float64=0.25
    max_reject::Float64=0.5
    min_npixels::Float64=5
    k_rej::Float64=2.5
    max_iterations::Int=5
end
(z::Zscale)(data::AbstractArray) = PlotUtils.zscale(vec(data), z.nsamples; z.contrast, z.max_reject, z.min_npixels, z.k_rej, z.max_iterations)
(z::Zscale)(data) = z(collect(data))
Base.show(io::IO, z::Zscale; kwargs...) = print(io, "Zscale()", kwargs...)
Base.broadcastable(z::Zscale) = Ref(z)

const _default_cmap  = Base.RefValue{Union{Symbol,Nothing}}(:magma)
const _default_clims = Base.RefValue{Any}(Percent(99.5))
const _default_stretch  = Base.RefValue{Any}(identity)

"""
    set_cmap!(cmap::Symbol)
    set_cmap!(cmap::Nothing)

Alter the default color map used to display images when using
`imview` or displaying an AstroImageMat.
"""
function set_cmap!(cmap)
    # Ensure it's valid
    _lookup_cmap(cmap)
    _default_cmap[] = cmap
end
"""
    set_clims!(clims::Tuple)
    set_clims!(clims::Callable)

Alter the default limits used to display images when using
`imview` or displaying an AstroImageMat.
"""
function set_clims!(clims)
    _default_clims[] = clims
end
"""
    set_stretch!(stretch::Function)

Alter the default value stretch functio used to display images when using
`imview` or displaying an AstroImageMat.
"""
function set_stretch!(stretch)
    _default_stretch[] = stretch
end

# Helper to iterate over data skipping missing and non-finite values.
skipmissingnan(itr) = Iterators.filter(el->!ismissing(el) && isfinite(el), itr)

# Convert argument into a colorscheme or AbstractColorList which allow converting
# from numerical data into colors.
function _lookup_cmap(cmap::Symbol)
    if cmap ∉ keys(ColorSchemes.colorschemes)
        error("$cmap not found in ColorSchemes.colorschemes. See: https://juliagraphics.github.io/ColorSchemes.jl/stable/catalogue/")
    end
    return ColorSchemes.colorschemes[cmap]
end
_lookup_cmap(::Nothing) = nothing
_lookup_cmap(acl::AbstractColorList) = acl
_lookup_cmap(colorant::Colorant) = PlotUtils.cgrad([:black, colorant])
_lookup_cmap(colorant::String) = PlotUtils.cgrad([:black, colorant])

function _resolve_clims(img::AbstractArray, clims)
    # Tuple or abstract array
    if typeof(clims) <: AbstractArray || typeof(clims) <: Tuple
        if length(clims) != 2
            error("clims must have exactly two values if provided.")
        end
        imgmin = first(clims)
        imgmax = last(clims)
    # Or as a callable that computes them given an iterator
    else
        imgmin, imgmax = clims(skipmissingnan(img))
    end

    return imgmin, imgmax
end

"""
    imview(img; clims=Percent(99.5), stretch=identity, cmap=:magma, contrast=1.0, bias=0.5)

Create a read only view of an array or AstroImageMat mapping its data values
to Colors according to `clims`, `stretch`, and `cmap`.

The data is first clamped to `clims`, which can either be a tuple of (min, max)
values or a function accepting an iterator of pixel values that returns (min, max).
By default, `clims=extrema` i.e. the minimum and maximum of `img`.
Convenient functions to use for `clims` are:
`extrema`, `zscale`, and `percent(p)`

Next, the data is rescaled to [0,1] and remapped according to the function `stretch`.
Stretch can be any monotonic fuction mapping values in the range [0,1] to some range [a,b].
Note that `log(0)` is not defined so is not directly supported.
For a list of convenient stretch functions, see:
`logstretch`, `powstretch`, `squarestretch`, `asinhstretch`, `sinhstretch`, `powerdiststretch`

Finally the data is mapped to RGB values according to `cmap`. If cmap is `nothing`,
grayscale is used. ColorSchemes.jl defines hundreds of colormaps. A few nice ones for
images include: `:viridis`, `:magma`, `:plasma`, `:thermal`, and `:turbo`.

Crucially, this function returns a view over the underlying data. If `img` is updated
then those changes will be reflected by this view with the exception of `clims` which
is not recalculated.

Note: if clims or stretch is a function, the pixel values passed in are first filtered
to remove non-finite or missing values.

### Defaults
The default values of `clims`, `stretch`, and `cmap` are `extrema`, `identity`, and `nothing`
respectively.
You may alter these defaults using `AstroImages.set_clims!`,  `AstroImages.set_stretch!`, and
`AstroImages.set_cmap!`.

### Automatic Display
Arrays wrapped by `AstroImageMat()` get displayed as images automatically by calling
`imview` on them with the default settings when using displays that support showing PNG images.

### Missing data
Pixels that are `NaN` or `missing` will be displayed as transparent when `cmap` is set
or black if.
+/- Inf will be displayed as black or white respectively.

### Exporting Images
The view returned by `imview` can be saved using general `FileIO.save` methods.
Example:
```julia
v = imview(data, cmap=:magma, stretch=asinhstretch, clims=percent(95))
save("output.png", v)
```
"""
function imview(
    img::AbstractArray{T};
    clims=_default_clims[],
    stretch=_default_stretch[],
    cmap=_default_cmap[],
    contrast=1.0,
    bias=0.5
) where {T}

    # Create flipped view of to match conventions of other programs.
    # Origin is centre of pixel (1,1) at bottom left.
    if ndims(img) == 2
        imgT = view(
            permutedims(img,(2,1)),
            reverse(axes(img,2)),
            :,
        )
    elseif ndims(img) >= 3
        newdims = (2,1, 3:ndims(img)...)
        ds = Tuple(((:) for _ in 2:ndims(img)))
        imgT = view(
            permutedims(img,newdims),
            reverse(axes(img,2)),
            ds...,
        )
    else
        imgT = img
    end
    isempt = isempty(imgT)
    if isempt
        @warn "imview called with empty argument"
        return fill(RGBA{N0f8}(0,0,0,0), 1,1)
    end
    # Users will occaisionally pass in data that is 0D, filled with NaN, or filled with missing.
    # We still need to do something reasonable in those caes.
    nonempty = any(x-> !ismissing(x) && isfinite(x), imgT)
    if !nonempty
        @warn "imview called with all missing or non-finite values"
        return map(px->RGBA{N0f8}(0,0,0,0), imgT)
    end

    imgmin, imgmax = _resolve_clims(imgT, clims)
    normed = clampednormedview(imgT, (imgmin, imgmax))
    return _imview(imgT, normed, stretch, _lookup_cmap(cmap), contrast, bias)
end

# Unwrap AstroImages before view, then rebuild.
# We have to permute the dimensions of the image to get the origin at the bottom left.
# But we don't want this to affect the dimensions of the array.
# Also, this reduces the number of methods we need to compile for imview by standardizing types
# earlier on. The compiled code for showing an array is the same as an array wrapped by an
# AstroImage, except for one unwrapping step.
function imview(
    img::AstroImage;
    kwargs...
)
    return shareheader(img, imview(parent(img); kwargs...))
end

# Special handling for complex images
"""
    imview(img::AbstractArray{<:Complex}; ...)

When applied to an image with complex values, display the magnitude
of the pixels using `imview` and display the phase angle as a panel below
using a cyclical color map.
For more customatization, you can create a view like this yourself:
```julia
vcat(
    imview(abs.(img)),
    imview(angle.(img)),
)
```
"""
function imview(img::AbstractArray{T}; kwargs...) where {T<:Complex}
    mag_view = imview(abs.(img); kwargs...)
    angle_view = imview(angle.(img), clims=(-pi, pi), stretch=identity, cmap=:cyclic_mygbm_30_95_c78_n256_s25)
    vcat(mag_view,angle_view)
end

function _imview(img, normed::AbstractArray{T}, stretch, cmap, contrast, bias) where T

    function colormap(pixr, pixn)::RGBA{N0f8}
        if ismissing(pixr) || !isfinite(pixr) || ismissing(pixn) || !isfinite(pixn)
            # We check pixr in addition to pixn because we want to preserve if the pixels
            # are +-Inf
            stretched = pixr
        else
            stretched = (stretch(pixn) - bias)*contrast+0.5
        end

        # We treat NaN/missing values as transparent
        pix= if ismissing(stretched) || isnan(stretched)
            RGBA{N0f8}(0,0,0,0)
        # We treat Inf values as white / -Inf as black
        elseif isinf(stretched)
            if stretched > 0
                RGBA{N0f8}(1,1,1,1)
            else
                RGBA{N0f8}(0,0,0,1)
            end
        else
            if isnothing(cmap)
                # true/false used as numerical values to prevent unucessary promotion
                s = clamp(stretched, false, true)
                RGBA{N0f8}(s,s,s,1)
            else
                # Look up colormap
                RGBA{N0f8}(get(cmap, stretched, (false, true)))
            end
        end
        return pix
    end
    mapper = mappedarray(colormap, img, normed)

    return maybe_copyheader(img, mapper)
end

"""
    imview_colorbar(img; orientation=:vertical)
Create a colorbar for a given image matching how it is displayed by
`imview`. Returns an image.

`orientation` can be `:vertical` or `:horizontal`.
"""
function imview_colorbar(
    img::AbstractArray;
    orientation=:vertical,
    clims=_default_clims[],
    stretch=_default_stretch[],
    cmap=_default_cmap[],
    contrast=1,
    bias=0.5
)
    imgmin, imgmax = _resolve_clims(img, clims)
    cbpixlen = 100
    data = repeat(range(imgmin, imgmax, length=cbpixlen), 1,10)
    if orientation == :vertical
        data = data'
    elseif orientation == :horizontal
        data = data
    else
        error("Unsupported orientation for colorbar \"$orientation\"")
    end

    # # Stretch the colors:
    # # Construct the image to use as a colorbar
    # cbimg =  imview(data; clims=(imgmin,imgmax), stretch, cmap, k_min=3)
    # # And the colorbar tick locations & labels
    # ticks, cbmin, cbmax = optimize_ticks(imgmin, imgmax)
    # # Now map these to pixel locations through streching and colorlimits:
    # stretchmin = stretch(zero(eltype(data)))
    # stretchmax = stretch(one(eltype(data)))
    # normedticks = clampednormedview(ticks, (imgmin, imgmax))
    # ticksloc = map(ticks,normedticks) do tick, tickn
    #     return cbpixlen * tickn
    # end

    # Strech the ticks
    # Construct the image to use as a colorbar
    cbimg = imview(data; clims=(imgmin,imgmax), stretch=identity, cmap, contrast, bias)
    # And the colorbar tick locations & labels
    ticks, _, _ = optimize_ticks(Float64(imgmin), Float64(imgmax), k_min=3)
    # Now map these to pixel locations through streching and colorlimits:
    stretchmin = stretch(zero(eltype(data)))
    stretchmax = stretch(oneunit(eltype(data)))
    normedticks = clampednormedview(ticks, (imgmin, imgmax))
    ticksloc = map(normedticks) do tickn
        stretched = stretch(tickn)
        stretchednormed = (stretched - stretchmin) * (stretchmax - stretchmin)
        return cbpixlen * stretchednormed
    end
    ticklabels = map(ticks) do t
        @sprintf("%4g", t)
    end
    return cbimg, (ticksloc, ticklabels)
end


struct GosperIterator
    n::Int64
    k::Int64
end

"""
    gosper(n, k)

Returns iterator over numbers whose binary representation correspond to the
indicator vector of a subset of {1,...,n} of size k.

Example:
   collect(gosper(3,2)) == [3, 5, 6]
"""
function gosper(n::Int, k::Int)
    @assert 0 < k <= n <= 62
    return GosperIterator(n, k)
end

const GosperState = Tuple{Int64, Int64}

eltype(it::GosperIterator) = Int64
length(it::GosperIterator) = binomial(it.n, it.k)

function start(it::GosperIterator)
    (2^(it.k) - 1, 2^(it.n) -1)
end

function iterate(it::GosperIterator, state::GosperState = start(it))

    if state[1] > state[2]
        return nothing
    end

        # Gosper's hack: Finds the next smallest number with exactly k bits
    # set to 1 in its binary representation.
    o = state[1]
    u = state[1] & -state[1]
    v = u + state[1]
    y = v + (div(xor(v, state[1]), u) >> 2)
    return o, (y, state[2])
end

struct BinaryIndexIterator
    d::Int64#  dimension
    indices::Array{Int64}# indices of 1's
    init_value::Int64# value that the iterator add the subnumbers to it
end

"""
For some a in {0,1}^d, that have 1's in the places "indices", iterate over all substes
of indices in "indices" and return the number of it's binary representation
"""
function BinaryItr(d::Int64, indices::Array{Int64}, init_value::Int64 = 0)
    @assert 0 < d <= 62

    BinaryIndexIterator(d, indices, init_value)
end

"""
Now the indices of the 1's are the number itself with those indices
"""
function BinaryItr(d::Int64, indices::Int64, init_value::Int64 = 0)
    @assert 0 < d <= 62

    _, arr_idx = norm_1_with_indices(indices)

    BinaryIndexIterator(d, Array{Int64}(arr_idx), init_value)
end

const BinaryIndexState = Int64

eltype(it::BinaryIndexIterator) = Int64
length(it::BinaryIndexIterator) = 2^(length(it.indices))

function start(it::BinaryIndexIterator)
   0
end

function iterate(it::BinaryIndexIterator, state::BinaryIndexState = start(it))

    if state == length(it)
        return nothing
    end

    sub_binary_number = it.init_value

    temp_state = state

    for i=1:length(it.indices)
        if temp_state == 0
            break
        end
        if temp_state%2 == 1
            sub_binary_number += 1<<(it.indices[i]-1)
        end
        temp_state >>= 1
    end

    state += 1

    return sub_binary_number, state

end

"""
Calculate the norm1 of binary number (the number of 1's in the number).
return: -norm1(alpha)
        -array with the indices of the 1's
"""
function norm_1_with_indices(alpha::Int64)

    @assert alpha >= 0

    count = 0
    index = 1
    idx = []
    while alpha > 0
        if alpha % 2 == 1
            count += 1
            push!(idx, index)
        end
        alpha >>= 1
        index += 1
    end
    count, Array{Int64}(idx)
end

"""
Calculate the norm1 of binary number (the number of 1's in the number).
return: -norm1(alpha)
        -array with the indices of the 1's
"""
function norm_1(alpha::Int64)

    @assert alpha >= 0

    count = 0
    while alpha > 0
        if alpha % 2 == 1
            count += 1
        end
        alpha >>= 1
    end
    count
end


# Recombinations
# ==============
function average(population::Vector{T}) where {T <: Vector}
    obj = zeros(eltype(T), length(population[1]))
    l = length(population)
    for i in 1:l
        obj += population[i]
    end
    return obj./l
end

function marriage(population::Vector{T}) where {T <: Vector}
    s = length(population)
    l = length(population[1])
    obj = zeros(eltype(T), l)
    for i in 1:l
        obj[i] = population[rand(1:s)][i]
    end
    return obj
end

# Strategy recombinations
# =======================
function averageSigma1(ss::Vector{S}) where {S <: Strategy}
    s = copy(ss[1])
    σ = 0.0
    l = length(ss)
    for i in 1:l
        σ += ss[i][:σ]
    end
    s[:σ] = σ/l
    return s
end

function averageSigmaN(ss::Vector{S}) where {S <: Strategy}
    s = copy(ss[1])
    σ = zeros(length(ss[1][:σ]))
    l = length(ss)
    for i in 1:l
        σ += ss[i][:σ]
    end
    s[:σ] = σ./l
    return s
end

# ==================
# Genetic algorithms
# ==================

# Binary crossovers
# -----------------

# Single point crossover
function singlepoint(v1::T, v2::T) where {T <: Vector}
    l = length(v1)
    c1 = copy(v1)
    c2 = copy(v2)
    pos = rand(1:l)
    for i in pos:l
        vswap!(c1, c2, i)
    end
    return c1, c2
end

# Two point crossover
function twopoint(v1::T, v2::T) where {T <: Vector}
    l = length(v1)
    c1 = copy(v1)
    c2 = copy(v2)
    from, to = rand(1:l, 2)
    from, to = from > to ? (to, from)  : (from, to)
    for i in from:to
        vswap!(c1, c2, i)
    end
    return c1, c2
end

# Uniform crossover
function uniform(v1::T, v2::T) where {T <: Vector}
    l = length(v1)
    c1 = copy(v1)
    c2 = copy(v2)
    xch = rand(Bool, l)
    for i in 1:l
        if xch[i]
            vswap!(c1, c2, i)
        end
    end
    return c1, c2
end

# Real valued crossovers
# ----------------------

function discrete(v1::T, v2::T) where {T <: Vector}
    l = length(v1)
    c1 = similar(v1)
    c2 = similar(v2)
    sltc = rand(Bool, 2, l)
    for i in 1:l
        c1[i] = sltc[1,i] ? v1[i] : v2[i]
        c2[i] = sltc[2,i] ? v2[i] : v1[i]
    end
    return c1, c2
end

# Weighted arithmetic mean
function waverage(w::Vector{Float64})
    function wavexvr(v1::T, v2::T) where {T <: Vector}
        c1 = (v1+v2)./w
        return c1, copy(c1)
    end
    return wavexvr
end

# Intermediate recombination
function intermediate(d::Float64 = 0.0)
    function intermxvr(v1::T, v2::T) where {T <: Vector}
        l = length(v1)
        α = (1.0+2d) * rand(l) .- d
        c1 = v2 .+ α .* (v1 - v2)
        α = (1.0+2d) * rand(l) .- d
        c2 = v1 .+ α .* (v2 - v1)
        return c1, c2
    end
    return intermxvr
end

# Line recombination
function line(d::Float64 = 0.0)
    function linexvr(v1::T, v2::T) where {T <: Vector}
        α1, α2 = (1.0+2d) * rand(2) .- d
        c1 = v2 .+ α2 * (v1 - v2)
        c2 = v1 .+ α1 * (v2 - v1)
        return c1, c2
    end
    return linexvr
end

# Permutation crossovers
# ----------------------

# Partially mapped crossover
function pmx(v1::T, v2::T) where {T <: Vector}
    s = length(v1)
    from, to = rand(1:s, 2)
    from, to = from > to ? (to, from)  : (from, to)
    c1 = similar(v1)
    c2 = similar(v2)

    # Swap
    c1[from:to] = v2[from:to]
    c2[from:to] = v1[from:to]

    # Fill in from parents
    for i in vcat(1:from-1, to+1:s)
        # Check conflicting offspring
        in1 = inmap(v1[i], c1, from, to)
        if in1 == 0
            c1[i] = v1[i]
        else
            tmpin = in1
            while tmpin > 0
                tmpin = inmap(c2[in1], c1, from, to)
                in1 = tmpin > 0 ? tmpin : in1
            end
            c1[i] = v1[in1]
        end

        in2 = inmap(v2[i], c2, from, to)
        if in2 == 0
            c2[i] = v2[i]
        else
            tmpin = in2
            while tmpin > 0
                tmpin = inmap(c1[in2], c2, from, to)
                in2 = tmpin > 0 ? tmpin : in2
            end
            c2[i] = v2[in2]
        end
    end
    return c1, c2
end

# Order crossover
function ox1(v1::T, v2::T) where {T <: Vector}
    s = length(v1)
    from, to = rand(1:s, 2)
    from, to = from > to ? (to, from)  : (from, to)
    c1 = zeros(v1)
    c2 = zeros(v2)
    # Swap
    c1[from:to] = v2[from:to]
    c2[from:to] = v1[from:to]
    # Fill in from parents
    k = to+1 > s ? 1 : to+1 #child1 index
    j = to+1 > s ? 1 : to+1 #child2 index
    for i in vcat(to+1:s,1:from-1)
        while in(v1[k],c1)
            k = k+1 > s ? 1 : k+1
        end
        c1[i] = v1[k]
        while in(v2[j],c2)
            j = j+1 > s ? 1 : j+1
        end
        c2[i] = v2[j]
    end
    return c1, c2
end

# Cycle crossover
function cx(v1::T, v2::T) where {T <: Vector}
    s = length(v1)
    c1 = zeros(v1)
    c2 = zeros(v2)

    f1 = true #switch
    k = 1
    while k > 0
        idx = k
        if f1
            #cycle from v1
            while c1[idx] == zero(T)
            c1[idx] = v1[idx]
            c2[idx] = v2[idx]
            idx = inmap(v2[idx],v1,1,s)
            end
        else
            #cycle from v2
            while c2[idx] == zero(T)
            c1[idx] = v2[idx]
            c2[idx] = v1[idx]
            idx = inmap(v1[idx],v2,1,s)
            end
        end
        f1 $= true
        k = inmap(zero(T),c2,1,s)
    end
    return c1,c2
end

# Order-based crossover
function ox2(v1::T, v2::T) where {T <: Vector}
    s = length(v1)
    c1 = copy(v1)
    c2 = copy(v2)

    for i in 1:s
        if rand(Bool)
            idx1 = inmap(v2[i],v1,1,s)
            idx2 = inmap(v1[i],v2,1,s)
            c1[idx1] = zero(T)
            c2[idx2] = zero(T)
        end
    end

    for i in 1:s
        if !in(v2[i],c1)
            tmpin = inmap(zero(T),c1,1,s)
            c1[tmpin] = v2[i]
        end
        if !in(v1[i],c2)
            tmpin = inmap(zero(T),c2,1,s)
            c2[tmpin] = v1[i]
        end
    end
    return c1,c2
end

# Position-based crossover
function pos(v1::T, v2::T) where {T <: Vector}
    s = length(v1)
    c1 = zeros(v1)
    c2 = zeros(v2)

    for i in 1:s
        if rand(Bool)
            c1[i] = v2[i]
            c2[i] = v1[i]
        end
    end

    for i in 1:s
        if !in(v1[i],c1)
            tmpin = inmap(zero(T),c1,1,s)
            c1[tmpin] = v1[i]
        end
        if !in(v2[i],c2)
            tmpin = inmap(zero(T),c2,1,s)
            c2[tmpin] = v2[i]
        end
    end
    return c1,c2
end

# Utils
# =====
function vswap!(v1::T, v2::T, idx::Int) where {T <: Vector}
    val = v1[idx]
    v1[idx] = v2[idx]
    v2[idx] = val
end

function inmap(v::T, c::Vector{T}, from::Int, to::Int) where{T}
    exists = 0
    for j in from:to
        if exists == 0 && v == c[j]
            exists = j
        end
    end
    return exists
end


import Base: *

"""
    *(A::AbstractOperator, ex::AbstractExpression)

Multiply an 'AbstractExpression` by an `AbstractOperator`.

# Example

```julia
julia> A = FiniteDiff(Float64, (10,))
δx  ℝ^10 -> ℝ^9

julia> x = Variable(10);

julia> ex = A*x;

julia> B = DCT(9)
ℱc  ℝ^9 -> ℝ^9

julia> ex2 = B*ex;

julia> affine(ex2)
ℱc*δx  ℝ^10 -> ℝ^9
```

"""
function (*)(L::AbstractOperator, a::AbstractExpression)
  A = convert(Expression,a)
  Expression{length(A.x)}(A.x,L*affine(A))
end

"""
    *(A::AbstractMatrix, ex::AbstractExpression)

Multiply an `AbstractExpression` by an `AbstractMatrix`.

```julia
julia> A = randn(10,5);

julia> x = Variable(5)
Variable(Float64, (5,))

julia> A*x

```

Other types of multiplications are also possible:

* left array multiplication
```julia
julia> X = Variable(10,5)
Variable(Float64, (10, 5))

julia> X*randn(5,6)

```

* scalar multiplication:
```julia
julia> π*X

```

* elementwise multiplication:
```julia
julia> randn(10,5).*X

```

"""
function (*)(m::T, a::Union{AbstractVector,AbstractMatrix}) where {T<:AbstractExpression}
  M = convert(Expression,m)
  op = LMatrixOp(codomainType(affine(M)),size(affine(M),1),a)
  return op*M
end
#LMatrixOp

function (*)(M::AbstractMatrix, a::T) where {T<:AbstractExpression}
  A = convert(Expression,a)
  op = MatrixOp(codomainType(affine(A)),size(affine(A),1),M)
  return op*A
end
#MatrixOp

function Broadcast.broadcasted(::typeof(*), d::D, a::T) where {D <: Union{Number,AbstractArray}, T<:AbstractExpression}
  A = convert(Expression,a)
  op = DiagOp(codomainType(affine(A)),size(affine(A),1),d)
  return op*A
end
Broadcast.broadcasted(::typeof(*), a::T, d::D) where {D <: Union{Number,AbstractArray}, T<:AbstractExpression} =
d.*a
#DiagOp

function (*)(coeff::T1, a::T) where {T1<:Number, T<:AbstractExpression}
  A = convert(Expression,a)
  return Expression{length(A.x)}(A.x,coeff*affine(A))
end
(*)(a::T, coeff::T1) where {T1<:Number, T<:AbstractExpression} = coeff*a
##Scale

"""
    *(A::AbstractExpression, ex::AbstractExpression)

Multiply an `AbstractExpression` by another `AbstractExpression`.

# Examples

```julia
julia> W1 = Variable(10,5)
Variable(Float64, (10, 5))

julia> W2 = Variable(5,15)
Variable(Float64, (5, 15))

julia> ex = W1*σ(W2);

julia> affine(ex)
I*σ  ℝ^(10, 5)  ℝ^(5, 15) -> ℝ^(10, 15)

```

`.*(A::AbstractExpression, ex::AbstractExpression)`

Elementwise multiplication between `AbstractExpression` (i.e. Hadamard product).

"""
function (*)(ex1::AbstractExpression, ex2::AbstractExpression)
  ex1 = convert(Expression,ex1)
  ex2 = convert(Expression,ex2)
  x = extract_variables((ex1,ex2))
  A = extract_affines(x, ex1)
  B = extract_affines(x, ex2)
  op = Ax_mul_Bx(A,B)
  exp3 = Expression{length(x)}(x,op)
  return exp3
end
# Ax_mul_Bx

function (*)(ex1::AdjointExpression, ex2::AbstractExpression)
  ex1 = ex1.ex
  ex2 = convert(Expression,ex2)
  x = extract_variables((ex1,ex2))
  A = extract_affines(x, ex1)
  B = extract_affines(x, ex2)
  op = Axt_mul_Bx(A,B)
  exp3 = Expression{length(x)}(x,op)
  return exp3
end
# Axt_mul_Bx

function (*)(ex1::AbstractExpression, ex2::AdjointExpression)
  ex1 = convert(Expression,ex1)
  ex2 = ex2.ex
  x = extract_variables((ex1,ex2))
  A = extract_affines(x, ex1)
  B = extract_affines(x, ex2)
  op = Ax_mul_Bxt(A,B)
  exp3 = Expression{length(x)}(x,op)
  return exp3
end
# Ax_mul_Bxt

function Broadcast.broadcasted(::typeof(*), ex1::AbstractExpression, ex2::AbstractExpression)
  ex1 = convert(Expression,ex1)
  ex2 = convert(Expression,ex2)
  x = extract_variables((ex1,ex2))
  A = extract_affines(x, ex1)
  B = extract_affines(x, ex2)
  op = HadamardProd(A,B)
  exp3 = Expression{length(x)}(x,op)
  return exp3
end
# Hadamard


using PtFEM

data = Dict(
  # GenericSolid(ndim, nst, nels, nn, nip, finite_element(nod, nodof), axisymmentrix)
  :struc_el => GenericSolid(3, 6, 7, 40, 27, Hexahedron(14, 3), false),
  :properties => [1.0 0.49 0.0;],
  :g_coord => [
    0.0000  0.0000  0.0000;     0.0000  1.0000  0.0000;
    0.0000  0.0000 -1.0000;     0.0000  1.0000 -1.0000;
    1.0000  0.0000  0.0000;     1.0000  1.0000  0.0000;
    1.0000  0.0000 -1.0000;     1.0000  1.0000 -1.0000;
    0.1650  0.2980 -0.2550;     0.2720  0.7700 -0.2500;
    0.3200  0.3570 -0.8140;     0.2490  0.8080 -0.6580;
    0.7880  0.3560 -0.3070;     0.8500  0.7370 -0.3510;
    0.6770  0.3170 -0.6950;     0.8260  0.7120 -0.7120;
    0.0000  0.5000 -0.5000;     0.5000  0.5000  0.0000;
    0.5000  0.0000 -0.5000;     0.5000  1.0000 -0.5000;
    0.5000  0.5000 -1.0000;     1.0000  0.5000 -0.5000;
    0.2515  0.5582 -0.4942;     0.5188  0.5403 -0.2908;
    0.4875  0.3320 -0.5177;     0.5493  0.7568 -0.4928;
    0.5180  0.5485 -0.7197;     0.7853  0.5305 -0.5162;
    0.1093  0.5170 -0.1263;     0.5305  0.8767 -0.1503;
    0.9095  0.5232 -0.1645;     0.4882  0.1635 -0.1405;
    0.1213  0.1637 -0.5173;     0.1303  0.8945 -0.4770;
    0.9190  0.8623 -0.5158;     0.8663  0.1682 -0.5005;
    0.1423  0.5412 -0.8680;     0.5187  0.8800 -0.8425;
    0.8758  0.5072 -0.8518;     0.4992  0.1685 -0.8773
  ],
  :g_num => [
    11,  9, 13, 15, 25, 23, 24, 28, 27, 12, 10, 14, 16, 26,
     3,  1,  9, 11, 33, 17, 29, 23, 37,  4,  2, 10, 12, 34,
     9,  1,  5, 13, 32, 29, 18, 31, 24, 10,  2,  6, 14, 30,
    15, 13,  5,  7, 36, 28, 31, 22, 39, 16, 14,  6,  8, 35,
     3, 11, 15,  7, 40, 37, 27, 39, 21,  4, 12, 16,  8, 38,
    12, 10, 14, 16, 26, 34, 30, 35, 38,  4,  2,  6,  8, 20,
     3,  1,  5,  7, 19, 33, 32, 36, 40, 11,  9, 13, 15, 25
  ],
  :support => [
    (1, [0 1 1]), (2, [0 0 1]), ( 3, [0 1 0]), ( 4, [0 0 0]), ( 6, [1 0 1]),
    (7, [1 1 0]), (8, [1 0 0]), (17, [0 1 1]), (20, [1 0 1]), (21, [1 1 0])
  ],
  :fixed_freedoms => [
    ( 1, 2, 0.01),
    ( 3, 2, 0.01),
    ( 5, 2, 0.01),
    ( 7, 2, 0.01),
    (19, 2, 0.01)
  ]
)

data |> display
println()

@time m, dis_df, fm_df = p54(data)
println()

dis_df |> display
println()

println("\nThe integration point (nip = $(data[:struc_el].nip)) stresses are:\n")

fm_df |> display
println()


module QuickHullTests

using QuickHull
using Base.Test
using FixedSizeArrays

# test data
getpoints(N) = map(Vec, rand(N), rand(N))
getpoints(xs,ys) = map(Vec, xs, ys)
getxys(N::Int) = rand(N), rand(N)
getxys{T<:Vec{2}}(points::Vector{T}) = map(p->p[1],points), map(p->p[2],points)

function test_property(property)
    points = getpoints(50)
    hull = qhull(points)

    property(points, hull)

    xys = getxys(50)
    xyhull = qhull(xys...)

    property(getpoints(xys...), getpoints(xyhull...))

    nothing
end

# property: all points in hull are unique
test_property() do points, hull
    @test length(hull) == length(unique(hull))
end

# property: no point is to the right of any line through adjacent hull members
#           this property assumes counter-clockwise orientation of the hull,
#           and tests also for the pair of last-first points
test_property() do points, hull
    for (a,b) in zip(hull[1:end], vcat(hull[2:end], hull[1]))
        for q in points
            # skip points in the hull
            (q == a || q == b) && continue
            # q is to the right if |(b-a)x(q-a)| < 0 with implied z-coordinate = 0
            @test (b[1]-a[1])*(q[2]-a[2])-(b[2]-a[2])*(q[1]-a[1]) > 0
        end
    end
end

end


"""
    Interpolation{dim, ref_shape, order}()

Return an `Interpolation` of given dimension `dim`, reference shape
(see see [`AbstractRefShape`](@ref)) `ref_shape` and order `order`.
`order` corresponds to the highest order term in the polynomial.
The interpolation is used to define shape functions to interpolate
a function between nodes.

The following interpolations are implemented:

* `Lagrange{1,RefCube,1}`
* `Lagrange{1,RefCube,2}`
* `Lagrange{2,RefCube,1}`
* `Lagrange{2,RefCube,2}`
* `Lagrange{2,RefTetrahedron,1}`
* `Lagrange{2,RefTetrahedron,2}`
* `Lagrange{3,RefCube,1}`
* `Serendipity{2,RefCube,2}`
* `Serendipity{3,RefCube,2}`
* `Lagrange{3,RefTetrahedron,1}`
* `Lagrange{3,RefTetrahedron,2}`

# Examples
```jldoctest
julia> ip = Lagrange{2,RefTetrahedron,2}()
Ferrite.Lagrange{2,Ferrite.RefTetrahedron,2}()

julia> getnbasefunctions(ip)
6
```
"""
abstract type Interpolation{dim,shape,order} end

Base.copy(ip::Interpolation) = ip

"""
Return the dimension of an `Interpolation`
"""
@inline getdim(ip::Interpolation{dim}) where {dim} = dim

"""
Return the reference shape of an `Interpolation`
"""
@inline getrefshape(ip::Interpolation{dim,shape}) where {dim,shape} = shape

"""
Return the polynomial order of the `Interpolation`
"""
@inline getorder(ip::Interpolation{dim,shape,order}) where {dim,shape,order} = order

"""
Compute the value of the shape functions at a point ξ for a given interpolation
"""
function value(ip::Interpolation{dim}, ξ::Vec{dim,T}) where {dim,T}
    [value(ip, i, ξ) for i in 1:getnbasefunctions(ip)]
end

"""
Compute the gradients of the shape functions at a point ξ for a given interpolation
"""
function derivative(ip::Interpolation{dim}, ξ::Vec{dim,T}) where {dim,T}
    [gradient(ξ -> value(ip, i, ξ), ξ) for i in 1:getnbasefunctions(ip)]
end

#####################
# Utility functions #
#####################

"""
Return the number of base functions for an [`Interpolation`](@ref) or `Values` object.
"""
getnbasefunctions

# struct that gathers all the information needed to distribute
# dofs for a given interpolation.
struct InterpolationInfo
    # TODO: Can be smaller than `Int` if that matters...
    nvertexdofs::Int
    nedgedofs::Int
    nfacedofs::Int
    ncelldofs::Int
    dim::Int
    InterpolationInfo(interpolation::Interpolation{dim}) where {dim} =
        new(nvertexdofs(interpolation), nedgedofs(interpolation),
            nfacedofs(interpolation),   ncelldofs(interpolation), dim)
end

# The following functions are used to distribute the dofs. Definitions:
#   vertexdof: dof on a "corner" of the reference shape
#   facedof: dof in the dim-1 dimension (line in 2D, surface in 3D)
#   edgedof: dof on a line between 2 vertices (i.e. "corners") (3D only)
#   celldof: dof that is local to the element

# Fallbacks for the interpolations which are used to distribute the dofs correctly
"""
Number of dofs per vertex
"""
nvertexdofs(::Interpolation) = 0
"""
Number of dofs per edge
"""
nedgedofs(::Interpolation)   = 0
"""
Number of dofs per face
"""
nfacedofs(::Interpolation)   = 0
"""
Total number of dofs in the interior
"""
ncelldofs(::Interpolation)   = 0

# Needed for distributing dofs on shells correctly (face in 2d is edge in 3d)
edges(ip::Interpolation{2}) = faces(ip)
nedgedofs(ip::Interpolation{2}) = nfacedofs(ip)

# Fallbacks for vertices
vertices(::Interpolation{2,RefCube}) = (1,2,3,4)
vertices(::Interpolation{3,RefCube}) = (1,2,3,4,5,6,7,8)
vertices(::Interpolation{2,RefTetrahedron}) = (1,2,3)
vertices(::Interpolation{3,RefTetrahedron}) = (1,2,3,4)

#########################
# DiscontinuousLagrange #
#########################
# TODO generalize to arbitrary basis positionings.
"""
Piecewise discontinous Lagrange basis via Gauss-Lobatto points.
"""
struct DiscontinuousLagrange{dim,shape,order} <: Interpolation{dim,shape,order} end

getlowerdim(::DiscontinuousLagrange{dim,shape,order}) where {dim,shape,order} = DiscontinuousLagrange{dim-1,shape,order}()
getlowerorder(::DiscontinuousLagrange{dim,shape,order}) where {dim,shape,order} = DiscontinuousLagrange{dim,shape,order-1}()

# TODO generalize properly
# ncelldofs(::DiscontinuousLagrange{dim,RefCube,order}) where {dim,order} = (order+1)^dim
# ncelldofs(::DiscontinuousLagrange{2,RefTetrahedron,order}) where {order} = (order+1)*(order+2)/2
# ncelldofs(::DiscontinuousLagrange{3,RefTetrahedron,order}) where {order} = (order+1)*(order+2)/2
getnbasefunctions(::DiscontinuousLagrange{dim,shape,order}) where {dim,shape,order} = getnbasefunctions(Lagrange{dim,shape,order}())
ncelldofs(::DiscontinuousLagrange{dim,shape,order}) where {dim,shape,order} = getnbasefunctions(DiscontinuousLagrange{dim,shape,order}())

getnbasefunctions(::DiscontinuousLagrange{dim,shape,0}) where {dim,shape} = 1
ncelldofs(::DiscontinuousLagrange{dim,shape,0}) where {dim,shape} = 1

faces(::DiscontinuousLagrange{dim,shape,order}) where {dim,shape,order} = ()

# Mirror the Lagrange element for now.
function reference_coordinates(ip::DiscontinuousLagrange{dim,shape,order}) where {dim,shape,order}
    return reference_coordinates(Lagrange{dim,shape,order}())
end
function value(ip::DiscontinuousLagrange{dim,shape,order}, i::Int, ξ::Vec{dim}) where {dim,shape,order}
    return value(Lagrange{dim, ref_type, order}())
end

# Excepting the L0 element.
function reference_coordinates(ip::DiscontinuousLagrange{dim,shape,0}) where {dim,shape}
    return [Vec{dim, Float64}(ntuple(x->0.0, dim))]
end
function value(ip::DiscontinuousLagrange{dim,shape,0}, i::Int, ξ::Vec{dim}) where {dim,shape}
    return 1.0
end

############
# Lagrange #
############
struct Lagrange{dim,shape,order} <: Interpolation{dim,shape,order} end

getlowerdim(::Lagrange{dim,shape,order}) where {dim,shape,order} = Lagrange{dim-1,shape,order}()
getlowerorder(::Lagrange{dim,shape,order}) where {dim,shape,order} = Lagrange{dim,shape,order-1}()
getlowerorder(::Lagrange{dim,shape,1}) where {dim,shape} = DiscontinuousLagrange{dim,shape,0}()

##################################
# Lagrange dim 1 RefCube order 1 #
##################################
getnbasefunctions(::Lagrange{1,RefCube,1}) = 2
nvertexdofs(::Lagrange{1,RefCube,1}) = 1

faces(::Lagrange{1,RefCube,1}) = ((1,), (2,))

function reference_coordinates(::Lagrange{1,RefCube,1})
    return [Vec{1, Float64}((-1.0,)),
            Vec{1, Float64}(( 1.0,))]
end

function value(ip::Lagrange{1,RefCube,1}, i::Int, ξ::Vec{1})
    ξ_x = ξ[1]
    i == 1 && return (1 - ξ_x) * 0.5
    i == 2 && return (1 + ξ_x) * 0.5
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

##################################
# Lagrange dim 1 RefCube order 2 #
##################################
getnbasefunctions(::Lagrange{1,RefCube,2}) = 3
nvertexdofs(::Lagrange{1,RefCube,2}) = 1
ncelldofs(::Lagrange{1,RefCube,2}) = 1

faces(::Lagrange{1,RefCube,2}) = ((1,), (2,))

function reference_coordinates(::Lagrange{1,RefCube,2})
    return [Vec{1, Float64}((-1.0,)),
            Vec{1, Float64}(( 1.0,)),
            Vec{1, Float64}(( 0.0,))]
end

function value(ip::Lagrange{1,RefCube,2}, i::Int, ξ::Vec{1})
    ξ_x = ξ[1]
    i == 1 && return ξ_x * (ξ_x - 1) * 0.5
    i == 2 && return ξ_x * (ξ_x + 1) * 0.5
    i == 3 && return 1 - ξ_x^2
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

##################################
# Lagrange dim 2 RefCube order 1 #
##################################
getnbasefunctions(::Lagrange{2,RefCube,1}) = 4
nvertexdofs(::Lagrange{2,RefCube,1}) = 1

faces(::Lagrange{2,RefCube,1}) = ((1,2), (2,3), (3,4), (4,1))

function reference_coordinates(::Lagrange{2,RefCube,1})
    return [Vec{2, Float64}((-1.0, -1.0)),
            Vec{2, Float64}(( 1.0, -1.0)),
            Vec{2, Float64}(( 1.0,  1.0,)),
            Vec{2, Float64}((-1.0,  1.0,))]
end

function value(ip::Lagrange{2,RefCube,1}, i::Int, ξ::Vec{2})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    i == 1 && return (1 - ξ_x) * (1 - ξ_y) * 0.25
    i == 2 && return (1 + ξ_x) * (1 - ξ_y) * 0.25
    i == 3 && return (1 + ξ_x) * (1 + ξ_y) * 0.25
    i == 4 && return (1 - ξ_x) * (1 + ξ_y) * 0.25
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

##################################
# Lagrange dim 2 RefCube order 2 #
##################################
getnbasefunctions(::Lagrange{2,RefCube,2}) = 9
nvertexdofs(::Lagrange{2,RefCube,2}) = 1
nfacedofs(::Lagrange{2,RefCube,2}) = 1
ncelldofs(::Lagrange{2,RefCube,2}) = 1

faces(::Lagrange{2,RefCube,2}) = ((1,2,5), (2,3,6), (3,4,7), (4,1,8))

function reference_coordinates(::Lagrange{2,RefCube,2})
    return [Vec{2, Float64}((-1.0, -1.0)),
            Vec{2, Float64}(( 1.0, -1.0)),
            Vec{2, Float64}(( 1.0,  1.0)),
            Vec{2, Float64}((-1.0,  1.0)),
            Vec{2, Float64}(( 0.0, -1.0)),
            Vec{2, Float64}(( 1.0,  0.0)),
            Vec{2, Float64}(( 0.0,  1.0)),
            Vec{2, Float64}((-1.0,  0.0)),
            Vec{2, Float64}(( 0.0,  0.0))]
end

function value(ip::Lagrange{2,RefCube,2}, i::Int, ξ::Vec{2})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    i == 1 && return (ξ_x^2 - ξ_x) * (ξ_y^2 - ξ_y) * 0.25
    i == 2 && return (ξ_x^2 + ξ_x) * (ξ_y^2 - ξ_y) * 0.25
    i == 3 && return (ξ_x^2 + ξ_x) * (ξ_y^2 + ξ_y) * 0.25
    i == 4 && return (ξ_x^2 - ξ_x) * (ξ_y^2 + ξ_y) * 0.25
    i == 5 && return (1 - ξ_x^2) * (ξ_y^2 - ξ_y) * 0.5
    i == 6 && return (ξ_x^2 + ξ_x) * (1 - ξ_y^2) * 0.5
    i == 7 && return (1 - ξ_x^2) * (ξ_y^2 + ξ_y) * 0.5
    i == 8 && return (ξ_x^2 - ξ_x) * (1 - ξ_y^2) * 0.5
    i == 9 && return (1 - ξ_x^2) * (1 - ξ_y^2)
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

#########################################
# Lagrange dim 2 RefTetrahedron order 1 #
#########################################
getnbasefunctions(::Lagrange{2,RefTetrahedron,1}) = 3
getlowerdim(::Lagrange{2, RefTetrahedron, order}) where {order} = Lagrange{1, RefCube, order}()
nvertexdofs(::Lagrange{2,RefTetrahedron,1}) = 1

vertices(::Lagrange{2,RefTetrahedron,1}) = (1,2,3)
faces(::Lagrange{2,RefTetrahedron,1}) = ((1,2), (2,3), (3,1))

function reference_coordinates(::Lagrange{2,RefTetrahedron,1})
    return [Vec{2, Float64}((1.0, 0.0)),
            Vec{2, Float64}((0.0, 1.0)),
            Vec{2, Float64}((0.0, 0.0))]
end

function value(ip::Lagrange{2,RefTetrahedron,1}, i::Int, ξ::Vec{2})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    i == 1 && return ξ_x
    i == 2 && return ξ_y
    i == 3 && return 1. - ξ_x - ξ_y
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

#########################################
# Lagrange dim 2 RefTetrahedron order 2 #
#########################################
getnbasefunctions(::Lagrange{2,RefTetrahedron,2}) = 6
nvertexdofs(::Lagrange{2,RefTetrahedron,2}) = 1
nfacedofs(::Lagrange{2,RefTetrahedron,2}) = 1

vertices(::Lagrange{2,RefTetrahedron,2}) = (1,2,3)
faces(::Lagrange{2,RefTetrahedron,2}) = ((1,2,4), (2,3,5), (3,1,6))

function reference_coordinates(::Lagrange{2,RefTetrahedron,2})
    return [Vec{2, Float64}((1.0, 0.0)),
            Vec{2, Float64}((0.0, 1.0)),
            Vec{2, Float64}((0.0, 0.0)),
            Vec{2, Float64}((0.5, 0.5)),
            Vec{2, Float64}((0.0, 0.5)),
            Vec{2, Float64}((0.5, 0.0))]
end

function value(ip::Lagrange{2,RefTetrahedron,2}, i::Int, ξ::Vec{2})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    γ = 1. - ξ_x - ξ_y
    i == 1 && return ξ_x * (2ξ_x - 1)
    i == 2 && return ξ_y * (2ξ_y - 1)
    i == 3 && return γ * (2γ - 1)
    i == 4 && return 4ξ_x * ξ_y
    i == 5 && return 4ξ_y * γ
    i == 6 && return 4ξ_x * γ
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

#########################################
# Lagrange dim 3 RefTetrahedron order 1 #
#########################################
getnbasefunctions(::Lagrange{3,RefTetrahedron,1}) = 4
nvertexdofs(::Lagrange{3,RefTetrahedron,1}) = 1

faces(::Lagrange{3,RefTetrahedron,1}) = ((1,2,3), (1,2,4), (2,3,4), (1,4,3))
edges(::Lagrange{3,RefTetrahedron,1}) = ((1,2), (2,3), (3,1), (1,4), (2,4), (3,4))

function reference_coordinates(::Lagrange{3,RefTetrahedron,1})
    return [Vec{3, Float64}((0.0, 0.0, 0.0)),
            Vec{3, Float64}((1.0, 0.0, 0.0)),
            Vec{3, Float64}((0.0, 1.0, 0.0)),
            Vec{3, Float64}((0.0, 0.0, 1.0))]
end

function value(ip::Lagrange{3,RefTetrahedron,1}, i::Int, ξ::Vec{3})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    ξ_z = ξ[3]
    i == 1 && return 1.0 - ξ_x - ξ_y - ξ_z
    i == 2 && return ξ_x
    i == 3 && return ξ_y
    i == 4 && return ξ_z
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

#########################################
# Lagrange dim 3 RefTetrahedron order 2 #
#########################################
getnbasefunctions(::Lagrange{3,RefTetrahedron,2}) = 10
nvertexdofs(::Lagrange{3,RefTetrahedron,2}) = 1
nedgedofs(::Lagrange{3,RefTetrahedron,2}) = 1

faces(::Lagrange{3,RefTetrahedron,2}) = ((1,2,3,5,6,7), (1,2,4,5,9,8), (2,3,4,6,10,9), (1,4,3,8,10,7))
edges(::Lagrange{3,RefTetrahedron,2}) = ((1,5,2), (2,6,3), (3,7,1), (1,8,4), (2,9,4), (3,10,4))

function reference_coordinates(::Lagrange{3,RefTetrahedron,2})
    return [Vec{3, Float64}((0.0, 0.0, 0.0)),
            Vec{3, Float64}((1.0, 0.0, 0.0)),
            Vec{3, Float64}((0.0, 1.0, 0.0)),
            Vec{3, Float64}((0.0, 0.0, 1.0)),
            Vec{3, Float64}((0.5, 0.0, 0.0)),
            Vec{3, Float64}((0.5, 0.5, 0.0)),
            Vec{3, Float64}((0.0, 0.5, 0.0)),
            Vec{3, Float64}((0.0, 0.0, 0.5)),
            Vec{3, Float64}((0.5, 0.0, 0.5)),
            Vec{3, Float64}((0.0, 0.5, 0.5))]
end

# http://www.colorado.edu/engineering/CAS/courses.d/AFEM.d/AFEM.Ch09.d/AFEM.Ch09.pdf
# http://www.colorado.edu/engineering/CAS/courses.d/AFEM.d/AFEM.Ch10.d/AFEM.Ch10.pdf
function value(ip::Lagrange{3,RefTetrahedron,2}, i::Int, ξ::Vec{3})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    ξ_z = ξ[3]
    i == 1  && return (-2 * ξ_x - 2 * ξ_y - 2 * ξ_z + 1) * (-ξ_x - ξ_y - ξ_z + 1)
    i == 2  && return ξ_x * (2 * ξ_x - 1)
    i == 3  && return ξ_y * (2 * ξ_y - 1)
    i == 4  && return ξ_z * (2 * ξ_z - 1)
    i == 5  && return ξ_x * (-4 * ξ_x - 4 * ξ_y - 4 * ξ_z + 4)
    i == 6  && return 4 * ξ_x * ξ_y
    i == 7  && return 4 * ξ_y * (-ξ_x - ξ_y - ξ_z + 1)
    i == 8  && return ξ_z * (-4 * ξ_x - 4 * ξ_y - 4 * ξ_z + 4)
    i == 9  && return 4 * ξ_x * ξ_z
    i == 10 && return 4 * ξ_y * ξ_z
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

##################################
# Lagrange dim 3 RefCube order 1 #
##################################
getnbasefunctions(::Lagrange{3,RefCube,1}) = 8
nvertexdofs(::Lagrange{3,RefCube,1}) = 1

faces(::Lagrange{3,RefCube,1}) = ((1,4,3,2), (1,2,6,5), (2,3,7,6), (3,4,8,7), (1,5,8,4), (5,6,7,8))
edges(::Lagrange{3,RefCube,1}) = ((1,2), (2,3), (3,4), (4,1), (1,5), (2,6), (3,7), (4,8), (5,6), (6,7), (7,8), (8,5))

function reference_coordinates(::Lagrange{3,RefCube,1})
    return [Vec{3, Float64}((-1.0, -1.0, -1.0)),
            Vec{3, Float64}(( 1.0, -1.0, -1.0)),
            Vec{3, Float64}(( 1.0,  1.0, -1.0)),
            Vec{3, Float64}((-1.0,  1.0, -1.0)),
            Vec{3, Float64}((-1.0, -1.0,  1.0)),
            Vec{3, Float64}(( 1.0, -1.0,  1.0)),
            Vec{3, Float64}(( 1.0,  1.0,  1.0)),
            Vec{3, Float64}((-1.0,  1.0,  1.0))]
end

function value(ip::Lagrange{3,RefCube,1}, i::Int, ξ::Vec{3})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    ξ_z = ξ[3]
    i == 1 && return 0.125(1 - ξ_x) * (1 - ξ_y) * (1 - ξ_z)
    i == 2 && return 0.125(1 + ξ_x) * (1 - ξ_y) * (1 - ξ_z)
    i == 3 && return 0.125(1 + ξ_x) * (1 + ξ_y) * (1 - ξ_z)
    i == 4 && return 0.125(1 - ξ_x) * (1 + ξ_y) * (1 - ξ_z)
    i == 5 && return 0.125(1 - ξ_x) * (1 - ξ_y) * (1 + ξ_z)
    i == 6 && return 0.125(1 + ξ_x) * (1 - ξ_y) * (1 + ξ_z)
    i == 7 && return 0.125(1 + ξ_x) * (1 + ξ_y) * (1 + ξ_z)
    i == 8 && return 0.125(1 - ξ_x) * (1 + ξ_y) * (1 + ξ_z)
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

##################################
# Lagrange dim 3 RefCube order 2 #
##################################
# Based on vtkTriQuadraticHexahedron (see https://kitware.github.io/vtk-examples/site/Cxx/GeometricObjects/IsoparametricCellsDemo/)
getnbasefunctions(::Lagrange{3,RefCube,2}) = 27
nvertexdofs(::Lagrange{3,RefCube,2}) = 1
nedgedofs(::Lagrange{3,RefCube,2}) = 1
nfacedofs(::Lagrange{3,RefCube,2}) = 1
ncelldofs(::Lagrange{3,RefCube,2}) = 1

faces(::Lagrange{3,RefCube,2}) = ((1,2,6,5, 9,18,13,17, 23), (2,3,7,6, 10,19,14,18, 22), (3,4,8,7, 11,20,15,19, 24), (1,5,8,4, 12,17,16,20, 21), (1,4,3,2, 9,10,11,12, 25), (5,6,7,8, 13,14,15,16, 26))
edges(::Lagrange{3,RefCube,2}) = ((1,2, 9), (2,3, 10), (3,4, 11), (4,1, 12), (1,5, 16), (2,6, 19), (3,7, 18), (4,8, 19), (5,6, 13), (6,7, 14), (7,8, 15), (8,5, 16))

function reference_coordinates(::Lagrange{3,RefCube,2})
           # vertex
    return [Vec{3, Float64}((-1.0, -1.0, -1.0)), #  0
            Vec{3, Float64}(( 1.0, -1.0, -1.0)), #  1
            Vec{3, Float64}(( 1.0,  1.0, -1.0)), #  2
            Vec{3, Float64}((-1.0,  1.0, -1.0)), #  3
            Vec{3, Float64}((-1.0, -1.0,  1.0)), #  4
            Vec{3, Float64}(( 1.0, -1.0,  1.0)), #  5
            Vec{3, Float64}(( 1.0,  1.0,  1.0)), #  6
            Vec{3, Float64}((-1.0,  1.0,  1.0)), #  7
            # edge
            Vec{3, Float64}(( 0.0, -1.0, -1.0)), #  8
            Vec{3, Float64}(( 1.0,  0.0, -1.0)),
            Vec{3, Float64}(( 0.0,  1.0, -1.0)),
            Vec{3, Float64}((-1.0,  0.0, -1.0)),
            Vec{3, Float64}(( 0.0, -1.0,  1.0)),
            Vec{3, Float64}(( 1.0,  0.0,  1.0)),
            Vec{3, Float64}(( 0.0,  1.0,  1.0)),
            Vec{3, Float64}((-1.0,  0.0,  1.0)),
            Vec{3, Float64}((-1.0, -1.0,  0.0)),
            Vec{3, Float64}(( 1.0, -1.0,  0.0)),
            Vec{3, Float64}(( 1.0,  1.0,  0.0)),
            Vec{3, Float64}((-1.0,  1.0,  0.0)), # 19
            # face
            Vec{3, Float64}(( 0.0, -1.0,  0.0)), # 20
            Vec{3, Float64}(( 1.0,  0.0,  0.0)),
            Vec{3, Float64}(( 0.0,  1.0,  0.0)),
            Vec{3, Float64}((-1.0,  0.0,  0.0)),
            Vec{3, Float64}(( 0.0,  0.0, -1.0)),
            Vec{3, Float64}(( 0.0,  0.0,  1.0)), # 25
            # interior
            Vec{3, Float64}((0.0, 0.0, 0.0)),    # 26
            ]
end

function value(ip::Lagrange{3,RefCube,2}, i::Int, ξ::Vec{3, T}) where {T}
    # Some local helpers.
    @inline φ₁(x::T) = -0.5*x*(1-x)
    @inline φ₂(x::T) = (1+x)*(1-x)
    @inline φ₃(x::T) = 0.5*x*(1+x)
    (ξ_x, ξ_y, ξ_z) = ξ
    # vertices
    i == 1 && return φ₁(ξ_x) * φ₁(ξ_y) * φ₁(ξ_z)
    i == 2 && return φ₃(ξ_x) * φ₁(ξ_y) * φ₁(ξ_z)
    i == 3 && return φ₃(ξ_x) * φ₃(ξ_y) * φ₁(ξ_z)
    i == 4 && return φ₁(ξ_x) * φ₃(ξ_y) * φ₁(ξ_z)
    i == 5 && return φ₁(ξ_x) * φ₁(ξ_y) * φ₃(ξ_z)
    i == 6 && return φ₃(ξ_x) * φ₁(ξ_y) * φ₃(ξ_z)
    i == 7 && return φ₃(ξ_x) * φ₃(ξ_y) * φ₃(ξ_z)
    i == 8 && return φ₁(ξ_x) * φ₃(ξ_y) * φ₃(ξ_z)
    # edges
    i ==  9 && return φ₂(ξ_x) * φ₁(ξ_y) * φ₁(ξ_z)
    i == 10 && return φ₃(ξ_x) * φ₂(ξ_y) * φ₁(ξ_z)
    i == 11 && return φ₂(ξ_x) * φ₃(ξ_y) * φ₁(ξ_z)
    i == 12 && return φ₁(ξ_x) * φ₂(ξ_y) * φ₁(ξ_z)
    i == 13 && return φ₂(ξ_x) * φ₁(ξ_y) * φ₃(ξ_z)
    i == 14 && return φ₃(ξ_x) * φ₂(ξ_y) * φ₃(ξ_z)
    i == 15 && return φ₂(ξ_x) * φ₃(ξ_y) * φ₃(ξ_z)
    i == 16 && return φ₁(ξ_x) * φ₂(ξ_y) * φ₃(ξ_z)
    i == 17 && return φ₁(ξ_x) * φ₁(ξ_y) * φ₂(ξ_z)
    i == 18 && return φ₃(ξ_x) * φ₁(ξ_y) * φ₂(ξ_z)
    i == 19 && return φ₃(ξ_x) * φ₃(ξ_y) * φ₂(ξ_z)
    i == 20 && return φ₁(ξ_x) * φ₃(ξ_y) * φ₂(ξ_z)
    # faces
    i == 21 && return φ₂(ξ_x) * φ₁(ξ_y) * φ₂(ξ_z)
    i == 22 && return φ₃(ξ_x) * φ₂(ξ_y) * φ₂(ξ_z)
    i == 23 && return φ₂(ξ_x) * φ₃(ξ_y) * φ₂(ξ_z)
    i == 24 && return φ₁(ξ_x) * φ₂(ξ_y) * φ₂(ξ_z)
    i == 25 && return φ₂(ξ_x) * φ₂(ξ_y) * φ₁(ξ_z)
    i == 26 && return φ₂(ξ_x) * φ₂(ξ_y) * φ₃(ξ_z)
    # interior
    i == 27 && return φ₂(ξ_x) * φ₂(ξ_y) * φ₂(ξ_z)
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

###################
# Bubble elements #
###################
"""
Lagrange element with bubble stabilization.
"""
struct BubbleEnrichedLagrange{dim,ref_shape,order} <: Interpolation{dim,ref_shape,order} end
getlowerdim(ip::BubbleEnrichedLagrange{dim,ref_shape,order}) where {dim,ref_shape,order} = Lagrange{dim-1,ref_shape,order}()

################################################
# Lagrange-Bubble dim 2 RefTetrahedron order 1 #
################################################
# Taken from https://defelement.com/elements/bubble-enriched-lagrange.html
getnbasefunctions(::BubbleEnrichedLagrange{2,RefTetrahedron,1}) where {dim, ref_shape} = 4
nvertexdofs(::BubbleEnrichedLagrange{2,RefTetrahedron,1}) where {dim, ref_shape} = 1
ncelldofs(::BubbleEnrichedLagrange{2,RefTetrahedron,1}) where {dim, ref_shape} = 1

faces(::BubbleEnrichedLagrange{2,RefTetrahedron,1}) = ((1,2), (2,3), (3,1))

function reference_coordinates(::BubbleEnrichedLagrange{2,RefTetrahedron,1})
    return [Vec{2, Float64}((1.0, 0.0)),
            Vec{2, Float64}((0.0, 1.0)),
            Vec{2, Float64}((0.0, 0.0)),
            Vec{2, Float64}((1/3, 1/3)),]
end

function value(ip::BubbleEnrichedLagrange{2,RefTetrahedron,1}, i::Int, ξ::Vec{2})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    i == 1 && return ξ_x*(9ξ_y^2 + 9ξ_x*ξ_y - 9ξ_y + 1)
    i == 2 && return ξ_y*(9ξ_x^2 + 9ξ_x*ξ_y - 9ξ_x + 1)
    i == 3 && return 9ξ_x^2*ξ_y + 9ξ_x*ξ_y^2 - 9ξ_x*ξ_y - ξ_x - ξ_y + 1
    i == 4 && return 27ξ_x*ξ_y*(1 - ξ_x - ξ_y)
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

###############
# Serendipity #
###############
struct Serendipity{dim,shape,order} <: Interpolation{dim,shape,order} end

#####################################
# Serendipity dim 2 RefCube order 2 #
#####################################
getnbasefunctions(::Serendipity{2,RefCube,2}) = 8
getlowerdim(::Serendipity{2,RefCube,2}) = Lagrange{1,RefCube,2}()
getlowerorder(::Serendipity{2,RefCube,2}) = Lagrange{2,RefCube,1}()
nvertexdofs(::Serendipity{2,RefCube,2}) = 1
nfacedofs(::Serendipity{2,RefCube,2}) = 1

faces(::Serendipity{2,RefCube,2}) = ((1,2,5), (2,3,6), (3,4,7), (4,1,8))

function reference_coordinates(::Serendipity{2,RefCube,2})
    return [Vec{2, Float64}((-1.0, -1.0)),
            Vec{2, Float64}(( 1.0, -1.0)),
            Vec{2, Float64}(( 1.0,  1.0)),
            Vec{2, Float64}((-1.0,  1.0)),
            Vec{2, Float64}(( 0.0, -1.0)),
            Vec{2, Float64}(( 1.0,  0.0)),
            Vec{2, Float64}(( 0.0,  1.0)),
            Vec{2, Float64}((-1.0,  0.0))]
end

function value(ip::Serendipity{2,RefCube,2}, i::Int, ξ::Vec{2})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    i == 1 && return (1 - ξ_x) * (1 - ξ_y) * 0.25(-ξ_x - ξ_y - 1)
    i == 2 && return (1 + ξ_x) * (1 - ξ_y) * 0.25( ξ_x - ξ_y - 1)
    i == 3 && return (1 + ξ_x) * (1 + ξ_y) * 0.25( ξ_x + ξ_y - 1)
    i == 4 && return (1 - ξ_x) * (1 + ξ_y) * 0.25(-ξ_x + ξ_y - 1)
    i == 5 && return 0.5(1 - ξ_x * ξ_x) * (1 - ξ_y)
    i == 6 && return 0.5(1 + ξ_x) * (1 - ξ_y * ξ_y)
    i == 7 && return 0.5(1 - ξ_x * ξ_x) * (1 + ξ_y)
    i == 8 && return 0.5(1 - ξ_x) * (1 - ξ_y * ξ_y)
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

#####################################
# Serendipity dim 3 RefCube order 2 #
#####################################
getnbasefunctions(::Serendipity{3,RefCube,2}) = 20
getlowerdim(::Serendipity{3,RefCube,2}) = Serendipity{2,RefCube,2}()
getlowerorder(::Serendipity{3,RefCube,2}) = Lagrange{3,RefCube,1}()
nvertexdofs(::Serendipity{3,RefCube,2}) = 1
nedgedofs(::Serendipity{3,RefCube,2}) = 1

faces(::Serendipity{3,RefCube,2}) = ((1,4,3,2,12,11,10,9), (1,2,6,5,9,18,13,17), (2,3,7,6,10,19,14,18), (3,4,8,7,11,20,15,19), (1,5,8,4,17,16,20,12), (5,6,7,8,13,14,15,16))

function reference_coordinates(::Serendipity{3,RefCube,2})
    return [Vec{3, Float64}((-1.0, -1.0, -1.0)),
            Vec{3, Float64}(( 1.0, -1.0, -1.0)),
            Vec{3, Float64}(( 1.0,  1.0, -1.0)),
            Vec{3, Float64}((-1.0,  1.0, -1.0)),
            Vec{3, Float64}((-1.0, -1.0,  1.0)),
            Vec{3, Float64}(( 1.0, -1.0,  1.0)),
            Vec{3, Float64}(( 1.0,  1.0,  1.0)),
            Vec{3, Float64}((-1.0,  1.0,  1.0)),
            Vec{3, Float64}((0.0, -1.0, -1.0)),
            Vec{3, Float64}((1.0, 0.0, -1.0)),
            Vec{3, Float64}((0.0, 1.0, -1.0)),
            Vec{3, Float64}((-1.0, 0.0, -1.0)),
            Vec{3, Float64}((0.0, -1.0, 1.0)),
            Vec{3, Float64}((1.0, 0.0, 1.0)),
            Vec{3, Float64}((0.0, 1.0, 1.0)),
            Vec{3, Float64}((-1.0, 0.0, 1.0)),
            Vec{3, Float64}((-1.0, -1.0, 0.0)),
            Vec{3, Float64}((1.0, -1.0, 0.0)),
            Vec{3, Float64}((1.0, 1.0, 0.0)),
            Vec{3, Float64}((-1.0, 1.0, 0.0)),]
end

function value(ip::Serendipity{3,RefCube,2}, i::Int, ξ::Vec{3})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    ξ_z = ξ[3]
    i == 1 && return 0.125(1 - ξ_x) * (1 - ξ_y) * (1 - ξ_z) - 0.5(value(ip,12,ξ) + value(ip,9,ξ) + value(ip,17,ξ))
    i == 2 && return 0.125(1 + ξ_x) * (1 - ξ_y) * (1 - ξ_z) - 0.5(value(ip,9,ξ) + value(ip,10,ξ) + value(ip,18,ξ))
    i == 3 && return 0.125(1 + ξ_x) * (1 + ξ_y) * (1 - ξ_z) - 0.5(value(ip,10,ξ) + value(ip,11,ξ) + value(ip,19,ξ))
    i == 4 && return 0.125(1 - ξ_x) * (1 + ξ_y) * (1 - ξ_z) - 0.5(value(ip,11,ξ) + value(ip,12,ξ) + value(ip,20,ξ))
    i == 5 && return 0.125(1 - ξ_x) * (1 - ξ_y) * (1 + ξ_z) - 0.5(value(ip,16,ξ) + value(ip,13,ξ) + value(ip,17,ξ))
    i == 6 && return 0.125(1 + ξ_x) * (1 - ξ_y) * (1 + ξ_z) - 0.5(value(ip,13,ξ) + value(ip,14,ξ) + value(ip,18,ξ))
    i == 7 && return 0.125(1 + ξ_x) * (1 + ξ_y) * (1 + ξ_z) - 0.5(value(ip,14,ξ) + value(ip,15,ξ) + value(ip,19,ξ))
    i == 8 && return 0.125(1 - ξ_x) * (1 + ξ_y) * (1 + ξ_z) - 0.5(value(ip,15,ξ) + value(ip,16,ξ) + value(ip,20,ξ))
    i == 9 && return 0.25(1 - ξ_x^2) * (1 - ξ_y) * (1 - ξ_z)
    i == 10 && return 0.25(1 + ξ_x) * (1 - ξ_y^2) * (1 - ξ_z)
    i == 11 && return 0.25(1 - ξ_x^2) * (1 + ξ_y) * (1 - ξ_z)
    i == 12 && return 0.25(1 - ξ_x) * (1 - ξ_y^2) * (1 - ξ_z)
    i == 13 && return 0.25(1 - ξ_x^2) * (1 - ξ_y) * (1 + ξ_z)
    i == 14 && return 0.25(1 + ξ_x) * (1 - ξ_y^2) * (1 + ξ_z)
    i == 15 && return 0.25(1 - ξ_x^2) * (1 + ξ_y) * (1 + ξ_z)
    i == 16 && return 0.25(1 - ξ_x) * (1 - ξ_y^2) * (1 + ξ_z)
    i == 17 && return 0.25(1 - ξ_x) * (1 - ξ_y) * (1 - ξ_z^2)
    i == 18 && return 0.25(1 + ξ_x) * (1 - ξ_y) * (1 - ξ_z^2)
    i == 19 && return 0.25(1 + ξ_x) * (1 + ξ_y) * (1 - ξ_z^2)
    i == 20 && return 0.25(1 - ξ_x) * (1 + ξ_y) * (1 - ξ_z^2)
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end

#############################
# Crouzeix–Raviart Elements #
#############################
"""
Classical non-conforming Crouzeix–Raviart element.

For details we refer ot the original paper:
M. Crouzeix and P. Raviart. "Conforming and nonconforming finite element
methods for solving the stationary Stokes equations I." ESAIM: Mathematical Modelling
and Numerical Analysis-Modélisation Mathématique et Analyse Numérique 7.R3 (1973): 33-75.
"""
struct CrouzeixRaviart{dim,order} <: Interpolation{dim,RefTetrahedron,order} end

getnbasefunctions(::CrouzeixRaviart{2,1}) = 3
nfacedofs(::CrouzeixRaviart{2,1}) = 1

vertices(::CrouzeixRaviart{2,1}) = ()
faces(::CrouzeixRaviart{2,1}) = ((1,), (2,), (3,))

function reference_coordinates(::CrouzeixRaviart{2,1})
    return [Vec{2, Float64}((0.5, 0.5)),
            Vec{2, Float64}((0.0, 0.5)),
            Vec{2, Float64}((0.5, 0.0))]
end

function value(ip::CrouzeixRaviart{2,1}, i::Int, ξ::Vec{2})
    ξ_x = ξ[1]
    ξ_y = ξ[2]
    i == 1 && return 2*ξ_x + 2*ξ_y - 1.0
    i == 2 && return 1.0 - 2*ξ_x
    i == 3 && return 1.0 - 2*ξ_y
    throw(ArgumentError("no shape function $i for interpolation $ip"))
end


using DelimitedFiles
using DataStructures

function process!(values::CircularBuffer{Int}, value::Int)
    # The core is below, if the sum of two number sums to value, the difference of the
    # value and the others values must be in values.
    if !isempty(intersect(values, value .- values))
        push!(values, value)
        return -1
    else
        return value
    end
end

function part1(xmas::Vector{Int}; preamble::Int=25)
    values = CircularBuffer{Int}(preamble)
    # init
    append!(values, xmas[1:preamble])
    for value in xmas[preamble + 1:end]
        test = process!(values, value)
        if test != -1
            return test
        end
    end
end

const xmas = dropdims(readdlm("day09.txt", Int); dims=2)

weakness = part1(xmas)
println("Part 1: $weakness")

function findweakness(xmas::Vector{Int}, target::Int)
    n = length(xmas)
    for i = 1:n - 1
        for j = i + 1:n
            s = sum(xmas[i:j])
            if s == target
                return sum(extrema(xmas[i:j]))
            elseif s > target
                break
            end
        end
    end
end

println("Part 2: $(findweakness(xmas, weakness))")


# -*- coding: utf-8 -*-
function generalized_phase_sensitivity_func(
    F, Imin, Imax, dI, dims::Int, Nθ::Int, initX=nothing, dt=1e-4, alg=Tsit5(),
    origin_val_idx::Int=1, origin_thr=nothing, itp="cubic", extrap=false,
    print_progress=true)
    """
    Compute angular frequency ω(I)
    and generalized phase sensitivity function ζ(θ, I), ξ(θ, I) with the adjoint method.

    Args:
    - F(X, I) : Vector field function of the oscillator model; (list of functions)
    - Imin : Minimum external force strength; (Scalar)
    - Imax : Maximum external force strength; (Scalar)
    - dI  : step of I
    - dims   : Dimension of states; (Int)
    - Nθ  : The number of splitting phase for one period; (Int)
    - initX : Array of initial ∈ R^(D x NI)
    - itp : interpolation mode. "linear" or "cubic"

    Returns: Interpolated object
    - ω(I)    : Angular frequency ∈ R^NI
    - ζ(θ, I) : Phase sensitivity function ∈ R^(Nθ x NI)
    - ξ(θ, I) : Phase sensitivity function ∈ R^(Nθ x NI)
    - Xs(θ, I): stable periodic solution ∈ R^(D x Nθ x NI)
    """
    Irange = Imin:dI:Imax
    NI = size(Irange)[1]

    flag_arraydt = dt isa Array
    @assert (flag_arraydt || (dt isa Number)) "'dt' must be a scalar or an array."
    if flag_arraydt
        @assert size(dt)[1] == NI "The size of the array of 'dt' must be the same as the number of conditions for the external force 'I'."
        dtarray = copy(dt)
    end

    if initX == nothing
        initX = rand(dims)
    end

    flag_initX = size(initX) == (NI, dims)
    if flag_initX
        initXarray = copy(initX)
    else
        @assert size(initX) == (dims, ) "The size of 'initX' must be the same as (dims, )."
    end

    θrange = range(0, 2π, length=Nθ)

    ZθI = zeros(dims, Nθ, NI)
    ωI = zeros(NI)
    XsI = zeros(dims, Nθ, NI)
    @showprogress "[1/3] Computing Xs(θ, I) and Z(θ, I)..." for i in 1:NI
        if flag_arraydt
            dt = dtarray[i]
        end

        if flag_initX
            initX = initXarray[i, :]
        end

        T, ω, Xs = find_stable_periodic_solution(F, Irange[i], dims, Nθ, initX, dt, alg, origin_val_idx, origin_thr, print_progress=false)
        Zθ = phase_sensitivity_func(F, Irange[i], dims, Nθ, T, Xs)

        ωI[i] = ω
        ZθI[:, :, i] = Zθ
        XsI[:, :, i] = Xs
    end

    ζθI = zeros(Nθ, NI)
    ξθI = zeros(Nθ, NI)
    JFI(X, I) = ForwardDiff.derivative(I -> F(X, I), I)

    # Compute ζ(θ, I)
    if print_progress println("[2/3] Computing ζ(θ, I)...") end

    for i in 1:NI
        for th in 1:Nθ
            ζθI[th, i] = JFI(XsI[:, th, i], Irange[i])' * ZθI[:, th, i]
        end
    end

    # numerical integration
    dθ = 2π / Nθ
    ζI = sum(ζθI, dims=1)[1, :] / Nθ # dθ/2π = 1/Nθ

    # scaled interpolated variables
    if itp == "cubic"
        sitpXsI = [scale(interpolate(XsI[i, :, :], BSpline(Cubic(Line(OnGrid())))), θrange, Irange) for i in 1:dims]
    else
        sitpXsI = [scale(interpolate(XsI[i, :, :], BSpline(Linear())), θrange, Irange) for i in 1:dims]
    end

    # Compute ξ(θ, I)
    if print_progress println("[3/3] Computing ξ(θ, I)...") end
    for i in 1:NI
        ξθI[1, i] = - [Interpolations.gradient(sitpXsI[j], 0, Irange[i])[2] for j in 1:dims]' * ZθI[:, 1, i]
    end

    for i in 1:NI
        for th in 2:Nθ
            ξθI[th, i] = ξθI[th-1, i] - (ζθI[th, i] - ζI[i]) * dθ / ωI[i]
        end
    end

    # scaled interpolated variables
    if itp == "cubic"
        sitpωI = scale(interpolate(ωI, BSpline(Cubic(Line(OnGrid())))), Irange)
        sitpζθI = scale(interpolate(ζθI, BSpline(Cubic(Line(OnGrid())))), θrange, Irange)
        sitpξθI = scale(interpolate(ξθI, BSpline(Cubic(Line(OnGrid())))), θrange, Irange)
    else
        sitpωI = scale(interpolate(ωI, BSpline(Linear())), Irange)
        sitpζθI = scale(interpolate(ζθI, BSpline(Linear())), θrange, Irange)
        sitpξθI = scale(interpolate(ξθI, BSpline(Linear())), θrange, Irange)
    end

    if extrap
        etpωI = extrapolate(sitpωI, Flat())
        etpζθI = extrapolate(sitpζθI, Flat())
        etpξθI = extrapolate(sitpξθI, Flat())
        etpXsI = [extrapolate(sitpXsI[i], Flat()) for i in 1:dims]
        return etpωI, etpζθI, etpξθI, etpXsI
    else
        return sitpωI, sitpζθI, sitpξθI, sitpXsI
    end
end


export Polynomial

"""
    Polynomial{T<:Number, X}(coeffs::AbstractVector{T}, [var = :x])

Construct a polynomial from its coefficients `coeffs`, lowest order first, optionally in
terms of the given variable `var` which may be a character, symbol, or a string.

If ``p = a_n x^n + \\ldots + a_2 x^2 + a_1 x + a_0``, we construct this through
`Polynomial([a_0, a_1, ..., a_n])`.

The usual arithmetic operators are overloaded to work with polynomials as well as
with combinations of polynomials and scalars. However, operations involving two
polynomials of different variables causes an error except those involving a constant polynomial.

!!! note
    `Polynomial` is not axis-aware, and it treats `coeffs` simply as a list of coefficients with the first
    index always corresponding to the constant term. In order to use the axis of `coeffs` as exponents,
    consider using a [`LaurentPolynomial`](@ref) or possibly a [`SparsePolynomial`](@ref).

# Examples
```jldoctest
julia> using Polynomials

julia> Polynomial([1, 0, 3, 4])
Polynomial(1 + 3*x^2 + 4*x^3)

julia> Polynomial([1, 2, 3], :s)
Polynomial(1 + 2*s + 3*s^2)

julia> one(Polynomial)
Polynomial(1.0)
```
"""
struct Polynomial{T <: Number, X} <: StandardBasisPolynomial{T, X}
    coeffs::Vector{T}
    function Polynomial{T, X}(coeffs::AbstractVector{S}) where {T <: Number, X, S}
        if Base.has_offset_axes(coeffs)
            @warn "ignoring the axis offset of the coefficient vector"
        end
        N = findlast(!iszero, coeffs)
        N == nothing && return new{T,X}(zeros(T,1))
        cs = T[coeffs[i] for i ∈ firstindex(coeffs):N]
        new{T,X}(cs)
    end
    # non-copying alternative assuming !iszero(coeffs[end])
    function Polynomial{T, X}(checked::Val{false}, coeffs::AbstractVector{T}) where {T <: Number, X}
        new{T, X}(coeffs)
    end
end

@register Polynomial

# scalar +,* faster  than standard-basis/common versions as it avoids a copy
function Base.:+(p::P, c::S) where {T, X, P <: Polynomial{T, X}, S<:Number}
    R = promote_type(T, S)
    Q = Polynomial{R,X}
    as = convert(Vector{R}, copy(coeffs(p)))
    as[1] += c
    iszero(as[end]) ? Q(as) : Q(Val(false), as)
end

function Base.:*(p::P, c::S) where {T, X, P <: Polynomial{T,X} , S <: Number}
    R = promote_type(T,S)
    Q = Polynomial{R, X}
    as = R[aᵢ * c for aᵢ ∈ coeffs(p)]
    iszero(as[end]) ? Q(as) : Q(Val(false), as)
end

# implement, as not copying speeds up multiplication by a factor of 2 or so
# over the default
function Base.:+(p1::P1, p2::P2) where {T,X, P1<:Polynomial{T,X},
                                        S,   P2<:Polynomial{S,X}}
    n1, n2 = length(p1), length(p2)
    R = promote_type(T,S)
    Q = Polynomial{R,X}
    if n1 == n2
        cs = ⊕(P1, p1.coeffs, p2.coeffs)
        return iszero(cs[end]) ? Q(cs) : Q(Val(false), cs)
    elseif n1 > n2
        cs = ⊕(P1, p1.coeffs, p2.coeffs)
    else
        cs = ⊕(P1, p2.coeffs, p1.coeffs)
    end

    Q(Val(false), cs)
end

function Base.:*(p::P, q::P) where {T,X, P<:Polynomial{T,X}}
    c = fastconv(p.coeffs, q.coeffs)
    return iszero(c[end]) ? P(c) : P(Val(false), c)
end


function displacement(r::AbstractMatrix, δ::Int)
    sqrt((r[1+δ, 2] - r[1, 2])^2 + (r[1+δ, 1] - r[1, 1])^2)
end

function displacement(r::AbstractMatrix, t::Int, δ::Int)
    sqrt((r[t+δ, 2] - r[t, 2])^2 + (r[t+δ, 1] - r[t, 1])^2)
end

squared_displacement(r::AbstractMatrix, t::Int, δ::Int) = displacement(r, t, δ)^2

function extract(df::DataFrame, n::Int, id::Symbol, x::Symbol)
    Vector(@where(df, cols(id) .== n)[!, x])
    # Vector{Float64}(df[df[:, id] .== n, x])
end

function extract(df::DataFrame, n::Int, id::Symbol, label::Vector{Symbol})
    Matrix(@where(df, cols(id) .== n)[!, label])
    # df[df[!, id] .== n, label]
end


# methods/simple.jl
#
# Method-based access to tensor operations using simple definitions.

"""`tensorcopy(A, IA, IC=IA)`

Creates a copy of `A`, where the dimensions of `A` are assigned indices from the iterable `IA` and the indices of the copy are contained in `IC`. Both iterables should contain the same elements in a different order.

The result of this method is equivalent to `permutedims(A,p)` where p is the permutation such that `IC=IA[p]`. The implementation of tensorcopy is however more efficient on average.
"""
function tensorcopy(A, IA, IC=IA)
    IA == IC && return copy(A)

    checkindices(A, IA)
    indCinA = add_indices(IA, IC)
    C = similar_from_indices(eltype(A), indCinA, A)
    add!(1, A, Val{:N}, 0, C, indCinA)
end

"""`tensoradd(A, IA, B, IB, IC=IA)`

Returns the result of adding arrays `A` and `B` where the iterabels `IA` and `IB` denote how the array data should be permuted in order to be added. More specifically, the result of this method is equivalent to

```
tensorcopy(A, IA, IC) + tensorcopy(B, IB, IC)
```

but without creating the temporary permuted arrays.
"""
function tensoradd(A, IA, B, IB, IC=IA)
    checkindices(A, IA)
    checkindices(B, IB)
    T = promote_type(eltype(A), eltype(B))
    if IA == IC
        C = similar_from_indices(T, 1:numind(A), A)
        copy!(C,A)
    else
        indCinA = add_indices(IA, IC)
        C = similar_from_indices(T, indCinA, A)
        add_native!(1, A, Val{:N}, 0, C, indCinA)
    end
    indCinB = add_indices(IB, IC)
    add!(1, B, Val{:N}, 1, C, indCinB)
end

"""`tensortrace(A, IA, IC=unique2(IA))`

Trace or contract pairs of indices of array `A`, by assigning them an identical indices in the iterable `IA`. The untraced indices, which are assigned a unique index, can be reordered according to the optional argument `IC`. The default value corresponds to the order in which they appear. Note that only pairs of indices can be contracted, so that every index in `IA` can appear only once (for an untraced index) or twice (for an index in a contracted pair).
"""
function tensortrace(A, IA, IC = unique2(IA))
    checkindices(A, IA)
    indCinA, cindA1, cindA2 = trace_indices(IA,IC)
    C = similar_from_indices(eltype(A), indCinA, A)
    trace!(1, A, Val{:N}, 0, C, indCinA, cindA1, cindA2)
end

"""`tensorcontract(A, IA, B, IB, IC=symdiff(IA,IB); method=:BLAS)`

Contract indices of array `A` with corresponding indices in array `B` by assigning them identical labels in the iterables `IA` and `IB`. The indices of the resulting array correspond to the indices that only appear in either `IA` or `IB` and can be ordered by specifying the optional argument `IC`. The default is to have all open indices of array `A` followed by all open indices of array `B`. Note that inner contractions of an array should be handled first with `tensortrace`, so that every label can appear only once in `labelsA` or `labelsB` seperately, and once (for open index) or twice (for contracted index) in the union of `labelsA` and `labelsB`.

The keyword argument `method` can take the values `:BLAS` or `:native`. The first option creates temporary copies of `A`, `B` and the resulting tensor where the indices are permuted such that the contractions become equivalent to a single matrix multiplication, which is then handled by BLAS. This is often the fastest approach and therefore the default value, but it does require sufficient memory and there is some overhead in allocating new memory (e.g. when doing this many times in a loop). In case `method` is set to `:native`, a native Julia function is called that performs the contraction without creating tempories, with special attention to cache-friendliness for maximal efficiency. This can be faster for small tensors or when the contraction pattern is non-generic, e.g. when it amounts to a scalar (no open indices) or a tensor product (no contraction indices).
"""
function tensorcontract(A, IA, B, IB, IC = symdiff(IA,IB); method::Symbol = :BLAS)
    checkindices(A, IA)
    checkindices(B, IB)

    oindA, cindA, oindB, cindB, indCinoAB = contract_indices(IA, IB, IC)
    indCinAB = vcat(oindA,length(IA)+oindB)[indCinoAB]

    T = promote_type(eltype(A), eltype(B))
    C = similar_from_indices(T, indCinAB, A, B)

    if method == :BLAS
        contract!(1, A, Val{:N}, B, Val{:N}, 0, C, oindA, cindA, oindB, cindB, indCinoAB,Val{:BLAS})
    elseif method == :native
        contract!(1, A, Val{:N}, B, Val{:N}, 0, C, oindA, cindA, oindB, cindB, indCinoAB,Val{:native})
    else
        throw(ArgumentError("unknown contraction method"))
    end
    return C
end

"""`tensorproduct(A, IA, B, IB, IC=union(IA,IB))`

Computes the tensor product of two arrays `A` and `B`, i.e. returns a new array `C` with `ndims(C)=ndims(A)+ndims(B)`. The indices of the output tensor are related to those of the input tensors by the pattern specified by the indices. Essentially, this is a special case of `tensorcontract` with no indices being contracted over. On top of just calling `tensorcontract` with `method=:native`, this method checks whether the indices indeed specify a tensor product instead of a genuine contraction.
"""
function tensorproduct(A, IA, B, IB, IC = symdiff(IA,IB))
    isempty(intersect(IA, IB)) || throw(IndexError("not a valid tensor product"))
    tensorcontract(A, IA, B, IB, IC; method = :native)
end