function rankedge{V}(graph::AbstractGraph{V})
    weights = zeros(num_edges(graph))
    for e in edges(graph)
        e_idx = edge_index(e, graph)
        u = source(e, graph)
        v = target(e, graph)
        u_neighbors = Set{V}(out_neighbors(u, graph))
        v_neighbors = Set{V}(out_neighbors(v, graph))
        common_neighbors = intersect(u_neighbors, v_neighbors)
        union_neighbors = union(u_neighbors, v_neighbors)
        union_neighbors = setdiff(union_neighbors, Set{V}({u,v}))
        no_common_neighbors = setdiff(union_neighbors, common_neighbors)
        weights[e_idx] = (length(common_neighbors) + 1) / (length(no_common_neighbors) + 1)
    end
    weights
end

function rankedge1{V}(graph::AbstractGraph{V})
    weights = zeros(num_edges(graph))
    for e in edges(graph)
        e_idx = edge_index(e, graph)
        u = source(e, graph)
        v = target(e, graph)
        u_neighbors = Set{V}(out_neighbors(u, graph))
        v_neighbors = Set{V}(out_neighbors(v, graph))
        common_neighbors = intersect(u_neighbors, v_neighbors)
        union_neighbors = union(u_neighbors, v_neighbors)
        union_neighbors = setdiff(union_neighbors, Set{V}({u,v}))
        no_common_neighbors = setdiff(union_neighbors, common_neighbors)
        weights[e_idx] = (length(common_neighbors) + 1) / (length(no_common_neighbors) + length(common_neighbors) + 1)
    end
    weights
end

function rankedgenocommon{V}(graph::AbstractGraph{V})
    weights = zeros(num_edges(graph))
    for e in edges(graph)
        e_idx = edge_index(e, graph)
        u = source(e, graph)
        v = target(e, graph)
        u_neighbors = Set{V}(out_neighbors(u, graph))
        v_neighbors = Set{V}(out_neighbors(v, graph))
        common_neighbors = intersect(u_neighbors, v_neighbors)
        union_neighbors = union(u_neighbors, v_neighbors)
        union_neighbors = setdiff(union_neighbors, Set{V}({u,v}))
        no_common_neighbors = setdiff(union_neighbors, common_neighbors)
        weights[e_idx] = length(no_common_neighbors)
    end
    weights
end

function rankedgebynei{V}(graph::AbstractGraph{V})
    weights = zeros(num_edges(graph))
    for e in edges(graph)
        e_idx = edge_index(e, graph)
        u = source(e, graph)
        v = target(e, graph)
        u_neighbors = Set{V}(out_neighbors(u, graph))
        v_neighbors = Set{V}(out_neighbors(v, graph))
        common_neighbors = intersect(u_neighbors, v_neighbors)
        weights[e_idx] = length(common_neighbors)
    end
    weights
end


using GrowthMaps, GeoData, Unitful, Test
using Unitful: °C, K, hr, d, mol, cal
using GrowthMaps: rate, condition, conditionalrate, combinelayers

dimz = Lat((10, 20)), Lon((100, 130))

# Models
lowerdata = [1. 2. 3.
             4. 5. 6.]
upperdata = [9. 8. 7.
             6. 5. 4.]
tempdata = [270.0 280.0 290.0
            300.0 310.0 320.0]

lowerunitful = lowerdata * K
upperunitful = upperdata * K
tempunitful = tempdata * K

threshold = 5K
mortalityrate = -1/K
lower = Layer{:lower,K}(LowerStress(threshold, mortalityrate))

@testset "Lower stress" begin
    @test rate(lower, 1.0K) == -4.
    @test condition(lower, 1.0K) == true
    @test rate(lower, 6.0K) == 1.
    @test condition(lower, 6.0K) == false
    @test keys(lower) == :lower
    @test condition.(Ref(lower), lowerdata * K) == [true true  true
                                                    true false false]
    @test conditionalrate.(Ref(lower), lowerdata) == [-4. -3. -2.
                                                      -1.  0.  0.]
end

threshold = 6K
mortalityrate = -2/K
upper = Layer(:upper, K, UpperStress(threshold, mortalityrate))

@testset "Upper stress" begin
    @test rate(upper, 9.0K) == -6.
    @test condition(upper, 9.0K) == true
    @test rate(upper, 0.0K) == 12.
    @test condition(upper, 0.0K) == false

    @test keys(upper) == :upper
    @test condition.(Ref(upper), upperdata * K) == [true  true  true
                                                    false false false]
    @test conditionalrate.(Ref(upper), upperdata) == [-6. -4. -2.
                                                       0.  0.  0.]
end

p = 0.3
ΔH_A = 2e4cal * mol^-1
ΔH_L = -1e5cal * mol^-1
ΔH_H = 3e5cal * mol^-1
T_halfL = 250.0K
T_halfH = 300.0K
T_ref = K(25.0°C)
growth = Layer(:temp, K, SchoolfieldIntrinsicGrowth(p, ΔH_A, ΔH_L, T_halfL, ΔH_H, T_halfH, T_ref))

@testset "Schoolfield Intrinsic growth" begin
    @test keys(growth) == :temp
    @test condition.(Ref(growth), tempdata * K) == Bool[1 1 1
                                                        1 1 1]
    rates = Array(conditionalrate.(Ref(growth), tempdata))
    refrates = [8.034064e-03 3.154654e-02 1.128590e-01; 1.856456e-01 1.007895e-07 7.046076e-14]
    # The original R code was not very accurate, we only use two significant figures.
    @test rates ≈ refrates rtol = 1e-2
end

@testset "Combined layers" begin
    stack = NamedTuple{(:lower, :upper, :temp)}((lowerdata, upperdata, tempdata))
    @test combinelayers((lower, upper), stack) == [-10. -7. -4.
                                                   -1.   0.  0.]
    combinelayers((growth, lower, upper), stack)
end


using ReachabilityAnalysis, SparseArrays
Dict([
    "opts" => Dict(:T=>20.0, :alg=>GLGM06(δ=5e-3)),
    "X0"   => Hyperrectangle(spzeros(28), sparsevec(1:8, fill(0.1, 8), 28)),
    "info" => Dict(
        "name" => "helicopter",
        "dim" => 28,
        "linear" => true,
        "continuous" => true,
        "domain" => "Automatic Control",
        "hybrid" => false,
        "description" => "An 8-dimensional helicopter model with a 20-dimensional controller. The controlled plant is a 28-dimensional continuous linear time-invariant (LTI) system. The plant is a small disturbance model of a helicopter, given as an 8-dimensional LTI system. The controller we examine is an H∞ mixed-sensitivity design for rejecting atmospheric turbulence, given as a 20-dimensional LTI system. The helicopter is representative of the Westland Lynx, a two-engined multi-purpose military helicopter of approximately 9000 lbs gross weight, with a four-blade semi-rigid main rotor.",
        "source" => "S. Skogestad and I. Postlethwaite. Multivariable Feedback Control: Analysis and Design. John Wiley & Sons, 2005. The model is taken from the Example Mdels from SpaceEx state space explorer (see http://spaceex.imag.fr/download-6).",
        "varnames" => Dict("x₁" => "Pitch Attitude (Θ)", "x₂" => "Roll Attitude (Φ)", "x₃" => "Roll Rate (p)", "x₄" => "Pitch Rate (q)",
                           "x₅" => "Yaw Rate (r)", "x₆" => "Forward Velocity (vx)", "x₇" => "Lateral Velocity (vy)", "x₈" => "Vertical Velocity (vz)"),
    )
])


using LinearAlgebra

"""
# enum SvdMode

. leftsv means that alogrithm operates on columns of data
passed in LowRankApproxMc and thus  giving
a orthonormal basis of left singular vector.

. rightsv means that algorithm stores the transpose of data passed
in LowRankApproxMc and thus runs on row of original data , thus giving
a orthonormal basis of right singular vector.

"""
@enum SvdMode begin
    leftsv
    rightsv
end

"""
# LowRankApproxMc structure

as described in:

1. Vempala Spectral Book (2009)
    paragraph on Iterative Fast Svd P 85 and Th 7.2 7.3

2. Lectures in Randomized Linear Algebra. Mahoney 2016 P121 and seq.

Cf also Deshpande Rademacher Vempala Wang
Matrix Approximation and Projective Clustering via Volume Sampling 2006

For a data matrix X(d,n) compute a orthogonal set of left singular vectors in a matrix W(rank, n)
Right singular vector are computed as W'*X.

FIELDS
------

 - X  :  the (d,n) matrix we want to reduce.
 - targetRank :   the rank of the data matrix we want to extract

 - currentRank : the rank reached at current iteration

 - U : Union{ Some{Matrix{Float64}}, Nothing} the matrix of left singular vectors if any
 - S : Union{ Some{Vector{Float64}}, Nothing} the vecor of singular values if any
 - Vt: Union{ Some{Matrix{Float64}}, Nothing} the transposed matrix of right singular vectors if any

CONSTRUCTORS
-----------

1.    `function LowRankApproxMc(data::Matrix{Float64}, targetRank::Int64)`
       initialize structure and rank of the approximation

"""
mutable struct LowRankApproxMc
    X::Matrix{Float64}
    #
    leftOrRight::SvdMode
    # rank we want in output
    targetRank::Int64
    targetEpsil::Float64
    # number of column sampling in a pass
    samplingSize::Int64
    #
    currentRank::Int64
    # store relative error
    residualErr::Float64
    #
    currentIter::Int64
    U::Union{ Some{Matrix{Float64}}, Nothing}
    S::Union{ Some{Vector{Float64}}, Nothing}
    Vt::Union{ Some{Matrix{Float64}}, Nothing}
    #
    function LowRankApproxMc(data::Matrix{Float64}; mode = leftsv)
        if mode == leftsv
            @info "LowRankApproxMc in left singular vector mode"
            new(data, mode, 0, 1. , 0,  0, 1. , 0,
                nothing, nothing, nothing)
        else
            @info "LowRankApproxMc in right singular vector mode, transposing data"
            new(transpose(data), mode, 0, 1. , 0,  0, 1. , 0,
                nothing, nothing, nothing)
        end
    end
end

#
# The data matrix are supposed to be stored in columns of length data dimension.
# We operate on columns so we ouptut Left singular vectors.
# So our implementation conforms to Mahoney "Lecture Notes on Randomized Algebra"
# paragraph 15.4 Th 21 as opposed to Kannan-Vempala "Spectral Algorithms"
# which operates on rows and output right singular vectors.
#

function reduceIfast(datapb::LowRankApproxMc, stop::Function)
    @debug "in LowRankApproxMc reduceIfast v2"
    epsil = 1.E-5
    d,n = size(datapb.X)
    datapb.currentIter = 1
    datapb.currentRank = 0
    E = Matrix(datapb.X)
    acceptanceRatio = 1.
    normX = norm(E,2)
    residualAtIter= Vector{Float64}()
    meanL2NormC = sqrt(normX * normX / n)
    @debug "meanL2NormC" meanL2NormC
    # We could use c as in Deshpande Vempala and let c decrease with iterations
    # to accelerate ?
    probaC = Vector{Float64}(undef,n)
    normC = Vector{Float64}(undef,n)
    A = Matrix{Float64}(undef, d, 0)
    more = true
    while more
        @debug "\n\n iteration " datapb.currentIter
        # sampled columns at this iter
        tSet = Set{Int64}()
        keptIdxNonZero = Vector{Int64}()
        # sample columns according to L2-norm of each columns
        normE = 0
        Threads.@threads for i = 1:n
            normC[i] = norm(E[:,i]) * norm(E[:,i])
        end
        for i = 1:n
            probaC[i] = i > 1 ? probaC[i-1] + normC[i] : normC[i]
            normE += normC[i]
        end
        if probaC[end] <= 0
            @debug "\n non positive proba for last slot=  :  " probaC[end]
            error("cannot sample column")
        end
        map!(x -> x/probaC[end], probaC, probaC)
        # get minimum non null column norm at iter t
        meanL2NormIter = mean(sqrt.(normC))
        @debug "\n c meanL2Norm at Iter  :  " meanL2NormIter
        for j = 1:datapb.samplingSize
            xsi = rand(Float64)
            s = searchsortedfirst(probaC, xsi)
            if s > length(probaC)
                @warn "\n sampling column failed xsi =  :  " xsi
                exit(1)
            end
            # avoid selecting some already sampled column or already a
            if !(s in tSet)
                push!(tSet, s)
            end
        end
        acceptanceRatio = length(tSet)/datapb.samplingSize
        @debug "acceptanceRatio" acceptanceRatio
        # get index of selected columns
        idxc = collect(tSet)
        At = datapb.X[:, idxc]
        # orthogonalize ... with preceding columns in A. Compute transpose(At)*A
        AttA = BLAS.gemm('T', 'N', At, A)
        Threads.@threads for i = 1:length(idxc)
            # with preceding columns in A. At[:,i] not normalized, A[:,j] is
            @simd for j = 1:size(A)[2]
                At[:,i] = At[:,i] - A[:,j] * AttA[i,j]
            end
        end
        @debug "QR orthogonalizing new block size At" size(At)
        At,tau  = LAPACK.geqrf!(At)
        Q=LAPACK.orgqr!(At, tau, length(tau))
        for i = 1:size(Q)[2]
            # within this block of columns in A.
            normi = norm(Q[:,i])
            # how much remains of At[:,i]. Should check with initial norm of column i
            if abs(normi - 1.) < 1.E-5
                push!(keptIdxNonZero, i)
#                @debug "adding a column vector of norm" normi idxc[i]
                A = hcat(A, Q[:,i])
                if size(A)[2] >=  min(d,n)
                    @debug "max dim reached"
                    more = false
                    break
                end
            else
                @debug "column not normalized" normi
            end
        end
        @info "A size" size(A)
        #
        datapb.currentRank = size(A)[2]
        # stopping criteria
        if stop(datapb)
            more = false
        else
            @debug "updating E"
            if length(keptIdxNonZero) < length(idxc)
                E = E - At[:, keptIdxNonZero] * BLAS.gemm('T', 'N', At[:, keptIdxNonZero], datapb.X)
            else
                E = E - At * BLAS.gemm('T', 'N', At, datapb.X)
            end
            datapb.residualErr = norm(E,2)/normX
            push!(residualAtIter, datapb.residualErr)
            # protection against stationarity
            if datapb.currentIter > 3 && residualAtIter[end] > 0.95 * residualAtIter[end-1]
                @warn "stationary iterations , stopping. perhaps increase rank"
                more = false
            end
            @info " iteration  , ||E|| set size "  datapb.currentIter datapb.residualErr  datapb.currentRank
            # if in reduceToEpsil mode we have to test now that we have datapb.residualErr
            if stop(datapb)
                more = false
            else
                datapb.currentIter += 1
            end
        end
    end
    # go from left singular to right singular vectors
    #
    @debug "computing right vectors"
    Vt = Matrix{Float64}(undef, size(A)[2], n)
    BLAS.gemm!('T', 'N', 1., A, datapb.X, 0., Vt)
    eigenValues = zeros(size(A)[2])
    Threads.@threads for i in 1:size(A)[2]
        eigenValues[i] = norm(Vt[i,:])
        Vt[i,:] = Vt[i,:] / eigenValues[i]
    end
    @debug "exiting LowRankApproxMc reduceIfast"
    datapb.U = Some(A)
    datapb.S = Some(eigenValues)
    datapb.Vt = Some(Vt)
end

"""
# function getResidualError(lrmc::LowRankApproxMc)

if X is the data matrix and U computation succeeded
    return the 2-uple (error, relerr) with :
```math
        error = norm(X - U * transpose(U) * X)
```
and :
```math
        relerr = Error/norm(X)
```

else throws an error
"""
function getResidualError(lrmc::LowRankApproxMc)
    U = something(lrmc.U)
    resErr = norm(lrmc.X - U* BLAS.gemm('T', 'N', U, lrmc.X))
    return resErr , resErr/norm(lrmc.X)
end

"""
# function getindex(LowRankApproxMc, Symbol)

returns U,S,Vt,V or rank according to Symbol :S , :U, :Vt , V or :k

"""
function getindex(datapb::LowRankApproxMc, d::Symbol)
    @match d begin
    :S    =>  datapb.S
    :U    =>  datapb.U
    :V    =>  datapb.Vt'
    :Vt   =>  datapb.Vt
    :k    =>  length(datapb.S)
    _     =>  throw(KeyError(d))
    end
end

"""
# function reduceToRank(datapb::LowRankApproxMc, expectedRank::Int64; nbiter = 2)

Implements incremental range approximation as described in
1: Vempala Spectral Book (2009)
    paragraph on Iterative Fast Svd P 85 and Th 7.2 7.3.

## Args
 -  expectedRank asked for.
    It must be noted that the returned rank can be different
   from expectedRank, especially if some columns are dominant in the data matrix. In this case
   The sampling size used to select colmuns is set to ``\\frac{expectedRank}{nbiter}``

 - number of iterations. Default to 2. Can be set to 1 ito get get
   a faster result at the expense of precision.

 ## Output

 The function does not return any output, instead it fills the fields U, S, Vt of the structure LowRankApproxMc.
 The fields can be retrieved by the function getIndex.
    If the rank extracted from the algorithm extractedRank we get as output :
  - U: a (d,extractedRank) matrix of orthonormalized left singular vector

  - S : a vector of singular values up to extractedRank

  - Vt : a (extractedRank , n) matrix , transposed of right singular vector.
    Nota the vector are not orthonormal.

    The svd approximation of data is:

    `` B = U * S * transpose(V) ``

    and verifies:

  `` E(|| data - B||^{2}) <=  1/(1-\\epsilon) * E(|| data - A_{k}|| ^{2}) + \\epsilon^{t} E(|| data ||^{2}) ``

 where ``A_{k}`` is the L2 best rank k approximation of data.
 B is of the form U * transpose(U) * data with U orthogonal made of left singular vectors.

 To get the corresponding result with right singular vector use a transpose Matrix as input.

 ## NOTA:

 **The Singular values are NOT sorted**

"""
function reduceToRank(datapb::LowRankApproxMc, rank::Int64; nbiter = 2)
    if rank > size(datapb.X)[1]
        error("reduceToRank reduction to rank greater than dimension")
    end
    maxdim = size(datapb.X)[2]
    datapb.currentIter = 1
    datapb.targetRank = rank
    # reset Epsil target in case of successive calls
    datapb.targetEpsil = 1.
    # we have rank target, 2 iterations should do
    if nbiter > 1
        datapb.samplingSize = floor(Int64,(1.05 * rank /nbiter))
        datapb.samplingSize = min(size(datapb.X)[2], datapb.samplingSize)
        @debug "LowRankApproxMc.reduceToRank setting samplingSize: " datapb.samplingSize
        maxiter = min(nbiter, 5)
        @warn "LowRankApproxMc.reduceToRank using maxiter " maxiter
        fstop2(datapb) = datapb.currentIter >= maxiter || datapb.currentRank >= datapb.targetRank ? true : false
        reduceIfast(datapb, fstop2)
    else
        # only one iter
        maxiter = 1
        datapb.samplingSize = floor(Int64,(1.10 * rank /nbiter))
        datapb.samplingSize = min(size(datapb.X)[2], datapb.samplingSize)
        @debug "LowRankApproxMc.reduceToRank setting samplingSize: " datapb.samplingSize
        fstop1(datapb) = datapb.currentIter >= maxiter ? true : false
        reduceIfast(datapb, fstop1)
    end
    # check we got rank
    if isnothing(datapb.U)
        @warn "reduceToRank  got not get left singular vectors"
    elseif size(datapb.U.value)[2] < rank
        @warn "reduceToRank  got only up to rank" size(datapb.U.value)[2]
    end
    # return rank approx
end

"""
# function reduceToEpsil(datapb::LowRankApproxMc, samplingSize::Int64, epsil::Float64;maxiter = 5)

This function computes an approximate svd up to a precision epsil.
    It computes U, S and Vt so that the matrix B = U*S*Vt verifies :
    `` E(|| data - B||_{2}) / E(|| data ||_{2}) <=  epsil ``
    It iterates until either the precision asked for is reached or *maxiter* iterations are done
        or rank reached is maximal.
## Args
------

-  samplingSize: the number of columns tentatively sampled by iteration.

- epsil : precision asked for. It is clear that a very small epsil will cause a full to be done
        with the corresponding costs. In this case possibly, a call to LAPACK.svd will be better.

- maxiter : the maximum number of iterations. default to 5

## Output:

The function does not return any output, instead it fills the fields U, S, Vt of the structure LowRankApproxMc.

The fields can be retrieved by the function getIndex.
If the rank extracted from the algorithm extractedRank we get as output :
- U: a (d,extractedRank) matrix of orthonormalized left singular vector

- S : a vector of singular values up to extractedRank

- Vt : a (extractedRank , n) matrix , transposed of right singular vector.
       Nota the vector are not orthonormal.

The svd approximation of data is: `` B = U * S * transpose(V) ``

and verifies:

`` E(|| data - B||^{2}) <=  1/(1-\\epsilon) * E(|| data - A_{k}|| ^{2}) + \\epsilon^{t} E(|| data ||^{2}) ``

where ``A_{k}`` is the L2 rank k approximation of data.
B is of the form U * transpose(U) * data with U orthogonal made of left singular vectors.

## NOTA:

 **The Singular values are NOT sorted**

"""
function reduceToEpsil(datapb::LowRankApproxMc, samplingSize::Int64, epsil::Float64; maxiter = 5)
    datapb.targetEpsil = epsil
    # reset targetRank in case of successive calls
    # rank is set to full dimension
    datapb.targetRank = min(size(datapb.X)[1], size(datapb.X)[2])
    if samplingSize <= 0
        throw("non strict positive samplingSize")
    end
    samplingSize = min(size(datapb.X)[2], samplingSize)
    datapb.samplingSize = samplingSize
    # epsil target
    # avoid doing one more iteration just for 1% of epsil
    fstop(datapb) = datapb.currentIter >= maxiter || datapb.residualErr <= epsil*(1.01) ||
            datapb.currentRank >= datapb.targetRank ? true : false
    reduceIfast(datapb, fstop)
end

# the following is faster as we do not ask for Vt

function getRangeApprox(data::Matrix{Float64}, rank::Int64)
    @debug " in getRangeApprox"
    svdpb = LowRankApproxMc(data)
    reduceToRank(svdpb, rank)
    U = something(svdpb.U)
    @debug "reduceToRank returned size" size(U)
    # compute all U columns and 0 columns of Vt
    U,S,Vt = LAPACK.gesvd!('A', 'O', U)
    @debug "lapack returned size U,S,Vt"  size(U) size(S) size(Vt)
    # return first k columns of U and first k values of diagonal of S
    U[:, (1:rank)],S[1:rank]
end


@add_benchmark function affine_negate_atom(handle_problem)
    x = Variable()
    p = minimize(-x, [x <= 0])
    handle_problem(p)
end

@add_benchmark function affine_kron_atom(handle_problem)
    x = ComplexVariable(3, 3)
    y = [1.0 2.0; 3.0 4.0]
    p = satisfy(kron(x,y) == kron(eye(3), y))
    handle_problem(p)
end

@add_benchmark function affine_mult_atom(handle_problem)
    probs=[
        let
            x = Variable(1)
            p = minimize(2.0 * x, [x >= 2, x <= 4])
        end,
        let
            x = Variable(2)
            A = 1.5 * eye(2)
            p = minimize([2 2] * x, [A * x >= [1.1; 1.1]])
        end,
        let
            y = Variable(1)
            x = Variable(3)
            z = [1.0, 2.0, 3.0] * y
            k = -y * [1.0, 2.0, 3.0]
            c = [y <= 3.0, y >= 0.0, x >= ones(3),  k <= x, x <= z]
            o = 3 * y
            p = Problem(:minimize, o, c)
        end,
        let
            x = ComplexVariable(2,2)
            p = minimize(   real( [1.0im, 0.0]' * x * [1.0im, 0.0] ),
                    [ x == [1.0 0.0; 0.0 1.0] ] )
        end
    ]
    handle_problem.(probs)
end

@add_benchmark function affine_dot_atom(handle_problem)
    probs = [
        let
            x = Variable(2)
            p = minimize(dot([2.0; 2.0], x), x >= [1.1; 1.1])
        end,
        let
            x = Variable(2,2)
            p = minimize(dot(fill(2.0, (2,2)), x), x >= 1.1)
        end
    ]

    handle_problem.(probs)
end

@add_benchmark function affine_add_atom(handle_problem)

    probs = [
        let
            x = Variable(1)
            y = Variable(1)
            p = minimize(x + y, [x >= 3, y >= 2])
        end,
        let
            x = Variable(1)
            p = minimize(x, [eye(2) + x >= eye(2)])
        end,
        let
            y = Variable()
            p = minimize(y - 5, y >= -1)
        end ]

    handle_problem.(probs)
end

@add_benchmark function affine_transpose_atom(handle_problem)
    probs = [
        let
            x = Variable(2)
            c = ones(2, 1)
            p = minimize(x' * c, x >= 1)
        end,
        let
            X = Variable(2, 2)
            c = ones(2, 1)
            p = minimize(c' * X' * c, [X >= ones(2, 2)])
        end,
        let
            rows = 2
            cols = 3
            r = rand(rows, cols)
            r_2 = rand(cols, rows)
            x = Variable(rows, cols)
            c = ones(1, cols)
            d = ones(rows, 1)
            p = minimize(c * x' * d + d' * x * c' + (c * x''''' * d)',
                        [x' >= r_2, x >= r, x''' >= r_2, x'' >= r])
        end ]

        handle_problem.(probs)
end

@add_benchmark function affine_index_atom(handle_problem)
        probs = [
        let
            x = Variable(2)
            p = minimize(x[1] + x[2], [x >= 1])
        end,
        let
            x = Variable(3)
            I = [true true false]
            p = minimize(sum(x[I]), [x >= 1])
        end,
        let
            rows = 6
            cols = 8
            n = 2
            X = Variable(rows, cols)
            A = randn(rows, cols)
            c = rand(1, n)
            p = minimize(c * X[1:n, 5:5+n-1]' * c', X >= A)
        end]
        handle_problem.(probs)
end

@add_benchmark function affine_sum_atom(handle_problem)
        probs = [
        let
            x = Variable(2,2)
            p = minimize(sum(x) - 2*x[1,1], x>=1, x[1,1]<=2)
        end,
        let
            x = Variable(2,2)
            p = minimize(sum(x) - 2*x[1,1], x>=1, x[1,1]<=2)
        end
        ]
    return handle_problem.(probs)
end

@add_benchmark function affine_diag_atom(handle_problem)
    probs = [
        let
            x = Variable(2,2)
            p = minimize(sum(diag(x,1)), x >= 1)
        end,
        let
            x = Variable(4, 4)
            p = minimize(sum(diag(x)), x >= 2)
        end,
    ]
    return handle_problem.(probs)

end

@add_benchmark function affine_tr_atom(handle_problem)
    x = Variable(2,2)
    p = minimize(tr(x), x >= 1)
    handle_problem(p)
end


using StructJuMP, JuMP
using StructJuMPSolverInterface

include("select_solver.jl")

#############
# A sample model
#############
scen = 2
m = StructuredModel(num_scenarios=scen)
@variable(m, x[1:2])
@NLobjective(m, Min, x[1]^2+x[2]^2)
@NLconstraint(m, x[1] * x[2] <= 100)

for i in 1:scen
    bl = StructuredModel(parent=m)
    @variable(bl, y)
    @NLconstraint(bl, x[2]*x[1] + x[1]*y <= 10)
    @NLobjective(bl, Min, y^2)
end

structJuMPSolve(m)

getVarValue(m)

#verification
# x[1]^2+x[2]^2 + y1^2 + y2^2

# x[1]*x[2]

# x[2]*x[1] + x[1]*y1

# x[2]*x[1] + x[1]*y2


export companion

function companion(Z)

    T = eltype(Z)
    K = size(Z,3)
    @assert K > 1

    M, N = size(Z)[1:2]
    C = similar(Z, M*(K-1), N*(K-1))
    fill!(C,0)

    @assert M == N
    Id = Matrix{T}(I, M, N)
    for m in 2:K-1
        n = m-1
        C[(m-1)*M+1:m*M, (n-1)*N+1:n*N] = Id
    end

    W = -inv(Z[:,:,1])
    for n in 1:K-1
        m = 1
        C[(m-1)*M+1:m*M, (n-1)*N+1:n*N] = W*Z[:,:,n+1]
    end

    return C
end


using Distributions, PDMats, Primes, Random, LinearAlgebra

function qsimvnv(Σ::AbstractMatrix, a, b;m=nothing)
    #= rev 1.13

    This function uses an algorithm given in the paper
    "Numerical Computation of Multivariate Normal Probabilities", in
     J. of Computational and Graphical Stat., 1(1992), pp. 141-149, by
    Alan Genz, WSU Math, PO Box 643113, Pullman, WA 99164-3113
    Email : alangenz@wsu.edu
    The primary references for the numerical integration are
    "On a Number-Theoretical Integration Method"
    H. Niederreiter, Aequationes Mathematicae, 8(1972), pp. 304-11, and
    "Randomization of Number Theoretic Methods for Multiple Integration"
    R. Cranley and T.N.L. Patterson, SIAM J Numer Anal, 13(1976), pp. 904-14.

    Re-coded in Julia from the MATLAB function qsimvnv(m,r,a,b)

    Alan Genz is the author the MATLAB qsimvnv() function.
    Alan Genz software website: http://archive.is/jdeRh
    Source code to MATLAB qsimvnv() function: http://archive.is/h5L37
    % QSIMVNV(m,r,a,b) and _chlrdr(r,a,b)
    %
    % Copyright (C) 2013, Alan Genz,  All rights reserved.
    %
    % Redistribution and use in source and binary forms, with or without
    % modification, are permitted provided the following conditions are met:
    %   1. Redistributions of source code must retain the above copyright
    %      notice, this list of conditions and the following disclaimer.
    %   2. Redistributions in binary form must reproduce the above copyright
    %      notice, this list of conditions and the following disclaimer in
    %      the documentation and/or other materials provided with the
    %      distribution.
    %   3. The contributor name(s) may not be used to endorse or promote
    %      products derived from this software without specific prior
    %      written permission.
    % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    % "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    % LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    % FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    % COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    % INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    % BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
    % OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    % ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
    % TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF USE
    % OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    %

    Julia dependencies
    Distributions
    PDMats
    Primes
    Random
    LinearAlgebra =#

    if isnothing(m)
        m = 1000 * size(Σ, 1)  # default is 1000 * dimension
    end

    # check for proper dimensions
    n = size(Σ, 1)
    nc = size(Σ, 2) 	# assume square Cov matrix nxn
    # check dimension > 1
    n >= 2   || throw(ErrorException("dimension of Σ must be 2 or greater. Σ dimension: $(size(Σ))"))
    n == nc  || throw(DimensionMismatch("Σ matrix must be square. Σ dimension: $(size(Σ))"))

    # check dimensions of lower vector, upper vector, and cov matrix match
    (n == size(a, 1) == size(b, 1)) || throw(DimensionMismatch("inconsistent argument dimensions. Sizes: Σ $(size(Σ))  a $(size(a))  b $(size(b))"))

    # check that a and b are column vectors; if row vectors, fix it
    if size(a, 1) < size(a, 2)
        a = transpose(a)
    end
    if size(b, 1) < size(b, 2)
        b = transpose(b)
    end

    # check that lower integration limit a < upper integration limit b for all elements
    all(a .<= b) || throw(ArgumentError("lower integration limit a must be <= upper integration limit b"))

    # check that Σ is positive definate; if not, print warning
    isa(Σ, AbstractPDMat) || isposdef(Σ) || @warn "covariance matrix Σ fails positive definite check"

    # check if Σ, a, or b contains NaNs
    if any(isnan.(Σ)) || any(isnan.(a)) || any(isnan.(b))
        p = NaN
        e = NaN
        return (p, e)
    end

    # check if a==b
    if a == b
        p = 0.0
        e = 0.0
        return (p, e)
    end

    # check if a = -Inf & b = +Inf
    if all(a .== -Inf) && all(b .== Inf)
        p = 1.0
        e = 0.0
        return (p, e)
    end

    # check input Σ, a, b are floats; otherwise, convert them
    if eltype(Σ) <: Signed
        Σ = float(Σ)
    end

    if eltype(a) <: Signed
        a = float(a)
    end

    if eltype(b) <: Signed
        b = float(b)
    end

    ##################################################################
    #
    # Special cases: positive Orthant probabilities for 2- and
    # 3-dimesional Σ have exact solutions. Integration range [0,∞]
    #
    ##################################################################

    if all(a .== zero(eltype(a))) && all(b .== Inf) && n <= 3
        Σstd = sqrt.(diag(Σ))
        Rcorr = cov2cor(Σ, Σstd)

        if n == 2
            p = 1 / 4 + asin(Rcorr[1,2]) / (2π)
            e = eps()
        elseif n == 3
            p = 1 / 8 + (asin(Rcorr[1,2]) + asin(Rcorr[2,3]) + asin(Rcorr[1,3])) / (4π)
            e = eps()
        end

        return (p, e)

    end

    ##################################################################
    #
    # get lower cholesky matrix and (potentially) re-ordered integration vectors
    #
    ##################################################################

    (ch, as, bs) = _chlrdr(Σ, a, b) # ch =lower cholesky; as=lower vec; bs=upper vec

    ##################################################################
    #
    # quasi-Monte Carlo integration of MVN integral
    #
    ##################################################################

    ### setup initial values
    ai = as[1]
    bi = bs[1]
    ct = ch[1,1]

    unitnorm = Normal() # unit normal distribution
    rng = RandomDevice()

    # if ai is -infinity, explicity set c=0
    # implicitly, the algorith classifies anythign > 9 std. deviations as infinity
    if ai > -9 * ct
        if ai < 9 * ct
            c1 = cdf.(unitnorm, ai / ct)
        else
            c1 = 1.0
        end
    else
        c1 = 0.0
    end

    # if bi is +infinity, explicity set d=0
    if bi > -9 * ct
        if bi < 9 * ct
            d1 = cdf(unitnorm, bi / ct)
        else
            d1 = 1.0
        end
    else
        d1 = 0.0
    end

    # n=size(Σ,1) 	# assume square Cov matrix nxn
    cxi = c1			# initial cxi; genz uses ci but it conflicts with Lin. Alg. ci variable
    dci = d1 - cxi		# initial dcxi
    p = 0.0			# probablity = 0
    e = 0.0			# error = 0

    # Richtmyer generators
    ps = sqrt.(primes(Int(floor(5 * n * log(n + 1) / 4)))) # Richtmyer generators
    q = ps[1:n - 1,1]
    ns = 12
    nv = Int(max(floor(m / ns), 1))

    Jnv    = ones(1, nv)
    cfill  = transpose(fill(cxi, nv)) 	# evaulate at nv quasirandom points row vec
    dpfill = transpose(fill(dci, nv))
    y      = zeros(n - 1, nv)				# n-1 rows, nv columns, preset to zero

    #= Randomization loop for ns samples
     j is the number of samples to integrate over,
         but each with a vector nv in length
     i is the number of dimensions, or integrals to comptue =#

    for j in 1:ns					# loop for ns samples
        c  = copy(cfill)
        dc = copy(dpfill)
        pv = copy(dpfill)
        for i in 2:n
            x = transpose(abs.(2.0 .* mod.((1:nv) .* q[i - 1] .+ rand(rng), 1) .- 1))	 # periodizing transformation
                # note: the rand() is not broadcast -- it's a single random uniform value added to all elements
            y[i - 1,:] = quantile.(unitnorm, c .+ x .* dc)
            s = transpose(ch[i,1:i - 1]) * y[1:i - 1,:]
            ct = ch[i,i]										# ch is cholesky matrix
            ai = as[i] .- s
            bi = bs[i] .- s
            c = copy(Jnv)										# preset to 1 (>9 sd, +∞)
            d = copy(Jnv)										# preset to 1 (>9 sd, +∞)

            c[findall(x -> isless(x, -9 * ct), ai)] .= 0.0		# If < -9 sd (-∞), set to zero
            d[findall(x -> isless(x, -9 * ct), bi)] .= 0.0		# if < -9 sd (-∞), set to zero
            tstl = findall(x -> isless(abs(x), 9 * ct), ai)		# find cols inbetween -9 and +9 sd (-∞ to +∞)
            c[tstl] .= cdf.(unitnorm, ai[tstl] / ct)			# for those, compute Normal CDF
            tstl = (findall(x -> isless(abs(x), 9 * ct), bi))	# find cols inbetween -9 and +9 sd (-∞ to +∞)
            d[tstl] .= cdf.(unitnorm, bi[tstl] / ct)
            @. dc = d - c
            @. pv = pv * dc
        end # for i=
        d = (mean(pv) - p) / j
        p += d
        e = (j - 2) * e / j + d^2
    end # for j=

    e = 3 * sqrt(e) 	# error estimate is 3 times standard error with ns samples

    return (p, e)  	# return probability value and error estimate
end # function qsimvnv

function _chlrdr(Σ, a, b)

# Rev 1.13

# define constants
# 64 bit machien error 1.0842021724855e-19 ???
# 32 bit machine error 2.220446049250313e-16 ???
    ep = 1e-10 # singularity tolerance
    if Sys.WORD_SIZE == 64
        fpsize = Float64
        ϵ = eps(0.0) # 64-bit machine error
    else
        fpsize = Float32
        ϵ = eps(0.0f0) # 32-bit machine error
    end

    if !@isdefined sqrt2π
        sqrt2π = √(2π)
    end

# unit normal distribution
    unitnorm = Normal()

    n = size(Σ, 1) # covariance matrix n x n square

    ckk = 0.0
    dem = 0.0
    am = 0.0
    bm = 0.0
    ik = 0.0

    if eltype(Σ) <: Signed
        c = copy(float(Σ))
    else
        c = copy(Σ)
    end

    if eltype(a) <: Signed
        ap = copy(float(a))
    else
        ap = copy(a)
    end

    if eltype(b) <: Signed
        bp = copy(float(b))
    else
        bp = copy(b)
    end

    d = sqrt.(diag(c))
    for i in 1:n
        if d[i] > 0.0
            c[:,i] /= d[i]
            c[i,:] /= d[i]
            ap[i] = ap[i] / d[i]     # ap n x 1 vector
            bp[i] = bp[i] / d[i]     # bp n x 1 vector
        end
    end

    y = zeros(fpsize, n) # n x 1 zero vector to start

    for k in 1:n
        ik = k
        ckk = 0.0
        dem = 1.0
        s = 0.0
    # pprinta(c)
        for i in k:n
            if c[i,i] > ϵ  # machine error
                cii = sqrt(max(c[i,i], 0))

                if i > 1 && k > 1
                    s = (c[i,1:(k - 1)] .* y[1:(k - 1)])[1]
                end

                ai = (ap[i] - s) / cii
                bi = (bp[i] - s) / cii
                de = cdf(unitnorm, bi) - cdf(unitnorm, ai)

                if de <= dem
                    ckk = cii
                    dem = de
                    am = ai
                    bm = bi
                    ik = i
                end
            end # if c[i,i]> ϵ
        end # for i=
        i = n

        if ik > k
            ap[ik], ap[k] = ap[k], ap[ik]
            bp[ik], bp[k] = bp[k], bp[ik]

            c[ik,ik] = c[k,k]

            if k > 1
                c[ik,1:(k - 1)], c[k,1:(k - 1)] = c[k,1:(k - 1)], c[ik,1:(k - 1)]
            end

            if ik < n
                c[(ik + 1):n,ik], c[(ik + 1):n,k] = c[(ik + 1):n,k], c[(ik + 1):n,ik]
            end

            if k <= (n - 1) && ik <= n
                c[(k + 1):(ik - 1),k], c[ik,(k + 1):(ik - 1)] = transpose(c[ik,(k + 1):(ik - 1)]), transpose(c[(k + 1):(ik - 1),k])
            end
        end # if ik>k

        if ckk > k * ep
            c[k,k] = ckk
            if k < n
                c[k:k,(k + 1):n] .= 0.0
            end

            for i in (k + 1):n
                c[i,k] /= ckk
                c[i:i,(k + 1):i] -= c[i,k] * transpose(c[(k + 1):i,k])
            end

            if abs(dem) > ep
                y[k] = (exp(-am^2 / 2) - exp(-bm^2 / 2)) / (sqrt2π * dem)
            else
                if am < -10
                    y[k] = bm
                elseif bm > 10
                y[k] = am
            else
                y[k] = (am + bm) / 2
                end
            end # if abs
        else
            c[k:n,k] .== 0.0
            y[k] = 0.0
        end # if ckk>ep*k
    end # for k=

    return (c, ap, bp)
end # function _chlrdr

function testmvn(;m=nothing)

# Typical Usage/Example Code
# Example multivariate Normal CDF for various vectors
    println()

# from MATLAB documentation
    r = [4 3 2 1;3 5 -1 1;2 -1 4 2;1 1 2 5]
 	a = [-Inf; -Inf; -Inf; -Inf] # -inf for each
	b = [1; 2; 3; 4 ]
	m = 5000
	# m=4000 # rule of thumb: 1000*(number of variables)
	(myp, mye) = qsimvnv(r, a, b;m=m)
	println("Answer should about 0.6044 to 0.6062")
	println(myp)
	println("The Error should be <= 0.001 - 0.0014");
	println(mye)

	r = [1 0.6 0.333333;
	   0.6 1 0.733333;
	   0.333333 0.733333 1]
	r  = [  1   3 / 5   1 / 3;
		  3 / 5    1    11 / 15;
		  1 / 3  11 / 15    1]
	a = [-Inf;-Inf;-Inf]
	b = [1;4;2]
	# m=3000;
	(myp, mye) = qsimvnv(r, a, b;m=4000)
	println()
	println("Answer shoudl be about 0.82798")
	# answer from Genz paper 0.827984897456834
	println(myp)
	println("The Error should be <= 2.5-05")
	println(mye)

	r = [1 0.25 0.2; 0.25 1 0.333333333; 0.2 0.333333333 1]
	a = [-1;-4;-2]
	b = [1;4;2]
	# m=3000;
	(myp, mye) = qsimvnv(r, a, b;m=4000);
	println()
	println("Answer should be about 0.6537")
	println(myp)
	println("The Error should be <= 2.5-05")
	println(mye)

	# Genz problem 1.5 p. 4-5  & p. 63
	# correct answer F(a,b) = 0.82798
    r = [1 / 3 3 / 5 1 / 3;
         3 / 5 1.0 11 / 15;
         1 / 3 11 / 15 1.0]
    a = [-Inf; -Inf; -Inf]
    b = [1; 4; 2]
	(myp, mye) = qsimvnv(r, a, b;m=4000)
	println()
	# println("Answer shoudl be about 0.82798")
	println("Answer should be 0.9432")
	println(myp)
	println("The error should be < 6e-05")
	println(mye)

	# Genz page 63 uses a different r Matrix
	r = [1 0 0;
		3 / 5 1 0;
		1 / 3 11 / 15 1]
		a = [-Inf; -Inf; -Inf]
		b = [1; 4; 2]
	(myp, mye) = qsimvnv(r, a, b;m=4000)
	println()
	println("Answer shoudl be about 0.82798")
	println(myp)
	println("The error should be < 6e-05")
	println(mye)
	# mystery solved - he used the wrong sigma Matrix
	# when computing the results on page 6

	# singular cov Example
	r = [1 1 1; 1 1 1; 1 1 1]
	a = [-Inf, -Inf, -Inf]
	b = [1, 1, 1]
	(myp, mye) = qsimvnv(r, a, b)
	println()
	println("Answer should be 0.841344746068543")
	println(myp)
	println("The error should be 0.0")
	println(mye)
	println("Cov matrix is singular")
	println("Problem reduces to a univariate problem with")
	println("p = cdf.(Normal(),1) = ", cdf.(Normal(), 1))

	# 5 dimensional Example
	# c = LinearAlgebra.tri(5)
	r = [1 1 1 1 1;
		 1 2 2 2 2;
		 1 2 3 3 3;
		 1 2 3 4 4;
		 1 2 3 4 5]
	a = [-1,-2,-3,-4,-5]
	b = [2,3,4,5,6]
	(myp, mye) = qsimvnv(r, a, b)
	println()
	println("Answer should be ~ 0.7613")
	# genz gives 0.4741284  p. 5 of book
	# Julia, MATLAB, and R all give ~0.7613 !
	println(myp)
	println("The error should be < 0.001")
	println(mye)

	# genz reversed the integration limits when computing
	# see p. 63
	a = sort!(a)
	b = 1 .- a
	(myp, mye) = qsimvnv(r, a, b)
	println()
	println("Answer should be ~ 0.4741284")
	# genz gives 0.4741284  p. 5 of book
	# now matches with reversed integration limits
	println(myp)
	println("The error should be < 0.001")
	println(mye)

	# positive orthant of above
	a = [0,0,0,0,0]
	(myp, mye) = qsimvnv(r, a, b)
	println()
	println("Answer should be ~  0.11353418")
	# genz gives 0.11353418   p. 6 of book
	println(myp)
	println("The error should be < 0.001")
	println(mye)

	# now with a = -inf
	a = [-Inf,-Inf,-Inf,-Inf,-Inf]
	(myp, mye) = qsimvnv(r, a, b)
	println()
	println("Answer should be ~ 0.81031455")
	# genz gives 0.81031455  p. 6 of book
	println(myp)
	println("The error should be < 0.001")
	println(mye)

	# eight dimensional Example
	r = [1 1 1 1 1 1 1 1;
		 1 2 2 2 2 2 2 2;
		 1 2 3 3 3 3 3 3;
		 1 2 3 4 4 4 4 4;
		 1 2 3 4 5 5 5 5;
		 1 2 3 4 5 6 6 6;
		 1 2 3 4 5 6 7 7;
		 1 2 3 4 5 6 7 8]
	a = -1 * [1,2,3,4,5,6,7,8]
	b = [2,3,4,5,6,7,8,9]
	(myp, mye) = qsimvnv(r, a, b)
	println()
	println("Answer should be ~ 0.7594")
	# genz gives 0.32395   p. 6 of book
	# MATLAB gives 0.7594362
	println(myp)
	println("The error should be < 0.001")
	println(mye)

	# eight dim orthant
	a = [0,0,0,0,0,0,0,0]
	b = [Inf,Inf,Inf,Inf,Inf,Inf,Inf,Inf]
	(myp, mye) = qsimvnv(r, a, b)
	println()
	println("Answer should be ~ 0.196396")
	# genz gives 0.076586  p. 6 of book
	# MATLB gives 0.196383
	println(myp)
	println("The error should be < 0.001")
	println(mye)

	# eight dim with a = -inf
	a = -Inf * [1,1,1,1,1,1,1,1]
	b = [2,3,4,5,6,7,8,9]
	# b = [0,0,0,0,0,0,0,0]
	(myp, mye) = qsimvnv(r, a, b)
	println()
	println("Answer should be ~ 0.9587")
	# genz gives 0.69675    p. 6 of book
	# MATLAB gives 0.9587
	println(myp)
	println("The error should be < 0.001")
	println(mye)
end # testmvn


abstract type MwlsObject end

"""
# User provided member variables
- `inputs::Array{Float64, 2}`: a 2d array of input points where each point is in a single column,
- `outputs::Array{Float64, N}`: a 2d array or a vector of output points where each output is in a single column,
- `EPS::Float64`: ε of the method (cell edge length and the default distance for range search),
- `weightfunc::Function`: weighting function θ of the method.

# Automatically created member variables
If created by the `mwls_naive` function, the member variables in this section are created automatically.

- `vars::Vector{PolyVar{true}}`: variables of the polynomial,
- `b::Vector{Monomial{true}}`: the basis of the polynomial,
- `matrix`: the result of `b * transpose(b)`.
"""
struct MwlsNaiveObject <: MwlsObject
  inputs::Array{Real, 2}
  outputs::Array{Real, N} where {N} # accepts both scalar and vector outputs
  EPS::Real
  weightfunc::Function
  vars::Vector{PolyVar{true}}
  b::Vector{Monomial{true}}
  matrix::Array{Term{true, Int}, 2}
end

function MwlsNaiveObject(inputs, outputs, EPS, weightfunc, vars, b)
  t = transpose(inputs)
  o = transpose(outputs)
  return MwlsNaiveObject(t, o, EPS, weightfunc, vars, b, b * transpose(b))
end

"""
Documentation is left out on purpose to discourage the use of MwlsNaiveObject.
K-d trees are always faster and cell linked lists are faster on datasets larger than 20.
`mwls_naive` has the same signature as `mwls_cll`.
"""
function mwls_naive(inputs::Array{T, N}, outputs::Array{U, M},
                    EPS::Real, weightfunc::Function;
                    maxDegree::Integer = 2) where {T <: Real, U <: Real, N, M}
  size(outputs, 1) != size(inputs, 1) &&
    error("The amount of inputs and outputs differs")

  vars::Vector{PolyVar{true}}, b :: Vector{Monomial{true}} =
    genmonomials(size(inputs, 2), maxDegree)

  return MwlsNaiveObject(inputs, outputs, EPS, weightfunc, vars, b)
end

"""
Documentation is left out on purpose to discourage the use of MwlsNaiveObject.
K-d trees are always faster and cell linked lists are faster on datasets larger than 20.
`mwls_naive` has the same signature as `mwls_cll`.
"""
function mwls_naive(input::Array{T, 2}, EPS::Real, weightfunc::Function;
                    outputDim::Integer = 1, maxDegree::Integer = 2) where {T <: Real}
  width = size(input, 2)
  inputEnd = width - outputDim
  outputStart = inputEnd + 1
  return mwls_naive(input[:, inputEnd == 1 ? 1 : 1:inputEnd],
                    input[:, outputStart == width ? width : outputStart:width],
                    EPS, weightfunc,
                    maxDegree = maxDegree)
end

mwlsNaive(a...;b...) = warn("`mwlsNaive` is deprecated, use `mwls_naive` instead.")

"""
# User provided member variables
- `inputs::Array{Float64, 2}`: a 2d array of input points where each point is in a single column,
- `outputs::Array{Float64, N}`: a 2d array or a vector of output points where each output is in a single column,
- `EPS::Float64`: ε of the method (cell edge length and the default distance for range search),
- `weightfunc::Function`: weighting function θ of the method.

# Automatically created member variables
If created by the `mwls_cll` function, the member variables in this section are created automatically.

- `vars::Vector{PolyVar{true}}`: variables of the polynomial,
- `b::Vector{Monomial{true}}`: the basis of the polynomial,
- `matrix`: the result of `b * transpose(b)`,
- `tree`: a k-d tree for range search.
"""
struct MwlsKdObject <: MwlsObject
  inputs::Array{Real, 2}
  outputs::Array{Real, N} where {N} # accepts both scalar and vector outputs
  EPS::Real
  weightfunc::Function
  vars::Vector{PolyVar{true}}
  b::Vector{Monomial{true}}
  matrix::Array{Term{true, Int}, 2}
  tree::KDTree
end

function MwlsKdObject(inputs, outputs, EPS, weightfunc, vars, b;
                      leafSize::Integer = 10)
  t = transpose(inputs)
  o = transpose(outputs)
  return MwlsKdObject(t, o, EPS, weightfunc,
                      vars, b, b * transpose(b),
                      KDTree(t, leafsize = leafSize))
end

"""
    mwls_kd(inputs::Array{T, N}, outputs::Array{U}, EPS::Real, weightfunc::Function) where {T <: Real, U <: Real, N}
    mwls_kd(inputs::Array{T, N}, outputs::Array{U}, EPS::Real, weightfunc::Function; leafsize::Int = 10, maxDegree::Int = 2) where {T <: Real, U <: Real, N}

Creates `MwlsKdObject` from sample input and sample output data, the cutoff distance ε and a weighting function θ.

# Arguments
- `inputs`: a 2d array or a vector of input points where each point is on a single row,
- `outputs`: a 2d array or a vector of output scalars where each output is on a single row,
- `EPS::Real`: ε of the method (default distance threshold for neighbor search),
- `weightfunc::Function`: weighting function of the method. It should be in form `(distance, EPS) -> Float64`.

# Keyword arguments
- `leafSize::Int`: the size of the leaves in the k-d-tree, 10 by default.
- `maxDegree::Int`: the maximal degree of polynomials used for approximation, 2 by default.
"""
function mwls_kd(inputs::Array{T, N}, outputs::Array{U},
                 EPS::Real, weightfunc::Function;
                 leafSize::Integer = 10, maxDegree::Integer = 2) where {T <: Real, U <: Real, N}
  size(outputs, 1) != size(inputs, 1) &&
    error("The amount of inputs and outputs differs.")

  vars::Vector{PolyVar{true}}, b::Vector{Monomial{true}} =
    genmonomials(size(inputs, 2), maxDegree)
  return MwlsKdObject(N == 1 ? hcat(inputs, zeros(size(inputs, 1))) : inputs,
                      outputs, EPS, weightfunc,
                      vars, b, leafSize = leafSize)
end

"""
    mwls_kd(input::Array{T, 2}, EPS::Real, weightfunc::Function) where {T <: Real}
    mwls_kd(input::Array{T, 2}, EPS::Real, weightfunc::Function; outputDim::Int = 1, leafSize::Int = 10, maxDegree::Int = 2) where {T <: Real}

In this `mwls_kd` function, the sample input and sample output data are passed in a single array.
It is assumed that each pair of input and output is on a single row.
Dimension of the output is specified with kwarg `outputDim`.
"""
function mwls_kd(input::Array{T, 2}, EPS::Real, weightfunc::Function;
                outputDim::Integer = 1, leafSize::Integer = 10, maxDegree::Int = 2) where {T <: Real}
  width = size(input, 2)
  inputEnd = width - outputDim
  outputStart = inputEnd + 1
  return mwls_kd(input[:, inputEnd == 1 ? 1 : 1:inputEnd],
                 input[:, outputStart == width ? width : outputStart:width],
                 EPS, weightfunc,
                 leafSize = leafSize, maxDegree = maxDegree)
end

mwlsKd(a...;b...) = warn("`mwlsKd` is deprecated, use `mwls_kd` instead.")

"""
# User provided member variables
- `inputs::Array{Float64, 2}`: a 2d array of input points where each point is in a single column,
- `outputs::Array{Float64, N}`: a 2d array or a vector of output points where each output is in a single column,
- `EPS::Float64`: ε of the method (cell edge length and the default distance for range search),
- `weightfunc::Function`: weighting function θ of the method.

# Automatically created member variables
If created by the `mwls_cll` function, the member variables in this section are created automatically.

- `vars::Vector{PolyVar{true}}`: variables of the polynomial,
- `b::Vector{Monomial{true}}`: the basis of the polynomial,
- `matrix`: the result of `b * transpose(b)`,
- `cll`: a cell linked list for range search.
"""
struct MwlsCllObject <: MwlsObject
  inputs::Array{Real, 2}
  outputs::Array{Real, N} where {N} # accepts both scalar and vector outputs
  EPS::Real
  weightfunc::Function
  vars::Vector{PolyVar{true}}
  b::Vector{Monomial{true}}
  matrix::Array{Term{true, Int}, 2}
  cll::CellLinkedList
end

function MwlsCllObject(inputs, outputs, EPS, weightfunc, vars, b)
  t = transpose(inputs)
  o = transpose(outputs)
  return MwlsCllObject(t, o, EPS, weightfunc,
                       vars, b, b * transpose(b),
                       CellLinkedList(t, EPS))
end

"""
    mwls_cll(inputs::Array{T, N}, outputs::Array{U}, EPS::Real, weightfunc::Function) where {T <: Real, U <: Real, N}
    mwls_cll(inputs::Array{T, N}, outputs::Array{U}, EPS::Real, weightfunc::Function; maxDegree::Int = 2) where {T <: Real, U <: Real, N}

Creates `MwlsCllObject` from sample input and sample output data, the cutoff distance ε and a weighting function θ.

# Arguments
- `inputs`: a 2d array of input points where each point is on a single row,
- `outputs`: a 2d array or a vector of output scalars where each output is on a single row,
- `EPS::Real`: ε of the method (cell edge length and the default distance for range search),
- `weightfunc::Function`: weighting function θ of the method. It should be in form `(distance between two vectors, EPS) -> Float64`.

# Keyword arguments
- `maxDegree::Int`: the maximal degree of polynomials used for approximation, 2 by default.
"""
function mwls_cll(inputs::Array{T, N}, outputs::Array{U},
                  EPS::Real, weightfunc::Function;
                  maxDegree::Integer = 2) where {T <: Real, U <: Real, N}
  size(outputs, 1) != size(inputs, 1) &&
    error("The amount of inputs and outputs differs.")

  vars::Vector{PolyVar{true}}, b::Vector{Monomial{true}} =
    genmonomials(size(inputs, 2), maxDegree)

  return MwlsCllObject(N == 1 ? hcat(inputs, zeros(size(inputs,1))) : inputs,
                       outputs, EPS, weightfunc, vars, b)
end

"""
    mwls_cll(input::Array{T, 2}, EPS::Real, weightfunc::Function) where {T <: Real}
    mwls_cll(input::Array{T, 2}, EPS::Real, weightfunc::Function; outputDim::Int = 1, maxDegree::Int = 2) where {T <: Real}

In this `mwls_cll` function, the sample input and sample output data are passed in a single array.
It is assumed that each pair of input and output is on a single row.
Dimension of the output is specified with kwarg `outputDim`.
"""
function mwls_cll(input::Array{T, 2}, EPS::Real, weightfunc::Function;
                  outputDim::Integer = 1, maxDegree::Integer = 2) where {T <: Real}
  width = size(input, 2)
  inputEnd = width - outputDim
  outputStart = inputEnd + 1
  return mwls_cll(input[:, inputEnd == 1 ? 1 : 1:inputEnd],
                  input[:, outputStart == width ? width : outputStart:width],
                  EPS, weightfunc, maxDegree = maxDegree)
end

mwlsCll(a...;b...) = warn("`mwlsCll` is deprecated, use `mwls_cll` instead.")


using FromFile
using SymbolicRegression, Test
@from "test_params.jl" import maximum_residual

_inv(x::Float32)::Float32 = 1f0/x
X = rand(Float32, 5, 100) .+ 1
y = 1.2f0 .+ 2 ./ X[3, :]

options = SymbolicRegression.Options(
    binary_operators=(+, *),
    unary_operators=(_inv,),
    npopulations=8
)
hallOfFame = EquationSearch(X, y, niterations=8, options=options, numprocs=4)

dominating = calculateParetoFrontier(X, y, hallOfFame, options)

best = dominating[end]
# Test the score
@test best.score < maximum_residual / 10


include("utils.jl")
using StatsBase: AbstractWeights, pweights
using Random: GLOBAL_RNG, seed!
import ManifoldsBase:
    manifold_dimension, exp!, log!, inner, zero_vector!, decorated_manifold, base_manifold
using Manifolds:
    AbstractEstimationMethod,
    CyclicProximalPointEstimation,
    GeodesicInterpolation,
    GeodesicInterpolationWithinRadius,
    GradientDescentEstimation,
    WeiszfeldEstimation
import Manifolds: mean!, median!, var, mean_and_var

struct TestStatsSphere{N} <: AbstractManifold{ℝ} end
TestStatsSphere(N) = TestStatsSphere{N}()
manifold_dimension(::TestStatsSphere{N}) where {N} = manifold_dimension(Sphere(N))
function exp!(::TestStatsSphere{N}, w, x, y; kwargs...) where {N}
    return exp!(Sphere(N), w, x, y; kwargs...)
end
function log!(::TestStatsSphere{N}, y, x, v; kwargs...) where {N}
    return log!(Sphere(N), y, x, v; kwargs...)
end
function inner(::TestStatsSphere{N}, x, v, w; kwargs...) where {N}
    return inner(Sphere(N), x, v, w; kwargs...)
end
function zero_vector!(::TestStatsSphere{N}, v, x; kwargs...) where {N}
    return zero_vector!(Sphere(N), v, x; kwargs...)
end

struct TestStatsEuclidean{N} <: AbstractManifold{ℝ} end
TestStatsEuclidean(N) = TestStatsEuclidean{N}()
manifold_dimension(::TestStatsEuclidean{N}) where {N} = manifold_dimension(Euclidean(N))
function exp!(::TestStatsEuclidean{N}, y, x, v; kwargs...) where {N}
    return exp!(Euclidean(N), y, x, v; kwargs...)
end
function log!(::TestStatsEuclidean{N}, w, x, y; kwargs...) where {N}
    return log!(Euclidean(N), w, x, y; kwargs...)
end
function inner(::TestStatsEuclidean{N}, x, v, w; kwargs...) where {N}
    return inner(Euclidean(N), x, v, w; kwargs...)
end
function zero_vector!(::TestStatsEuclidean{N}, v, x; kwargs...) where {N}
    return zero_vector!(Euclidean(N), v, x; kwargs...)
end

struct TestStatsNotImplementedEmbeddedManifold <:
       AbstractEmbeddedManifold{ℝ,TransparentIsometricEmbedding} end
decorated_manifold(::TestStatsNotImplementedEmbeddedManifold) = Sphere(2)
base_manifold(::TestStatsNotImplementedEmbeddedManifold) = Sphere(2)

struct TestStatsNotImplementedEmbeddedManifold2 <:
       AbstractEmbeddedManifold{ℝ,DefaultIsometricEmbeddingType} end
decorated_manifold(::TestStatsNotImplementedEmbeddedManifold2) = Sphere(2)
base_manifold(::TestStatsNotImplementedEmbeddedManifold2) = Sphere(2)

struct TestStatsNotImplementedEmbeddedManifold3 <:
       AbstractEmbeddedManifold{ℝ,DefaultEmbeddingType} end
decorated_manifold(::TestStatsNotImplementedEmbeddedManifold3) = Sphere(2)
base_manifold(::TestStatsNotImplementedEmbeddedManifold3) = Sphere(2)

function test_mean(M, x, yexp=nothing, method...; kwargs...)
    @testset "mean unweighted" begin
        y = mean(M, x; kwargs...)
        @test is_point(M, y; atol=10^-9)
        if yexp !== nothing
            @test isapprox(M, y, yexp; atol=10^-7)
        end
        y2, _ = mean_and_var(M, x; kwargs...)
        @test isapprox(M, y2, y; atol=10^-7)
        y3, _ = mean_and_std(M, x; kwargs...)
        @test isapprox(M, y3, y; atol=10^-7)
    end

    @testset "mean weighted" begin
        n = length(x)
        w1 = pweights(ones(n) / n)
        w2 = pweights(ones(n))
        w3 = pweights(2 * ones(n))
        y = mean(M, x; kwargs...)
        for w in (w1, w2, w3)
            @test is_point(M, mean(M, x, w; kwargs...); atol=10^-9)
            @test isapprox(M, mean(M, x, w; kwargs...), y)

            @test isapprox(M, mean_and_var(M, x, w; kwargs...)[1], y; atol=10^-7)
            @test isapprox(M, mean_and_std(M, x, w; kwargs...)[1], y; atol=10^-7)
        end
        @test_throws DimensionMismatch mean(M, x, pweights(ones(n + 1)); kwargs...)
    end
    return nothing
end

function test_median(
    M,
    x,
    yexp=nothing;
    method::Union{Nothing,AbstractEstimationMethod}=nothing,
    kwargs...,
)
    @testset "median unweighted$(!isnothing(method) ? " ($method)" : "")" begin
        y = isnothing(method) ? median(M, x; kwargs...) : median(M, x, method; kwargs...)
        @test is_point(M, y; atol=10^-9)
        if yexp !== nothing
            @test isapprox(M, y, yexp; atol=10^-5)
        end
    end

    @testset "median weighted$(!isnothing(method) ? " ($method)" : "")" begin
        n = length(x)
        w1 = pweights(ones(n) / n)
        w2 = pweights(ones(n))
        w3 = pweights(2 * ones(n))
        y = median(M, x; kwargs...)
        for w in (w1, w2, w3)
            if isnothing(method)
                @test is_point(M, median(M, x, w; kwargs...); atol=10^-9)
            else
                @test is_point(M, median(M, x, w, method; kwargs...); atol=10^-9)
            end
            @test isapprox(M, median(M, x, w; kwargs...), y; atol=10^-4)
        end
        if isnothing(method)
            @test_throws Exception median(M, x, pweights(ones(n + 1)); kwargs...)
        else
            @test_throws Exception median(M, x, pweights(ones(n + 1)), method; kwargs...)
        end
    end
    return nothing
end

function test_var(M, x, vexp=nothing; kwargs...)
    n = length(x)
    @testset "var unweighted" begin
        v = var(M, x; kwargs...)
        if vexp !== nothing
            @test v ≈ vexp
        end
        @test v ≈ var(M, x; corrected=true, kwargs...)
        _, v2 = mean_and_var(M, x; kwargs...)
        @test v2 ≈ v
        m = mean(M, x; kwargs...)
        @test var(M, x, m; kwargs...) ≈ var(M, x; kwargs...)
        @test var(M, x; corrected=false, kwargs...) ≈
              var(M, x, m; corrected=false, kwargs...)
        @test var(M, x; corrected=false, kwargs...) ≈ var(M, x; kwargs...) * (n - 1) / n
        @test var(M, x, m; corrected=false, kwargs...) ≈
              var(M, x, m; kwargs...) * (n - 1) / n
    end

    @testset "var weighted" begin
        n = length(x)
        w1 = pweights(ones(n) / n)
        w2 = pweights(ones(n))
        w3 = pweights(2 * ones(n))

        v = var(M, x; corrected=false, kwargs...)
        for w in (w1, w2, w3)
            @test var(M, x, w; kwargs...) ≈ v
            @test var(M, x, w; corrected=false, kwargs...) ≈ v
            @test mean_and_var(M, x, w; kwargs...)[2] ≈ var(M, x, w; kwargs...)
            m = mean(M, x, w; kwargs...)
            @test var(M, x, w, m; kwargs...) ≈ var(M, x, w; kwargs...)
            @test var(M, x, w; corrected=true, kwargs...) ≈
                  var(M, x, w, m; corrected=true, kwargs...)
            @test var(M, x, w; corrected=true, kwargs...) ≈
                  var(M, x, w; kwargs...) * n / (n - 1)
            @test var(M, x, w, m; corrected=true, kwargs...) ≈
                  var(M, x, w, m; kwargs...) * n / (n - 1)
        end
        @test_throws DimensionMismatch var(M, x, pweights(ones(n + 1)); kwargs...)
    end
    return nothing
end

function test_std(M, x, sexp=nothing; kwargs...)
    n = length(x)
    @testset "std unweighted" begin
        s = std(M, x; kwargs...)
        if sexp !== nothing
            @test s ≈ sexp
        end
        @test s ≈ std(M, x; corrected=true, kwargs...)
        @test s ≈ √var(M, x; corrected=true, kwargs...)
        _, s2 = mean_and_std(M, x; kwargs...)
        @test s2 ≈ s
        m = mean(M, x; kwargs...)
        @test std(M, x, m; kwargs...) ≈ std(M, x; kwargs...)
        @test std(M, x; corrected=false, kwargs...) ≈
              std(M, x, m; corrected=false, kwargs...)
        @test std(M, x; corrected=false, kwargs...) ≈
              std(M, x; kwargs...) * sqrt((n - 1) / n)
        @test std(M, x, m; corrected=false, kwargs...) ≈
              std(M, x, m; kwargs...) * sqrt((n - 1) / n)
    end

    @testset "std weighted" begin
        n = length(x)
        w1 = pweights(ones(n) / n)
        w2 = pweights(ones(n))
        w3 = pweights(2 * ones(n))

        s = std(M, x; corrected=false, kwargs...)
        for w in (w1, w2, w3)
            @test std(M, x, w; kwargs...) ≈ s
            @test std(M, x, w; corrected=false, kwargs...) ≈ s
            @test mean_and_std(M, x, w; kwargs...)[2] ≈ std(M, x, w; kwargs...)
            m = mean(M, x, w; kwargs...)
            @test std(M, x, w, m; kwargs...) ≈ std(M, x, w; kwargs...)
            @test std(M, x, w; corrected=true, kwargs...) ≈
                  std(M, x, w, m; corrected=true, kwargs...)
            @test std(M, x, w; corrected=true, kwargs...) ≈
                  std(M, x, w; kwargs...) * sqrt(n / (n - 1))
            @test std(M, x, w, m; corrected=true, kwargs...) ≈
                  std(M, x, w, m; kwargs...) * sqrt(n / (n - 1))
        end
        @test_throws DimensionMismatch std(M, x, pweights(ones(n + 1)); kwargs...)
    end
    return nothing
end

function test_moments(M, x)
    n = length(x)
    @testset "moments unweighted" begin
        m = mean(M, x)
        for i in 1:5
            @test moment(M, x, i) ≈ mean(distance.(Ref(M), Ref(m), x) .^ i)
            @test moment(M, x, i, m) ≈ moment(M, x, i)
        end
        @test moment(M, x, 2) ≈ var(M, x; corrected=false)
        @test skewness(M, x) ≈ moment(M, x, 3) / moment(M, x, 2)^(3 / 2)
        @test kurtosis(M, x) ≈ moment(M, x, 4) / moment(M, x, 2)^2 - 3

        @test moment(M, x, 2, m) ≈ var(M, x; corrected=false)
        @test skewness(M, x, m) ≈ moment(M, x, 3) / moment(M, x, 2)^(3 / 2)
        @test kurtosis(M, x, m) ≈ moment(M, x, 4) / moment(M, x, 2)^2 - 3
    end

    @testset "moments weighted" begin
        n = length(x)
        w1 = pweights(ones(n) / n)
        w2 = pweights(ones(n))
        w3 = pweights(2 * ones(n))

        for w in (w1, w2, w3)
            m = mean(M, x, w)
            for i in 1:5
                @test moment(M, x, i, w) ≈ mean(distance.(Ref(M), Ref(m), x) .^ i, w)
                @test moment(M, x, i, w, m) ≈ moment(M, x, i, w)
            end
            @test moment(M, x, 2, w) ≈ var(M, x, w; corrected=false)
            @test skewness(M, x, w) ≈ moment(M, x, 3, w) / moment(M, x, 2, w)^(3 / 2)
            @test kurtosis(M, x, w) ≈ moment(M, x, 4, w) / moment(M, x, 2, w)^2 - 3

            @test moment(M, x, 2, w, m) ≈ var(M, x, w; corrected=false)
            @test skewness(M, x, w, m) ≈ moment(M, x, 3, w) / moment(M, x, 2, w)^(3 / 2)
            @test kurtosis(M, x, w, m) ≈ moment(M, x, 4, w) / moment(M, x, 2, w)^2 - 3
        end
        @test_throws DimensionMismatch moment(M, x, 3, pweights(ones(n + 1)))
        @test_throws DimensionMismatch skewness(M, x, pweights(ones(n + 1)))
        @test_throws DimensionMismatch kurtosis(M, x, pweights(ones(n + 1)))
    end
    return nothing
end

struct TestStatsOverload1 <: AbstractManifold{ℝ} end
struct TestStatsOverload2 <: AbstractManifold{ℝ} end
struct TestStatsOverload3 <: AbstractManifold{ℝ} end
struct TestStatsMethod1 <: AbstractEstimationMethod end

function mean!(
    ::TestStatsOverload1,
    y,
    ::AbstractVector,
    ::AbstractWeights,
    ::GradientDescentEstimation,
)
    return fill!(y, 3)
end
mean!(::TestStatsOverload2, y, ::AbstractVector, ::AbstractWeights) = fill!(y, 4)
function mean!(
    ::TestStatsOverload2,
    y,
    ::AbstractVector,
    ::AbstractWeights,
    ::GradientDescentEstimation,
)
    return fill!(y, 3)
end
function mean!(
    ::TestStatsOverload3,
    y,
    ::AbstractVector,
    ::AbstractWeights,
    ::TestStatsMethod1=TestStatsMethod1(),
)
    return fill!(y, 5)
end

function median!(
    ::TestStatsOverload1,
    y,
    ::AbstractVector,
    ::AbstractWeights,
    ::CyclicProximalPointEstimation,
)
    return fill!(y, 3)
end
median!(::TestStatsOverload2, y, ::AbstractVector, ::AbstractWeights) = fill!(y, 4)
function median!(
    ::TestStatsOverload2,
    y,
    ::AbstractVector,
    ::AbstractWeights,
    ::CyclicProximalPointEstimation,
)
    return fill!(y, 3)
end
function median!(
    ::TestStatsOverload3,
    y,
    ::AbstractVector,
    ::AbstractWeights,
    ::TestStatsMethod1=TestStatsMethod1(),
)
    return fill!(y, 5)
end

function var(::TestStatsOverload1, ::AbstractVector, ::AbstractWeights, m; corrected=false)
    return 4 + 5 * corrected
end
function mean_and_var(
    ::TestStatsOverload1,
    ::AbstractVector,
    ::AbstractWeights;
    corrected=false,
    kwargs...,
)
    return [4.0], 4 + 5 * corrected
end
function mean_and_var(
    ::TestStatsOverload1,
    ::AbstractVector,
    ::AbstractWeights,
    ::TestStatsMethod1;
    corrected=false,
    kwargs...,
)
    return [5.0], 9 + 7 * corrected
end

@testset "Statistics" begin
    @testset "defaults and overloads" begin
        w = pweights([2.0])
        x = [[0.0]]
        @testset "mean" begin
            M = TestStatsOverload1()
            @test mean(M, x) == [3.0]
            @test mean(M, x, w) == [3.0]
            @test mean(M, x, w, GradientDescentEstimation()) == [3.0]
            @test mean(M, x, GradientDescentEstimation()) == [3.0]
            M = TestStatsOverload2()
            @test mean(M, x) == [4.0]
            @test mean(M, x, w) == [4.0]
            @test mean(M, x, w, GradientDescentEstimation()) == [3.0]
            @test mean(M, x, GradientDescentEstimation()) == [3.0]
            M = TestStatsOverload3()
            @test mean(M, x) == [5.0]
            @test mean(M, x, w) == [5.0]
        end

        @testset "median" begin
            M = TestStatsOverload1()
            @test median(M, x) == [3.0]
            @test median(M, x, w) == [3.0]
            @test median(M, x, w, CyclicProximalPointEstimation()) == [3.0]
            @test median(M, x, CyclicProximalPointEstimation()) == [3.0]
            M = TestStatsOverload2()
            @test median(M, x) == [4.0]
            @test median(M, x, w) == [4.0]
            @test median(M, x, w, CyclicProximalPointEstimation()) == [3.0]
            @test median(M, x, CyclicProximalPointEstimation()) == [3.0]
            M = TestStatsOverload3()
            @test median(M, x) == [5.0]
            @test median(M, x, w) == [5.0]
        end

        @testset "var" begin
            M = TestStatsOverload1()
            @test mean_and_var(M, x) == ([4.0], 9)
            @test mean_and_var(M, x, w) == ([4.0], 4)
            @test mean_and_std(M, x) == ([4.0], 3.0)
            @test mean_and_std(M, x, w) == ([4.0], 2.0)
            @test var(M, x) == 9
            @test var(M, x, 2) == 9
            @test var(M, x, w) == 4
            @test var(M, x, w, 2) == 4
            @test std(M, x) == 3.0
            @test std(M, x, 2) == 3.0
            @test std(M, x, w) == 2.0
            @test std(M, x, w, 2) == 2.0

            @test mean_and_var(M, x, TestStatsMethod1()) == ([5.0], 16)
            @test mean_and_var(M, x, w, TestStatsMethod1()) == ([5.0], 9)
            @test mean_and_std(M, x, TestStatsMethod1()) == ([5.0], 4.0)
            @test mean_and_std(M, x, w, TestStatsMethod1()) == ([5.0], 3.0)
        end
    end

    @testset "decorator dispatch" begin
        ps = [normalize([1, 0, 0] .+ 0.1 .* randn(3)) for _ in 1:3]
        M1 = TestStatsNotImplementedEmbeddedManifold()
        @test mean!(M1, similar(ps[1]), ps) == mean!(Sphere(2), similar(ps[1]), ps)
        @test mean(M1, ps) == mean(Sphere(2), ps)
        @test median!(M1, similar(ps[1]), ps) == median!(Sphere(2), similar(ps[1]), ps)
        @test median(M1, ps) == median(Sphere(2), ps)

        M2 = TestStatsNotImplementedEmbeddedManifold2()
        @test_throws ErrorException mean(M2, ps)
        @test_throws ErrorException mean!(M2, similar(ps[1]), ps)
        @test_throws ErrorException median(M2, ps)
        @test_throws ErrorException median!(M2, similar(ps[1]), ps)

        M3 = TestStatsNotImplementedEmbeddedManifold3()
        @test_throws ErrorException mean(M3, ps)
        @test_throws ErrorException mean!(M3, similar(ps[1]), ps)
        @test_throws ErrorException median(M3, ps)
        @test_throws ErrorException median!(M3, similar(ps[1]), ps)
    end

    @testset "TestStatsSphere" begin
        M = TestStatsSphere(2)

        @testset "consistency" begin
            p = [0.0, 0.0, 1.0]
            n = 3
            x = [
                exp(M, p, π / 6 * [cos(α), sin(α), 0.0]) for
                α in range(0, 2 * π - 2 * π / n, length=n)
            ]
            test_mean(M, x)
            test_median(M, x; atol=1e-12)
            test_median(M, x; method=CyclicProximalPointEstimation(), atol=1e-12)
            test_median(M, x; method=WeiszfeldEstimation())
            test_var(M, x)
            test_std(M, x)
            test_moments(M, x)
        end

        @testset "zero variance" begin
            x = fill([0.0, 0.0, 1.0], 5)
            test_mean(M, x, [0.0, 0.0, 1.0])
            test_median(M, x, [0.0, 0.0, 1.0]; atol=10^-12)
            test_var(M, x, 0.0)
            test_std(M, x, 0.0)
        end

        @testset "three points" begin
            x = [[1.0, 0.0, 0.0], [1.0, 1.0, 0.0] / √2, [0.0, 1.0, 0.0]]
            θ = π / 4
            @test isapprox(M, mean(M, x), geodesic(M, x[1], x[3], θ))
            test_mean(M, x, [1.0, 1.0, 0.0] / √2)
            test_median(M, x, [1.0, 1.0, 0.0] / √2; atol=10^-12)
            test_var(M, x, θ^2)
            test_std(M, x, θ)
            test_moments(M, x)
        end
    end

    @testset "TestStatsEuclidean{N}" begin
        @testset "N=1" begin
            M = TestStatsEuclidean(1)

            rng = MersenneTwister(42)
            x = [randn(rng, 1) for _ in 1:10]
            vx = vcat(x...)

            test_mean(M, x, mean(x))
            test_median(M, x; atol=10^-12)
            test_var(M, x, var(vx))
            test_std(M, x, std(vx))
            test_moments(M, x)

            w = pweights(rand(rng, 10))
            @test mean(M, x, w) ≈ [mean(vx, w)]
            @test var(M, x, w) ≈ var(vx, w)
            @test std(M, x, w) ≈ std(vx, w)
        end

        @testset "N=5" begin
            M = TestStatsEuclidean(5)

            rng = MersenneTwister(42)
            x = [randn(rng, 5) for _ in 1:10]
            test_mean(M, x, mean(x))
            test_var(M, x, sum(var(x)))
            test_std(M, x, sqrt(sum(std(x) .^ 2)))
            test_moments(M, x)

            w = pweights(rand(rng, 10))
            ax = hcat(x...)
            @test mean(M, x, w) ≈ mean(ax, w; dims=2)
            @test var(M, x, w) ≈ sum(var(ax, w, 2))
            @test std(M, x, w) ≈ sqrt(sum(std(ax, w, 2) .^ 2))
        end
    end

    @testset "Euclidean statistics" begin
        @testset "N=1" begin
            M = Euclidean()
            @testset "scalar" begin
                x = [1.0, 2.0, 3.0, 4.0]
                w = pweights(ones(length(x)) / length(x))
                @test mean(M, x) ≈ mean(x)
                @test mean(M, x, w) ≈ mean(x, w)
                @test median(M, x; rng=MersenneTwister(1212), atol=10^-12) ≈ median(x)
                @test median(M, x, w; rng=MersenneTwister(1212), atol=10^-12) ≈ median(x, w)
                @test var(M, x) ≈ var(x)
                @test var(M, x, w) ≈ var(x, w)
                @test std(M, x) ≈ std(x)
                @test std(M, x, w) ≈ std(x, w)

                test_mean(M, x)
                test_median(M, x; atol=10^-12)
                test_var(M, x)
                test_std(M, x)
                test_moments(M, x)
            end

            @testset "vector" begin
                x = [[1.0], [2.0], [3.0], [4.0]]
                vx = vcat(x...)
                w = pweights(ones(length(x)) / length(x))
                @test mean(M, x) ≈ mean(x)
                @test mean(M, x, w) ≈ [mean(vx, w)]
                @test var(M, x) ≈ var(vx)
                @test var(M, x, w) ≈ var(vx, w)
                @test std(M, x) ≈ std(vx)
                @test std(M, x, w) ≈ std(vx, w)

                test_mean(M, x)
                test_median(M, x; atol=10^-12)
                test_var(M, x)
                test_std(M, x)
                test_moments(M, x)
            end
        end
    end

    @testset "GeodesicInterpolation" begin
        @testset "mean" begin
            rng = MersenneTwister(1212)
            @testset "exact for Euclidean" begin
                M = Euclidean(2, 2)
                x = [randn(rng, 2, 2) for _ in 1:10]
                w = pweights([rand(rng) for _ in 1:10])
                @test mean(M, x, GeodesicInterpolation()) ≈ mean(x)
                @test mean(M, x, w, GeodesicInterpolation()) ≈
                      mean(M, x, w, GradientDescentEstimation())
            end

            @testset "three points" begin
                S = Sphere(2)
                x = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0] / √2]
                @test mean(S, [x[1], x[1]], GeodesicInterpolation()) ≈ x[1]
                @test mean(S, x[1:2], GeodesicInterpolation()) ≈ x[3]
                @test mean(S, x[1:2], pweights([1, 0]), GeodesicInterpolation()) ≈ x[1]
                @test mean(S, x[1:2], pweights([0, 1]), GeodesicInterpolation()) ≈ x[2]
                @test mean(S, x[1:2], pweights([1, 2]), GeodesicInterpolation()) ≈
                      shortest_geodesic(S, x[1], x[2], 2 / 3)
                m = mean(S, x, pweights([1, 2, 3]), GeodesicInterpolation())
                @test m ≈ shortest_geodesic(
                    S,
                    shortest_geodesic(S, x[1], x[2], 2 / 3),
                    x[3],
                    1 / 2,
                )
            end

            @testset "resumable" begin
                S = Sphere(2)
                x = [normalize(randn(rng, 3)) for _ in 1:10]
                w = pweights([rand(rng) for _ in 1:10])
                ypart = mean(S, x[1:5], pweights(w[1:5]), GeodesicInterpolation())
                yfull = mean(S, x, w, GeodesicInterpolation())
                x2 = [[ypart]; x[6:end]]
                w2 = pweights([sum(w[1:5]); w[6:end]])
                @test mean(S, x2, w2, GeodesicInterpolation()) ≈ yfull
            end
        end

        @testset "welford" begin
            rng = MersenneTwister(56)

            @testset "exact for Euclidean" begin
                M = TestStatsEuclidean(4)
                x = [randn(rng, 4) for _ in 1:10]
                w = pweights([rand(rng) for _ in 1:10])
                m1, v1 = mean_and_var(M, x, GeodesicInterpolation())
                @test m1 ≈ mean(x)
                @test v1 ≈ sum(var(x))
                m2, v2 = mean_and_var(M, x, w, GeodesicInterpolation())
                @test m2 ≈ mean(M, x, w, GradientDescentEstimation())
                @test v2 ≈ var(M, x, w)
            end

            @testset "three points" begin
                S = Sphere(2)
                x = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0] / √2]
                m1, v1 = mean_and_var(S, [x[1], x[1]], GeodesicInterpolation())
                @test m1 ≈ x[1]
                @test v1 ≈ 0 atol = 1e-6
                m2, v2 = mean_and_var(S, x[1:2], GeodesicInterpolation())
                @test m2 ≈ x[3]
                @test v2 ≈ (π / 4)^2 * 2 / (2 - 1)
                m3, v3 = mean_and_var(S, x[1:2], pweights([1, 0]), GeodesicInterpolation())
                @test m3 ≈ x[1]
                @test v3 ≈ 0 atol = 1e-6
                m4, v4 = mean_and_var(S, x[1:2], pweights([0, 1]), GeodesicInterpolation())
                @test m4 ≈ x[2]
                @test v4 ≈ 0 atol = 1e-6
                m5, v5 = mean_and_var(S, x, pweights([1, 2, 3]), GeodesicInterpolation())
                @test m5 ≈ shortest_geodesic(
                    S,
                    shortest_geodesic(S, x[1], x[2], 2 / 3),
                    x[3],
                    1 / 2,
                )
                @test v5 ≈ var(S, x, pweights([1, 2, 3]), m5)
            end

            @testset "within radius" begin
                rng = MersenneTwister(47)
                @test repr(GeodesicInterpolationWithinRadius(Inf)) ==
                      "GeodesicInterpolationWithinRadius(Inf)"
                @test_throws DomainError GeodesicInterpolationWithinRadius(-0.1)
                S = Sphere(2)
                x = [[1.0, 0, 0], [0, 1.0, 0]]
                m = mean(S, x, GeodesicInterpolation())
                mg = mean(S, x, GradientDescentEstimation(); p0=m)
                vg = var(S, x, mg)

                @test mean(S, x, GeodesicInterpolationWithinRadius(Inf)) == m
                @test mean(S, x, GeodesicInterpolationWithinRadius(π)) == m
                @test mean(S, x, GeodesicInterpolationWithinRadius(π / 8)) != m
                @test mean(S, x, GeodesicInterpolationWithinRadius(π / 8)) == mg

                m, v = mean_and_var(S, x, GeodesicInterpolation())
                @test mean_and_var(S, x, GeodesicInterpolationWithinRadius(Inf)) == (m, v)
                @test mean_and_var(S, x, GeodesicInterpolationWithinRadius(π)) == (m, v)
                @test mean_and_var(S, x, GeodesicInterpolationWithinRadius(π / 8)) != (m, v)
                @test mean_and_var(S, x, GeodesicInterpolationWithinRadius(π / 8)) ==
                      (mg, vg)
            end
        end

        @testset "SymmetricPositiveDefinite default" begin
            rng = MersenneTwister(36)
            P1 = SymmetricPositiveDefinite(3)
            P2 = MetricManifold(P1, LinearAffineMetric())
            @testset "$P" for P in [P1, P2]
                p0 = collect(exp(Symmetric(randn(rng, 3, 3) * 0.1)))
                x = [exp(P, p0, Symmetric(randn(rng, 3, 3) * 0.1)) for _ in 1:10]
                w = pweights([rand(rng) for _ in 1:length(x)])
                m = mean(P, x, w)
                mg = mean(P, x, w, GeodesicInterpolation())
                mf = mean(P, x, w, GradientDescentEstimation())
                @test m == mg
                @test m != mf
            end
        end

        @testset "Sphere default" begin
            rng = MersenneTwister(47)
            S = Sphere(2)
            p0 = [1.0, 0, 0]
            x = [normalize(randn(rng, 3)) for _ in 1:10]
            x = [x; -x]
            w = pweights([rand(rng) for _ in 1:length(x)])
            m = mean(S, x, w)
            mg = mean(S, x, w, GeodesicInterpolation())
            mf = mean(S, x, w, GradientDescentEstimation(); p0=mg)
            @test m != mg
            @test m == mf

            μ = randn(rng, 3) .* 10
            x = [normalize(randn(rng, 3) .+ μ) for _ in 1:10]
            w = pweights([rand(rng) for _ in 1:length(x)])
            m = mean(S, x, w)
            mg = mean(S, x, w, GeodesicInterpolation())
            @test m == mg
        end

        @testset "ProjectiveSpace default" begin
            rng = MersenneTwister(47)
            M = ProjectiveSpace(2)
            p0 = [1.0, 0, 0]
            x = [normalize(randn(rng, 3)) for _ in 1:10]
            x = [x; -x]
            w = pweights([rand(rng) for _ in 1:length(x)])
            m = mean(M, x, w)
            mg = mean(M, x, w, GeodesicInterpolation())
            mf = mean(M, x, w, GradientDescentEstimation(); p0=mg)
            @test !isapprox(M, m, mg)
            @test isapprox(M, m, mf)

            μ = randn(rng, 3) .* 10
            x = [normalize(randn(rng, 3) .+ μ) for _ in 1:10]
            w = pweights([rand(rng) for _ in 1:length(x)])
            m = mean(M, x, w)
            mg = mean(M, x, w, GeodesicInterpolation())
            @test m == mg
        end

        @testset "Rotations default" begin
            rng = MersenneTwister(47)
            R = Manifolds.Rotations(3)
            p0 = collect(Diagonal(ones(3)))
            v1 = hat(R, p0, [1.0, 0.0, 0.0])
            v2 = hat(R, p0, [0.0, 1.0, 0.0])
            x = [geodesic(R, p0, v1, π / 2 * (1:4)); geodesic(R, p0, v2, π / 2 * (1:4))]
            w = pweights([rand(rng) for _ in 1:length(x)])
            m = mean(R, x, w)
            mg = mean(R, x, w, GeodesicInterpolation())
            mf = mean(R, x, w, GradientDescentEstimation(); p0=mg)
            @test m != mg
            @test m == mf

            μ = project(R, randn(3, 3))
            d = normal_tvector_distribution(R, μ, 0.1)
            x = [exp(R, μ, rand(rng, d)) for _ in 1:10]
            w = pweights([rand(rng) for _ in 1:length(x)])
            m = mean(R, x, w)
            mg = mean(R, x, w, GeodesicInterpolation())
            @test m == mg
        end

        @testset "Grassmann default" begin
            rng = MersenneTwister(47)
            G = Manifolds.Grassmann(3, 2)
            p0 = [1.0 0.0; 0.0 1.0; 0.0 0.0]
            x = [exp(G, p0, project(G, p0, randn(rng, 3, 2) * 10)) for _ in 1:10]
            w = pweights([rand(rng) for _ in 1:length(x)])
            m = mean(G, x, w)
            mg = mean(G, x, w, GeodesicInterpolation())
            mf = mean(G, x, w, GradientDescentEstimation(); p0=mg)
            @test m != mg
            @test m == mf

            x = [exp(G, p0, project(G, p0, randn(rng, 3, 2) * 0.01)) for _ in 1:10]
            w = pweights([rand(rng) for _ in 1:length(x)])
            m = mean(G, x, w)
            mg = mean(G, x, w, GeodesicInterpolation())
            @test m == mg
        end
    end

    @testset "Extrinsic mean" begin
        rng = MersenneTwister(47)
        S = Sphere(2)
        x = [normalize(randn(rng, 3)) for _ in 1:10]
        w = pweights([rand(rng) for _ in 1:length(x)])
        m = normalize(mean(reduce(hcat, x), w; dims=2)[:, 1])
        mg = mean(S, x, w, ExtrinsicEstimation())
        @test isapprox(S, m, mg)
    end

    @testset "Extrinsic median" begin
        rng = MersenneTwister(47)
        S = Sphere(2)
        x = [normalize(randn(rng, 3)) for _ in 1:10]
        w = pweights([rand(rng) for _ in 1:length(x)])
        m = normalize(median(Euclidean(3), x, w))
        mg = median(S, x, w, ExtrinsicEstimation())
        @test isapprox(S, m, mg)
    end

    @testset "Covariance matrix, Euclidean" begin
        rng = MersenneTwister(47)
        M = Euclidean(3)
        x = [randn(rng, 3) for _ in 1:10]
        @test isapprox(cov(M, x), cov(x))
        covest = SimpleCovariance(; corrected=false)
        @test isapprox(cov(M, x; tangent_space_covariance_estimator=covest), cov(covest, x))
    end
    @testset "Covariance matrix, sphere" begin
        rng = MersenneTwister(47)
        S = Sphere(2)
        x = [normalize(randn(rng, 3)) for _ in 1:10]
        covm = cov(S, x)
        @test size(covm) == (2, 2)
        @test isposdef(covm)
        @test issymmetric(covm)
    end
end


using Cliffords, Test, LinearAlgebra

import Cliffords: IZ, ZI, XI, IX, YI, IY, II, XX, YY, ZZ, XY, XZ, YX, ZX,
        generators

eye(n::Integer) = Matrix{Float64}(I, n, n)
eye(T::Type, n::Integer) = Matrix{T}(I, n, n)

include("testpaulis.jl")
include("testcliffords.jl")


using StaticArrays

push_str!(ex::Vector{Expr}, s::String) = push!(ex, Meta.parse(s))
append_str!(ex::Vector{Expr}, s::Vector{String}) = append!(ex, Meta.parse.(s))

# univariate monomial
@inline _m1(α::Number, x::T) where {T <: Number} =
      (@fastmath x^α)

# univariate monomial derivative
@inline _m1d(α::Number, x::T) where {T <: Number} =
      α == 0 ? zero(T) : (@fastmath α * x^(α-1))

function monomial(α, x::SVector{K, T}) where {K, T}
   m = one(T)
   for i = 1:K
      @inbounds m *= _m1(α[i], x[i])
   end
   return m
end

@generated function monomial_d(α, x::SVector{K, T}) where {K, T}
   exprs = Expr[]
   # @assert length(α) >= K
   # evaluate the scalar monomials
   #  f = SVector{...}( x[1]^α[1], ...)
   # df = SVector{...}( α[1] * x[1]^(α[1]-1), ...)
   ex_f = "f = @SVector $T["
   ex_df = "df = @SVector $T["
   for i = 1:K
      ex_f  *= " _m1(α[$i], x[$i]), "
      ex_df *= "_m1d(α[$i], x[$i]), "
   end
   ex_f  =  ex_f[1:end-2] * "]"
   ex_df = ex_df[1:end-2] * "]"

   push_str!(exprs, ex_f)
   push_str!(exprs, ex_df)
   push_str!(exprs, "m = 1.0")
   for j = 1:K
      push_str!(exprs, "m *= f[$j]")
   end

   # evaluate the derivative
   for i = 1:K
      push_str!(exprs, "dm_$i = 1.0")
      for j = 1:K
         if i == j
            push_str!(exprs, "dm_$i *= df[$j]")
         else
            push_str!(exprs, "dm_$i *= f[$j]")
         end
      end
   end
   ex_dm = "m_d = @SVector $T["
   for i = 1:K
      ex_dm *= "dm_$i, "
   end
   ex_dm = ex_dm[1:end-2] * "]"
   push_str!(exprs, ex_dm)

   quote
      $(Expr(:meta, :inline))
      @inbounds $(Expr(:block, exprs...))
      return m, m_d
   end
end


function solver_OPF(optimizer::Type{<:AbstractOptimizer},resultfile::String="")
#function solver_DeterministicEquivalent(optimizer::Type{<:AbstractOptimizer},resultfile::String="",load_st2::Type{<:AbstractArray{Int64,2}})
#Deterministic Equivalent------------------------------------------------------#
println("Loading inputs...")
#=
(load_st2,Carbprice) = loadinputs("data")

for i in set_scenarios
D[i]=maximum(load_st2[:,i])
end
=#

(syscost_det,p_G_value,p_D_value,Dual_constraint6,f_value)=OPF(optimizer,set_generators,set_nodes,set_demands,generators,demands,capacity,varcost,load_node,utility_node,links,F_max_dict,B_dict,MapG,MapD)

#=
println("")
for j in set_opt
println("x[$j] = ", px_1_det[j])
end
=#

resultfile != "" && open(resultfile, truncate = true) do f

    println(f,"*****************OPF Solution*****************")
    println(f,"")
    println(f,"Objective Function:",syscost_det)
    println(f,"")
    println(f,"Production level of generator g:",p_G_value)
    println(f,"")
    println(f,"Consumption level of demand d:",p_D_value)
    println(f,"")
    println(f,"Electricity Price:",Dual_constraint6)
    println(f,"")
    println(f,"Power Flow:", f_value)
    println(f,"")
    global j=1
    for k in links
    println(f,"Power Flow line $k:", f_value[j])
    global j=j+1
    end
    #=
    println(f,"Second Stage Dispatch Decisions:")
    println(f,p_y_det)
    =#
end
return (syscost_det,p_G_value,p_D_value,Dual_constraint6,f_value)
end


# Define some points and data in 2D
arrayPoints = permutedims([0.0 0.0; 0.0 1.0; 1.0 0.0; 1.0 1.0], (2,1))
adjointPoints = [0.0 0.0; 0.0 1.0; 1.0 0.0; 1.0 1.0]'
data = [0.0; 0.5; 0.5; 1.0]

@testset "Shepard" begin

    @testset "Constructors" for idw in (:Shepard, )
        @eval @test $idw(2) == $idw()
    end

    @testset "Evaluation" for points in (arrayPoints, adjointPoints)

        itp = interpolate(Shepard(), points, data)

        # Check that we get back the original data at the sample points and that we get close
        # when evaluating near the sampling points
        ev = evaluate(itp, points)
        @test ev ≈ data
        ev = evaluate(itp, points .+ 0.001*randn(size(points)))
        @test all(isapprox.(data, ev, atol = 1e-2))
    end
end


struct Binomial <: Distribution{Int} end

"""
	binom(n::Integer, p::Real)
Sample an `Int` from the Binomial distribution with parameters `n` (number of
trials) and `p` (probability of success in each trial).
"""
const binom = Binomial()

function logpdf(::Binomial, x::Integer, n::Integer, p::Real)
    Distributions.logpdf(Distributions.Binomial(n, p), x)
end

function logpdf_grad(::Binomial, x::Integer, n::Integer, p::Real)
	(nothing, nothing, (x / p - (n - x) / (1 - p)))
end

function random(::Binomial, n::Integer, p::Real)
	rand(Distributions.Binomial(n, p))
end

(::Binomial)(n, p) = random(Binomial(), n, p)

has_output_grad(::Binomial) = false
has_argument_grads(::Binomial) = (false, true)
is_discrete(::Binomial) = true

export binom


export AbstractModel, Heisenberg
export heisenberg_ij, hamiltonian, heisenberg_term, ground_state, energy, energy_exact, get_bonds, energy, heisenberg_2d, nspin

abstract type AbstractModel{D} end
abstract type AbstractHeisenberg{D} <: AbstractModel{D} end

nspin(model::AbstractModel) = prod(size(model))

"""
Exact ground state energy.
"""
function energy_exact(tc::QuantumMPS, model::AbstractModel)
    nbit = nbit_simulated(tc)
    expect(hamiltonian(model), state_exact(tc)) |> real
end

"""
Ground state energy by sampling Quantum MPS.
The hamiltonian is limited to Heisenberg and J1J2 Type.
"""
function energy(chem::QuantumMPS, model::AbstractHeisenberg)
    energy(chem, X, model) + energy(chem, Y, model) + energy(chem, Z, model)
end

function energy(chem::QuantumMPS, pauli::PauliGate, model::AbstractHeisenberg)
    res = gensample(chem, pauli)
    local eng = 0.0
    for bond in get_bonds(model)
        eng += bond[3]*mean(res[:,bond[1]].*res[:,bond[2]])
    end
    eng/4
end

function ground_state(model::AbstractModel)
    # get the ground state
    hami = hamiltonian(model)
    E, v = eigsolve(mat(hami), 1, :SR)
    register(v[1])
end

include("Heisenberg.jl")
include("J1J2.jl")


# # Example Catalog

using Plots, AnalogDataAssimilation

σ = 10.0
ρ = 28.0
β = 8.0 / 3

dt_integration = 0.01
dt_states = 1
dt_obs = 8
parameters = [σ, ρ, β]
var_obs = [1]
nb_loop_train = 100
nb_loop_test = 10
sigma2_catalog = 0.0
sigma2_obs = 2.0

ssm = StateSpaceModel(
    lorenz63,
    dt_integration,
    dt_states,
    dt_obs,
    parameters,
    var_obs,
    nb_loop_train,
    nb_loop_test,
    sigma2_catalog,
    sigma2_obs,
)

xt, yo, catalog = generate_data(ssm, [10.0; 0.0; 0.0]);

# ## Time series

plot(catalog.analogs[1, :])
plot!(catalog.analogs[2, :])
plot!(catalog.analogs[3, :])

# ## Phase space plot

p = plot3d(
    1,
    xlim = (-25, 25),
    ylim = (-25, 25),
    zlim = (0, 50),
    title = "Lorenz 63",
    marker = 1,
)
for x in eachcol(catalog.analogs)
    push!(p, x...)
end
p


# Parameters:
#	N::Int
#	- the problem size
#	PA::Array{Array{Int, 1}, 1}
#	- PA[i] is a Permutation of N
#	VA::Array{Float64, 1}
#	- VA[i] is the Value associated with PA[i]
# Return Values:
#	SNF::Array{Float64, 1}
#	- SNF[i] is the value associated with the permutation that permutation_index() maps to i
#	- this is the format for the SNF parameter of sn_fft()
# Notes:
#	- any permutation of N not represented in PA will be assigned a value of zero
function snf(N::Int, PA::Array{Array{Int, 1}, 1}, VA::Array{Float64, 1})
	SNF = zeros(Float64, factorial(N))
	for i = 1:length(PA)
		Permutation = PA[i]
		Index = permutation_index(Permutation)
		SNF[Index] = VA[i]
	end
	return SNF
end

# Parameters:
#	N::Int
#	- the problem size
#	K::Int
#	- the problem is homogenous at N-K
#	PA::Array{Array{Int, 1}, 1}
#	- PA[i] is a Permutation of N
#	VA::Array{Float64, 1}
#	- VA[i] is the Value associated with PA[i]
# Return Values:
#	SNF::Array{Float64, 1}
#	- SNF[i] is the value associated with all of the permutations that permutation_index() maps to any value in the range ((i - 1) * factorial(N - K) + 1):(i * factorial(N - K))
#	- this is the format for the SNF parameter of sn_fft_bl()
# Notes:
#	- any homogenous coset that doesn't have a representative permutation in PA will be assigned a value of zero
function snf_bl(N::Int, K::Int, PA::Array{Array{Int, 1}, 1}, VA::Array{Float64, 1})
	N_F = factorial(N)
	BS = factorial(N - K)
	SNF = zeros(Float64, round(Int, N_F / BS))
	for i = 1:length(PA)
		Permutation = PA[i]
		Index = permutation_index(P)
		Index = ceil(Index / BS)
		SNF[Index] = VA[i]
	end
	return SNF
end

# Parameters:
#	N::Int
#	- the problem size
#	PA::Array{Array{Int, 1}, 1}
#	- PA[i] is a Permutation of N
#	VA::Array{Float64, 1}
#	- VA[i] is the Value associated with PA[i]
# Return Values:
#	SNF::Array{Float64, 1}
#	- SNF[i] is the value associated with the permutation that permutation_index() maps to NZL[i]
#	- this is the format for the SNF parameter of sn_fft_sp()
#	NZL::Array{Int, 1}
#	- NZL must in increasing order
#	- this is the format for the NZL parameter of sn_fft_sp()
# Notes:
#	- the values in VA should be non-zero
function snf_sp(N::Int, PA::Array{Array{Int, 1}, 1}, VA::Array{Float64, 1})
	L = length(PA)
	SNF = Array(Float64, L)
	NZL = Array(Int, L)
	SNF[1] = VA[1]
	NZL[1] = permutation_index(PA[1])
	for i = 2:L
		val = VA[i]
		index = permutation_index(PA[i])
		j = 1
		while j <= (i - 1)
			if index < NZL[j]
				break
			end
			j += 1
		end
		for k = i:-1:(j + 1)
			SNF[k] = SNF[k - 1]
			NZL[k] = NZL[k - 1]
		end
		SNF[j] = val
		NZL[j] = index
	end
	return SNF, NZL
end


@assert begin
    msh=FinVolMesh{1}()

    nnds=10

    pts=[add_point!(msh, [x]) for x in LinRange(0.0, 1.0, nnds)]

    fcs=[
        add_face!(msh, [pt]) for pt in pts
    ]

    vols=[
        add_volume!(msh, [fcs[i], fcs[i+1]]) for i=1:(length(fcs)-1)
    ]

    process_geometry!(msh)

    u_nodes=collect(LinRange(0.0, 1.0, nnds))
    u_cells=(u_nodes[1:(end-1)].+u_nodes[2:end])./2

    u_faces=Vector{Float64}(undef, nnds)

    face_average!(msh, u_cells, u_faces)

    eqs=isapprox.(u_faces, u_nodes)

    @assert all(eqs[2:(end-1)])

    u_faces[1]=0.0
    u_faces[end]=1.0

    grads=zeros(Float64, nnds-1)
    volume_gradient!(msh, grads, u_faces, 1)

    @assert isapprox(grads, ones(Float64, nnds-1); atol=1e-3)

    flux_ints=zeros(Float64, nnds-1)

    flux_integral!(msh, flux_ints, u_faces)
    @assert isapprox(flux_ints, -1.0*ones(Float64, nnds-1); atol=1e-3)

    true
end