function model(du,u,p,t)
    ts = [50.0, 100.0, 200.0, 300.0]
    freq = 0.2
    amp = p["amp"]
    if t < ts[1]
        p["maxA"] = 0
    elseif t < ts[3]
        p["maxA"] = 2.3
    else
        p["maxA"] = 2.3*0.65
        p["gA"] = 0.005
    end
    if t < ts[2]
        p["maxF"] = 0
    elseif t < ts[3]
        p["maxF"] = 2.1
    else
        p["maxF"] = 2.1*0.65
        p["gF"] = 0.005
    end
    if t < ts[4]
        p["muA"] = 0
        p["muF"] = 0
    else
        Random.seed!(floor(Int, (t-ts[4])*freq)*p["y"]*floor(Int, p["x"]))
        p["muA"] =   sign(sin((t-ts[4])*pi*freq)) * amp * randn()*u[5]
        p["muF"] =  sign(sin((t-ts[4])*pi*freq)) * amp * randn()*u[6]
    end
    P5, P8, R5, R8, A, F, r2, c15, c25, c35, r9, c18, c28, c38 = u
    x, gA, gF, maxA, maxF, s05, s08, K5A, K8F, n5A, n8F, K58, n58, K85, n85  = p["x"], p["gA"], p["gF"], p["maxA"], p["maxF"], p["s05"], p["s08"], p["K5A"], p["K8F"], p["n5A"], p["n8F"], p["K58"], p["n58"], p["K85"], p["n85"]
    kR5, kR8, l05, l08, kP5, kP8 = p["kR5"], p["kR8"], p["l05"], p["l08"], p["kP5"], p["kP8"]
    s5, s8  = p["s5"], p["s8"]
    muA, muF = p["muA"], p["muF"]
    du[1] = dP5 = l05 * R5 - kP5 * P5
    du[2] = dP8 = l08 * R8 - kP8 * P8
    du[3] = dR5 = s5*((A/K5A)^n5A)/(1+(P8/K58)^n58+(A/K5A)^n5A) + s05 - kR5 * R5
    du[4] = dR8 = s8*((F/K8F)^n8F)/(1+(F/K8F)^n8F) + s08 - kR8 * R8
    du[5] = dA = gA*(maxA * exp(-0.01 * x) - A) + muA
    du[6] = dF = gF*(maxF * exp(-0.01 * (99 - x)) - F) + muF
    du[7] = dr2 = -r2
    du[8] = dc15 = -c15
    du[9] = dc25 = -c25
    du[10] = dc35 = -c35
    du[11] = dr9 = -r9
    du[12] = dc18 = -c18
    du[13] = dc28 = -c28
    du[14] = dc38 = -c38
end

d_ics1 = Dict("R8"=> 0.00900023576123889, "c15"=> 1.761337400332505e-06, "P8"=> 2.9560034347325604e-06, "P5"=> 3.4020485285993286e-07, "c25"=> 9.118941517979407e-05, "c35"=> 0.004721137474792935, "A"=> 1.5e-323, "c18"=> 8.844673354103184e-06, "R5"=> 0.000500023861019239, "c38"=> 0.001867111128611335, "F"=> 1.5e-323, "r2"=> 0.3524443000060298, "r9"=> 0.33001432105772416, "c28"=> 0.0002687664938900425)
svlist = ["P5", "P8", "R5", "R8", "A", "F", "r2", "c15", "c25", "c35", "r9", "c18","c28", "c38"]
u0 = [d_ics1[x] for x in svlist]
tspan = (0.0,1000.0)
p=Dict("x"=>50
    ,"amp"=>0.001
    ,"kR5"=>1.0
    ,"l05"=>1.0
    ,"kP5"=>1.0
    ,"maxA"=>2.3
    ,"maxF"=>2.1
    ,"kR8"=>1.0
    ,"l08"=>1.0
    ,"kP8"=>1.0
    ,"gA"=>1.0
    ,"gF"=>1.0
    ,"gc"=>1.0
    ,"s05"=>0.0002
    ,"s5"=>1.0
    ,"s08"=>0.0002
    ,"s8"=>1.0
    ,"K5A"=>0.7
    ,"n5A"=>60
    ,"K58"=>0.05
    ,"n58"=>20
    ,"K85"=>1000
    ,"n85"=>6.0
    ,"K8F"=>0.75
    ,"n8F"=>60
    ,"y"=>1)


#
# Symetric.jl – The manifold of symmetric matrices
#
# ---
# Manopt.jl - Ronny Bergmann - 2017-07-06

import LinearAlgebra: norm, dot
import Base: exp, log, show

export Symmetric, SymPoint, SymTVector, show
# also indicates which functions are available (already) for Sym
export distance, dot, exp, norm, dot, manifoldDimension, parallelTransport
export validateMPoint, validateTVector
export zeroTVector, typeofMPoint, typeofTVector
# Types
# ---
@doc doc"""
    Symmetric <: Manifold

The manifold $\mathcal M = \mathrm{Sym}(n)$, where $\mathrm{Sym}(n) = \{
x \in \mathbb R^{n\times n} | x = x^\mathrm{T}
\}$, $n\in\mathbb N$, denotes the manifold of symmetric matrices
equipped with the trace inner product and its induced Forbenius norm.

# Abbreviation
`Sym` or `Sym(n)`, respectively.

# Constructor
    Symmetric(n)

generates the manifold of `n`-by-`n` symmetric matrices.
"""
struct Symmetric <: Manifold
  name::String
  dimension::Int
  abbreviation::String
  Symmetric(dimension::Int) = new("$dimension-by-$dimension symmetric matrices",(dimension*(dimension+1)/2),"Sym($dimension)")
end
@doc doc"""
    SymPoint <: MPoint

A point $x$ on the manifold $\mathcal M = \mathrm{Sym}(n)$ of $n\times n$
symmetric matrices, represented in the redundant way of a
symmetric matrix (instead of storing just the upper half).
"""
struct SymPoint{T <: AbstractFloat} <: MPoint
	value::Matrix{T}
	SymPoint{T}(v::Matrix{T}) where {T <: AbstractFloat} = new(v);
end
SymPoint(v::Matrix{T}) where {T <: AbstractFloat} = SymPoint{T}(v)
getValue(x::SymPoint) = x.value
@doc doc"""
    SymTVector <: TVector

A tangent vector $\xi$ in $T_x\mathcal M$ of a symmetric matrix $x\in\mathcal M$.
"""
struct SymTVector{T <: AbstractFloat} <: TVector
	value::Matrix{T}
  	SymTVector{T}(value::Matrix{T}) where {T <: AbstractFloat} = new(value);
end
SymTVector(v::Matrix{T}) where {T <: AbstractFloat} = SymTVector{T}(v)
getValue(ξ::SymTVector) = ξ.value
# Traits
# ---
# (a) P(n) is a matrix manifold
@traitimpl IsMatrixM{Symmetric}
@traitimpl IsMatrixP{SymPoint}
@traitimpl IsMatrixTV{SymTVector}
# Functions
# ---
@doc doc"""
    distance(M,x,y)

distance of two [`SymPoint`](@ref)s `x,y` on the [`Symmetric`](@ref) manifold `M``
inherited
from embedding them in $\mathbb R^{n\times n}$, i.e. use the Frobenious norm
of the difference.
"""
distance(M::Symmetric,x::SymPoint,y::SymPoint) = norm( getValue(x) - getValue(y) )
@doc doc"""
    dot(M,x,ξ,ν)

inner product of two [`SymTVector`](@ref)s `ξ,ν` lying in the tangent
space of the [`SymPoint`](@ref) `x` on the [`Symmetric`](@ref) manifold `M`.
"""
dot(M::Symmetric, x::SymPoint, ξ::SymTVector, ν::SymTVector) = dot( getValue(ξ), getValue(ν) )
@doc doc"""
    exp(M,x,ξ[, t=1.0])

compute the exponential map on the [`Symmetric`](@ref) manifold `M` given a
[`SymPoint`](@ref) `x` and a [`SymTVector`](@ref) `ξ`, as well as an optional
scaling factor `t`. The exponential map is given by

$\exp_{x}ξ = x+ξ.$
"""
exp(M::Symmetric, x::SymPoint, ξ::SymTVector, t::Float64=1.0) = SymPoint( getValue(x) + t*getValue(ξ) )
@doc doc"""
    log(M,x,y)

compute the logarithmic map for two [`SymPoint`](@ref)` x,y` on the [`Symmetric`](@ref) `M`,
which is given by $\log_xy = y-x$.
"""
log(M::Symmetric,x::SymPoint,y::SymPoint) = SymTVector( getValue(y) - getValue(x) )
"""
    manifoldDimension(M)

returns the manifold dimension of the [`Symmetric`](@ref) manifold `M`.
"""
manifoldDimension(M::Symmetric) = M.dimension
"""
    manifoldDimension(x)

returns the manifold dimension the [`SymPoint`](@ref) `x` belongs to.
"""
manifoldDimension(x::SymPoint) = size( getValue(x), 1)*(size( getValue(x), 1)+1)/2
"""
    norm(M,x,ξ)

computes the norm of the [`SymTVector`](@ref) `ξ` in the tangent space of
the [`SymPoint`](@ref) `x` on the [`Symmetric`](@ref) `M` embedded in the
Euclidean space, i.e. by its Frobenius norm.
"""
norm(M::Symmetric,x::SymPoint,ξ::SymTVector) = norm( getValue(ξ) )
"""
    parallelTransport(M,x,y,ξ)

compute the parallel transport of a [`SymTVector`](@ref) `ξ` from the tangent
space at the [`SymPoint`](@ref) `x` to the [`SymPoint`](@ref)` y` on the
[`Symmetric`](@ref) manifold `M`.
Since the metric is inherited from the embedding space, it is just the identity.
"""
parallelTransport(M::Symmetric,x::SymPoint,y::SymPoint,ξ::SymTVector) = ξ

typeofTVector(::Type{SymPoint{T}}) where T = SymTVector{T}
typeofMPoint(::Type{SymTVector{T}}) where T = SymPoint{T}

@doc doc"""
    typicalDistance(M)

returns the typical distance on the [`Symmetric`](@ref) manifold `M`,
i.e. $\sqrt{n}$.
"""
typicalDistance(M::Symmetric) = sqrt( - 0.5 + sqrt(1/4 + 2*manifoldDimension(M) ) ) # manDim to n

@doc doc"""
    validateMPoint(M,x)

validate, that the [`SymPoint`](@ref) `x` is a valid point on the
[`Symmetric`](@ref) manifold `M`,
i.e. that its dimensions are correct and that the matrix is symmetric.
"""
function validateMPoint(M::Symmetric, x::SymPoint)
    if manifoldDimension(M) ≠ manifoldDimension(x)
        throw(DomainError(
            "The point $x does not lie on $M,, since the manifold dimension of $M ($(manifoldDimension(M)))does not fit the manifold dimension of $x ($(manifoldDimension(x)))."
        ))
    end
    if norm(getValue(x) - transpose(getValue(x))) > 10^(-14)
        throw(DomainError(
            "The point $x does not lie on $M, since the matrix of $x is not symmetric."
        ))
    end
    return true
end
@doc doc"""
    validateTVector(M,x,ξ)

validate, that the [`SymTVector`](@ref) is a valid tangent vector to the
[`SymPoint`](@ref) `x` on the [`Symmetric`](@ref) manifold `M`,
i.e. that its dimensions are correct and that the matrix is symmetric.
"""
function validateTVector(M::Symmetric, x::SymPoint, ξ::SymTVector)
    ξs = size( getValue(ξ), 1)*(size( getValue(ξ), 1)+1)/2
    if (manifoldDimension(M) ≠ manifoldDimension(x)) || (ξs ≠ manifoldDimension(x))
        throw(DomainError(
            "The tangent vector $ξ of size $(ξs), the point $x ($(manifoldDimension(x))) and the manifold $M ($(manifoldDimension(M))) are not all equal in dimensions, so the tangent vector can not be correct."
        ))
    end
    if norm(getValue(ξ) - transpose(getValue(ξ))) > 10^(-14)
        throw(DomainError(
            "The tangent vector $ξ is not a symmetric matrix and hence can not lie in the tangent space of $x on $M."
        ))
    end
    return true
end
@doc doc"""
    ξ = zeroTVector(M,x)

returns a zero vector in the tangent space $T_x\mathcal M$ of the
[`SymPoint`](@ref) `x` on  the [`Symmetric`](@ref) manifold `M`.
"""
zeroTVector(M::Symmetric, x::SymPoint) = SymTVector(  zero( getValue(x) )  );
# Display
# ---
show(io::IO, M::Symmetric) = print(io, "The Manifold of $(M.name).")
show(io::IO, p::SymPoint) = print(io, "Sym($(p.value))")
show(io::IO, ξ::SymTVector) = print(io, "SymT($(ξ.value))")


#
# This is a script that re-generates the test cases used in experiment1.jl
# and experiment2.jl, that are save in MAT files. Having these tests already
# pre-computed saves an enourmous time while testing.
#

using MAT
using BivMatFun
using LinearAlgebra
using SpecialFunctions

include("experiments_common.jl")

println("Data directory: ", data_directory)

function generate_test_1()
    # This data needs to be kept in sync with experiment1.jl
    a = 1/2;
    f = (x,y,i,j) -> (x+y).^(-a-i-j) * gamma(a+i+j) ./ gamma(a) * (-1)^(i+j);
    ntests = 5;

    for i = 1 : ntests
        n = 32 * i;

        println("Generating experiment1, n = ", n, " random")

        A, B, C, Y = construct_random_benchmark(f, n)
        errest1 = BivMatFun.fun2m_condest(f, A, B, C) * eps();

        matwrite("$(data_directory)/experiment1_$(n)_random.mat", Dict(
        "A" => A,
        "B" => B,
        "C" => C,
        "Y" => Y,
        "errest" => errest1
        ));

        println("Generating experiment1, n = ", n, " grcar-random")

        A = grcar(n) + I

        A = convert(Matrix{ComplexF64}, A)
        B = convert(Matrix{ComplexF64}, B)
        C = convert(Matrix{ComplexF64}, C)

        Y2 = evaluate_reference_solution(f, A, B, C)
        errest2 = BivMatFun.fun2m_condest(f, A, B, C) * eps();

        matwrite("$(data_directory)/experiment1_$(n)_grcar.mat", Dict(
        "A" => A,
        "B" => B,
        "C" => C,
        "Y" => Y2,
        "errest" => errest2
        ));
    end
end

function generate_test_2()

    matrices = [ "jordbloc1", "grcar", "smoke", "kahan2", "lesp", "sampling", "grcar-randn" ];

    m = 64;
    n = m;
    a = .5;

    fncts = [
    Dict("name" => "sqrt", "f" => (x,y,i,j) -> sqrt(x+y)),
    Dict("name" => "invsqrt", "f" => (x,y,i,j)  -> 1 ./ sqrt(x+y)),
    Dict("name" => "phi1", "f" => (x,y,i,j) -> expm1(x+y) ./ (x+y)),
    Dict("name" => "expa", "f" => (x,y,i,j) -> exp((x+y).^a))
    ]

    shift = 1.00;

    lesp = n -> convert(Matrix{ComplexF64}, diagm(-1 => 1 ./ (2:n), 0 => -(5 : 2 : 2*n+3), 1 => 2 : n));
    smoke = n -> diagm(0 => exp.(2im * pi * (1 : n) ./ n), 1 => complex(ones(n-1)), -n+1 => [ 1.0 + 0im ]);

    for i = 1 : length(fncts)
        fname = fncts[i]["name"];
        f = fncts[i]["f"];

        for j = 1 : length(matrices);
            matrix_name = matrices[j];

            println("Generating experiment2, $(matrix_name), $(fname)")

            if matrix_name == "jordbloc1"
                k = min(m, n, 8);

                J = diagm(1 => ones(k-1)) + 0.1 * I;

                A = randn(m-k, m-k); A = A / opnorm(A); A = A + shift * I;
                A = cat(J, A, dims=[1,2]);
                Q = qr(randn(m, m)).Q;
                A = Q * A * Q';

                B = randn(n-k, n-k); B = B / opnorm(B); B = B + shift * I;
                B = cat(J, B, dims=[1,2]);
                Q = qr(randn(m, m)).Q;
                B = Q * B * Q';

            elseif matrix_name == "kahan2"
                k = min(min(m,n), 64);
                XA = randn(m-k, m-k)
                XA = XA / opnorm(XA) + shift * I;
                XB = randn(n-k, n-k)
                XB = XB / opnorm(XB) + shift * I;
                A = cat(kahan(k), XA, dims = [1,2]);
                B = cat(kahan(k), XB, dims = [1,2]);

            elseif matrix_name == "smoke"
                A = schur(smoke(m)).T;
                B = A;

            elseif matrix_name == "grcar-randn"
                A = grcar(m);
                B = randn(n, n);

            elseif matrix_name == "grcar"
                A = grcar(m);
                B = grcar(n);

            elseif matrix_name == "lesp"
                k = min(32, min(m,n));
                XA = randn(m-k, m-k)
                XA = XA / opnorm(XA) - shift * I;
                XB = randn(n-k, n-k)
                XB = XB / opnorm(XB) - shift * I;
                A = -cat(schur(lesp(k)).T, XA, dims = [1,2]);
                B = -cat(schur(lesp(k)).T, XB, dims = [1,2]);

            elseif matrix_name == "sampling"
                k = min(32, min(m,n));

                A = sampling(k) + I;
                B = sampling(k) + I;

                XA = randn(m-k, m-k)
                XA = XA / opnorm(XA) + shift * I;
                XB = randn(n-k, n-k)
                XB = XB / opnorm(XB) + shift * I;

                A = cat(A, XA, dims=[1,2])
                B = cat(B, XB, dims=[1,2])
            end

            A = complex(A);
            B = complex(B)

            C = randn(m, n); C = C / norm(C);
            C = complex(C)

            Y = evaluate_reference_solution(f, A, B, C)
            errest = BivMatFun.fun2m_condest(f, A, B, C) * eps();

            matwrite("$(data_directory)/experiment2_$(matrix_name)_$(fname).mat", Dict(
            "A" => A,
            "B" => B,
            "C" => C,
            "Y" => Y,
            "errest" => errest
            ))
        end
    end
end

generate_test_1()
generate_test_2()


function within{T <: Real}(scenario::Vector{T}, gamma::Gamma, p::DeploymentProblem)
    # returns true if scenario ∈ gamma; false otherwise
    if any(scenario .> gamma._single)
        return false
    elseif sum(scenario) > gamma._global
        return false
    else
        for j in 1:p.nregions
            if sum(scenario[vec(p.adjacency[j,:])]) > gamma._local[j]
                return false
            end
        end
        for i in 1:p.nlocations
            if sum(scenario[p.coverage[:,i]]) > gamma._regional[i]
                return false
            end
        end
    end
    true
end

type Evaluation
    objvalue::Int
    shortfall::Vector{Int}
    dispatch::Matrix{Int}
end

function evaluate{T1, T2 <: Real}(x::Vector{T1}, scenario::Vector{T2}, p::DeploymentProblem;
    solver=GurobiSolver(OutputFlag=0,PrePasses=3))

    I = 1:p.nlocations
    J = 1:p.nregions

    m = JuMP.Model(solver=solver)
    JuMP.@variable(m, y[1:p.nlocations,1:p.nregions] >= 0, Int)
    JuMP.@variable(m, z[1:p.nregions] >= 0, Int)

    JuMP.@objective(m, Min, sum(z[j] for j in J))

    # flow constraints at each station
    for i in I
        JuMP.@expression(m, outflow, sum(y[i,j] for j in filter(j->p.coverage[j,i], J)))
        JuMP.@constraint(m, x[i] >= outflow)
    end
    # shortfall from satisfying demand/calls
    for j in J
        JuMP.@expression(m, inflow, sum(y[i,j] for i in filter(i->p.coverage[j,i], I)))
        JuMP.@constraint(m, z[j] >= scenario[j] - inflow)
    end
    status = JuMP.solve(m)
    @assert status == :Optimal

    Evaluation(Int(JuMP.getObjectiveValue(m)),
               [round(Int,z) for z in JuMP.getValue(z)],
               map(y->round(Int,y), JuMP.getValue(y)))
end

function evaluate{T <: Real}(x::Vector{T}, p::DeploymentProblem)
    Evaluation[evaluate(x,vec(p.demand[i,:]),p) for i in p.test]
end

type Result
    stoch_x
    robust_x
    bad_scenarios
    upperbounds
    lowerbounds
    stoch_timing
    robust_timing
end

function performance(p::DeploymentProblem, α::Float64; verbose=false)
    stoch_model = StochasticDeployment(p)
    tic()
    solve(stoch_model)
    stoch_timing = toq()
    robust_model = RobustDeployment(p, α=α, verbose=verbose, master_verbose=verbose)
    tic()
    solve(robust_model, p, verbose=verbose)
    robust_timing = toq()
    Result( deployment(stoch_model),
            deployment(robust_model),
            robust_model.scenarios,
            robust_model.upperbounds,
            robust_model.lowerbounds,
            stoch_timing,
            robust_timing)
end

function test_performance(p::DeploymentProblem; namb::Vector{Int}=[25:5:45], alpha::Vector{Float64}=[0.1,0.05,0.01,0.001,0.0001], verbose=false)
    results = Array(Result, (5,5))
    for (i,n) in enumerate(namb)
        print(n)
        p.nambulances = n
        stoch_model = StochasticDeployment(p)
        tic()
        solve(stoch_model)
        stoch_timing = toq()
        for (j,α) in enumerate(alpha)
            print(" $α")
            robust_model = RobustDeployment(p, α=α, verbose=verbose, master_verbose=verbose)
            tic()
            solve(robust_model, p, verbose=verbose)
            robust_timing = toq()
            results[i,j] = Result(  deployment(stoch_model),
                                    deployment(robust_model),
                                    robust_model.scenarios,
                                    robust_model.upperbounds,
                                    robust_model.lowerbounds,
                                    stoch_timing,
                                    robust_timing)
        end
        println("")
    end
    results
end


"""
```
eqcond(m::Schorf)
```

Expresses the equilibrium conditions in canonical form using Γ0, Γ1, C, Ψ, and Π matrices.
Using the mappings of states/equations to integers defined in schorf.jl, coefficients are
specified in their proper positions.

### Outputs

* `Γ0` (`n_states` x `n_states`) holds coefficients of current time states.
* `Γ1` (`n_states` x `n_states`) holds coefficients of lagged states.
* `C`  (`n_states` x `1`) is a vector of constants
* `Ψ`  (`n_states` x `n_shocks_exogenous`) holds coefficients of iid shocks.
* `Π`  (`n_states` x `n_states_expectational`) holds coefficients of expectational states.
"""
function eqcond(m::AnSchorfheide)
    endo = m.endogenous_states
    exo  = m.exogenous_shocks
    ex   = m.expected_shocks
    eq   = m.equilibrium_conditions

    Γ0 = zeros(n_states(m), n_states(m))
    Γ1 = zeros(n_states(m), n_states(m))
    C  = zeros(n_states(m))
    Ψ  = zeros(n_states(m), n_shocks_exogenous(m))
    Π  = zeros(n_states(m), n_shocks_expectational(m))

    ### ENDOGENOUS STATES ###

    ### 1. Consumption Euler Equation

    Γ0[eq[:eq_euler], endo[:y_t]]  = 1.
    Γ0[eq[:eq_euler], endo[:R_t]] = 1/m[:τ]
    Γ0[eq[:eq_euler], endo[:g_t]] = -(1-m[:ρ_g])
    Γ0[eq[:eq_euler], endo[:z_t]] = -m[:ρ_z]/m[:τ]
    Γ0[eq[:eq_euler], endo[:Ey_t]] = -1
    Γ0[eq[:eq_euler], endo[:Eπ_t]] = -1/m[:τ]

    ### 2. NK Phillips Curve

    Γ0[eq[:eq_phillips], endo[:y_t]] = -m[:κ]
    Γ0[eq[:eq_phillips], endo[:π_t]] = 1
    Γ0[eq[:eq_phillips], endo[:g_t]] = m[:κ]
    Γ0[eq[:eq_phillips], endo[:Eπ_t]] = -1/(1+m[:rA]/400)

    ### 3. Monetary Policy Rule

    Γ0[eq[:eq_mp], endo[:y_t]] = -(1-m[:ρ_R])*m[:ψ_2]
    Γ0[eq[:eq_mp], endo[:π_t]] = -(1-m[:ρ_R])*m[:ψ_1]
    Γ0[eq[:eq_mp], endo[:R_t]] = 1
    Γ0[eq[:eq_mp], endo[:g_t]] = (1-m[:ρ_R])*m[:ψ_2]
    Γ1[eq[:eq_mp], endo[:R_t]] = m[:ρ_R]
    Ψ[eq[:eq_mp], exo[:rm_sh]] = 1

    ### 4. Output lag

    Γ0[eq[:eq_y_t1], endo[:y_t1]] = 1
    Γ1[eq[:eq_y_t1], endo[:y_t]] = 1

    ### 5. Government spending

    Γ0[eq[:eq_g], endo[:g_t]] = 1
    Γ1[eq[:eq_g], endo[:g_t]] = m[:ρ_g]
    Ψ[eq[:eq_g], exo[:g_sh]] = 1

    ### 6. Technology

    Γ0[eq[:eq_z], endo[:z_t]] = 1
    Γ1[eq[:eq_z], endo[:z_t]] = m[:ρ_z]
    Ψ[eq[:eq_z], exo[:z_sh]] = 1

    ### 7. Expected output

    Γ0[eq[:eq_Ey], endo[:y_t]] = 1
    Γ1[eq[:eq_Ey], endo[:Ey_t]] = 1
    Π[eq[:eq_Ey], ex[:Ey_sh]] = 1

    ### 8. Expected inflation

    Γ0[eq[:eq_Eπ], endo[:π_t]] = 1
    Γ1[eq[:eq_Eπ], endo[:Eπ_t]] = 1
    Π[eq[:eq_Eπ], ex[:Eπ_sh]] = 1

    return Γ0, Γ1, C, Ψ, Π
end


mutable struct StateAM{F <: AbstractFloat, V <: AbstractVector{<:Real}}
  gammaC::F
  gammaT::F
  etaC::V  # K
  etaT::V  # K
  mu::V  # K. mixture loc.
  sigma::V  # K. mixture scale.
  nu::V  # K. mixture df.
  phi::V  # K. mixture skew.
end

function StateAM(data::Data, prior::PriorAM)
  gammaC = rand(prior.gamma)
  gammaT = rand(prior.gamma)
  etaC = rand(prior.eta)
  etaT = rand(prior.eta)
  mu = rand(prior.mu, prior.K)
  sigma = rand(prior.sigma, prior.K)
  nu = rand(prior.nu, prior.K)
  phi = rand(prior.phi, prior.K)

  return StateAM(gammaC, gammaT, etaC, etaT, mu, sigma, nu, phi)
end

# TODO
function tovec(s::StateAM)
  return [s.mu; log.(s.sigma); log.(s.nu); s.phi;
          logit(s.gammaC); logit(s.gammaT);
          MCMC.simplex_transform(s.etaC);
          MCMC.simplex_transform(s.etaT)]
end

mufromvec(K::Integer, v::AbstractVector{<:Real}) = v[1:K]
sigmafromvec(K::Integer, v::AbstractVector{<:Real}) = v[K+1:2K]
nufromvec(K::Integer, v::AbstractVector{<:Real}) = v[2K+1:3K]
phifromvec(K::Integer, v::AbstractVector{<:Real}) = v[3K+1:4K]
gammaCfromvec(K::Integer, v::AbstractVector{<:Real}) = v[4K+1]
gammaTfromvec(K::Integer, v::AbstractVector{<:Real}) = v[4K+2]
etaCfromvec(K::Integer, v::AbstractVector{<:Real}) = v[4K+3:5K+1]
etaTfromvec(K::Integer, v::AbstractVector{<:Real}) = v[5K+2:6K]

function fromvec!(s::StateAM, v::AbstractVector{<:Real})
  K = length(s.mu)
  s.gammaC = logistic(gammaCfromvec(K, v))
  s.gammaT = logistic(gammaTfromvec(K, v))
  s.etaC = MCMC.simplex_invtransform(etaCfromvec(K, v))
  s.etaT = MCMC.simplex_invtransform(etaTfromvec(K, v))
  s.mu = mufromvec(K, v)
  s.sigma = exp.(sigmafromvec(K, v))
  s.nu = exp.(nufromvec(K, v))
  s.phi = phifromvec(K, v)
end


mutable struct Spline
    α::AbstractVector
    k::Integer
end

#=
\[f(x)=\sum_i\alpha_iB_{i,k,t}(x)\equiv B(x)\vec{\alpha} = \langle B|\vec{\alpha}\rangle.\]
=#

(S::Spline)(Bᵢ::AbstractMatrix) = Bᵢ*S.α
function (S::Spline)(B::Vector{M}) where {M<:AbstractMatrix}
    kB = length(B)
    S.k > kB && error("No support for splines of order $(S.k) in basis of order $(kB)")
    S(B[S.k])
end
(S::Spline)(B::Basis) =  S(B.B)

export Spline


using Test
using ModelingToolkit, OrdinaryDiffEq

# Basic electric components
@parameters t
@connector function Pin(;name)
    @variables v(t) i(t)
    ODESystem(Equation[], t, [v, i], [], name=name, defaults=[v=>1.0, i=>1.0])
end

function ModelingToolkit.connect(::Type{Pin}, ps...)
    eqs = [
           0 ~ sum(p->p.i, ps) # KCL
          ]
    # KVL
    for i in 1:length(ps)-1
        push!(eqs, ps[i].v ~ ps[i+1].v)
    end

    return eqs
end

function Ground(;name)
    @named g = Pin()
    eqs = [g.v ~ 0]
    ODESystem(eqs, t, [], [], systems=[g], name=name)
end

function ConstantVoltage(;name, V = 1.0)
    val = V
    @named p = Pin()
    @named n = Pin()
    @parameters V
    eqs = [
           V ~ p.v - n.v
           0 ~ p.i + n.i
          ]
    ODESystem(eqs, t, [], [V], systems=[p, n], defaults=Dict(V => val), name=name)
end

function Resistor(;name, R = 1.0)
    val = R
    @named p = Pin()
    @named n = Pin()
    @variables v(t)
    @parameters R
    eqs = [
           v ~ p.v - n.v
           0 ~ p.i + n.i
           v ~ p.i * R
          ]
    ODESystem(eqs, t, [v], [R], systems=[p, n], defaults=Dict(R => val), name=name)
end

function Capacitor(;name, C = 1.0)
    val = C
    @named p = Pin()
    @named n = Pin()
    @variables v(t)
    @parameters C
    D = Differential(t)
    eqs = [
           v ~ p.v - n.v
           0 ~ p.i + n.i
           D(v) ~ p.i / C
          ]
    ODESystem(eqs, t, [v], [C], systems=[p, n], defaults=Dict(C => val), name=name)
end

function Inductor(; name, L = 1.0)
    val = L
    @named p = Pin()
    @named n = Pin()
    @variables v(t) i(t)
    @parameters L
    D = Differential(t)
    eqs = [
           v ~ p.v - n.v
           0 ~ p.i + n.i
           i ~ p.i
           D(i) ~ v / L
          ]
    ODESystem(eqs, t, [v, i], [L], systems=[p, n], defaults=Dict(L => val), name=name)
end


function grammar_vladislavleva_c(; erc=false)
    Base.eval(Main, :(protectedDiv(x, y) = Benchmarks.protectedDiv(x, y)))
    Base.eval(Main, :(protectedSin(x) = Benchmarks.protectedSin(x)))
    Base.eval(Main, :(protectedCos(x) = Benchmarks.protectedCos(x)))
    grammar = erc ?
        (@grammar begin
            R = x
            R = y
            R = R + R
            R = R - R
            R = R * R
            R = protectedDiv(R,R)
            R = R^2
            R = exp(R)
            R = exp(-R)
            R = protectedSin(R)
            R = protectedCos(R)
            R = abs(R)^E
            R = R + E
            R = R * E
            E = _(5*rand())
        end) :
        (@grammar begin
            R = x
            R = y
            R = R + R
            R = R - R
            R = R * R
            R = protectedDiv(R,R)
            R = R^2
            R = exp(R)
            R = exp(-R)
            R = protectedSin(R)
            R = protectedCos(R)
            R = abs(R)^E
            R = R + E
            R = R * E
            E = |(-5.0:1.0:5.0)
        end)
    grammar
end

gt_vladislavleva_3(x,y) = exp(-x)*x^3*(protectedCos(x)*protectedSin(x))*(protectedCos(x)*protectedSin(x)^2-1)*(y-5)
function loss_vladislavleva_3(S::SymbolTable, tree::RuleNode, grammar::Grammar)
    ex = get_executable(tree, grammar)
    los = 0.0
    n = 0
    for x in 0.05:0.1:10.0, y in 0.05:2.0:10.05
        S[:x] = x
        S[:y] = y
        los += abs2(Core.eval(S,ex) - gt_vladislavleva_3(x,y))
        n += 1
    end
    los / n
end

function run_vladislavleva_3(seed::Int=0, n_pop::Int=300)
    Random.seed!(seed)

    grammar = grammar_vladislavleva_c(erc=true)
    S = SymbolTable(grammar, ExprOptimization.Benchmarks)
    loss(tree::RuleNode, grammar::Grammar) = loss_vladislavleva_3(S, tree, grammar)

    init_method = RandomInit()
    select_method = TournamentSelection(3)
    p = GeneticProgram(n_pop, #pop_size
                       40,  #iterations
                       10,  #max_depth
                       0.4, #p_reproduction
                       0.5, #p_crossover
                       0.1, #p_mutation
                       init_method=init_method,
                       select_method=select_method)
    tstart = CPUtime_us()
    result = optimize(p, grammar, :R, loss)
    dt = CPUtime_us() - tstart
    result, dt
end


module TwoPointFunctions

using ForwardDiff
using StaticArrays

export cosχ
export ∇ϕ₁cosχ
export ∇ϕ₂cosχ
export ∂²θ₁cosχ
export ∂²θ₂cosχ
export ∂²ϕ₁cosχ
export ∂²ϕ₂cosχ
export ∇₁cosχ
export ∇₂cosχ

_cosχ2(x) = sin(x[1])sin(x[3])cos(x[2] - x[4])
_cosχ2((θ₁, ϕ₁), (θ₂, ϕ₂)) = sin(θ₁)sin(θ₂)cos(ϕ₁ - ϕ₂)
cosχ(x::AbstractVector{<:Real}) = cos(x[1])cos(x[3]) + _cosχ2(x)

∂cosχ(x) = ForwardDiff.gradient(cosχ, x)
∂²cosχ(x) = ForwardDiff.hessian(cosχ, x)

"""
	cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Cosine of the angle between the unit vectors denoted by the spherical coordinates
``(\\theta_1,\\phi_1)`` and ``(\\theta_2,\\phi_2)``. This is equivalent to the cosine of the
angular distance along a great circle between these two points on a sphere.

Numerically this is given by
```math
\\cos\\chi((\\theta_1,\\phi_1), (\\theta_2,\\phi_2)) = \\cos(\\theta_1)\\cos(\\theta_2) +
\\sin(\\theta_1)\\sin(\\theta_2)\\cos(\\phi_1 - \\phi_2)
```

The unicode character at the end of the function's name is `\\chi`, and not `x`.

# Examples
```jldoctest
julia> cosχ((0,0), (0,0))
1.0

julia> cosχ((0,0), (pi,0))
-1.0
```
"""
function cosχ end

"""
	∂θ₁cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Partial derivative of [`cosχ`](@ref) with respect to `θ₁`.

# Examples
```jldoctest
julia> ∂θ₁cosχ((0,0), (0,0))
0.0

julia> ∂θ₁cosχ((0,0), (pi/2,0))
1.0
```

See also: [`∂θ₂cosχ`](@ref)
"""
function ∂θ₁cosχ end

"""
	∂θ₂cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Partial derivative of [`cosχ`](@ref) with respect to `θ₂`.

# Examples
```jldoctest
julia> ∂θ₂cosχ((0,0), (0,0))
0.0

julia> ∂θ₂cosχ((0,0), (pi/2,pi/2))
-1.0
```

See also: [`∂θ₁cosχ`](@ref)
"""
function ∂θ₂cosχ end

"""
	∂ϕ₁cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Partial derivative of [`cosχ`](@ref) with respect to `ϕ₁`.

# Examples
```jldoctest
julia> ∂ϕ₁cosχ((pi/2, 0), (pi/2, pi/3)) ≈ sin(pi/3)
true

julia> ∂ϕ₁cosχ((pi/2, 0), (pi/2, pi/2))
1.0
```

See also: [`∂ϕ₂cosχ`](@ref)
"""
function ∂ϕ₁cosχ end

"""
	∂ϕ₂cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Partial derivative of [`cosχ`](@ref) with respect to `ϕ₂`.

# Examples
```jldoctest
julia> ∂ϕ₂cosχ((pi/2, 0), (pi/2, pi/3)) ≈ -sin(pi/3)
true

julia> ∂ϕ₂cosχ((pi/2, 0), (pi/2, pi/2))
-1.0
```

See also: [`∂ϕ₁cosχ`](@ref)
"""
function ∂ϕ₂cosχ end

for fn in ["cosχ","∂cosχ","∂²cosχ"]
	@eval $(Symbol(fn))((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = $(Symbol(fn))(@SVector [θ₁,ϕ₁,θ₂,ϕ₂])
end

# use subscripted variables
subscripts = Dict(1=>'\u2081',2=>'\u2082') # \_1 and \_2
powers = Dict(2=>'\u00B2',3=>'\u00B3')
for n in 4:9
	powers[n] = '\u2070' + n
end

# First derivatives
for pt in 1:2, (coord_ind,coord) in enumerate(("θ","ϕ"))
	fname = Symbol("∂$(coord)$(subscripts[pt])cosχ")
	flat_ind = (pt-1)*2 + coord_ind
	@eval ($fname)(n1, n2) = ∂cosχ(n1, n2)[$flat_ind]
	@eval export $fname
end

# Second derivatives
for pt1 in 1:2, (coord1_ind,coord1) in enumerate(("θ","ϕ"))
	for pt2 in 1:2, (coord2_ind,coord2) in enumerate(("θ","ϕ"))
		fname = Symbol("∂$(coord1)$(subscripts[pt1])∂$(coord2)$(subscripts[pt2])cosχ")
		coord1_flat_ind = (pt1-1)*2 + coord1_ind
		coord2_flat_ind = (pt2-1)*2 + coord2_ind
		@eval ($fname)(n1, n2) = ∂²cosχ(n1, n2)[$coord1_flat_ind,$coord2_flat_ind]
	end
end

"""
	∂²θ₁cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Second derivative of [`cosχ`](@ref) with respect to `θ₁`.

# Examples
```jldoctest
julia> ∂²θ₁cosχ((0,0), (0,0))
-1.0

julia> ∂²θ₁cosχ((0,0), (pi,0))
1.0
```
"""
∂²θ₁cosχ(n1, n2) = -cosχ(n1, n2)

"""
	∂²θ₂cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Second derivative of [`cosχ`](@ref) with respect to `θ₂`.

# Examples
```jldoctest
julia> ∂²θ₂cosχ((0,0), (0,0))
-1.0

julia> ∂²θ₂cosχ((0,0), (pi,0))
1.0
```
"""
∂²θ₂cosχ(n1, n2) = -cosχ(n1, n2)

"""
	TwoPointFunctions.∂θ₁∂θ₂cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Mixed derivative of [`cosχ`](@ref) with respect to `θ₁` and `θ₂`.

# Examples
```jldoctest
julia> TwoPointFunctions.∂θ₁∂θ₂cosχ((0,0), (0,0))
1.0

julia> TwoPointFunctions.∂θ₁∂θ₂cosχ((0,0), (pi,0))
-1.0
```
"""
function ∂θ₁∂θ₂cosχ end

# The following functions are not defined if the gradients are evaluated at the poles

"""
	∇ϕ₁cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

The ``{\\hat{\\phi}}_1`` component of ``\\nabla_1 \\cos \\chi`` in the spherical polar basis.
Numerically this is equivalent to

```math
\\nabla \\phi_1 \\cos\\chi ((\\theta_1, \\phi_1), (\\theta_2, \\phi_2)) =
\\frac{1}{\\sin(\\theta_1)} \\partial \\phi_1 \\cos\\chi ((\\theta_1, \\phi_1), (\\theta_2, \\phi_2))
```

except `∇ϕ₁cosχ` is defined to avoid coordinate singularities at the poles.

# Examples
```jldoctest
julia> ∇ϕ₁cosχ((0, 0), (0, 0))
-0.0

julia> ∇ϕ₁cosχ((pi/2, 0), (pi/2, pi/2))
1.0
```

See also: [`∂ϕ₁cosχ`](@ref), [`∇ϕ₂cosχ`](@ref)
"""
∇ϕ₁cosχ((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = -sin(θ₂)sin(ϕ₁-ϕ₂)

"""
	∇ϕ₂cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

The ``{\\hat{\\phi}}_2`` component of ``\\nabla_2 \\cos \\chi`` in the spherical polar basis.
Numerically this is equivalent to

```math
\\nabla \\phi_2 \\cos\\chi ((\\theta_1, \\phi_1), (\\theta_2, \\phi_2)) =
\\frac{1}{\\sin(\\theta_2)} \\partial \\phi_2 \\cos\\chi ((\\theta_1, \\phi_1), (\\theta_2, \\phi_2))
```

except `∇ϕ₂cosχ` is defined to avoid coordinate singularities at the poles.

# Examples
```jldoctest
julia> ∇ϕ₂cosχ((0, 0), (0, 0))
0.0

julia> ∇ϕ₂cosχ((pi/2, 0), (pi/2, pi/2))
-1.0
```

See also: [`∂ϕ₂cosχ`](@ref), [`∇ϕ₁cosχ`](@ref)
"""
∇ϕ₂cosχ((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = sin(θ₁)sin(ϕ₁-ϕ₂)

"""
	∂²ϕ₁cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Second derivative of [`cosχ`](@ref) with respect to `ϕ₁`.

# Examples
```jldoctest
julia> ∂²ϕ₁cosχ((0,0), (0,0))
-0.0

julia> ∂²ϕ₁cosχ((pi/2,0), (pi/2, 0))
-1.0
```
"""
function ∂²ϕ₁cosχ end

"""
	∂²ϕ₂cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Second derivative of [`cosχ`](@ref) with respect to `ϕ₂`.

# Examples
```jldoctest
julia> ∂²ϕ₂cosχ((0,0), (0,0))
-0.0

julia> ∂²ϕ₂cosχ((pi/2,0), (pi/2, 0))
-1.0
```
"""
function ∂²ϕ₂cosχ end

for pt in 1:2
	∂²ϕ = "∂²ϕ$(subscripts[pt])cosχ"
	∂ϕ∂ϕ = "∂ϕ$(subscripts[pt])∂ϕ$(subscripts[pt])cosχ"
	@eval $(Symbol(∂²ϕ))(n1, n2) = -_cosχ2(n1, n2)
end

∇ϕ₁∂ϕ₁cosχ((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = -sin(θ₂)cos(ϕ₁-ϕ₂)
∇ϕ₁∂ϕ₂cosχ((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = sin(θ₂)cos(ϕ₁-ϕ₂)
∂ϕ₁∇ϕ₂cosχ((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = sin(θ₁)cos(ϕ₁-ϕ₂)
∇ϕ₂∂ϕ₂cosχ((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = -sin(θ₂)cos(ϕ₁-ϕ₂)
∂ϕ₁∇ϕ₁cosχ((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = ∇ϕ₁∂ϕ₁cosχ((θ₁,ϕ₁),(θ₂,ϕ₂))
∂ϕ₂∇ϕ₂cosχ((θ₁,ϕ₁)::Tuple{<:Real,<:Real}, (θ₂,ϕ₂)::Tuple{<:Real,<:Real}) = ∇ϕ₂∂ϕ₂cosχ((θ₁,ϕ₁),(θ₂,ϕ₂))

# Higher-order derivatives wrt. ϕ
for pt in 1:2
	for order in 3:2:9
		fn = "∂$(powers[order])ϕ$(subscripts[pt])cosχ"
		@eval $(Symbol(fn))(n1, n2) = $(Symbol("∂ϕ$(subscripts[pt])cosχ"))(n1, n2) * real(im^($order-1))
	end
	for order in 4:2:8
		fn = "∂$(powers[order])ϕ$(subscripts[pt])cosχ"
		@eval $(Symbol(fn))(n1, n2) = $(Symbol("∂²ϕ$(subscripts[pt])cosχ"))(n1, n2) * real(im^($order-2))
	end
end

"""
	∇₁cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Return
``(\\hat{\\theta}_1 \\cdot \\nabla_1 \\cos \\chi,\\,
\\hat{\\phi}_1 \\cdot \\nabla_1 \\cos \\chi)``, where ``{\\nabla}_1`` represents
the gradient on the unit sphere with respect to ``(\\theta_1, \\phi_1)``.

# Examples
```jldoctest
julia> ∇₁cosχ((0,0), (pi/2, pi/3)) == (cos(pi/3), sin(pi/3))
true
```
"""
function ∇₁cosχ end

"""
	∇₂cosχ((θ₁,ϕ₁), (θ₂,ϕ₂))

Return
``(\\hat{\\theta}_2 \\cdot \\nabla_2 \\cos \\chi,\\,
\\hat{\\phi}_2 \\cdot \\nabla_2 \\cos \\chi)``, where ``{\\nabla}_2`` represents
the gradient on the unit sphere with respect to ``(\\theta_2, \\phi_2)``.

# Examples
```jldoctest
julia> ∇₂cosχ((0,0), (pi/2, pi/3))
(-1.0, -0.0)
```
"""
function ∇₂cosχ end

for pt in 1:2
	fname = "∇$(subscripts[pt])cosχ"
	∇θ = "∂θ$(subscripts[pt])cosχ"
	∇ϕ = "∇ϕ$(subscripts[pt])cosχ"
	@eval $(Symbol(fname))(n1, n2) = ($(Symbol(∇θ))(n1, n2), $(Symbol(∇ϕ))(n1, n2))
end

end


export
    tsp_hobo

# functions below assumes that hobos accepts ony one or zero

function are_same(hobos::Vector{HOBO{T}}, n::Int) where T<:Real
    bit_length = length(hobos)
    bits = reverse(digits(n, base=2, pad=bit_length))
    @assert length(bits) == bit_length
    reduce(and, [b == 0 ? one(T) - h : h  for (h,b)=zip(hobos, bits)])
end

function are_different(v1::Vector{HOBO{T}}, v2::Vector{HOBO{T}}) where T<:Real
    neg(reduce(or, xor.(v1, v2)))
end

# assumes numbering from 0 and that the vectors of HOBO
# are of the form var[...]
function smaller_equal_than(hobos::Vector{HOBO{T}}, n::Int) where T<:Real
    k = length(hobos)
    # no point for n = 2^k-1 or n = 0
    @assert n+1 == 2^k || 1 <= n <= 2^k-2
    bits = reverse(digits(n, base=2))
    zero_pos = findall(x->x==0, bits)
    @assert bits[1] == 1
    @assert length(bits) == k

    result = zero(T)*hobos[1]
    for ind=zero_pos
        result_tmp = prod(b==0 ? one(T) - h : h for (h,b)=zip(hobos, bits[1:ind-1]))
        result += result_tmp*hobos[ind]
    end
    result
end

function _hamiltonian_cycle_complete_hobo(vars::Variables)
    n, logn = indexcapacities(vars)
    hobo = HOBO(vars)

    # prohibit numbers
    for t = 1:n
        hobo += smaller_equal_than([vars[t,i] for i=1:logn], n-1)
    end

    # different for different time
    for t1 = 1:n, t2 = (t1+1):n
        hobo += are_different([vars[t1,i] for i=1:logn], [vars[t2,i] for i=1:logn])
    end
    hobo
end

function _hobo_tsp_part(w::Matrix{T}, vars::Variables) where T<:Real
    n = size(w, 1)
    logn = ceil(Int, log(2, n))

    @assert size(w, 2) == n
    hobo = HOBO(vars)

    # prohibit numbers
    for t=1:n, u=1:n, v=1:n
        if u != v
            hobo_tmp = are_same([vars[t,i] for i=1:logn], u-1)
            hobo_tmp *= are_same([vars[t%n+1,i] for i=1:logn], v-1)
            hobo += w[u,v]*hobo_tmp
        end
    end
    hobo
end

function tsp_hobo(w::Matrix{T}, vars::Variables; B::Real=1., A::Real=2*B*maximum(w)) where T<:Real
    n = size(w, 1)
    logn = ceil(Int, log(2, n))

    @assert size(w, 2) == n
    @assert indexcapacities(vars) == [n, logn]
    @assert A >= zero(T) && B >= zero(T)

    A*_hamiltonian_cycle_complete_hobo(vars) + B*_hobo_tsp_part(w, vars)
end

function tsp_hobo(w::Matrix; name::String="TSP", B::Real=1., A::Real=2*B*maximum(w))
    n = size(w, 1)
    tsp_hobo(w, Variables(name, [n, ceil(Int, log(2, n))], ["timestep", "city_binary"]), B=B, A=A)
end


"""
Structure for UFOFDM
# --- Syntax
- nFFT		: FFT size [Int]
- L			: Filter size [Int]
- allocatedSubcarriers	: Vector of allocated subbcarriers [Array{Int}]
- sizeRB	: Carrier size in terms of subcarrier (often 12)  [Array{Int}]
- applyPD	: Apply Pre-distortion at Tx stage (default 1, 0 : not applied) [Int=0 or 1]
- attenuation : dolphChebyshev filter attenation
- filterTaps  : Filter impulse response coefficient [Array{Float64}]
- pd		  : PD coefficients [Array{Complex{Float64}}]
# ---
# v 1.0 - Robin Gerzaguet.
"""
struct StrucUFOFDM<: Waveform
	nFFT::Int;
	L::Int;
	allocatedSubcarriers::Array{Int};
	sizeRB::Int;
	applyPD::Int;
	attenuation::Float64;
	cir::Array{Float64};
	pd::Array{Complex{Float64}};
end

"""
---
Create UF-OFDM structure
# --- Syntax
ufofdm	= initUFOFDM(nFFT,L,allocatedSubcarriers;sizeRB=12,applyPD=1,attenuation=40)
# --- Input parameters
- nFFT		: FFT size [Int]
- L			: Filter size [Int]
- allocatedSubcarriers	: Vector of allocated subbcarriers [Array{Int}]
- sizeRB	: Carrier size in terms of subcarrier (often 12)  [Array{Int}]
- applyPD	: Apply Pre-distortion at Tx stage (default 1, 0 : not applied) [Int=0 or 1]
- attenuation : dolphChebyshev filter attenation
- filterTaps  : Filter impulse response coefficient [Array{Float64}]-
# --- Output parameters
- ufofdm	: UF-OFDM structure [StrucUFOFDM]
# ---
# v 1.0 - Robin Gerzaguet.
"""
function initUFOFDM(nFFT,L,allocatedSubcarriers;sizeRB=12,applyPD=1,attenuation=40)
	# ---Checking FFT size
	if maximum(allocatedSubcarriers) > nFFT
		error("Subcarrier allocation is impossible");
	end
	# --- Create the filter response
	filterTaps = dolphChebyshev(L,attenuation);
	# --- Calculate PD coefficients
	pdCoeffs	= calcPDCoefficients(filterTaps,nFFT,sizeRB,allocatedSubcarriers,applyPD);
	# --- Create the OFDM structure
	return StrucUFOFDM(nFFT,L,allocatedSubcarriers,sizeRB,applyPD,attenuation,filterTaps,pdCoeffs);
end

"""
---
Apply Universal Filtered Orthogonal Frequency Division Multiplexing (UF-OFDM) to the time frequency matrix qamMat and returns a time domain UF-OFDM signal [1,2]

ufofdm is parametrized by its FFT size, the filter length (in samples) and the allocatedSubcarriers vector. Optional parameters are carrier size in subcarrier (by default RB size which is 12) Dolph-Chebyshev window attenuation (40) and predistortion application (set to 1)
# --- Syntax
sigId	= genereSignalufofdm(qamMat,nFFT,nCp,allocatedSubcarriers;sizeRB=12,applyPD=1,attenuation=40)
# ---  Input parameters
- qamMat  : Time frequency matrix : [Array{Complex{Float64},nbSubcarriers,nbSymb}]
- nbSymb			: Number of ufofdm symbol tro be transmitted
- nbSubcarriers	: Number of allocated subcarriers (shall be < nFFT)
- nFFT	  : FFT size [Int]
- L		  : Dolph Chebyshev filter length [Int]
- allocatedSubcarriers : Vector of index of allocated subcarriers [Array{Int,nbSubcarriers}]
- sizeRB  : Carrier size in subcarriers (default : LTE RB size: 12) [Int]
- applyPD : Filter shape compensation (enabled by default) [Int]
- attenuation : DC filter attenation in dB (default: 90) [Float64]
- filterTaps : Filter coefficient (default empty and recreated)
- pdCoeffs	  : Predistortion  Filter coefficient (default empty and recreated)
# ---  Output parameters
- sigId	  : ufofdm signal in time domain [Array{Complex{Float64},nbEch}]
# References
- [1] R. Gerzaguet and al. The 5G candidate waveform race: a comparison of complexity and performance. EURASIP Journal on Wireless Communications and Networking, 2017
- [2] V. Vakilian and al: Universal-filtered multi-carrier technique for wireless systems beyond LTE. Proc. IEEE Globecom Workshops (GC Wkshps), 2013
# ---
# v 1.0 - Robin Gerzaguet.
"""
function ufofdmSigGen(qamMat,nFFT,L,allocatedSubcarriers;sizeRB=12,applyPD=1,attenuation=40,filterTaps=Array{Float64}(undef,0),pdCoeffs=Array{Float64}(undef,0))
	# ----------------------------------------------------
	# --- Getting filter info
	# ----------------------------------------------------
	if isempty(filterTaps)
		# No filter is given, so create one based on input parameters
		filterTaps = dolphChebyshev(L,attenuation);
	end
	# ----------------------------------------------------
	# --- UF-OFDM parameters
	# ----------------------------------------------------
	# --- Symbol size (classic convolution)
	sizeSymb	  = nFFT + L - 1;
	nbSymb		  = size(qamMat,2);
	# --- Number of physical RB (i.e carriers)
	nbRB		  = Int(floor(length(allocatedSubcarriers) / sizeRB));
	nbDataSubcarrier = length(allocatedSubcarriers);
	# ----------------------------------------------------
	# --- Initiatiaisation of filter parameters
	# ----------------------------------------------------
	if isempty(pdCoeffs)
		pdCoeffs = calcPDCoefficients(filterTaps,nFFT,sizeRB,allocatedSubcarriers,applyPD)
	end
	# ----------------------------------------------------
	## --- Signal Generation
	# ----------------------------------------------------
	# --- Init Signal vector
	sigId					= zeros(Complex{Float64},sizeSymb,nbSymb);
	carrierRB		  = zeros(Int,nbRB,sizeRB);
	filterMat	  = zeros(Complex{Float64},nbRB,L);		# Matrix of frequency translated filterStopBand
	# --- Iterative UFOFDM signal generation
	for iB = 1 : 1 : nbSymb
	    # --- Init sum operator
		filteredSignal		  = zeros(Complex{Float64},nbRB,sizeSymb);
		# --- Pre-disrtorition
		matrixSeqPD			  = qamMat[:,iB] ./ pdCoeffs;
	    # --- Filtering Ressource blocks
	    for iB1 = 1 : 1: nbRB
			if iB == 1
				# --- Init subcarrier in carrier matrix
				carrierRB[iB1,:]	  = allocatedSubcarriers[1+(iB1-1)*sizeRB:iB1*sizeRB];
				# --- Spectrum location of given carrier
				spectrumLoc			  = carrierRB[iB1,1] + (carrierRB[iB1,end]-carrierRB[iB1,1])/2;
				# --- Getting frequency shift filter
				filterMat[iB1,:]      = filterTaps .* (exp.(2*1im*pi*spectrumLoc*(0:L-1)/nFFT));
			end
	        # --- Block Data to send
	        dataBlock			  = matrixSeqPD[1+(iB1-1)*sizeRB:iB1*sizeRB];
	        # --- Setting data to adequate RB
			sigToFFT			  = zeros(Complex{Float64},nFFT);
			sigToFFT[carrierRB[iB1,:]]	  = dataBlock;
	        # --- IFFT
	        sigIFFT				  = ifft(sigToFFT,1);
	        # --- Apply filter on RB
	        #convT!(filteredSignal[iB1,:],sigIFFT,filterMat[iB1,:]);
	        filteredSignal[iB1,:] = convT(sigIFFT,filterMat[iB1,:]);
	    end
	    # --- Output UFOFDM symbol
	    sigId[:,iB]	  = sum(filteredSignal,dims=1);
	end
	sigId = sigId[:];
	return sigId;
end
# --- MD is waveform structure is given

"""
---
Perform time domain naive convolution. For UF-OFDM, size of filter is small so doing it in frequency domain is not appropriate
# --- Syntax
 c = convT(a,b);
# --- Input parameters
- a	  : First signal
- b	  : Second signal
# --- Output parameters
- c	  : a * b
# ---
# v 1.0 - Robin Gerzaguet.
"""
function convT(a::Array{T},b::Array{T}) where T
	# --- Output container
	c = zeros(T,length(a)+length(b)-1);
	# --- Double foor loop
	@inbounds for j=1: length(a)
		for k=1: length(b)
            c[j+k-1] += a[j]*b[k]
        end
    end
    return c;
end
function convT!(c::Array{T},a::Array{T},b::Array{T}) where T
	# --- Double foor loop
	@inbounds for j=1: length(a)
		for k=1: length(b)
            c[j+k-1] += a[j]*b[k]
        end
    end
    return c;
end

function calcPDCoefficients(filterTaps,nFFT,sizeRB,allocatedSubcarriers,applyPD)
# Purpose is to defined each filter translated at the appropriate carrier frequency (center of carrier group)
	# Init variables
	sF			  = 0;
	nbRB		  = Int(floor(length(allocatedSubcarriers) / sizeRB));
	L			  = length(filterTaps);
	carrierRB	  = zeros(Int,nbRB,sizeRB);		# Matrix of carriers (carrier on line, subcarrier on column)
	filterMat	  = zeros(Complex{Float64},nbRB,L);		# Matrix of frequency translated filterStopBand
	# --- Iterative spectrum generation
	for iB1 = 1 : 1 : nbRB
		# --- Getting current RB
		carrierRB[iB1,:]	  = allocatedSubcarriers[1+(iB1-1)*sizeRB:iB1*sizeRB];
		# --- Finding carrier centering
		spectrumLoc			  = carrierRB[iB1,1] + (carrierRB[iB1,end]-carrierRB[iB1,1])/2;
		# --- Setting ones to appropriate spectral location
		# Setting 1 on the carrier to get the amplitude reduction for PD stage
		sigToFFT			  = zeros(Complex{Float64},nFFT);
		sigToFFT[carrierRB[iB1,:]]	  = ones(Complex{Float64},sizeRB);
		# --- Switch to time domain
		ifft!(sigToFFT);
		# --- Getting frequency shift filter
		filterMat[iB1,:]      = filterTaps .* (exp.(2*1im*pi*spectrumLoc*(0:L-1)/nFFT));
		# --- Apply filter on RB
		sF					  = sF .+ convT(sigToFFT,filterMat[iB1,:]);
	end
	# --- Pre-distortion stage
	if applyPD == 1
		# --- Apply FFT on ZP data
		sigFFT		  = fft([sF;zeros(Complex{Float64},nFFT-L+1)]);
		# --- Channel estimation for current symbol
		sigPD         = sigFFT[1:2:end];
		# --- Deduce pre-distortion filter
		chestPD		  = sigPD[allocatedSubcarriers];
	else
		# --- No predistortion filter
		chestPD		  = ones(Int,nbDataSubcarrier);
	end
	return chestPD
end

"""
---
Apply Universal Filtered Orthogonal Frequency Division Multiplexing (UF-OFDM) to the time frequency matrix qamMat and returns a time domain UF-OFDM signal [1,2]

ufofdm is parametrized by its FFT size, the filter length (in samples) and the allocatedSubcarriers vector. Optional parameters are carrier size in subcarrier (by default RB size which is 12) Dolph-Chebyshev window attenuation (40) and predistortion application (set to 1)
# --- Syntax
sigId	= genereSignalufofdm(qamMat,ufofdm);
# ---  Input parameters
- qamMat  : Time frequency matrix : [Array{Complex{Float64},nbSubcarriers,nbSymb}]
- ufofdm  : UF-OFDM structure [StrucUFOFDM]
# ---  Output parameters
- sigId	  : ufofdm signal in time domain [Array{Complex{Float64},nbEch}]
# References
- [1] R. Gerzaguet and al. The 5G candidate waveform race: a comparison of complexity and performance. EURASIP Journal on Wireless Communications and Networking, 2017
- [2] V. Vakilian and al: Universal-filtered multi-carrier technique for wireless systems beyond LTE. Proc. IEEE Globecom Workshops (GC Wkshps), 2013
# ---
# v 1.0 - Robin Gerzaguet.
"""
function ufofdmSigGen(qamMat,ufofdm::StrucUFOFDM)
	return ufofdmSigGen(qamMat,ufofdm.nFFT,ufofdm.L,ufofdm.allocatedSubcarriers,sizeRB=ufofdm.sizeRB,applyPD=ufofdm.applyPD,attenuation=ufofdm.attenuation);
end


using LinearAlgebra
using CompScienceMeshes
using SauterSchwab3D

const pI   = point(1,5,3)
const pII  = point(2,5,3)
const pIII = point(7,1,0)
const pIV  = point(3,4,0)

const qI  = point(10,11,12)
const qII  = point(10,11,13)
const qIII = point(11,11,12)

const P = simplex(pI,pII,pIII,pIV)
const Q = simplex(qI,qII,qIII)

const sing = SauterSchwab3D.singularity_detection(P,Q)

Accuracy2 = 16
pd_ref = PositiveDistance5D(sing,SauterSchwab3D._legendre(Accuracy2,0.0,1.0))

function integrand(x,y)
			return(exp(-im*1*norm(x-y))/(4pi*norm(x-y)))
end

function INTEGRAND(u,v)
   j1 = volume(P) * factorial(dimension(P))
   j2 = volume(Q) * factorial(dimension(Q))
   x = barytocart(P,u)
   y = barytocart(Q,v)
   output = integrand(x,y)*j1*j2
   return(output)
end

print("Ref: ")
ref = sauterschwab_parameterized(INTEGRAND, pd_ref)
println(ref)
println()

res_tp =[]
res_sp =[]
res_gm =[]
n1 = []
n2 = []
n3 = []

for i in 2:1:14
   Accuracy = i
   pd = PositiveDistance5D(sing,SauterSchwab3D._legendre(Accuracy,0.0,1.0))

   int_tp = sauterschwab_parameterized(INTEGRAND, pd)

   num_pts = length(pd.qps)^5
   push!(n1,num_pts)
   push!(res_tp,int_tp)
end

for i in 2:1:7
   Accuracy = i
   pd_s = PositiveDistance5D_S(sing,(SauterSchwab3D._shunnham3D(Accuracy),
                                SauterSchwab3D._shunnham2D(Accuracy)))

   int_sp = sauterschwab_parameterized(INTEGRAND, pd_s)

   num_pts = length(pd_s.qps[1])*length(pd_s.qps[2])
   push!(n2,num_pts)
   push!(res_sp,int_sp)
end

for i in 2:1:12
   Accuracy = i
   pd_gm = PositiveDistance5D_S(sing,(SauterSchwab3D._grundmannMoeller3D(2*Accuracy-1),
                                SauterSchwab3D._grundmannMoeller2D(2*Accuracy-1)))

   int_gm = sauterschwab_parameterized(INTEGRAND, pd_gm)

   num_pts = length(pd_gm.qps[1])*length(pd_gm.qps[2])
   push!(n3,num_pts)
   push!(res_gm,int_gm)
end

err_tp = norm.(res_tp.-ref)/norm(ref)
err_sp = norm.(res_sp.-ref)/norm(ref)
err_gm = norm.(res_gm.-ref)/norm(ref)

println(err_tp)
println(err_sp)
println(err_gm)

using Plots
plot( yaxis=:log, xaxis=:log, fontfamily="Times")
plot!(n1,err_tp, label="Gauss Tensor-Product",markershape=:circle)
plot!(n2,err_sp, label="Simplex Tensor-Product",markershape=:rect)
#plot!(n3,err_gm, label="Simplex-Product GM",markershape=:x)
plot!(xlims=(5,10^5),ylims=(1e-10,1e-1))
plot!(xlabel="Quad. points/Func. evals.", ylabel="Rel. Error.", title="Positive Distance 5D",legend=:bottomleft)

using BenchmarkTools

ref = 0.0024685313290508802 + 0.002663638878229659im

pd = PositiveDistance5D(sing,SauterSchwab3D._legendre(4,0.0,1.0))

int_tp = sauterschwab_parameterized(INTEGRAND, pd)

num_pts = length(pd.qps)^5
println(num_pts)
err_tp = norm.(int_tp.-ref)/norm(ref)
println(err_tp)
@time for i in 1:100 sauterschwab_parameterized(INTEGRAND, pd) end

pd_s = PositiveDistance5D_S(sing,(SauterSchwab3D._shunnham3D(4),
SauterSchwab3D._shunnham2D(4)))

int_sp = sauterschwab_parameterized(INTEGRAND, pd_s)

num_pts = length(pd_s.qps[1])*length(pd_s.qps[2])
println(num_pts)
err_sp = norm.(int_sp.-ref)/norm(ref)
println(err_sp)
@time for i in 1:100 sauterschwab_parameterized(INTEGRAND, pd_s) end

#=

pd_gm = PositiveDistance5D_S((SauterSchwab3D._grundmannMoeller3D(2*4-1),
SauterSchwab3D._grundmannMoeller2D(2*4-1)))

int_gm = sauterschwab_parameterized(INTEGRAND, pd_gm)

num_pts = length(pd_gm.qps[1])*length(pd_gm.qps[2])
println(num_pts)
err_gm = norm.(int_gm.-ref)/norm(ref)
println(err_gm)
=#


"""
    surrogate_model(plan::AbstractArray{T,2}, samples::AbstractArray{T,2}; options=Options()) where T

Returns a surrogate model function based on an optimized Radial Basis Function
interpolant. Depending on the supplied options; the kernel, kernel width and scaling of
input data is optimized.

...
# Arguments
- `plan::AbstractArray{T,2}`:
    sample locations where each column corresponds to the location of one point.
    `size(plan) = (num_dimensions,num_samples)`.
- `samples::AbstractArray{T,2}`:
    function value at each sample location. each column contains one value from
    the corresponding plan location. `size(samples) = (1,num_samples)`.
- `options=Options()`:
    all options available to customize the surrogate optimization.
...
"""
function surrogate_model(plan::AbstractArray{T,2}, samples::AbstractArray{T,2}; options::Options=Options()) where T

    @unpack num_interpolants, trace, parallel_surrogate, smooth = options

    (trace == :verbose) && println("Creating optimized surrogate model")

    (length(samples) != size(plan,2)) && error("plan and samples do not have the correct sizes")

    @timeit_debug "hypers opt" begin
    #Optimize RBF hypers for the ensamble of interpolants
    if parallel_surrogate
        mres = pmap(  (x)->rbf_hypers_opt(samples, plan, options),
                        1:num_interpolants)
    else
        mres = map(  (x)->rbf_hypers_opt(samples, plan, options),
                        1:num_interpolants)
    end
    end

    optres = first.(mres)
    fitness = last.(mres)

    #Print hyperparameter results
    (trace == :verbose) && print_sm_opt(fitness,optres,smooth)

    # Sample order for ScatteredInterpolation
    samples_vec = vec(samples)

    # Interpolation object based on the optimization results
    itp = Array{ScatteredInterpolation.RBFInterpolant,1}(undef,num_interpolants)
    for i = 1:num_interpolants
        # Preprocess the points based on the settings used.
        preprocessed_point = preprocess_point(plan,optres[i].scaling,base_scale=plan)

        itp[i] = interpolate(optres[i].kernelFunc, preprocessed_point,
                            samples_vec, smooth=optres[i].smooth)
    end

    # Pre allocate and calculate to reduce cost of evaluating surrogate model.
    preprocessed_est_point = Array{Float64,2}(undef,size(plan,1),1)
    old_min = minimum(plan,dims=2)
    old_max = maximum(plan,dims=2)

    sm_func = (estimation_point) ->  surrogate_evaluate.(Ref(preprocessed_est_point),
                                        Ref(estimation_point),Tuple(itp),Tuple(optres),
                                        Ref(old_min),Ref(old_max))

    return sm_func, optres
end

function print_sm_opt(fitness,optres,smooth)
    print(@sprintf("    mean surrogate fitness (smaller is better)= %.5g,  (min = %.5g, max = %.5g)\n",mean(fitness),minimum(fitness),maximum(fitness)))

    sc_res = [opt.scaling./opt.scaling[1] for opt in optres]
    println("    mean dim. scale (norm. to first dim.) = ", mean(sc_res),
                                        " (min = ",minimum(sc_res),
                                        ", max = ",maximum(sc_res),")")
    sm_res = [opt.smooth for opt in optres]
    if smooth == :variable
        println("    mean smoothing = ", mean.(sm_res),
                                        " (min = ",minimum.(sm_res),
                                        ", max = ",maximum.(sm_res),") \n")
    else
        println(@sprintf("    mean smoothing = %.5g,  (min = %.5g, max = %.5g)\n",mean(sm_res),minimum(sm_res),maximum(sm_res)))
    end
end


@testset "NumField/Elem" begin
  Qx, x = PolynomialRing(FlintQQ, "x")
  QasNumberField, _ = NumberField(x - 1)
  Kt, t = PolynomialRing(QasNumberField, "t")
  K1, a1 = NumberField(x^3 - 2)
  K2, (a2, ) = NumberField([x^3 - 2])
  K3, a3 = NumberField(t^3 - 2)
  K4, (a4, ) = NumberField([t^3 - 2])

  @testset for (K, a) in [(K1, a1), (K2, a2), (K3, a3), (K4, a4)]
    b = one(K)
    fl = @inferred isintegral(b)
    @test fl
    fl = @inferred isintegral(a)

    B = @inferred basis(K)
    c = @inferred basis_matrix([one(K), a^4])
    @assert nrows(c) == 2
    @assert ncols(c) == 3
    @assert sum(B[i] * c[1, i] for i in 1:3) == one(K)
    @assert sum(B[i] * c[2, i] for i in 1:3) == a^4

    f = @inferred charpoly(one(K()))
    @test f == (gen(parent(f)) - 1)^3
    f = @inferred minpoly(one(K))
    @test f == (gen(parent(f)) - 1)

    f = @inferred charpoly(a)
    @test f == gen(parent(f))^3 - 2
    f = @inferred minpoly(a)
    @test f == gen(parent(f))^3 - 2

  end

  @testset "NumField/Component" begin
    Qx, x = PolynomialRing(FlintQQ, "x")
    QasNumberField, _ = NumberField(x - 1)
    Kt, t = PolynomialRing(QasNumberField, "t")
    K3, a3 = NumberField(t^3 - 2)
    K4, (a4, ) = NumberField([t^3 - 2])
    K41, mK4 = component(K4, 1)
    @test isisomorphic(K41, K3)[1]
    @test mK4(gen(K41)) == a4

    K1, a1 = NumberField(x^3 - 2)
    K2, (a2, ) = NumberField([x^3 - 2])
    K21, mK2 = component(K2, 1)
    @test isisomorphic(K21, K1)[1]
    @test mK2(gen(K21)) == a2
  end

end


# Expected reconstruction error for Bernoulli output with diagonal covariance

@defstruct BernoulliReconLossLayer Layer (
    name :: AbstractString = "bernoulli-recon-loss",
    (weight :: AbstractFloat = 1.0, weight >= 0),
    (bottoms :: Vector{Symbol} = Symbol[:p, :x], length(bottoms) == 2),
)

@characterize_layer(BernoulliReconLossLayer,
    has_loss  => true,
    can_do_bp => true,
    is_sink   => true,
    has_stats => true,
)

type BernoulliReconLossLayerState{T, B<:Blob} <: LayerState
    layer      :: BernoulliReconLossLayer
    loss       :: T
    loss_accum :: T
    n_accum    :: Int

    tmp_blobs :: Dict{Symbol, B}
end

function setup(backend::CPUBackend, layer::BernoulliReconLossLayer,
            inputs::Vector{Blob}, diffs::Vector{Blob})
    data_type = eltype(inputs[1])
    return BernoulliReconLossLayerState(layer, zero(data_type), zero(data_type),
                                                0, Dict{Symbol, CPUBlob}())
end

function shutdown(backend::Backend, state::BernoulliReconLossLayerState)
    for blob in values(state.tmp_blobs)
        destroy(blob)
    end
end

function reset_statistics(state::BernoulliReconLossLayerState)
    state.n_accum = 0
    state.loss_accum = zero(typeof(state.loss_accum))
end

function dump_statistics(storage, state::BernoulliReconLossLayerState, show::Bool)
    update_statistics(storage, "$(state.layer.name)-recon-loss", state.loss_accum)

    if show
      loss = @sprintf("%.4f", state.loss_accum)
      @info("  bernoulli-recon-loss (avg over $(state.n_accum)) = $loss")
    end
end

function forward(backend::CPUBackend, state::BernoulliReconLossLayerState,
            inputs::Vector{Blob})
    data_type = eltype(inputs[1])
    N = get_num(inputs[1])
    ND = length(inputs[1])
    p = inputs[1].data
    x = inputs[2].data

    state.loss = 0.
    for i in 1:ND
        state.loss -= x[i]log(p[i])
        state.loss -= (1 - x[i])log(1 - p[i])
    end
    state.loss *= state.layer.weight / N

    # accumulate statistics
    state.loss_accum *= state.n_accum
    state.loss_accum += state.loss * N
    state.loss_accum /= state.n_accum + N

    state.n_accum += N
end

function backward(backend::CPUBackend, state::BernoulliReconLossLayerState,
            inputs::Vector{Blob}, diffs::Vector{Blob})
    data_type = eltype(inputs[1])
    N = get_num(inputs[1])
    ND = length(inputs[1])
    p = inputs[1].data
    x = inputs[2].data

    if isa(diffs[1], CPUBlob)
        for i in 1:ND
            diffs[1].data[i] = x[i] / p[i] - (1 - x[i]) / (1 - p[i])
        end
        diffs[1].data[:] *= -state.layer.weight / N
    end

    if isa(diffs[2], CPUBlob)
        raise("last blob should be a data blob")
    end
end


# knn
module kNNClassifier
	import NearestNeighbors, Distances
	export kNNModel, kNN_train, kNN_exec

	using j4pr: countapp

	# kNNModel struct {T - type of neighbour, S - type of labels, U - type of distance}
	struct kNNModel{T<:Real, S<:Vector{<:Real}, U<:NearestNeighbors.NNTree, V<:Distances.Metric}
		k::T 						# k: T <: Real (if k is Int, do classic kNN, if k is Float, do range search)
		targets::S					# targets: Vector{T}, if T <: Float64, regression , classification otherwise
		treedata::U					# training data: a tree of sorts
		metric::V					# distance: Distances.Metric
		fsmooth::Function 				# smoother function::Function
		priors::Dict{Int,Float64}
	end

	# Aliases
	const kNNModelClassificationNN{T<:Int, S<:Vector{Int}} = kNNModel{T,S}
	const kNNModelClassificationR{T<:Float64, S<:Vector{Int}} = kNNModel{T,S}
	const kNNModelRegressionNN{T<:Int, S<:Vector{Float64}} = kNNModel{T,S}
	const kNNModelRegressionR{T<:Float64, S<:Vector{Float64}} = kNNModel{T,S}

	# Printers
	Base.show(io::IO, m::kNNModelClassificationNN) = print(io, "KNN model, classification, NN, k=$(m.k)")
	Base.show(io::IO, m::kNNModelClassificationR) = print(io, "KNN model, classification, range, r=$(m.k)")
	Base.show(io::IO, m::kNNModelRegressionNN) = print(io, "KNN model, regression, NN, k=$(m.k)")
	Base.show(io::IO, m::kNNModelRegressionR) = print(io, "KNN model, regression, range, r=$(m.k)")

	# Train method (classification)
	_parse_smooth_clf_(smooth::Symbol)::Function = begin
		# Check for smoothing option
		if smooth == :none fsmooth = no_smoother!
		elseif smooth == :ml fsmooth = ml_smoother!
		elseif smooth == :laplace fsmooth = laplace_smoother!
		elseif smooth == :mest fsmooth = mest_smoother!
		elseif smooth == :dist fsmooth = dist_smoother!
		else
			warn("Unrecognized posterior smoothing function, defaulting to :none")
			fsmooth = no_smoother!
		end
		return fsmooth
	end

	_parse_smooth_reg_(smooth::Symbol)::Function = begin
		# Check for smoothing option
		if smooth == :ml fsmooth = ml_smoother_r!
		elseif smooth == :dist fsmooth = dist_smoother_r!
		else
			warn("Unrecognized regression smoothing function, defaulting to `:ml`")
			fsmooth = ml_smoother_r!
		end
		return fsmooth
	end

	# Train methods (classification)
	kNN_train(x::Matrix{Float64}, y::Vector{Int}, k::Int; metric::Distances.Metric=Distances.Euclidean(), leafsize::Int=10, smooth::Symbol=:none) =
		return kNNModel(k, y, NearestNeighbors.KDTree(x, metric, leafsize=leafsize), metric, _parse_smooth_clf_(smooth), Dict(yi=>sum(yi.==y)/length(y) for yi in unique(y)))
	kNN_train(x::Matrix{Float64}, y::Vector{Int}, k::Float64; metric::Distances.Metric=Distances.Euclidean(), leafsize::Int=10, smooth::Symbol=:none) =
		return kNNModel(k, y, NearestNeighbors.BallTree(x, metric, leafsize=leafsize), metric, _parse_smooth_clf_(smooth), Dict(yi=>sum(yi.==y)/length(y) for yi in unique(y)))

	# Train methods (regression)
	kNN_train(x::Matrix{Float64}, y::Vector{Float64}, k::Int; metric::Distances.Metric=Distances.Euclidean(), leafsize::Int=10, smooth::Symbol=:ml) =
		return kNNModel(k, y, NearestNeighbors.KDTree(x, metric, leafsize=leafsize), metric, _parse_smooth_reg_(smooth), Dict{Int, Float64}())
	kNN_train(x::Matrix{Float64}, y::Vector{Float64}, k::Float64; metric::Distances.Metric=Distances.Euclidean(), leafsize::Int=10, smooth::Symbol=:ml) =
		return kNNModel(k, y, NearestNeighbors.BallTree(x, metric, leafsize=leafsize), metric, _parse_smooth_reg_(smooth), Dict{Int, Float64}())

	# Execution methods (classification)
	kNN_exec(m::kNNModelClassificationNN, x::Matrix{Float64}, classes::Vector{Int})=begin
		idx, d = NearestNeighbors.knn(m.treedata, x, m.k, true)
		out = zeros(Float64, length(classes), size(x,2))
		@inbounds for j = 1:size(x,2)
			m.fsmooth(view(out,:,j), m.targets[idx[j]], classes, d[j], m.priors)
		end
		return out
	end

	kNN_exec(m::kNNModelClassificationR, x::Matrix{Float64}, classes::Vector{Int})=begin
		idx = NearestNeighbors.inrange(m.treedata, x, m.k, false)
		out = zeros(Float64, length(classes), size(x,2))
		@inbounds for j = 1:size(x,2)
			d = zeros(Float64, size(idx[j]))
			@inbounds for i = 1: length(d)
				d[i] = Distances.evaluate(m.metric, m.treedata.data[idx[j][i]], x[:,j])
			end
			m.fsmooth(view(out,:,j), m.targets[idx[j]], classes, d, m.priors)
		end
		return out
	end

	# Execution methods (regression)
	kNN_exec(m::kNNModelRegressionNN, x::Matrix{Float64})=begin
		idx, d = NearestNeighbors.knn(m.treedata, x, m.k, true)
		out = zeros(Float64, 1, size(x,2))
		@inbounds for j = 1:size(x,2)
			m.fsmooth(view(out,:,j), m.targets[idx[j]], d[j])
		end
		return out
	end

	kNN_exec(m::kNNModelRegressionR, x::Matrix{Float64})=begin
		idx = NearestNeighbors.inrange(m.treedata, x, m.k, false)
		out = zeros(Float64, 1, size(x,2))
		@inbounds for j = 1:size(x,2)
			d = zeros(Float64, length(idx[j]))
			@inbounds for i = 1: length(d)
				d[i] = Distances.evaluate(m.metric, m.treedata.data[idx[j][i]], x[:,j])
			end
			m.fsmooth(view(out,:,j), m.targets[idx[j]], d)
		end
		return out
	end

	# Posterior smoothers (classification); parameters: labels of neighbors, unique labels in dataset and distances
	# All smoothers return a vector yu in length
	no_smoother!(out::T where T<:AbstractVector{Float64}, y::Vector{Int}, yu::Vector{Int}, d=zeros(length(y)), priors=Dict{Int,Float64}()) = begin
		n = countapp(y,yu)
		@inbounds out[findmax(n)[2]] = 1.0
		return out
	end
	ml_smoother!(out::T where T<:AbstractVector{Float64}, y::Vector{Int}, yu::Vector{Int}, d=zeros(length(y)), priors=Dict{Int,Float64}()) = begin
		n = countapp(y,yu)
		@fastmath out[:] = n/length(y)
		return out
	end
	laplace_smoother!(out::T where T<:AbstractVector{Float64}, y::Vector{Int}, yu::Vector{Int}, d=zeros(length(y)), priors=Dict{Int,Float64}()) = begin
		n = countapp(y,yu)
		@fastmath out[:] = (n+1.0)/(length(y)+length(yu))
		return out
	end

	mest_smoother!(out::T where T<:AbstractVector{Float64}, y::Vector{Int}, yu::Vector{Int}, d=zeros(length(y)), priors=Dict{Int,Float64}()) = begin
		n = countapp(y,yu)
		p = [priors[i] for i in yu]
		m = 10.0
		@fastmath out[:] = (n+m*p)/(length(y)+m)
		return out
	end

	dist_smoother!(out::T where T<:AbstractVector{Float64}, y::Vector{Int}, yu::Vector{Int}, d::Vector{Float64}, priors=Dict{Int,Float64}()) = begin
		dc = zeros(Float64, size(yu))
		m = length(yu)
		n = length(y)
		for j in 1:m
			for i in 1:n
				@inbounds if y[i] == yu[j]
					@fastmath dc[j] += 1/(eps()+d[i])
				end
			end
		end
		out[:] = dc / sum(dc)
		return out
	end

	# Posterior smoothers (regression)
	ml_smoother_r!(out::T where T<:AbstractVector{Float64}, y::Vector{Float64}, d=zeros(length(y))) = begin
		@fastmath out[1] = mean(y)
		return out
	end
	dist_smoother_r!(out::T where T<:AbstractVector{Float64}, y::Vector{Float64}, d::Vector{Float64}) = begin
		t1 = @. y*(1/(eps()+d))
		t2 = @. 1/(eps()+d)
		out[1] = sum(t1)/sum(t2)
		return out
	end
end

##########################
# FunctionCell Interface #
##########################
"""
	knn(k=1 [;kwargs])

Constructs an untrained cell that when piped data inside, returns a knn classifier trained
function cell based on the input data and labels.

# Arguments
  * `k::Int` represents the number of neighbours to be considered
  * `k::Float64=1.0` represents a range in which neighboring samples are to be searched (if no neighbours are found,
  equal class posteriors are generated)

# Keyword arguments
  * `metric::Distances.Metric` metric from `Distances.jl` which specifies how distances are calculated between samples (default `Distances.Euclidean()`)
  * `leafsize::Int` Size of the leafs for the tree structures holding the training data (default `10`)
  * `smooth::Symbol` method for smoothing class posteriors; available: `:none`, `:ml`, `:laplace`,`mest (m=10)` and `:dist` (default `:none`)

The posterior smoothing is as such: `:none` return probability `1.0` for the closest class, `:ml` returns class-wise mean probabilities or mean of
neighbouring values for regression, `:laplace` is similar to `:ml` but adds some constants, `:mest` regularizes towards class priors and `:dist` used
the distances from sample to neighbours to obtain the posterior in the case of classification while for regression it weighs neighbor values in inverse
proportion to their corresponding distance.

For more information on posterior smoothing:
	[1] L. Kuncheva "Combining Pattern Classifiers 2'nd Ed." 2014, ISBN 978-1-118-31523-1

Read the `NearestNeighbours.jl` and `Distances.jl` documentation for more information.
"""
knn(k::Real=1; kwargs...) = FunctionCell(knn, (k,), ModelProperties(), kwtitle(k isa Int ? "$k-NN classifier" : "$k-range-NN classifier", kwargs); kwargs...)

############################
# DataCell/Array Interface #
############################
"""
	knn(x, k=1 [;kwargs])

Trains a knn classifcation model that using the data `x` and `k` neighbors.
"""
# Training
knn(x::T where T<:CellDataL, k::Real=1; kwargs...) = knn((getx!(x), gety(x)), k; kwargs...)
knn(x::Tuple{T,S} where T<:AbstractVector where S<:AbstractVector, k::Real=1; kwargs...) = knn((mat(x[1], LearnBase.ObsDim.Constant{2}()), x[2]), k; kwargs...)
knn(x::Tuple{T,S} where T<:AbstractMatrix where S<:AbstractVector, k::Real=1; kwargs...) = begin

	@assert nobs(x[1]) == nobs(x[2]) "[knn] Expected $(nobs(x[1])) labels/values, got $(nobs(x[2]))."

	# Transform labels first
	enc = labelencn(x[2])
	yenc = label2ind.(x[2],enc)

	# Train model
	knndata = kNNClassifier.kNN_train(getobs(x[1]), yenc, k; kwargs...)

	# Build model properties
	modelprops = ModelProperties(nvars(x[1]), length(enc.label), enc)

	FunctionCell(knn, Model(knndata, modelprops), kwtitle(k isa Int ? "$k-NN classifier" : "$k-range-NN classifier", kwargs))

end

# Execution
knn(x::T where T<:CellData, model::Model{<:kNNClassifier.kNNModel}) =
	datacell(knn(getx!(x), model), gety(x))

knn(x::T where T<:AbstractVector, model::Model{<:kNNClassifier.kNNModel}) =
	knn(mat(x, LearnBase.ObsDim.Constant{2}()), model)

knn(x::T where T<:AbstractMatrix, model::Model{<:kNNClassifier.kNNModel}) =
	kNNClassifier.kNN_exec(model.data, getobs(x), collect(1:model.properties.odim))

##########################
# FunctionCell Interface #
##########################
"""
	knnr(k=1 [;kwargs])

Constructs an untrained cell that when piped data inside, returns a knn trained regressor
function cell based on the input data and labels.

# Arguments
  * `k::Int` represents the number of neighbours to be considered
  * `k::Float64=1.0` represents a range in which neighboring samples are to be searched (if no neighbours are found,
  equal class posteriors are generated)

# Keyword arguments
  * `metric::Distances.Metric` metric from `Distances.jl` which specifies how distances are calculated between samples (default `Distances.Euclidean()`)
  * `leafsize::Int` Size of the leafs for the tree structures holding the training data (default `10`)
  * `smooth::Symbol` method for smoothing class posteriors; available: `:ml`, `:dist` (default `:ml`)

Read the `NearestNeighbours.jl` and `Distances.jl` documentation for more information.
"""
knnr(k::Real=1; kwargs...) = FunctionCell(knnr, (k,), ModelProperties(), kwtitle(k isa Int ? "$k-NN regressor" : "$k-range-NN regressor", kwargs); kwargs...)

############################
# DataCell/Array Interface #
############################
"""
	knnr(x, k=1 [;kwargs])

Trains a knn regression model that using the data `x` and `k` neighbors.
"""
# Training
knnr(x::T where T<:CellDataL, k::Real=1; kwargs...) = knnr((getx!(x), gety(x)), k; kwargs...)
knnr(x::Tuple{T,S} where T<:AbstractVector where S<:AbstractVector, k::Real=1; kwargs...) =
	knnr((mat(x[1], LearnBase.ObsDim.Constant{2}()), x[2]), k; kwargs...)
knnr(x::Tuple{T,S} where T<:AbstractMatrix where S<:AbstractVector, k::Real=1; kwargs...) = begin

	@assert nobs(x[1]) == nobs(x[2]) "[knnr] Expected $(nobs(x[1])) labels/values, got $(nobs(x[2]))."

	# Train model
	knndata = kNNClassifier.kNN_train(getobs(x[1]), getobs(x[2]), k; kwargs...)

	FunctionCell(knnr, Model(knndata, ModelProperties(nvars(x[1]),1)), kwtitle(k isa Int ? "$k-NN regressor" : "$k-range-NN regressor", kwargs))

end

# Execution
knnr(x::T where T<:CellData, model::Model{<:kNNClassifier.kNNModel}) = datacell(knnr(getx!(x), model), gety(x))
knnr(x::T where T<:AbstractVector, model::Model{<:kNNClassifier.kNNModel}) = knnr(mat(x, LearnBase.ObsDim.Constant{2}()), model)
knnr(x::T where T<:AbstractMatrix, model::Model{<:kNNClassifier.kNNModel}) = kNNClassifier.kNN_exec(model.data, getobs(x))


type LMMCurveState <: CurveState
  numberOfRates::Int
  rateTimes::Vector{Float64}
  rateTaus::Vector{Float64}
  firstIdx::Int
  discRatios::Vector{Float64}
  forwardRates::Vector{Float64}
  cmSwapRates::Vector{Float64}
  cmSwapAnnuties::Vector{Float64}
  cotSwapRates::Vector{Float64}
  cotAnnuities::Vector{Float64}
  firstCotAnnuityComped::Int
end

function LMMCurveState(rateTimes::Vector{Float64})
  numberOfRates = isempty(rateTimes) ? 0 : length(rateTimes) - 1
  rateTaus = check_increasing_times_and_calculate_taus(rateTimes)

  discRatios = ones(numberOfRates + 1)
  forwardRates = Vector{Float64}(numberOfRates)
  cmSwapRates = Vector{Float64}(numberOfRates)
  cmSwapAnnuties = fill(rateTaus[numberOfRates], numberOfRates)
  cotSwapRates = Vector{Float64}(numberOfRates)
  cotAnnuities = fill(rateTaus[numberOfRates], numberOfRates)

  return LMMCurveState(numberOfRates, rateTimes, rateTaus, numberOfRates+1, discRatios, forwardRates, cmSwapRates, cmSwapAnnuties, cotSwapRates,
                      cotAnnuities, numberOfRates+1)
end

function set_on_forward_rates!(lmm::LMMCurveState, rates::Vector{Float64}, firstValidIndex::Int=1)
  length(rates) == lmm.numberOfRates || error("rates mismatch")
  firstValidIndex <= lmm.numberOfRates || error("first valid index must be less than or equal to numberOfRates")

  # first copy input
  lmm.firstIdx = firstValidIndex
  lmm.forwardRates[lmm.firstIdx:end] = rates[lmm.firstIdx:end]

  # then calculate the discount ratios
  # taken care at constructor time discRatios[numberOfRates] = 1.0
  for i = lmm.firstIdx:lmm.numberOfRates
    lmm.discRatios[i+1] = lmm.discRatios[i] / (1.0 + lmm.forwardRates[i] * lmm.rateTaus[i])
  end

  # lazy evaluation of :
  # - coterminal swap rates/annuities
  # - constant maturity swap rates/annuities

  lmm.firstCotAnnuityComped = lmm.numberOfRates+1
end

function discount_ratio(lmm::LMMCurveState, i::Int, j::Int)
  lmm.firstIdx <= lmm.numberOfRates || error("curve state not initialized yet")
  min(i, j) >= lmm.firstIdx || error("invalid index")
  max(i, j) <= lmm.numberOfRates + 1 || error("invalid index")

  return lmm.discRatios[i] / lmm.discRatios[j]
end

function coterminal_from_discount_ratios!(lmm::LMMCurveState, firstValidIndex::Int, discountFactors::Vector{Float64}, taus::Vector{Float64},
                                        cotSwapRates::Vector{Float64}, cotSwapAnnuities::Vector{Float64})
  nCotSwapRates = length(cotSwapRates)
  length(taus) == nCotSwapRates || error("taus size != cotSwapRate size")
  length(cotSwapAnnuities) == nCotSwapRates || error("cotSwapAnnuities size != cotSwapRate size")
  length(discountFactors) == nCotSwapRates + 1 || error("discountFactors size != cotSwapRate size + 1")

  cotSwapAnnuities[nCotSwapRates] = taus[nCotSwapRates] * discountFactors[nCotSwapRates + 1]
  cotSwapRates[nCotSwapRates] = (discountFactors[nCotSwapRates] - discountFactors[nCotSwapRates + 1]) / cotSwapAnnuities[nCotSwapRates]

  for i = nCotSwapRates:-1:firstValidIndex + 1
    cotSwapAnnuities[i-1] = cotSwapAnnuities[i] + taus[i-1] * discountFactors[i]
    cotSwapRates[i-1] = (discountFactors[i-1] - discountFactors[nCotSwapRates+1]) / cotSwapAnnuities[i-1]
  end

  return cotSwapAnnuities, cotSwapRates
end

function forward_rate(lmm::LMMCurveState, i::Int)
  lmm.firstIdx <= lmm.numberOfRates || error("curve state not initialized yet")
  (i >= lmm.firstIdx && i <= lmm.numberOfRates) || error("invalid index")

  return lmm.forwardRates[i]
end

function coterminal_swap_annuity(lmm::LMMCurveState, numeraire::Int, i::Int)
  lmm.firstIdx <= lmm.numberOfRates || error("curve state not initialized yet")
  (numeraire >= lmm.firstIdx && numeraire <= lmm.numberOfRates + 1) || error("invalid numeraire")
  (i >= lmm.firstIdx && i <= lmm.numberOfRates) || error("invalid index")

  if lmm.firstCotAnnuityComped <= i
    return lmm.cotAnnuities[i] / lmm.discRatios[numeraire]
  end

  if lmm.firstCotAnnuityComped == lmm.numberOfRates+1
    lmm.cotAnnuities[lmm.numberOfRates] = lmm.rateTaus[lmm.numberOfRates] * lmm.discRatios[lmm.numberOfRates + 1]
    lmm.firstCotAnnuityComped -= 1
  end

  for j = lmm.firstCotAnnuityComped-1:-1:i
    lmm.cotAnnuities[j] = lmm.cotAnnuities[j+1] + lmm.rateTaus[j] * lmm.discRatios[j+1]
  end

  lmm.firstCotAnnuityComped = i

  return lmm.cotAnnuities[i] / lmm.discRatios[numeraire]
end

function coterminal_swap_rate(lmm::LMMCurveState, i::Int)
  lmm.firstIdx <= lmm.numberOfRates || error("curve state not initialized yet")
  (i >= lmm.firstIdx && i <= lmm.numberOfRates) || error("invalid index")

  res = (lmm.discRatios[i] / lmm.discRatios[lmm.numberOfRates+1] - 1.0) / coterminal_swap_annuity(lmm, lmm.numberOfRates+1, i)

  return res
end

function coterminal_swap_rates!(lmm::LMMCurveState)
  lmm.firstIdx <= lmm.numberOfRates || error("curve state not initialized yet")
  coterminal_from_discount_ratios!(lmm, lmm.firstIdx, lmm.discRatios, lmm.rateTaus, lmm.cotSwapRates, lmm.cotAnnuities)

  return lmm.cotSwapRates
end

clone(lmm::LMMCurveState) = LMMCurveState(lmm.numberOfRates, copy(lmm.rateTimes), copy(lmm.rateTaus), lmm.firstIdx, copy(lmm.discRatios), copy(lmm.forwardRates),
                                          copy(lmm.cmSwapRates), copy(lmm.cmSwapAnnuties), copy(lmm.cotSwapRates), copy(lmm.cotAnnuities), lmm.firstCotAnnuityComped)


# small scrip to compute the solution of Lipman Schinwer equation
# In this case we use the donwsampled function using linear interpolation

using PyPlot
using IterativeSolvers
using SpecialFunctions

include("../src/FastConvolutionDual.jl")
include("../src/FastConvolution.jl")
include("../src/Preconditioner.jl")
include("../src/FastConvolutionSlowDual.jl")
include("../src/FastConvolutionDualDownSampled.jl")

FFTW.set_num_threads(4);
BLAS.set_num_threads(4)

#Defining Omega
h = 0.00025
k = 1/h

# size of box
a = 1;
b = 0.125;
x = collect(-a/2:h:a/2)
y = collect(-b/2:h:b/2)
(n,m) = length(x), length(y)
N = n*m
X = repmat(x, 1, m)[:];
Y = repmat(y', n,1)[:];
# we solve \triangle u + k^2(1 + nu(x))u = 0

# We use the modified quadrature in Duan and Rohklin
(ppw,D) = referenceValsTrapRule();
D0 = D[1];

window(y,alpha) = 1*(abs.(y).<=alpha/2) + (abs.(y).>alpha/2).*(abs.(y).<alpha).*
                     exp.(2*exp.(-0.5*alpha./(abs.(y)-alpha/2))./
                         ((abs.(y)-alpha/2)./(0.5*alpha)-1) )

window(y,alpha, beta) = 1*(abs.(y).<=beta) + (abs.(y).>beta).*(abs.(y).<alpha).*
                           exp.(2*exp.(-(alpha- beta)./(abs.(y)-beta))./
                               ((abs.(y)-beta)./(alpha- beta)-1) )

# Defining the smooth perturbation of the slowness
nu(x,y) = -0.05*(sin(4*pi*x/(0.96))).*
          window(y,0.96*b/2, 0.48*b/2).*
          window(x,0.96*0.5, 0.3);

figure(1); clf();
imshow(real(reshape( (1 + nu(X,Y)),n,m)),
       extent=[y[1], y[end], x[1], x[end]]); cb =  colorbar();

fastconvSlowDual = buildFastConvolutionSlowDual(x,y,h,k,nu, [1.0, 0.0],
                                                quadRule ="Greengard_Vico");

rhsSlowDual = -k^2*nu(X,Y) + zeros(Complex128,N);

# allocating the solution
sigmaSlow = zeros(Complex128,N);

# solving the system using GMRES
@time info =  gmres!(sigmaSlow, fastconvSlowDual, rhsSlowDual, maxiter = 10 )
# println(info[2].residuals[:])

using Interpolations

downsampleX = 16
downsampleY = 2

fastconvSlowDualDown = FastDualDownSampled(fastconvSlowDual,
                                           downsampleX, downsampleY)

index1 = 1:downsampleX:n
index2 = 1:downsampleY:m

Sindex = spzeros(n,m)
for ii = 0:round(Integer,(n-1)/downsampleX)
    for jj = 0:round(Integer,(m-1)/downsampleY)
        Sindex[1+ii*downsampleX,1+jj*downsampleY] = 1
    end
end

index = find(Sindex[:])
S = speye(n*m, n*m)
Sampling = S[index,:];

sigmaSlowDown = zeros(Complex128, size(Sampling)[1])

@time info =  gmres!(sigmaSlowDown, fastconvSlowDualDown, Sampling*rhsSlowDual, maxiter = 10 )
# println(info[2].residuals[:])

sigmaDownsampled = reshape(sigmaSlowDown, round(Integer,(n-1)/downsampleX) +1,
                          round(Integer,(m-1)/downsampleY) +1)

figure(17);
clf();
imshow(real(sigmaDownsampled)); colorbar();
title("sigma Slow in coarse mesh")

knots = (collect(1:downsampleX:n), collect(1:downsampleY:m))

itp_real = interpolate(knots, real(sigmaDownsampled), Gridded(Linear()))
itp_imag = interpolate(knots, imag(sigmaDownsampled), Gridded(Linear()))

interpsigma = itp_real[collect(1:n),collect(1:m) ]
              + 1im*itp_imag[collect(1:n),collect(1:m) ]

figure(15);
clf();
imshow(real(interpsigma)); colorbar();
title("interpolated sigma slow")

normSigmaSlow = maximum(abs.(sigmaSlow))

figure(15);
clf();
imshow(real(interpsigma - reshape(sigmaSlow,n,m))/normSigmaSlow)
colorbar()
title("relative error");


# Precompose with diagonal scaling and translation

export PrecomposeDiagonal

"""
**Precomposition with diagonal scaling/translation**

    PrecomposeDiagonal(f, a, b)

Returns the function
```math
g(x) = f(\\mathrm{diag}(a)x + b)
```
where ``f`` is a convex function. Furthermore, ``f`` must be separable, or `a` must be a scalar, for the `prox` of ``g`` to be computable. Parametes `a` and `b` can be arrays of multiple dimensions, according to the shape/size of the input `x` that will be provided to the function: the way the above expression for ``g`` should be thought of, is `g(x) = f(a.*x + b)`.
"""
struct PrecomposeDiagonal{T <: ProximableFunction, R <: Union{Real, AbstractArray}, S <: Union{Real, AbstractArray}} <: ProximableFunction
    f::T
    a::R
    b::S
    function PrecomposeDiagonal{T,R,S}(f::T, a::R, b::S) where {T <: ProximableFunction, R <: Union{Real, AbstractArray}, S <: Union{Real, AbstractArray}}
        if !is_convex(f)
            error("`f` must be convex")
        end
        if !(eltype(a) <: Real)
            error("`a` must have real elements")
        end
        if any(a == 0.0)
            error("elements of `a` must be nonzero")
        else
            new(f, a, b)
        end
    end
end

is_separable(f::PrecomposeDiagonal) = is_separable(f.f)
is_prox_accurate(f::PrecomposeDiagonal) = is_prox_accurate(f.f)
is_convex(f::PrecomposeDiagonal) = is_convex(f.f)
is_set(f::PrecomposeDiagonal) = is_set(f.f)
is_singleton(f::PrecomposeDiagonal) = is_singleton(f.f)
is_cone(f::PrecomposeDiagonal) = is_cone(f.f)
is_affine(f::PrecomposeDiagonal) = is_affine(f.f)
is_smooth(f::PrecomposeDiagonal) = is_smooth(f.f)
is_quadratic(f::PrecomposeDiagonal) = is_quadratic(f.f)
is_generalized_quadratic(f::PrecomposeDiagonal) = is_generalized_quadratic(f.f)
is_strongly_convex(f::PrecomposeDiagonal) = is_strongly_convex(f.f)

PrecomposeDiagonal(f::T, a::S=1.0, b::S=0.0) where {T <: ProximableFunction, S <: Real} = PrecomposeDiagonal{T, S, S}(f, a, b)

PrecomposeDiagonal(f::T, a::R, b::S=0.0) where {T <: ProximableFunction, R <: AbstractArray, S <: Real} = PrecomposeDiagonal{T, R, S}(f, a, b)

PrecomposeDiagonal(f::T, a::R, b::S) where {T <: ProximableFunction, R <: Union{AbstractArray, Real}, S <: AbstractArray} = PrecomposeDiagonal{T, R, S}(f, a, b)

function (g::PrecomposeDiagonal)(x::AbstractArray{T}) where T <: RealOrComplex
    return g.f((g.a).*x .+ g.b)
end

function gradient!(y::AbstractArray{T}, g::PrecomposeDiagonal, x::AbstractArray{T}) where T <: RealOrComplex
    z = g.a .* x .+ g.b
    v = gradient!(y, g.f, z)
    y .*= g.a
    return v
end

function prox!(y::AbstractArray{T}, g::PrecomposeDiagonal, x::AbstractArray{T}, gamma::Union{R, AbstractArray{R}}=R(1)) where {R <: Real, T <: RealOrComplex{R}}
    z = g.a .* x .+ g.b
    v = prox!(y, g.f, z, (g.a .* g.a) .* gamma)
    y .-= g.b
    y ./= g.a
    return v
end

function prox_naive(g::PrecomposeDiagonal, x::AbstractArray{T}, gamma::Union{R, AbstractArray{R}}=R(1)) where {R <: Real, T <: RealOrComplex{R}}
    z = g.a .* x .+ g.b
    y, fy = prox_naive(g.f, z, (g.a .* g.a) .* gamma)
    return (y .- g.b)./g.a, fy
end

fun_name(f::PrecomposeDiagonal) = string("Precomposition by affine diagonal mapping of ", fun_name(f.f))
fun_dom(f::PrecomposeDiagonal) = fun_dom(f.f)
fun_expr(f::PrecomposeDiagonal) = "x ↦ f(diag(a)*x + b)"
fun_params(f::PrecomposeDiagonal) = string("f(x) = ", fun_expr(f.f), ", a = ", length(f.a) == 1 ? string(f.a[1]) : string(typeof(f.a)), ", b = ", length(f.b) == 1 ? string(f.b[1]) : string(typeof(f.b)))


# This file contains type Attributes and some functions related with it.
# The idea of Attributes class structure is that the instances do not contain data
# (i.e. attributes), but they define how the attributes are supposed to be treated
# (created, analyzed). The attributes itself should be kept in seperate Array objects.
#
# Attributes types inherit over AbstractAttributes.
# A few attribute types are defined (see PHD thesis, PJ Górski, p.94).
# The most basic is BinaryAttributes. Agents possess a vector of +-1, e.g. [+1, -1, -1].
# OrderedAttributes, UnorderedAttributes, UnorderedPositiveAttributes are for agents
# with vectors of integer attributes of 1, 2, 3, ..., e.g. [1, 4, 2, 3, 1].
# (The max value is defined.)
# OrderedAttributes assume attributes can be placed in certain order.
# UnorderedAttributes and UnorderedPositiveAttributes assume attributes either are the same
# or different (there is no many different levels of difference between different attributes).
# UnorderedAttributes assume different attributes have negative contribution to attribute's distance.
# UnorderedPositiveAttributes assume different attributes have neutral contribution to attribute's distance.
#
# To run requires to add to LOAD_PATH the filepath to this file, e.g.
# > push!(LOAD_PATH, pwd())

# module Attributes

using LinearAlgebra

abstract type AbstractAttributes end
export AbstractAttributes

struct BinaryAttributes <: AbstractAttributes
    g::Int
end
export BinaryAttributes

struct OrderedAttributes <: AbstractAttributes
    g::Int# number of attributes
    threshold::Float64#threshold when to accept the nodes are friends
    v::Int# number of different attribute values
end
export OrderedAttributes

struct UnorderedAttributes <: AbstractAttributes
    g::Int# number of attributes
    threshold::Float64#threshold when to accept the nodes are friends
    v::Int# number of different attribute values
end
export UnorderedAttributes

struct UnorderedPositiveAttributes <: AbstractAttributes
    g::Int# number of attributes
    threshold::Float64#threshold when to accept the nodes are friends
    v::Int# number of different attribute values
end
export UnorderedPositiveAttributes

#Returns the Array of attributes of type BinaryAttributes (attributes are +-1)
get_attributes(b::BinaryAttributes, n::Int) = sign.(rand(n, b.g) .- 0.5)
export get_attributes

#Returns the Array of attributes of type (Un)OrderedAttributes (attributes are 1, 2, ..., v)
get_attributes(b::AbstractAttributes, n::Int) = rand(1:b.v, n, b.g)
export get_attributes

#Returns weights of the AL in the case of BinaryAttributes
get_attribute_layer_weights(b::BinaryAttributes, attr::AbstractArray{<:Number}) =
    triu(attr * attr' / b.g, 1)
export get_attribute_layer_weights

function get_attribute_layer_weights(b::AbstractAttributes, attr::AbstractArray{<:Number})
    n = size(attr, 1)
    weights = zeros(n, n)
    for i = 1:n, j = (i+1):n
        if isa(b, OrderedAttributes)
            weights[i, j] =
                2(b.threshold - sum(abs.(attr[i, :] - attr[j, :])) / (b.v - 1) / b.g)
        elseif isa(b, UnorderedAttributes)
            weights[i, j] = 2(sum(attr[i, :] .== attr[j, :]) / b.g - b.threshold)
        elseif isa(b, UnorderedPositiveAttributes)
            weights[i, j] = sum(attr[i, :] .== attr[j, :]) / b.g
        end
    end
    return weights
end
export get_attribute_layer_weights

get_name(b::AbstractAttributes) = string(typeof(b))
export get_name

# Base.string(b::AbstractAttributes) = get_name(b)

get_threshold(b::AbstractAttributes) = b.threshold
get_threshold(b::BinaryAttributes) = 0.5
export get_threshold

get_degeneracy(b::AbstractAttributes) = b.v
get_degeneracy(b::BinaryAttributes) = 2
export get_degeneracy

# end


"""
    Yeoh(; λ, μ, c₂, c₃)

Hyper elastic material
#Arguments
- `λ::Float64`: Lamé parameter
- `μ::Float64`: Lamé parameter (shear modulus)
- `c₂::Float64`: Material constant
- `c₃::Float64`: Material constant
"""
struct Yeoh <: AbstractMaterial
    λ::Float64
    μ::Float64
    c₂::Float64
    c₃::Float64
end

struct YeohState <: AbstractMaterialState
end

function initial_material_state(::Yeoh)
    return YeohState()
end

function Yeoh(; λ::T, μ::T, c₂::T, c₃::T) where T <: AbstractFloat
    return Yeoh(λ, μ, c₂, c₃)
end

strainmeasure(::Yeoh) = RightCauchyGreen()

function ψ(mp::Yeoh, C::SymmetricTensor{2,3})
    J = sqrt(det(C))
    I = tr(C)
    return mp.μ/2 * (I-3) + mp.c₂*(I-3)^2 + mp.c₃*(I-3)^3 - mp.μ*log(J) + mp.λ/2 * log(J)^2
end

function material_response(mp::Yeoh, C::SymmetricTensor{2,3}, state::YeohState = YeohState(),
                           Δt=nothing; cache=nothing, options=nothing)
    invC=inv(C)
    detC=det(C);
    J=sqrt(detC);

    dlnJdc = 1/2*invC
    d2lnJdcdc = 1/2*inv(otimesu(-C,C))

    𝐈 = one(C)
    Ic = tr(C)

    S = 2*(mp.μ/2*𝐈 + 2*mp.c₂*(Ic-3)*𝐈 + 3*mp.c₃*(Ic-3)^2*𝐈 - mp.μ*dlnJdc + mp.λ* log(J) * dlnJdc)
    ∂S∂E = 4 * (2*mp.c₂*(𝐈 ⊗ 𝐈) + 6*mp.c₃*(Ic-3)*(𝐈 ⊗ 𝐈) - mp.μ*d2lnJdcdc + mp.λ*(log(J)*d2lnJdcdc + (dlnJdc ⊗ dlnJdc)))
    ∂S∂E = symmetric(∂S∂E)
    # TODO use transform tangents from traits branch

    return S, ∂S∂E, YeohState()
end


module TestBackground
using Test
using BackgroundSubtraction
using LinearAlgebra
function synthetic_data(n = 128, m = 16)
    f(x) = sin(2π*x) + 1.1
    x = range(0, stop = 1, length = n)
    fx = f.(x)
    A = repeat(fx, 1, m)

    # add noise
    σ = 1e-2
    @. A += σ * randn()
    # add positive outliers (i.e. peaks)
    npeaks = n*m ÷ 4
    outind = rand(eachindex(A), npeaks)
    @. A[outind] += exp(randn())
    return A, x, fx
end

function synthetic_images(nx = 128, ny = 128, m = 16)
    f(x, y) = sin(2π*(x+y)) + 1.1
    x = range(0, stop = 1, length = nx)
    y = range(0, stop = 1, length = ny)
    fxy = f.(x, y')
    A = repeat(fxy, 1, 1, m)
    # add noise
    σ = 1e-2
    @. A += σ * randn()
    # add positive outliers (i.e. peaks)
    npeaks = nx*ny*m ÷ 4
    outind = rand(eachindex(A), npeaks)
    @. A[outind] += .1exp(randn())
    return A, x, y, fxy
end

@testset "background" begin
    n, m = 128, 16
    A, x, fx = synthetic_data(n, m)
    k = 2
    l = 1/2
    background = mcbl(A, k, x, l)
    @test size(background) == size(A)
    @test all(≥(0), background)
    tol = 5e-2
    @test maximum(abs, background[:, 1]-fx) < tol

    doplot = false
    if doplot
        using Plots
        plotly()
        plot(x, A[:, i], label = "data")
        plot!(x, background[:, i], label = "inferred background")
        plot!(x, fx, label = "background")
        gui()
    end

    # testing with only vector input: less powerful, and needs stronger l regularization
    i = 1
    background = mcbl(A[:,i], x, l, maxiter = 16)

    @test background isa Vector
    @test length(background) == length(A[:, i])
    tol = 5e-2
    @test maximum(abs, background[:, 1]-fx) < tol

    # testing smooth 2D projection
    lc = 1. # composition length scale
    c = randn(2, m) # 2 dimensional composition dimension
    a = A
    background = mcbl(a, x, l, c, lc)
    tol = 5e-2
    @test maximum(abs, background[:, 1]-fx) < tol

    doplot = false
    if doplot
        using Plots
        plotly()
        plot(x, A[:, i], label = "data")
        plot!(x, background[:, i], label = "inferred background")
        plot!(x, fx, label = "background")
        gui()
    end

    # testing 3D mcbl
    nx, ny, m = 128, 128, 16
    k = 1
    lx, ly = .2, .2
    A, x, y, fxy = synthetic_images(nx, ny, m)
    background = mcbl(A, k, x, lx, y, ly)
    tol = 5e-2
    @test maximum(abs, background[:, :, 1]-fxy) < tol

    # testing 3D smooth projection
    background = mcbl(A, x, lx, y, ly, c, lc)
    tol = 5e-2
    @test maximum(abs, background[:, :, 1]-fxy) < tol

    doplot = false
    if doplot
        using Plots
        plotly()
        surface(x, y, A[:, :, i], label = "data")
        gui()
        surface(x, y, background[:, :, i], label = "inferred background")
        gui()
        surface(x, y, fxy[:, :, i], label = "background")
        gui()
    end

    # testing 3D smooth projection
    # update default parameters

end

end # TestBackground


# Interface
# single input
function forward(op::Linear{T, Matrix{T}, Vector{T}, Vector{T}}, input::Vector{T}) where T
    op.input = input # NOTE: this is only a reference, no extra memmory alloc here
    BLAS.gemv('N', op.weight, input) + op.bias
end

# batched input
function forward(op::Linear{T, Matrix{T}, Vector{T}, Matrix{T}}, input::Matrix{T}) where T
    # NOTE: we use colum as a sample of data
    # PyTorch use the first dimension
    op.input = input
    return BLAS.gemm('N', 'N', op.weight, input) .+ op.bias # Be careful, here has a broadcast .+
end

# static array (immutable & mutable arrays)
# single input
function forward(op::Linear{T, M, B, I}, input::I) where {
        T,
        M<:StaticMatrix,
        B<:StaticVector,
        I<:StaticVector
    }

    op.input = input
    return op.weight * input + op.bias
end

# batched input
function forward(op::Linear{T, M, B, I}, input::I) where {
        T,
        M<:StaticMatrix,
        B<:StaticVector,
        I<:StaticMatrix
    }

    op.input = input
    return op.weight * input .+ op.bias
end


### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ 82abdf71-6f38-4957-8bf7-044d36008bbd
using FFTW, SampledSignals, Plots

# ╔═╡ 02e74af4-b5ac-11eb-3971-e79cdb113be1
md"# Discrete Summation fomulas
Discrete summation fomulas can be used to create waveforms with
harmonics at regular intervals.
Discrete summation formulae can be parameterized and combined to make a variety of spectra but here we will look at basic every harmonic (saw-like) and an odd harmonic (square-like) tones"

# ╔═╡ f1e8126d-02c0-42c7-9b84-f84cfc17b391
begin
    fs = 8000
	freqs = SampleBuf(Array(LinRange(0, 2*pi*220, fs)), fs)
	t = Array(0:1/100:1) * 2 * pi
end

# ╔═╡ 65e90c08-5ca0-4ea6-a9d5-2eb2a5a85c81
# A naive sin sum
function sine_sum(a, t, b, n)
	sum(a^i*sin(t + i*b) for i=0:n)
end

# ╔═╡ 94bf7a3f-752c-4130-b908-c8c14a32e247
# An equivalent closed form calculation
function dsf(a, t, b, n)
	"""
	a: ratio between adjacent harmonics
	t: base frequency
	b: frequency offset between harmonics
	   (harmonics of t + mb for 0 <= m <= n)
	n: number of harmonics
	"""
	out = sin(t) - a*sin(t-b)
	out -= a^(n+1)*(sin(t + (n+1)*b) - a*sin(t + n*b))
	out /= 1 + a*a - 2*a*cos(b)
end

# ╔═╡ ea9f553a-b624-46d0-a3db-df1ec5a1dc7b
begin
	# Demonstration that they are the same, try random values
	a, t2, b, n = .234, .34534, .234, 34
	sine_sum(a, t2, b, n), dsf(a, t2, b, n)
end

# ╔═╡ 09eec3c8-55c6-4b69-8c6a-21f64d261713
function normalize(tone)
	m = minimum(tone)
	M = maximum(tone)
	2*(tone.-m)/(M-m) .-1
end

# ╔═╡ 4e5cc60c-5d67-4cd3-afa0-6ab938e22f65
function plotfreqs(tone, title)
	freqs = fft(tone) |> fftshift
	domain = FFTW.fftfreq(length(tone), fs) |> fftshift
    plot(domain, 10*log10.(abs.(freqs)/fs), title=title,
		xlimit=(0, fs/2), ylimit=(-50, 0))
end

# ╔═╡ ed2e86f6-17cd-4ae7-821a-ab935111aca6
function dsfsaw(t)
	dsf(.8, t, t, 16)/3
end

# ╔═╡ c377d413-6dbc-4175-b418-0f71bf2b29aa
dsfsaw_tone = normalize(dsfsaw.(freqs))

# ╔═╡ 989d247f-84d8-46b9-ba18-abaaec50a94d
plotfreqs(dsfsaw_tone, "DSF saw Spectrum")

# ╔═╡ e6930c81-3007-4e35-a695-a8b13f32b0e9
md"DSF can be used to create very bright sounds like a saw or square without aliasing. It also can mimic the sharp cutoff above a limit that a filter would produce. However the amplitude of the harmonics is geometric $(a^n)$ rather than harmonic $(1/n)$ so an exact bandlimited saw or square can't be replicated"

# ╔═╡ Cell order:
# ╟─02e74af4-b5ac-11eb-3971-e79cdb113be1
# ╠═82abdf71-6f38-4957-8bf7-044d36008bbd
# ╠═f1e8126d-02c0-42c7-9b84-f84cfc17b391
# ╠═65e90c08-5ca0-4ea6-a9d5-2eb2a5a85c81
# ╠═94bf7a3f-752c-4130-b908-c8c14a32e247
# ╠═ea9f553a-b624-46d0-a3db-df1ec5a1dc7b
# ╟─09eec3c8-55c6-4b69-8c6a-21f64d261713
# ╟─4e5cc60c-5d67-4cd3-afa0-6ab938e22f65
# ╠═ed2e86f6-17cd-4ae7-821a-ab935111aca6
# ╠═c377d413-6dbc-4175-b418-0f71bf2b29aa
# ╠═989d247f-84d8-46b9-ba18-abaaec50a94d
# ╟─e6930c81-3007-4e35-a695-a8b13f32b0e9


for N in [Float64, Float32, Rational{Int}]
    c1 = N[1, 2]
    c2 = N[3, 4, 5]
    Z1 = Zonotope(c1, N[1 0; 0 1])
    Z2 = Zonotope(c2, N[1 0 0; 0 1 0; 0 0 1])
    H1 = Hyperrectangle(c1, N[1, 1])
    H2 = Hyperrectangle(c2, N[1, 1, 1])
    B1 = BallInf(c1, N(1))
    B2 = BallInf(c2, N(1))

    Z1Z2 = cartesian_product(Z1, Z2)
    Z1H2 = cartesian_product(Z1, H2)
    H1Z2 = cartesian_product(H1, Z2)
    H1H2 = cartesian_product(H1, H2)
    H1B2 = cartesian_product(H1, B2)
    B1B2 = cartesian_product(B1, B2)
    for X in [Z1Z2, Z1H2, H1Z2]
        @test X isa Zonotope{N}
    end
    for X in [H1H2, H1B2, B1B2]
        @test X isa Hyperrectangle{N}
    end
    if test_suite_polyhedra || N <: AbstractFloat
        for X in [Z1Z2, Z1H2, H1Z2, H1H2, H1B2]
            @test isequivalent(X, B1B2)
        end
    end

    # Universe is handled correctly (#2735)
    P = HalfSpace(N[1, 2], N(3))
    U = Universe{N}(2)
    @test isequivalent(cartesian_product(P, U), HalfSpace(N[1, 2, 0, 0], N(3)))
    @test isequivalent(cartesian_product(U, P), HalfSpace(N[0, 0, 1, 2], N(3)))
    @test cartesian_product(U, U) == Universe{N}(4)

    # hyperrectangle and Universe
    H = Hyperrectangle(N[1, 2], N[3, 4])
    HU = cartesian_product(H, U)
    @test HU isa HPolyhedron{N, SingleEntryVector{N}} && isequivalent(HU,
        HPolyhedron([HalfSpace(N[1, 0, 0, 0], N(4)),
                     HalfSpace(N[-1, 0, 0, 0], N(2)),
                     HalfSpace(N[0, 1, 0, 0], N(6)),
                     HalfSpace(N[0, -1, 0, 0], N(2))]))
    UH = cartesian_product(U, H)
    @test UH isa HPolyhedron{N, SingleEntryVector{N}} && isequivalent(UH,
        HPolyhedron([HalfSpace(N[0, 0, 1, 0], N(4)),
                     HalfSpace(N[0, 0, -1, 0], N(2)),
                     HalfSpace(N[0, 0, 0, 1], N(6)),
                     HalfSpace(N[0, 0, 0, -1], N(2))]))

    # HalfSpace and Universe
    H1 = HalfSpace(N[0, 4, 0], N(5))
    H2 = HalfSpace(sparsevec([2], N[4], 3), N(5))
    H3 = HalfSpace(SingleEntryVector(2, 3, N(4)), N(5))
    @test isequivalent(H1, H2) && isequivalent(H1, H3)
    H1a = cartesian_product(H1, Universe{N}(2))
    H1p = cartesian_product(Universe{N}(2), H1)
    @test isequivalent(H1a, cartesian_product(H2, Universe{N}(2))) &&
          isequivalent(H1a, cartesian_product(H3, Universe{N}(2))) &&
          isequivalent(H1a, HalfSpace(N[0, 4, 0, 0, 0], N(5)))
    @test isequivalent(H1p, cartesian_product(Universe{N}(2), H2)) &&
          isequivalent(H1p, cartesian_product(Universe{N}(2), H3)) &&
          isequivalent(H1p, HalfSpace(N[0, 0, 0, 4, 0], N(5)))

    # polyhedron and Universe
    P = HPolyhedron([HalfSpace(N[1], N(2))])
    PU = cartesian_product(P, U)
    @test isequivalent(PU, HPolyhedron([HalfSpace(N[1, 0, 0], N(2))]))
    UP = cartesian_product(U, P)
    @test isequivalent(UP, HPolyhedron([HalfSpace(N[0, 0, 1], N(2))]))
end


@everywhere pri = Factored(Normal(1, 0.5), DiscreteUniform(1, 10))
@everywhere sim((n, du)) = (n * n + du) * (n + randn() * 0.01)
@everywhere cost(x) = abs(sim(x) - 5.5)
@everywhere model_abc = ApproxPosterior(pri, cost, 0.01)
@everywhere res = sample(model_abc, AIS(100), 1000, discard_initial = 10000, progress = false)
@show sim(Tuple(res)) ≈ 5.5
res = smc(pri, cost; verbose = true)
display(res)
@show res.P[2] ≈ 5
@everywhere function brownianrms((μ, σ), N, samples = 200)
    t = 0:N
    #    rand()<1/20 && sleep(0.001)
    @.(sqrt(μ * μ * t * t + σ * σ * t)) .* (0.95 + 0.1 * rand())
end

@everywhere params = (0.5, 2.0)
@everywhere tdata = brownianrms(params, 30, 10000)
@everywhere prior = Factored(Uniform(0, 1), Uniform(0, 4))
@everywhere cost(x) = sum(abs, brownianrms(x, 30) .- tdata) / length(tdata)
@everywhere modelabc = ApproxPosterior(prior, cost, 0.1)
all(smc(prior, cost, verbose=false,min_r_ess=0.55).P .≈ params)