function percolation(adjacency::Array{Float64,2})

    degrees = sum(adjacency, dims = 1)
    κ = mean(degrees .^ 2) / mean(degrees)

    return 1 - 1 / (κ - 1)
end


"""
An `Interval{L,R}(left, right)` where L,R are :open or :closed
is an interval set containg `x` such that
1. `left ≤ x ≤ right` if `L == R == :closed`
2. `left < x ≤ right` if `L == :open` and `R == :closed`
3. `left ≤ x < right` if `L == :closed` and `R == :open`, or
4. `left < x < right` if `L == R == :open`
"""
struct Interval{L,R,T}  <: TypedEndpointsInterval{L,R,T}
    left::T
    right::T

    Interval{L,R,T}(l, r) where {L,R,T} = ((a, b) = checked_conversion(T, l, r); new{L,R,T}(a, b))
end

"""
A `ClosedInterval(left, right)` is an interval set that includes both its upper and lower bounds. In
mathematical notation, the constructed range is `[left, right]`.
"""
const ClosedInterval{T} = Interval{:closed,:closed,T}

"""
An `TypedEndpointsInterval{:open,:open}(left, right)` is an interval set that includes both its upper and lower bounds. In
mathematical notation, the constructed range is `(left, right)`.
"""
const OpenInterval{T} = Interval{:open,:open,T}

Interval{L,R,T}(i::AbstractInterval) where {L,R,T} = Interval{L,R,T}(endpoints(i)...)
Interval{L,R}(left, right) where {L,R} = Interval{L,R,promote_type(typeof(left), typeof(right))}(left,right)
Interval{L,R}(left::T, right::T) where {L,R,T} = Interval{L,R,T}(left, right)
Interval(left, right) = ClosedInterval(left, right)

# Interval(::AbstractInterval) allows open/closed intervals to be changed
Interval{L,R}(i::AbstractInterval) where {L,R} = Interval{L,R}(endpoints(i)...)
Interval(i::AbstractInterval) = Interval{isleftclosed(i) ? (:closed) : (:open),
                                         isrightclosed(i) ? (:closed) : (:open)}(i)
Interval(i::TypedEndpointsInterval{L,R}) where {L,R} = Interval{L,R}(i)

endpoints(i::Interval) = (i.left, i.right)

for L in (:(:open),:(:closed)), R in (:(:open),:(:closed))
    @eval begin
        convert(::Type{Interval{$L,$R}}, i::Interval{$L,$R}) = i
        convert(::Type{Interval{$L,$R,T}}, i::Interval{$L,$R,T}) where T = i
    end
end
convert(::Type{Interval}, i::Interval) = i

function convert(::Type{II}, i::AbstractInterval) where II<:ClosedInterval
    isclosedset(i) ||  throw(InexactError(:convert,II,i))
    II(i)
end
function convert(::Type{II}, i::AbstractInterval) where II<:OpenInterval
    isopenset(i) ||  throw(InexactError(:convert,II,i))
    II(i)
end
function convert(::Type{II}, i::AbstractInterval) where II<:Interval{:open,:closed}
    (isleftopen(i) && isrightclosed(i)) ||  throw(InexactError(:convert,II,i))
    II(i)
end
function convert(::Type{II}, i::AbstractInterval) where II<:Interval{:closed,:open}
    (isleftclosed(i) && isrightopen(i)) ||  throw(InexactError(:convert,II,i))
    II(i)
end

convert(::Type{Interval}, i::AbstractInterval) = Interval(i)

convert(::Type{Domain{T}}, d::Interval{L,R}) where {L,R,T} = convert(Interval{L,R,T}, d)
convert(::Type{AbstractInterval{T}}, d::Interval{L,R}) where {L,R,T} = convert(Interval{L,R,T}, d)
convert(::Type{AbstractInterval{T}}, d::Interval{L,R,T}) where {L,R,T} = convert(Interval{L,R,T}, d)
convert(::Type{TypedEndpointsInterval{L,R,T}}, d::Interval{L,R}) where {L,R,T} = convert(Interval{L,R,T}, d)
convert(::Type{TypedEndpointsInterval{L,R,T}}, d::Interval{L,R,T}) where {L,R,T} = convert(Interval{L,R,T}, d)
convert(::Type{Domain}, d::Interval{L,R}) where {L,R} = d
convert(::Type{AbstractInterval}, d::Interval{L,R}) where {L,R} = d
convert(::Type{TypedEndpointsInterval{L,R}}, d::Interval{L,R}) where {L,R} = d

"""
    iv = l..r

Construct a ClosedInterval `iv` spanning the region from `l` to `r`.

(The symbol `..` is the same as in the package EllipsisNotation.jl.)
"""
..

(::Ellipsis)(x, y) = ClosedInterval(x, y)

"""
    iv = center±halfwidth

Construct a ClosedInterval `iv` spanning the region from
`center - halfwidth` to `center + halfwidth`.
"""
±(x, y) = ClosedInterval(x - y, x + y)
±(x::CartesianIndex, y) = (xy = y * one(x); map(ClosedInterval, (x - xy).I, (x + xy).I))

show(io::IO, I::ClosedInterval) = print(io, leftendpoint(I), "..", rightendpoint(I))
show(io::IO, I::OpenInterval) = print(io, leftendpoint(I), "..", rightendpoint(I), " (open)")
show(io::IO, I::Interval{:open,:closed}) = print(io, leftendpoint(I), "..", rightendpoint(I), " (open–closed)")
show(io::IO, I::Interval{:closed,:open}) = print(io, leftendpoint(I), "..", rightendpoint(I), " (closed–open)")

# The following are not typestable for mixed endpoint types
_left_intersect_type(::Type{Val{:open}}, ::Type{Val{L2}}, a1, a2) where L2 = a1 < a2 ? (a2,L2) : (a1,:open)
_left_intersect_type(::Type{Val{:closed}}, ::Type{Val{L2}}, a1, a2) where L2 = a1 ≤ a2 ? (a2,L2) : (a1,:closed)
_right_intersect_type(::Type{Val{:open}}, ::Type{Val{R2}}, b1, b2) where R2 = b1 > b2 ? (b2,R2) : (b1,:open)
_right_intersect_type(::Type{Val{:closed}}, ::Type{Val{R2}}, b1, b2) where R2 = b1 ≥ b2 ? (b2,R2) : (b1,:closed)

function intersect(d1::TypedEndpointsInterval{L1,R1,T}, d2::TypedEndpointsInterval{L2,R2,T}) where {L1,R1,L2,R2,T}
    a1, b1 = endpoints(d1); a2, b2 = endpoints(d2)
    a,L = _left_intersect_type(Val{L1}, Val{L2}, a1, a2)
    b,R = _right_intersect_type(Val{R1}, Val{R2}, b1, b2)
    Interval{L,R}(a,b)
end

function intersect(d1::TypedEndpointsInterval{L,R,T}, d2::TypedEndpointsInterval{L,R,T}) where {L,R,T}
    a1, b1 = endpoints(d1); a2, b2 = endpoints(d2)
    Interval{L,R}(max(a1,a2),min(b1,b2))
end

intersect(d1::TypedEndpointsInterval{L1,R1,T}, d2::TypedEndpointsInterval{L2,R2,V}) where {L1,R1,L2,R2,T,V} =
    intersect(promote(d1, d2)...)

intersect(d1::AbstractInterval, d2::AbstractInterval) = intersect(Interval(d1), Interval(d2))

union(d1::TypedEndpointsInterval{:closed,:closed},  d2::TypedEndpointsInterval{:closed,:closed}) =
    _leq_union(d1,d2)
union(d1::Interval{:open,:closed},                  d2::TypedEndpointsInterval{:closed,:closed}) =
    _leq_union(d1,d2)
union(d1::Interval{:closed,:open},                  d2::TypedEndpointsInterval{:closed,:closed}) =
    _leq_union(d1,d2)
union(d1::TypedEndpointsInterval{:closed,:closed},  d2::TypedEndpointsInterval{:open,:closed}) =
    _leq_union(d1,d2)
union(d1::TypedEndpointsInterval{:closed,:closed},  d2::TypedEndpointsInterval{:closed,:open}) =
    _leq_union(d1,d2)
union(d1::TypedEndpointsInterval{:open,:closed},    d2::TypedEndpointsInterval{:open,:closed}) =
    _leq_union(d1,d2)
union(d1::TypedEndpointsInterval{:closed,:open},    d2::TypedEndpointsInterval{:closed,:open}) =
    _leq_union(d1,d2)
union(d1::TypedEndpointsInterval{:closed,:closed},  d2::TypedEndpointsInterval{:open,:open}) =
    _leq_union(d1,d2)
union(d1::TypedEndpointsInterval{:open,:open},      d2::TypedEndpointsInterval{:closed,:closed}) =
    _leq_union(d1,d2)

function _leq_union(d1, d2)
    isempty(d1) && return d2
    isempty(d2) && return d1
    leftendpoint(d1) ≤ leftendpoint(d2) ≤ rightendpoint(d1)  || leftendpoint(d1) ≤ rightendpoint(d2) ≤ rightendpoint(d1) ||
        leftendpoint(d2) ≤ leftendpoint(d1) ≤ rightendpoint(d2) || leftendpoint(d2) ≤ rightendpoint(d1) ≤ rightendpoint(d2) ||
        throw(ArgumentError("Cannot construct union of disjoint sets."))
    _union(d1, d2)
end

function union(d1::TypedEndpointsInterval{:open,:open}, d2::TypedEndpointsInterval{:open,:open})
    isempty(d1) && return d2
    isempty(d2) && return d1
    leftendpoint(d1) ≤ leftendpoint(d2) < rightendpoint(d1)  || leftendpoint(d1) < rightendpoint(d2) ≤ rightendpoint(d1) ||
        leftendpoint(d2) ≤ leftendpoint(d1) < rightendpoint(d2)  || leftendpoint(d2) < rightendpoint(d1) ≤ rightendpoint(d2) ||
        throw(ArgumentError("Cannot construct union of disjoint sets."))
    _union(d1, d2)
end

function union(d1::TypedEndpointsInterval{:open,:open}, d2::TypedEndpointsInterval{:open,:closed})
    isempty(d1) && return d2
    isempty(d2) && return d1
    leftendpoint(d1) ≤ leftendpoint(d2) < rightendpoint(d1)  || leftendpoint(d1) ≤ rightendpoint(d2) ≤ rightendpoint(d1) ||
        leftendpoint(d2) ≤ leftendpoint(d1) ≤ rightendpoint(d2)  || leftendpoint(d2) < rightendpoint(d1) ≤ rightendpoint(d2) ||
        throw(ArgumentError("Cannot construct union of disjoint sets."))
    _union(d1, d2)
end

function union(d1::TypedEndpointsInterval{:open,:open}, d2::TypedEndpointsInterval{:closed,:open})
    isempty(d1) && return d2
    isempty(d2) && return d1
    leftendpoint(d1) ≤ leftendpoint(d2) ≤ rightendpoint(d1)  || leftendpoint(d1) < rightendpoint(d2) ≤ rightendpoint(d1) ||
        leftendpoint(d2) ≤ leftendpoint(d1) < rightendpoint(d2)  || leftendpoint(d2) ≤ rightendpoint(d1) ≤ rightendpoint(d2) ||
        throw(ArgumentError("Cannot construct union of disjoint sets."))
    _union(d1, d2)
end

union(d1::TypedEndpointsInterval, d2::TypedEndpointsInterval{:open,:open}) = union(d2, d1)

function union(d1::TypedEndpointsInterval{:closed,:open}, d2::TypedEndpointsInterval{:open,:closed})
    isempty(d1) && return d2
    isempty(d2) && return d1
    leftendpoint(d1) ≤ leftendpoint(d2) < rightendpoint(d1)  || leftendpoint(d1) ≤ rightendpoint(d2) ≤ rightendpoint(d1) ||
        leftendpoint(d2) ≤ leftendpoint(d1) ≤ rightendpoint(d2)  || leftendpoint(d2) < rightendpoint(d1) ≤ rightendpoint(d2) ||
        throw(ArgumentError("Cannot construct union of disjoint sets."))
    _union(d1, d2)
end

union(d1::TypedEndpointsInterval{:open,:closed}, d2::TypedEndpointsInterval{:closed,:open}) = union(d2, d1)

# these assume overlap
function _union(A::TypedEndpointsInterval{L,R}, B::TypedEndpointsInterval{L,R}) where {L,R}
    left = min(leftendpoint(A), leftendpoint(B))
    right = max(rightendpoint(A), rightendpoint(B))
    Interval{L,R}(left, right)
end

# this is not typestable
function _union(A::TypedEndpointsInterval{L1,R1}, B::TypedEndpointsInterval{L2,R2}) where {L1,R1,L2,R2}
    if leftendpoint(A) == leftendpoint(B)
        L = L1 == :closed ? :closed : L2
    elseif leftendpoint(A) < leftendpoint(B)
        L = L1
    else
        L = L2
    end
    if rightendpoint(A) == rightendpoint(B)
        R = R1 == :closed ? :closed : R2
    elseif rightendpoint(A) > rightendpoint(B)
        R = R1
    else
        R = R2
    end
    left = min(leftendpoint(A), leftendpoint(B))
    right = max(rightendpoint(A), rightendpoint(B))

    Interval{L,R}(left, right)
end

ClosedInterval{T}(i::AbstractUnitRange{I}) where {T,I<:Integer} = ClosedInterval{T}(minimum(i), maximum(i))
ClosedInterval(i::AbstractUnitRange{I}) where {I<:Integer} = ClosedInterval{I}(minimum(i), maximum(i))

Base.promote_rule(::Type{Interval{L,R,T1}}, ::Type{Interval{L,R,T2}}) where {L,R,T1,T2} = Interval{L,R,promote_type(T1, T2)}

# convert should only work if they represent the same thing.
@deprecate convert(::Type{R}, i::ClosedInterval{I}) where {R<:AbstractUnitRange,I<:Integer} R(i)
@deprecate length(i::ClosedInterval) IntervalSets.duration(i)


"""
direct product of two or more sets
"""
struct DirectProduct{T} <: AbstractProduct
    itr :: T
end
const DProd = DirectProduct

# Direct product INITIALIZERS

"""
return direct product of finite sets xs
"""
DirectProduct(xs...) = DirectProduct(product(xs...))

# from vector/tuple of finite sets
function DirectProduct(v :: Union{Vector{T}, NTuple{N, T}}) where T <: FinSet where N
    return DirectProduct(v...)
end

# from vector/tuple of Integers
function DirectProduct(v :: Union{Vector{T}, NTuple{N, T}}) where T <: Integer where N
    dom = map(Segment, v)
    return DirectProduct(dom)
end

# INTERFACE implementation
iterator(dp :: DirectProduct) = dp.itr
factors(dp :: DirectProduct) = dp.itr.iterators


lookandsay(input::Integer, iters = 1) = lookandsay!(reverse(digits(input)), iters)
function lookandsay!(input::AbstractVector{Int}, iters = 1)
    result = input
    input = Int[]
    for _ in 1:iters
        input, result = result, input
        while !isempty(input)
            next = popfirst!(input)
            count = 1
            while !isempty(input) && first(input) == next
                popfirst!(input)
                count += 1
            end
            push!(result, reverse(digits(count))..., next)
        end
    end
    join(result)
end

@assert lookandsay(1) == "11"
@assert lookandsay(11) == "21"
@assert lookandsay(1, 2) == "21"
@assert lookandsay(1211) == "111221"
@assert lookandsay(111221) == "312211"
@assert lookandsay(1, 5) == "312211"
@time length(lookandsay(3113322113, 40))
#   0.213305 seconds (3.49 M allocations: 162.478 MiB, 8.26% gc time)
# 329356
@time length(lookandsay(3113322113, 50))
#   3.463646 seconds (49.40 M allocations: 2.089 GiB, 14.32% gc time)
# 4666278


module TestClustering

using MLJBase
using Test
using Random:seed!
import LinearAlgebra: norm
import Distances: evaluate
using CategoricalArrays

# load code to be tested:
import MLJModels
import Clustering # MLJModels.Clustering_ now available for loading
using MLJModels.Clustering_

seed!(132442)

X, y = @load_crabs

####
#### KMEANS
####

barekm = KMeans()

fitresult, cache, report = fit(barekm, 1, X)

R = matrix(transform(barekm, fitresult, X))

X_array = matrix(X)

# distance from first point to second center
@test R[1, 2] ≈ norm(view(X_array, 1, :) .- view(fitresult[1], :, 2))^2
@test R[10, 3] ≈ norm(view(X_array, 10, :) .- view(fitresult[1], :, 3))^2

p = predict(barekm, fitresult, X)

@test argmin(R[1, :]) == p[1]
@test argmin(R[10, :]) == p[10]

infos = info_dict(barekm)

@test infos[:package_name] == "Clustering"
@test infos[:is_pure_julia]
@test infos[:package_license] == "MIT"

@test infos[:input_scitype] == Table(Continuous)
@test infos[:output_scitype] == Table(Continuous)

infos[:docstring]

####
#### KMEDOIDS
####

barekm = KMedoids()

fitresult, cache, report = fit(barekm, 1, X)

R = matrix(transform(barekm, fitresult, X))

@test R[1, 2] ≈ evaluate(barekm.metric, view(X_array, 1, :), view(fitresult[1], :, 2))
@test R[10, 3] ≈ evaluate(barekm.metric, view(X_array, 10, :), view(fitresult[1], :, 3))

p = predict(barekm, fitresult, X)

@test all(report.assignments .== p)

# km = machine(barekm, X)
# fit!(km)

infos = info_dict(barekm)

@test infos[:input_scitype] == Table(Continuous)
@test infos[:output_scitype] == Table(Continuous)

end # module
true


module CombUtils

export swap!
function swap!{T<:Any, N<:Integer}(a::Array{T, 1}, i::N, j::N)
    tmp = a[i]
    a[i] = a[j]
    a[j] = tmp
end

export next_permutation!
# Precondition: Input is array consists of unique integers, initially sorted in
# strictly ascending order
function next_permutation!{N<:Integer}(ar::Array{N, 1})
    # 1. Find the highest index, i1 such that ar[i1] is the first of a pair of
    # elements in ascending order. If there isn't one, the sequence is the
    # highest permutation, so reverse the whole thing to begin again.
    alen = length(ar)
    i1 = alen - 1
    while i1 > 0
        if ar[i1] < ar[i1 + 1]
            break
        end
        i1 -= 1
    end
    if i1 > 0
        # 2. Find the highest index i2, such that i2 > i1 and ar[i2] > ar[i1].
        i2 = alen
        while i2 > i1
            if ar[i2] > ar[i1]
                break
            end
            i2 -= 1
        end
        # 3. Swap ar[i1] and ar[i2].
        swap!(ar, i1, i2)
        # 4. The elements from ar[i1 + 1] to the end are now in descending order
        # (a later permutation), so reverse them.
        reverse!(ar, i1 + 1, alen)
    else
        reverse!(ar, 1, alen)
    end
end

end


using NearestNeighbors
using StaticArrays

using Test
using LinearAlgebra

import Distances: Metric, evaluate
struct CustomMetric1 <: Metric end
evaluate(::CustomMetric1, a::AbstractVector, b::AbstractVector) = maximum(abs.(a .- b))
function NearestNeighbors.interpolate(::CustomMetric1,
                                      a::V,
                                      b::V,
                                      x,
                                      d,
                                      ab) where {V <: AbstractVector}
    idx = (abs.(b .- a) .>= d - x)
    c = copy(Array(a))
    c[idx] = (1 - x / d) * a[idx] + (x / d) * b[idx]
    return c, true
end
struct CustomMetric2 <: Metric end
evaluate(::CustomMetric2, a::AbstractVector, b::AbstractVector) = norm(a - b) / (norm(a) + norm(b))

# TODO: Cityblock()
const metrics = [Chebyshev(), Euclidean(), Minkowski(3.5)]
const fullmetrics = [metrics; Hamming(); CustomMetric1(); CustomMetric2()]
const trees = [KDTree, BallTree]
const trees_with_brute = [BruteTree; trees]

include("test_knn.jl")
include("test_inrange.jl")
include("test_monkey.jl")
include("test_datafreetree.jl")


@testset "Utilities" begin
  a, b, c = P2(0,0), P2(1,0), P2(0,1)
  @test signarea(a, b, c) == T(0.5)
  a, b, c = P2(0,0), P2(0,1), P2(1,0)
  @test signarea(a, b, c) == T(-0.5)

  p1, p2, p3 = P2(0,0), P2(1,1), P2(0.25,0.5)
  s = Segment(P2(0.5,0.0), P2(0.0,1.0))
  @test sideof(p1, s) == :LEFT
  @test sideof(p2, s) == :RIGHT
  @test sideof(p3, s) == :ON

  p1, p2, p3 = P2(0.5,0.5), P2(1.5,0.5), P2(1,1)
  c = Chain(P2[(0,0),(1,0),(1,1),(0,1),(0,0)])
  @test sideof(p1, c) == :INSIDE
  @test sideof(p2, c) == :OUTSIDE
  @test sideof(p3, c) == :INSIDE

  # drop units from unitful value and type
  @test Meshes.dropunits(1.0u"mm") == Float64
  @test Meshes.dropunits(typeof(1.0u"mm")) == Float64

  # return the same type in case of no units
  @test Meshes.dropunits(1.0) == Float64
  @test Meshes.dropunits(Float64) == Float64
end


"""
$(SIGNATURES)

Fill empty sparse matrix A with random nonzero elements from interval [1,2]
using incremental assembly.
"""
function sprand!(A::AbstractSparseMatrix{Tv,Ti},xnnz::Int) where {Tv,Ti}
    m,n=size(A)
    for i=1:xnnz
        i=rand((1:m))
        j=rand((1:n))
        a=1.0+rand(Tv)
        A[i,j]+=a
    end
    A
end

"""
$(SIGNATURES)

Fill sparse matrix  with random entries such that  it becomes strictly
diagonally  dominant  and  thus  invertible and  has  a  fixed  number
`nnzrow` (default: 4) of nonzeros in its rows. The matrix bandwidth is
bounded by  sqrt(n) in order to resemble  a typical matrix of  a 2D
piecewise linear FEM discretization.
"""
function sprand_sdd!(A::AbstractSparseMatrix{Tv,Ti}; nnzrow=4) where {Tv,Ti}
    m,n=size(A)
    @assert m==n
    nnzrow=min(n,nnzrow)
    bandwidth=convert(Int,ceil(sqrt(n)))
    for i=1:n
        aii=0
        for k=1:nnzrow
            jmin=max(1,i-bandwidth)
            jmax=min(n,i+bandwidth)
            j=rand((jmin:jmax))
            if i!=j
                aij=rand()
                A[i,j]=aij
                aii+=abs(aij)
            end
        end
        A[i,i]=aii+rand() # make it strictly diagonally dominant
    end
    A
end

"""
$(SIGNATURES)

After setting  all nonzero entries  to zero, fill resp.  update matrix
with finite  difference discretization data  on a unit  hypercube. See
[`fdrand`](@ref) for documentation of the parameters.

It is required that `size(A)==(N,N)` where `N=nx*ny*nz`
"""
function fdrand!(A::T,
                 nx,ny,nz;
                 update= (A,v,i,j)-> A[i,j]+=v,
                 rand=()-> rand()) where T<:AbstractMatrix

    sz=size(A)
    N=nx*ny*nz
    if sz[1]!=N || sz[2]!=N
        error("Matrix size mismatch")
    end

    _flush!(m::ExtendableSparseMatrix)=flush!(m)
    _flush!(m::SparseMatrixCSC)=m
    _flush!(m::SparseMatrixLNK)=m
    _flush!(m::Matrix)=m

    _nonzeros(m::Matrix)=vec(m)
    _nonzeros(m::ExtendableSparseMatrix)=nonzeros(m)
    _nonzeros(m::SparseMatrixLNK)=m.nzval
    _nonzeros(m::SparseMatrixCSC)=nonzeros(m)

    function update_pair(A,v,i,j)
        update(A,-v,i,j)
        update(A,-v,j,i)
        update(A,v,i,i)
        update(A,v,j,j)
    end

    nzv=_nonzeros(A)
    nzv.=0.0

    hx=1.0/nx
    hy=1.0/ny
    hz=1.0/nz

    nxy=nx*ny
    l=1
    for k=1:nz
        for j=1:ny
            for i=1:nx
                if i<nx
                    update_pair(A,rand()*hy*hz/hx,l,l+1)
                end
                if i==1|| i==nx
                    update(A,rand()*hy*hz,l,l)
                end
                if j<ny
                    update_pair(A,rand()*hx*hz/hy,l,l+nx)
                end
                if ny>2&&(j==1|| j==ny)
                    update(A,rand()*hx*hz,l,l)
                end
                if k<nz
                    update_pair(A,rand()*hx*hy/hz,l,l+nxy)
                end
                if nz>2&&(k==1|| k==nz)
                    update(A,rand()*hx*hy,l,l)
                end
                l=l+1
            end
        end
    end
    _flush!(A)
end

"""
$(SIGNATURES)

Create SparseMatrixCSC via COO intermedite arrrays
"""

function fdrand_coo(nx,ny,nz;
                    rand=()-> rand())
    N=nx*ny*nz
    I=zeros(Int64,0)
    J=zeros(Int64,0)
    V=zeros(Float64,0)

    function update(v,i,j)
        push!(I,i)
        push!(J,j)
        push!(V,v)
    end

    function update_pair(v,i,j)
        update(-v,i,j)
        update(-v,j,i)
        update(v,i,i)
        update(v,j,j)
    end

    hx=1.0/nx
    hy=1.0/ny
    hz=1.0/nz

    nxy=nx*ny
    l=1
    for k=1:nz
        for j=1:ny
            for i=1:nx
                if i<nx
                    update_pair(rand()*hy*hz/hx,l,l+1)
                end
                if i==1|| i==nx
                    update(rand()*hy*hz,l,l)
                end
                if j<ny
                    update_pair(rand()*hx*hz/hy,l,l+nx)
                end
                if ny>2&&(j==1|| j==ny)
                    update(rand()*hx*hz,l,l)
                end
                if k<nz
                    update_pair(rand()*hx*hy/hz,l,l+nxy)
                end
                if nz>2&&(k==1|| k==nz)
                    update(rand()*hx*hy,l,l)
                end
                l=l+1
            end
        end
    end
    sparse(I,J,V)
end
"""
$(SIGNATURES)

Create matrix  for a mock  finite difference operator for  a diffusion
problem with random coefficients on a unit hypercube ``\\Omega\\subset\\mathbb R^d``.
with ``d=1`` if  `nx>1 && ny==1 && nz==1`, ``d=2`` if  `nx>1 && ny>1 && nz==1` and
``d=3`` if  `nx>1 && ny>1 && nz>1` .

```math
    \\begin{align*}
             -\\nabla a \\nabla u &= f&&  \\text{in}\\;  \\Omega  \\\\
    a\\nabla u\\cdot \\vec n + bu &=g && \\text{on}\\;  \\partial\\Omega
    \\end{align*}
```

The matrix is irreducibly diagonally dominant, has positive main diagonal entries
and nonpositive off-diagonal entries, hence it has the M-Property.
Therefore, its inverse will be a dense matrix with positive entries.
Moreover it is symmmetric, and  positive definite, and the spectral radius
of the  Jacobi iteration matrix ``\rho(I-D(A)^{-1}A)<1`` .

Parameters+ default values:

|   Parameter + default vale        | Description                        |
| ---------------------------------:|:---------------------------------- |
|                              `nx` | Number of unknowns in x direction  |
|                              `ny` | Number of unknowns in y direction  |
|                              `nz` | Number of unknowns in z direction  |
|    `matrixtype = SparseMatrixCSC` | Matrix type                        |
|  `update = (A,v,i,j)-> A[i,j]+=v` | Element update function            |
|    `rand =()-> rand()`            | Random number generator            |

The sparsity structure is fixed to an orthogonal grid, resulting in a 3, 5 or 7
diagonal matrix depending on dimension. The entries
are random unless e.g.  `rand=()->1` is passed as random number generator.
Tested for Matrix, SparseMatrixCSC,  ExtendableSparseMatrix, SparseMatrixLNK and `:COO`

"""
function fdrand(nx,ny,nz;
                matrixtype::Union{Type,Symbol}=SparseMatrixCSC,
                update = (A,v,i,j)-> A[i,j]+=v,
                rand= ()-> 0.1+rand())
    N=nx*ny*nz
    if matrixtype==:COO
        return fdrand_coo(nx,ny,nz, rand=rand)
    elseif matrixtype==ExtendableSparseMatrix
        A=ExtendableSparseMatrix(N,N)
    elseif matrixtype==SparseMatrixLNK
        A=SparseMatrixLNK(N,N)
    elseif matrixtype==SparseMatrixCSC
        A=spzeros(N,N)
    elseif matrixtype==Matrix
        A=zeros(N,N)
    end
    fdrand!(A,nx,ny,nz,update = update, rand=rand)
end


using LinearAlgebra

##
using Plots
theme(:dark)
N = 200
x = rand(N)
y = rand(N)
scatter(x,y,marker_z = x.^2 + y.^2 .<= 1,
  colorbar=false, color=:blues, legend=false, size=(300,300))
savefig("darts-2d.pdf")

##
using Plots

# Yanfei fit these, will post code at some point.
ps = [1.0000000000000007
1.9071251844719876
2.9163505597292922
4.022490241233555
5.216497321076429
6.489971145314907
7.835660661642428
9.247384556353152
10.719862319175242
12.248553044593804
13.829521448758406
15.459330401914192
17.134658253998534
18.853008360316284
20.61139369797272
22.406483457522032
24.2335290146664
26.084939572427672
27.538569418766958]

##
k = 2
p = ps[k-1]
thetas = range(0.0, stop=pi/2, length=100)
plot(cos.(thetas).^p, sin.(thetas).^p, label="")

for k=3:10
  p = ps[k-1]
  plot!(cos.(thetas).^p, sin.(thetas).^p, label="")
end
plot!(size=(200,200), xaxis=false, yaxis=false, aspect_ratio=:equal, background=:transparent)
savefig("darts-nd.pdf")


#
# Special case for three tensors
#

function compute_cost(external_dims::Tuple{Int,Int,Int}, internal_dims::Tuple{Int,Int,Int})
  dim11, dim22, dim33 = external_dims
  dim12, dim23, dim31 = internal_dims
  cost12 = dim11 * dim22 * dim12 * dim23 * dim31
  return cost12 + dim11 * dim22 * dim33 * dim31 * dim23
end

function three_tensor_contraction_sequence(which_sequence::Int)::Vector{Any}
  @assert 1 ≤ which_sequence ≤ 3
  return if which_sequence == 1
    Any[3, [1, 2]]
  elseif which_sequence == 2
    Any[1, [2, 3]]
  else
    Any[2, [3, 1]]
  end
end

function optimal_contraction_sequence(is1, is2, is3)
  N1 = length(is1)
  N2 = length(is2)
  N3 = length(is3)
  dim2 = dim(is2)
  dim3 = dim(is3)
  dim11 = 1
  dim12 = 1
  dim31 = 1
  @inbounds for n1 in 1:N1
    i1 = is1[n1]
    n2 = findfirst(==(i1), is2)
    if isnothing(n2)
      n3 = findfirst(==(i1), is3)
      if isnothing(n3)
        dim11 *= dim(i1)
        continue
      end
      dim31 *= dim(i1)
      continue
    end
    dim12 *= dim(i1)
  end
  dim23 = 1
  @inbounds for n2 in 1:length(is2)
    i2 = is2[n2]
    n3 = findfirst(==(i2), is3)
    if !isnothing(n3)
      dim23 *= dim(i2)
    end
  end
  dim22 = dim2 ÷ (dim12 * dim23)
  dim33 = dim3 ÷ (dim23 * dim31)
  external_dims1 = (dim11, dim22, dim33)
  internal_dims1 = (dim12, dim23, dim31)
  external_dims2 = (dim22, dim33, dim11)
  internal_dims2 = (dim23, dim31, dim12)
  external_dims3 = (dim33, dim11, dim22)
  internal_dims3 = (dim31, dim12, dim23)
  cost1 = compute_cost(external_dims1, internal_dims1)
  cost2 = compute_cost(external_dims2, internal_dims2)
  cost3 = compute_cost(external_dims3, internal_dims3)
  mincost, which_sequence = findmin((cost1, cost2, cost3))
  sequence = three_tensor_contraction_sequence(which_sequence)
  return sequence
end


module Correlation_Functions
export form_atomic_time_series, autocorrelation_function, custom_acf, plot_correlation_function, vdos, plot_vdos
using FFTW, DSP, Plots, LaTeXStrings, ProgressBars

    function form_atomic_time_series(frames::AbstractArray)
        return transpose(hcat(vec.(frames)...))
    end

    function autocorrelation_function(time_series::AbstractArray, block_width::Int, num_lags_per_window::Int=20, max_blocks::Int=0)
        """
        Compute the autocorrelation function of a single time series or many time series computed
        as a matrix of time series where each column is a time series. This will average the acf over
        as many disconnected blocks of length block_width as possible, unless the optional max_blocks is specified.
        """
        signal = zeros(block_width)
        num_blocks = convert(Int, floor(size(time_series, 1) / block_width))
        if max_blocks > 0 && max_blocks < num_blocks
            num_blocks = max_blocks
        end
        block_stride = convert(Int, floor(block_width / 20))

        for col in ProgressBar(eachcol(time_series))
            for i_block in 1:num_blocks
                for i_slide in 1:block_width:block_stride
                    block = @inbounds @view col[(i_block-1)*block_width+1+(i_slide-1):i_block*block_width+(i_slide-1)]
                    signal += DSP.xcorr(block, block)[block_width:end]
                end
                signal /= num_lags_per_window
            end
            signal /= num_blocks
        end
        return signal / signal[begin]
    end

    function plot_correlation_function(time_series::AbstractArray, frame_spacing::Float64)
        plotlyjs()
        display(plot((1:length(time_series)) * frame_spacing, time_series,
        label="ACF Signal",
        xlims=(0, 2500),
        xlabel="Time (fs)",
        ylabel="Signal Correlation"))
    end

    function vdos(signal::AbstractArray, frame_spacing::Float64)
        """
        Computes the vibrational density of states from the velocity autocorrelation function.
        Args:
            signal: 1-D array representing some time series
            frame_spacing: float which is the spacing in femtoseconds of the frames
        Return: both the fourier frequencies and the signal intensity as a tuple.
        """
        freqs = FFTW.rfftfreq(length(signal), 1 / frame_spacing)[1:floor(Int64, length(signal)/2)] * 10^(15) / (2.9979*(10^10)) # fs to cm^-1
        vdos = real.(FFTW.rfft(signal)[1:floor(Int64, length(signal)/2)])
        return freqs, vdos
    end

    function plot_vdos(frequencies::AbstractArray, vdos::AbstractArray)
        plotlyjs()
        plot(frequencies, vdos,
        label="VDOS",
        xlims=(1000, 4400),
        ylims=(0, 15),
        xlabel=(L"Frequency (cm^{-1})"),
        ylabel=("Intensity"))
    end

end


export ==, !=, hash, isless

#############################
## Degree
#############################
function isless(deg1::Degree, deg2::Degree)
  return (deg1.explvar<deg2.explvar) || (deg1.explvar==deg2.explvar && deg1.conjvar<deg2.conjvar)
end

#############################
## Variable
#############################
function isless(a::Variable, b::Variable)
  return isless(a.name, b.name)
end

#############################
## Exponent
#############################
function isless(exp1::Exponent, exp2::Exponent)
    ## Handle one exponent, empty structures are not handled by following general sort alg.
    if length(exp1) == 0        # if exp1 == 1
        return length(exp2) > 0
    end

    if length(exp2) == 0        # exp1!=1, if exp2 == 1
        return false
    end

    state1 = start(exp1)
    state2 = start(exp2)
    while !done(exp1, state1) && !done(exp2, state2)
        (i1, state1) = next(exp1, state1)
        (i2, state2) = next(exp2, state2)

        if isequal(i1, i2)
            continue
        else
            return isless(i1,i2)
        end
        if done(exp1, state1) && done(exp2, state2)
            return false
        elseif done(exp1, state1) && !done(exp2, state2)
            return true
        elseif !done(exp1, state1) && done(exp2, state2)
            return false
        end
    end

    return false
end

"""
    isless_degree(exp1::Exponent, exp2::Exponent)

    Order sorting elements on their sum of degrees at a first level.
    Test show performance is comparable to previous sorting function.
"""
function isless_degree(exp1::Exponent, exp2::Exponent)
    # First order level: sum of degrees
    exp1_deg = 0
    state1 = start(exp1)
    while !done(exp1, state1)
        (i1, state1) = next(exp1, state1)
        exp1_deg += i1[2].explvar + i1[2].conjvar
    end

    exp2_deg = 0
    state2 = start(exp2)
    while !done(exp2, state2)
        (i2, state2) = next(exp2, state2)
        exp2_deg += i2[2].explvar + i2[2].conjvar
    end

    if exp1_deg < exp2_deg
        return true
    elseif exp1_deg > exp2_deg
        return false
    end

    # Second order level: sort with variables and degrees
    state1 = start(exp1)
    state2 = start(exp2)
    while !done(exp1, state1) && !done(exp2, state2)
        (i1, state1) = next(exp1, state1)
        (i2, state2) = next(exp2, state2)

        if isequal(i1, i2)
            continue
        else
            return isless(i1,i2)
        end
        if done(exp1, state1) && done(exp2, state2)
            return false
        elseif done(exp1, state1) && !done(exp2, state2)
            return true
        elseif !done(exp1, state1) && done(exp2, state2)
            return false
        end
    end

    return false
end

#############################
## Polynomial
#############################
# NOTE: order on polynomials ?


using Printf

function inter_node_num_mistakes(labels_in_node::AbstractVector{T}) where {T <: Real}
  n = length(labels_in_node)
  n == 0 && return 0  ## Corner case

  n_pos = sum(labels_in_node .== 1)  ## Count the number of 1's (safe loans)
  n_neg = n - n_pos ## or(labels_in_node == -1).sum()

  ## Return the number of mistakes that the majority classifier makes.
  return n_pos ≥ n_neg ? n_neg : n_pos
end

function best_splitting_feature(df::DF, features::Vector{Symbol},
                                target::Symbol) where {DF <: Union{DataFrame, SubDataFrame}}
  best_feature, best_error = nothing, 2
  ## Note: Since error is always ≤ 1, init best_error with something > 1

  num_data_points = size(df)[1]
  for f ∈ features
    l_split = df[df[!, f] .== 0, :]
    r_split = df[df[!, f] .== 1, :]

    l_mistakes = inter_node_num_mistakes(l_split[!, target])
    r_mistakes = inter_node_num_mistakes(r_split[!, target])

    error = (l_mistakes + r_mistakes) / num_data_points
    if error < best_error
      best_error = error
      best_feature = f
    end
  end

  best_feature
end

function create_leaf(target_values;
                     splitting_feature=nothing, left=nothing, right=nothing, is_leaf=true)
  ## Create a leaf node
  leaf = Dict{Symbol, Any}(
                           :splitting_feature => splitting_feature,
                           :prediction => nothing,
                           :left => left,
                           :right => right,
                           :is_leaf => is_leaf,
                           )

  if is_leaf
    ## Count the number of data points that are +1 and -1 in this node.
    num_1s = length(target_values[target_values .== 1, :])
    num_minus_1s = length(target_values[target_values .== -1, :])

    ## For the leaf node, set the prediction to be the majority class.
    ## Store the predicted class (1 or -1) in leaf['prediction']
    leaf[:prediction] = num_1s > num_minus_1s ? 1 : -1
  end

  return leaf
end

function count_nodes(tree)
    isnothing(tree) || tree[:is_leaf] ?
    1 : 1 + count_nodes(tree[:left]) + count_nodes(tree[:right])
end

function count_leaves(tree)
    isnothing(tree) || tree[:is_leaf] ?
    1 : count_nodes(tree[:left]) + count_nodes(tree[:right])
end

function classify(tree, x::DataFrameRow; annotate=false)
  # if the node is a leaf node.
  if tree[:is_leaf]
    annotate && @printf("At leaf, predicting %s\n", tree[:prediction])
    return tree[:prediction]
  end

  ## split on feature.
  split_feature_value = x[tree[:splitting_feature]]
  annotate &&
    @printf("Split on %s => %s\n", tree[:splitting_feature], split_feature_value)

  split_feature_value == 0 ? classify(tree[:left], x; annotate) :
    classify(tree[:right], x; annotate)
end

function eval_classification_error(tree, df::DF,
                                   target::Symbol) where {DF <: Union{DataFrame, SubDataFrame}}
  ## 1. get the predictions
  ŷ = map(x -> classify(tree, x), eachrow(df))

  ## 2. calculate the classification error
  num_mistakes = sum(ŷ .≠ df[!, target])
  return num_mistakes / size(df)[1]
end


#=

 Unit tests from C-XSC version 2.5.4
 converted into portable ITL format by Oliver Heimlich.

 Copyright 1990-2000 Institut fuer Angewandte Mathematik,
                     Universitaet Karlsruhe, Germany
 Copyright 2000-2014 Wiss. Rechnen/Softwaretechnologie
                     Universitaet Wuppertal, Germany
 Copyright 2015-2016 Oliver Heimlich (oheim@posteo.de)

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

=#
#Language imports

#Test library imports
using Test

#Arithmetic library imports
using IntervalArithmetic

#Preamble
setprecision(53)
setprecision(Interval, Float64)
setrounding(Interval, :tight)
# Set full format, and show decorations
@format full

# Tests A+B, B+A, A-B, B-A, -A, +A
@testset "cxsc.intervaladdsub" begin
    @test Interval(10.0, 20.0) + Interval(13.0, 17.0) == Interval(23.0, 37.0)
    @test Interval(13.0, 17.0) + Interval(10.0, 20.0) == Interval(23.0, 37.0)
    @test Interval(10.0, 20.0) - Interval(13.0, 16.0) == Interval(-6.0, 7.0)
    @test Interval(13.0, 16.0) - Interval(10.0, 20.0) == Interval(-7.0, 6.0)
    @test -(Interval(10.0, 20.0)) == Interval(-20.0, -10.0)
    @test +Interval(10.0, 20.0) == Interval(10.0, 20.0)
end

# Tests A*B, B*A, A/B, B/A
@testset "cxsc.intervalmuldiv" begin
    @test Interval(1.0, 2.0) * Interval(3.0, 4.0) == Interval(3.0, 8.0)
    @test Interval(-1.0, 2.0) * Interval(3.0, 4.0) == Interval(-4.0, 8.0)
    @test Interval(-2.0, 1.0) * Interval(3.0, 4.0) == Interval(-8.0, 4.0)
    @test Interval(-2.0, -1.0) * Interval(3.0, 4.0) == Interval(-8.0, -3.0)
    @test Interval(1.0, 2.0) * Interval(-3.0, 4.0) == Interval(-6.0, 8.0)
    @test Interval(-1.0, 2.0) * Interval(-3.0, 4.0) == Interval(-6.0, 8.0)
    @test Interval(-2.0, 1.0) * Interval(-3.0, 4.0) == Interval(-8.0, 6.0)
    @test Interval(-2.0, -1.0) * Interval(-3.0, 4.0) == Interval(-8.0, 6.0)
    @test Interval(1.0, 2.0) * Interval(-4.0, 3.0) == Interval(-8.0, 6.0)
    @test Interval(-1.0, 2.0) * Interval(-4.0, 3.0) == Interval(-8.0, 6.0)
    @test Interval(-2.0, 1.0) * Interval(-4.0, 3.0) == Interval(-6.0, 8.0)
    @test Interval(-2.0, -1.0) * Interval(-4.0, 3.0) == Interval(-6.0, 8.0)
    @test Interval(1.0, 2.0) * Interval(-4.0, -3.0) == Interval(-8.0, -3.0)
    @test Interval(-1.0, 2.0) * Interval(-4.0, -3.0) == Interval(-8.0, 4.0)
    @test Interval(-2.0, -1.0) * Interval(-4.0, -3.0) == Interval(3.0, 8.0)
    @test Interval(1.0, 2.0) / Interval(4.0, 8.0) == Interval(0.125, 0.5)
    @test Interval(-1.0, 2.0) / Interval(4.0, 8.0) == Interval(-0.25, 0.5)
    @test Interval(-2.0, 1.0) / Interval(4.0, 8.0) == Interval(-0.5, 0.25)
    @test Interval(-2.0, -1.0) / Interval(4.0, 8.0) == Interval(-0.5, -0.125)
    @test Interval(1.0, 2.0) / Interval(-4.0, 8.0) == entireinterval(Float64)
    @test Interval(-1.0, 2.0) / Interval(-4.0, 8.0) == entireinterval(Float64)
    @test Interval(-2.0, 1.0) / Interval(-4.0, 8.0) == entireinterval(Float64)
    @test Interval(-2.0, -1.0) / Interval(-4.0, 8.0) == entireinterval(Float64)
    @test Interval(1.0, 2.0) / Interval(-8.0, 4.0) == entireinterval(Float64)
    @test Interval(-1.0, 2.0) / Interval(-8.0, 4.0) == entireinterval(Float64)
    @test Interval(-2.0, 1.0) / Interval(-8.0, 4.0) == entireinterval(Float64)
    @test Interval(-2.0, -1.0) / Interval(-8.0, 4.0) == entireinterval(Float64)
    @test Interval(1.0, 2.0) / Interval(-8.0, -4.0) == Interval(-0.5, -0.125)
    @test Interval(-1.0, 2.0) / Interval(-8.0, -4.0) == Interval(-0.5, 0.25)
    @test Interval(-2.0, 1.0) / Interval(-8.0, -4.0) == Interval(-0.25, 0.5)
    @test Interval(-2.0, -1.0) / Interval(-8.0, -4.0) == Interval(0.125, 0.5)
end

# Tests A|B, B|A, A&B, B&A
@testset "cxsc.intervalsetops" begin
    @test hull(Interval(-2.0, 2.0), Interval(-4.0, -3.0)) == Interval(-4.0, 2.0)
    @test ∪(Interval(-2.0, 2.0), Interval(-4.0, -3.0)) == Interval(-4.0, 2.0)
    @test hull(Interval(-2.0, 2.0), Interval(-4.0, -1.0)) == Interval(-4.0, 2.0)
    @test ∪(Interval(-2.0, 2.0), Interval(-4.0, -1.0)) == Interval(-4.0, 2.0)
    @test hull(Interval(-2.0, 2.0), Interval(-4.0, 4.0)) == Interval(-4.0, 4.0)
    @test ∪(Interval(-2.0, 2.0), Interval(-4.0, 4.0)) == Interval(-4.0, 4.0)
    @test hull(Interval(-2.0, 2.0), Interval(-1.0, 1.0)) == Interval(-2.0, 2.0)
    @test ∪(Interval(-2.0, 2.0), Interval(-1.0, 1.0)) == Interval(-2.0, 2.0)
    @test hull(Interval(-2.0, 2.0), Interval(1.0, 4.0)) == Interval(-2.0, 4.0)
    @test ∪(Interval(-2.0, 2.0), Interval(1.0, 4.0)) == Interval(-2.0, 4.0)
    @test hull(Interval(-2.0, 2.0), Interval(3.0, 4.0)) == Interval(-2.0, 4.0)
    @test ∪(Interval(-2.0, 2.0), Interval(3.0, 4.0)) == Interval(-2.0, 4.0)
    @test hull(Interval(-4.0, -3.0), Interval(-2.0, 2.0)) == Interval(-4.0, 2.0)
    @test ∪(Interval(-4.0, -3.0), Interval(-2.0, 2.0)) == Interval(-4.0, 2.0)
    @test hull(Interval(-4.0, -1.0), Interval(-2.0, 2.0)) == Interval(-4.0, 2.0)
    @test ∪(Interval(-4.0, -1.0), Interval(-2.0, 2.0)) == Interval(-4.0, 2.0)
    @test hull(Interval(-4.0, 4.0), Interval(-2.0, 2.0)) == Interval(-4.0, 4.0)
    @test ∪(Interval(-4.0, 4.0), Interval(-2.0, 2.0)) == Interval(-4.0, 4.0)
    @test hull(Interval(-1.0, 1.0), Interval(-2.0, 2.0)) == Interval(-2.0, 2.0)
    @test ∪(Interval(-1.0, 1.0), Interval(-2.0, 2.0)) == Interval(-2.0, 2.0)
    @test hull(Interval(1.0, 4.0), Interval(-2.0, 2.0)) == Interval(-2.0, 4.0)
    @test ∪(Interval(1.0, 4.0), Interval(-2.0, 2.0)) == Interval(-2.0, 4.0)
    @test hull(Interval(3.0, 4.0), Interval(-2.0, 2.0)) == Interval(-2.0, 4.0)
    @test ∪(Interval(3.0, 4.0), Interval(-2.0, 2.0)) == Interval(-2.0, 4.0)
    @test intersect(Interval(-2.0, 2.0), Interval(-4.0, -3.0)) == ∅
    @test ∩(Interval(-2.0, 2.0), Interval(-4.0, -3.0)) == ∅
    @test intersect(Interval(-2.0, 2.0), Interval(-4.0, -1.0)) == Interval(-2.0, -1.0)
    @test ∩(Interval(-2.0, 2.0), Interval(-4.0, -1.0)) == Interval(-2.0, -1.0)
    @test intersect(Interval(-2.0, 2.0), Interval(-4.0, 4.0)) == Interval(-2.0, 2.0)
    @test ∩(Interval(-2.0, 2.0), Interval(-4.0, 4.0)) == Interval(-2.0, 2.0)
    @test intersect(Interval(-2.0, 2.0), Interval(-1.0, 1.0)) == Interval(-1.0, 1.0)
    @test ∩(Interval(-2.0, 2.0), Interval(-1.0, 1.0)) == Interval(-1.0, 1.0)
    @test intersect(Interval(-2.0, 2.0), Interval(1.0, 4.0)) == Interval(1.0, 2.0)
    @test ∩(Interval(-2.0, 2.0), Interval(1.0, 4.0)) == Interval(1.0, 2.0)
    @test intersect(Interval(-2.0, 2.0), Interval(3.0, 4.0)) == ∅
    @test ∩(Interval(-2.0, 2.0), Interval(3.0, 4.0)) == ∅
    @test intersect(Interval(-4.0, -3.0), Interval(-2.0, 2.0)) == ∅
    @test ∩(Interval(-4.0, -3.0), Interval(-2.0, 2.0)) == ∅
    @test intersect(Interval(-4.0, -1.0), Interval(-2.0, 2.0)) == Interval(-2.0, -1.0)
    @test ∩(Interval(-4.0, -1.0), Interval(-2.0, 2.0)) == Interval(-2.0, -1.0)
    @test intersect(Interval(-4.0, 4.0), Interval(-2.0, 2.0)) == Interval(-2.0, 2.0)
    @test ∩(Interval(-4.0, 4.0), Interval(-2.0, 2.0)) == Interval(-2.0, 2.0)
    @test intersect(Interval(-1.0, 1.0), Interval(-2.0, 2.0)) == Interval(-1.0, 1.0)
    @test ∩(Interval(-1.0, 1.0), Interval(-2.0, 2.0)) == Interval(-1.0, 1.0)
    @test intersect(Interval(1.0, 4.0), Interval(-2.0, 2.0)) == Interval(1.0, 2.0)
    @test ∩(Interval(1.0, 4.0), Interval(-2.0, 2.0)) == Interval(1.0, 2.0)
    @test intersect(Interval(3.0, 4.0), Interval(-2.0, 2.0)) == ∅
    @test ∩(Interval(3.0, 4.0), Interval(-2.0, 2.0)) == ∅
end

# Tests A|B, B|A, A&B, B&A, B is scalar-type
@testset "cxsc.intervalmixsetops" begin
    @test hull(Interval(-2.0, 2.0), Interval(-4.0, -4.0)) == Interval(-4.0, 2.0)
    @test ∪(Interval(-2.0, 2.0), Interval(-4.0, -4.0)) == Interval(-4.0, 2.0)
    @test hull(Interval(-2.0, 2.0), Interval(1.0, 1.0)) == Interval(-2.0, 2.0)
    @test ∪(Interval(-2.0, 2.0), Interval(1.0, 1.0)) == Interval(-2.0, 2.0)
    @test hull(Interval(-2.0, 2.0), Interval(4.0, 4.0)) == Interval(-2.0, 4.0)
    @test ∪(Interval(-2.0, 2.0), Interval(4.0, 4.0)) == Interval(-2.0, 4.0)
    @test hull(Interval(-4.0, -4.0), Interval(-2.0, 2.0)) == Interval(-4.0, 2.0)
    @test ∪(Interval(-4.0, -4.0), Interval(-2.0, 2.0)) == Interval(-4.0, 2.0)
    @test hull(Interval(1.0, 1.0), Interval(-2.0, 2.0)) == Interval(-2.0, 2.0)
    @test ∪(Interval(1.0, 1.0), Interval(-2.0, 2.0)) == Interval(-2.0, 2.0)
    @test hull(Interval(4.0, 4.0), Interval(-2.0, 2.0)) == Interval(-2.0, 4.0)
    @test ∪(Interval(4.0, 4.0), Interval(-2.0, 2.0)) == Interval(-2.0, 4.0)
    @test intersect(Interval(-2.0, 2.0), Interval(-4.0, -4.0)) == ∅
    @test ∩(Interval(-2.0, 2.0), Interval(-4.0, -4.0)) == ∅
    @test intersect(Interval(-2.0, 2.0), Interval(1.0, 1.0)) == Interval(1.0, 1.0)
    @test ∩(Interval(-2.0, 2.0), Interval(1.0, 1.0)) == Interval(1.0, 1.0)
    @test intersect(Interval(-2.0, 2.0), Interval(4.0, 4.0)) == ∅
    @test ∩(Interval(-2.0, 2.0), Interval(4.0, 4.0)) == ∅
    @test intersect(Interval(-4.0, -4.0), Interval(-2.0, 2.0)) == ∅
    @test ∩(Interval(-4.0, -4.0), Interval(-2.0, 2.0)) == ∅
    @test intersect(Interval(1.0, 1.0), Interval(-2.0, 2.0)) == Interval(1.0, 1.0)
    @test ∩(Interval(1.0, 1.0), Interval(-2.0, 2.0)) == Interval(1.0, 1.0)
    @test intersect(Interval(4.0, 4.0), Interval(-2.0, 2.0)) == ∅
    @test ∩(Interval(4.0, 4.0), Interval(-2.0, 2.0)) == ∅
end

# Tests A|B, B|A, A and B are scalar-type
@testset "cxsc.scalarmixsetops" begin
    @test hull(Interval(-2.0, -2.0), Interval(-4.0, -4.0)) == Interval(-4.0, -2.0)
    @test ∪(Interval(-2.0, -2.0), Interval(-4.0, -4.0)) == Interval(-4.0, -2.0)
    @test hull(Interval(-2.0, -2.0), Interval(-2.0, -2.0)) == Interval(-2.0, -2.0)
    @test ∪(Interval(-2.0, -2.0), Interval(-2.0, -2.0)) == Interval(-2.0, -2.0)
    @test hull(Interval(-2.0, -2.0), Interval(2.0, 2.0)) == Interval(-2.0, 2.0)
    @test ∪(Interval(-2.0, -2.0), Interval(2.0, 2.0)) == Interval(-2.0, 2.0)
    @test hull(Interval(-4.0, -4.0), Interval(-2.0, -2.0)) == Interval(-4.0, -2.0)
    @test ∪(Interval(-4.0, -4.0), Interval(-2.0, -2.0)) == Interval(-4.0, -2.0)
    @test hull(Interval(-2.0, -2.0), Interval(-2.0, -2.0)) == Interval(-2.0, -2.0)
    @test ∪(Interval(-2.0, -2.0), Interval(-2.0, -2.0)) == Interval(-2.0, -2.0)
    @test hull(Interval(2.0, 2.0), Interval(-2.0, -2.0)) == Interval(-2.0, 2.0)
    @test ∪(Interval(2.0, 2.0), Interval(-2.0, -2.0)) == Interval(-2.0, 2.0)
end

# Tests A<B, A>B, A<=B, A>=B, A==B
@testset "cxsc.intervalsetcompops" begin
    @test ⪽(Interval(-1.0, 2.0), Interval(-1.0, 2.0)) == false
    @test isinterior(Interval(-1.0, 2.0), Interval(-1.0, 2.0)) == false
    @test ⪽(Interval(-2.0, 1.0), Interval(-3.0, 2.0))
    @test isinterior(Interval(-2.0, 1.0), Interval(-3.0, 2.0))
    @test ⪽(Interval(-2.0, 2.0), Interval(-1.0, 1.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(-1.0, 1.0)) == false
    @test ⪽(Interval(-2.0, 2.0), Interval(-1.0, 2.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(-1.0, 2.0)) == false
    @test ⪽(Interval(-2.0, 2.0), Interval(-2.0, 1.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(-2.0, 1.0)) == false
    @test ⪽(Interval(-2.0, 2.0), Interval(-2.0, 3.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(-2.0, 3.0)) == false
    @test ⪽(Interval(-2.0, 2.0), Interval(-3.0, 2.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(-3.0, 2.0)) == false
    @test ⪽(Interval(-1.0, 2.0), Interval(-1.0, 2.0)) == false
    @test isinterior(Interval(-1.0, 2.0), Interval(-1.0, 2.0)) == false
    @test ⪽(Interval(-3.0, 2.0), Interval(-2.0, 1.0)) == false
    @test isinterior(Interval(-3.0, 2.0), Interval(-2.0, 1.0)) == false
    @test ⪽(Interval(-1.0, 1.0), Interval(-2.0, 2.0))
    @test isinterior(Interval(-1.0, 1.0), Interval(-2.0, 2.0))
    @test ⪽(Interval(-1.0, 2.0), Interval(-2.0, 2.0)) == false
    @test isinterior(Interval(-1.0, 2.0), Interval(-2.0, 2.0)) == false
    @test ⪽(Interval(-2.0, 1.0), Interval(-2.0, 2.0)) == false
    @test isinterior(Interval(-2.0, 1.0), Interval(-2.0, 2.0)) == false
    @test ⪽(Interval(-2.0, 3.0), Interval(-2.0, 2.0)) == false
    @test isinterior(Interval(-2.0, 3.0), Interval(-2.0, 2.0)) == false
    @test ⪽(Interval(-3.0, 2.0), Interval(-2.0, 2.0)) == false
    @test isinterior(Interval(-3.0, 2.0), Interval(-2.0, 2.0)) == false
    @test ⊆(Interval(-1.0, 2.0), Interval(-1.0, 2.0))
    @test ⊆(Interval(-2.0, 1.0), Interval(-3.0, 2.0))
    @test ⊆(Interval(-2.0, 2.0), Interval(-1.0, 1.0)) == false
    @test ⊆(Interval(-2.0, 2.0), Interval(-1.0, 2.0)) == false
    @test ⊆(Interval(-2.0, 2.0), Interval(-2.0, 1.0)) == false
    @test ⊆(Interval(-2.0, 2.0), Interval(-2.0, 3.0))
    @test ⊆(Interval(-2.0, 2.0), Interval(-3.0, 2.0))
    @test ⊆(Interval(-3.0, 2.0), Interval(-2.0, 1.0)) == false
    @test ⊆(Interval(-1.0, 1.0), Interval(-2.0, 2.0))
    @test ⊆(Interval(-1.0, 2.0), Interval(-2.0, 2.0))
    @test ⊆(Interval(-2.0, 1.0), Interval(-2.0, 2.0))
    @test ⊆(Interval(-2.0, 3.0), Interval(-2.0, 2.0)) == false
    @test ⊆(Interval(-3.0, 2.0), Interval(-2.0, 2.0)) == false
    @test ==(Interval(-1.0, 2.0), Interval(-1.0, 2.0))
    @test ==(Interval(-2.0, 1.0), Interval(-3.0, 2.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(-1.0, 1.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(-1.0, 2.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(-2.0, 1.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(-2.0, 3.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(-3.0, 2.0)) == false
end

# Tests A<B, A>B, A<=B, A>=B, A==B, B<A, B>A, B<=A, B>=A, B==A, where B is scalar
@testset "cxsc.intervalscalarsetcompops" begin
    @test ⪽(Interval(-1.0, 2.0), Interval(-2.0, -2.0)) == false
    @test isinterior(Interval(-1.0, 2.0), Interval(-2.0, -2.0)) == false
    @test ⪽(Interval(-2.0, 2.0), Interval(-2.0, -2.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(-2.0, -2.0)) == false
    @test ⪽(Interval(-2.0, 2.0), Interval(0.0, 0.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(0.0, 0.0)) == false
    @test ⪽(Interval(-2.0, 2.0), Interval(2.0, 2.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(2.0, 2.0)) == false
    @test ⪽(Interval(-2.0, 2.0), Interval(3.0, 3.0)) == false
    @test isinterior(Interval(-2.0, 2.0), Interval(3.0, 3.0)) == false
    @test ⪽(Interval(-1.0, -1.0), Interval(1.0, 1.0)) == false
    @test isinterior(Interval(-1.0, -1.0), Interval(1.0, 1.0)) == false
    @test ⪽(Interval(-1.0, -1.0), Interval(-1.0, -1.0)) == false
    @test isinterior(Interval(-1.0, -1.0), Interval(-1.0, -1.0)) == false
    @test ⪽(Interval(-2.0, -2.0), Interval(-1.0, 2.0)) == false
    @test isinterior(Interval(-2.0, -2.0), Interval(-1.0, 2.0)) == false
    @test ⪽(Interval(-2.0, -2.0), Interval(-2.0, 2.0)) == false
    @test isinterior(Interval(-2.0, -2.0), Interval(-2.0, 2.0)) == false
    @test ⪽(Interval(0.0, 0.0), Interval(-2.0, 2.0))
    @test isinterior(Interval(0.0, 0.0), Interval(-2.0, 2.0))
    @test ⪽(Interval(2.0, 2.0), Interval(-2.0, 2.0)) == false
    @test isinterior(Interval(2.0, 2.0), Interval(-2.0, 2.0)) == false
    @test ⪽(Interval(3.0, 3.0), Interval(-2.0, 2.0)) == false
    @test isinterior(Interval(3.0, 3.0), Interval(-2.0, 2.0)) == false
    @test ⪽(Interval(1.0, 1.0), Interval(-1.0, -1.0)) == false
    @test isinterior(Interval(1.0, 1.0), Interval(-1.0, -1.0)) == false
    @test ⪽(Interval(-1.0, -1.0), Interval(-1.0, -1.0)) == false
    @test isinterior(Interval(-1.0, -1.0), Interval(-1.0, -1.0)) == false
    @test ⊆(Interval(-1.0, 2.0), Interval(-2.0, -2.0)) == false
    @test ⊆(Interval(-2.0, 2.0), Interval(-2.0, -2.0)) == false
    @test ⊆(Interval(-2.0, 2.0), Interval(0.0, 0.0)) == false
    @test ⊆(Interval(-2.0, 2.0), Interval(2.0, 2.0)) == false
    @test ⊆(Interval(-2.0, 2.0), Interval(3.0, 3.0)) == false
    @test ⊆(Interval(-1.0, -1.0), Interval(1.0, 1.0)) == false
    @test ⊆(Interval(-1.0, -1.0), Interval(-1.0, -1.0))
    @test ⊆(Interval(-2.0, -2.0), Interval(-1.0, 2.0)) == false
    @test ⊆(Interval(-2.0, -2.0), Interval(-2.0, 2.0))
    @test ⊆(Interval(0.0, 0.0), Interval(-2.0, 2.0))
    @test ⊆(Interval(2.0, 2.0), Interval(-2.0, 2.0))
    @test ⊆(Interval(3.0, 3.0), Interval(-2.0, 2.0)) == false
    @test ⊆(Interval(1.0, 1.0), Interval(-1.0, -1.0)) == false
    @test ⊆(Interval(-1.0, -1.0), Interval(-1.0, -1.0))
    @test ==(Interval(-1.0, 2.0), Interval(-2.0, -2.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(-2.0, -2.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(0.0, 0.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(2.0, 2.0)) == false
    @test ==(Interval(-2.0, 2.0), Interval(3.0, 3.0)) == false
    @test ==(Interval(-1.0, -1.0), Interval(1.0, 1.0)) == false
    @test ==(Interval(-1.0, -1.0), Interval(-1.0, -1.0))
end

@testset "cxsc.intervalstdfunc" begin
    @test Interval(11.0, 11.0) ^2 == Interval(121.0, 121.0)
    @test Interval(11.0, 11.0) ^(2//1) == Interval(121.0, 121.0)
    @test Interval(11.0, 11.0) ^(2.0) == Interval(121.0, 121.0)
    @test Interval(0.0, 0.0) ^2 == Interval(0.0, 0.0)
    @test Interval(0.0, 0.0) ^(2//1) == Interval(0.0, 0.0)
    @test Interval(0.0, 0.0) ^(2.0) == Interval(0.0, 0.0)
    @test Interval(-9.0, -9.0) ^2 == Interval(81.0, 81.0)
    @test Interval(-9.0, -9.0) ^(2//1) == Interval(81.0, 81.0)
    @test Interval(-9.0, -9.0) ^(2.0) == Interval(81.0, 81.0)
    @test sqrt(Interval(121.0, 121.0)) == Interval(11.0, 11.0)
    @test (Interval(121.0, 121.0))^(1/2) == Interval(11.0, 11.0)
    @test (Interval(121.0, 121.0))^(0.5) == Interval(11.0, 11.0)
    @test (Interval(121.0, 121.0))^(1//2) == Interval(11.0, 11.0)
    @test sqrt(Interval(0.0, 0.0)) == Interval(0.0, 0.0)
    @test (Interval(0.0, 0.0))^(1/2) == Interval(0.0, 0.0)
    @test (Interval(0.0, 0.0))^(0.5) == Interval(0.0, 0.0)
    @test (Interval(0.0, 0.0))^(1//2) == Interval(0.0, 0.0)
    @test sqrt(Interval(81.0, 81.0)) == Interval(9.0, 9.0)
    @test (Interval(81.0, 81.0))^(1/2) == Interval(9.0, 9.0)
    @test (Interval(81.0, 81.0))^(0.5) == Interval(9.0, 9.0)
    @test (Interval(81.0, 81.0))^(1//2) == Interval(9.0, 9.0)
    @test Interval(2.0, 2.0) ^ Interval(2.0, 2.0) == Interval(4.0, 4.0)
    @test Interval(4.0, 4.0) ^ Interval(5.0, 5.0) == Interval(1024.0, 1024.0)
    # Negativ geht noch nicht
    @test Interval(2.0, 2.0) ^ Interval(3.0, 3.0) == Interval(8.0, 8.0)
end
# FactCheck.exitstatus()


using QuickCheck2
using Base.Test

function is_tiny(x)
    abs(x) < 10.0^(-6)
end

function is_huge(x)
    x > 10.0^(6)
end

function is_increasing(f::Function)
            eval(:(@test_formany -Inf<x::Float64<Inf, -Inf<y::Float64<Inf, x < y --> $f(x) < $f(y)))
end

@test isa( ( @test_exists 0<x::Float64<<1, is_tiny(x) ) , Base.Test.Pass )

## borrowed from julia/test/test.jl, Fail/Error results do not throw errors when tests are performed.
type NoThrowTestSet <: Base.Test.AbstractTestSet
    results::Vector
    NoThrowTestSet(desc) = new([])
end
Base.Test.record(ts::NoThrowTestSet, t::Base.Test.Result) = (push!(ts.results, t); t)
Base.Test.finish(ts::NoThrowTestSet) = ts.results

fails = @testset NoThrowTestSet begin
	#1 Fail
	@test_formany 0<x::Float64<10, x<9 ntests = 1000
	#2 Fail
	@test_exists -Inf<x::Int64<Inf, x>10000
	#3 Fail
	is_increasing(x -> x^2)
	#4 Error: types of variables must be specified
	@test_formany Inf > x > 0, x<y<10, x>0
	#5 Pass
	@test_forall i in 1:20, 1==1
	#6 Pass
	is_increasing(x -> x)
	#7 Pass
	is_increasing(x -> x^3)
end

for i in 1:3
    @test isa(fails[i], Base.Test.Fail)
end

@test isa(fails[4],Base.Test.Error)

for i in 5:7
	@test isa(fails[i],Base.Test.Pass)
end

    #Note is_increasing is a property of the function, so what I really want
    #this to mean is "not @test is_increasing(x->x^2)", i.e.
    #"this test fails."
    #This is slightly different from the @exists idea above, because
    #(a) I'm not trying to help the test find a counterexample, but just
    #declaring that when it does I'm happy, and
    #(b) I'm obscuring where the property is. In other words to express this
    #in terms of an @exists I'd have to write
    #@exists x::Float64, y::Float64, x < y, x^2 > y^2
    #But I don't want to think about that at *all*. I just want to say
    #"x^2 is not increasing" and trust that random examples will figure this
    #out.
    #Maybe think about a syntax that makes it feasible to also specify
    #a generator form with this sort of test, if I have some idea about
    #how to guide it.


using Pkg;
Pkg.activate("./")
Pkg.instantiate()
using TimeseriesSurrogates, BenchmarkTools, Plots, Distributions, DynamicalSystems, Random, StatsPlots;
gr()

# Here, we compute the mean time to generate a single surrogate for each of the methods
# implemented in TimeseriesSurrogates.jl at the time of submission to JOSS.
# We'll use the same test process as in Lancaster et al, and generate a 2000-pt long
# time series with additive noise from a normal distribution with zero mean and
# standard deviation 0.3.
f(t) = sin(2π*(t + 0.5*sin(2π*t/10))/3)
npts = 2000

# Add some noise to the signal.
rng = Random.MersenneTwister(1234)
t = collect(1:npts) .+ rand(rng, npts)
x = f.(t) .+ rand(rng, Normal(0, 0.3), npts)

# Write to file, to import in matlab
using DelimitedFiles
writedlm("./paper/timing/code/data.csv",  x, ',')

# Pre-initialize all different surrogate types
# Shuffle based surrogates
rs = surrogenerator(x, RandomShuffle(), rng)
rp = surrogenerator(x, RandomFourier(true), rng)
aaft = surrogenerator(x, AAFT(), rng)
iaaft = surrogenerator(x, IAAFT(), rng)

# Pseudoperiodic surrogates (PPS)
τ = estimate_delay(x, "ac_min")
d = findmin(delay_fnn(x, τ))[2]
ρ = noiseradius(x, d, τ, 0.025:0.025:0.5)
pps = surrogenerator(x, PseudoPeriodic(d, τ, ρ, true))

# Collect surrogate generators and run once to trigger precompilation
surrogens = [rs, rp, aaft, iaaft, pps];
surrogates = [s() for s in surrogens];

# Define labels for plot.
methods = ["RandomShuffle()", "RandomFourier(true)", "AAFT()", "IAAFT()", "PseudoPeriodic()"];
n_methods = length(methods)

function generate_surrs(surrogenerator, n)
    for i = 1:n
        surrogenerator()
    end
end

"""
    time_n(methods; n = 100)

Compute `n` surrogates using each pre-initialized `Surrogenerator` in `methods` and compute the
mean time to generate a surrogate.
"""
function time_n(methods; n = 100)
    benchmarks = zeros(length(methods))
    for (i, surrogen) in enumerate(methods)
        benchmarks[i] = @belapsed generate_surrs($surrogen, $n)
    end

    return benchmarks ./ n
end
time_n(surrogens, n = 1);

# Compute average time to generate surrogate over 30 realizations of each surrogate type
timings = time_n(surrogens, n = 100);

# [rp, ft, aaft, iaaft, pps]; these were generated in `surrogate.m`
matlab_timings = [2.0335e-04, 6.3073e-05, 1.4029e-04, 0.0048, 0.1482]

# Plot the results
x = repeat(methods, outer = 2)
gp = repeat(["TimeseriesSurrogates.jl", "MATLAB"], inner = 5)
c = repeat(["Black", "Red"], inner = 5)

groupedbar(
    [timings matlab_timings],
    group = gp,
    c = c,
    xrotation = 45,
    ylabel = "Mean runtime (s)",
    yaxis = :log10,
    yticks = [10.0^(i) for i = -6:1],
    xticks = (1:5, ["rp", "ft", "aaft", "iaaft", "pps"]),
    legend = :topleft,
)

savefig("timings.png")


n = 10 # rand(1:10)
@test matrixdepot("hilb", n) == matrixdepot("hilb", n, n)
@test matrixdepot("hilb" , Float16, n) ≈ matrixdepot("hilb", Float32, n)
x = Float64[1:n;]
y = x .- 1
@test matrixdepot("hilb", n) ≈ matrixdepot("cauchy", x, y)
println("'hilb' passed test...")


data = reshape([4.939	2.432;
    7.976	7.237;
    10.976	5.826;
    13.976	3.874;
    16.976	6.697;
    19.976	7.778;
    22.976	6.937;
    26.012	5.736],2,8)

dist = 3.5/8


# ---
# title: 1022. Sum of Root To Leaf Binary Numbers
# id: problem1022
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Tree
# link: <https://leetcode.com/problems/sum-of-root-to-leaf-binary-numbers/description/>
# hidden: true
# ---
#
# You are given the `root` of a binary tree where each node has a value `0` or
# `1`.  Each root-to-leaf path represents a binary number starting with the most
# significant bit.  For example, if the path is `0 -> 1 -> 1 -> 0 -> 1`, then
# this could represent `01101` in binary, which is `13`.
#
# For all leaves in the tree, consider the numbers represented by the path from
# the root to that leaf.
#
# Return _the sum of these numbers_. The answer is **guaranteed** to fit in a
# **32-bits** integer.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2019/04/04/sum-of-root-to-leaf-binary-
# numbers.png)
#
#
#
#     Input: root = [1,0,1,0,1,0,1]
#     Output: 22
#     Explanation: (100) + (101) + (110) + (111) = 4 + 5 + 6 + 7 = 22
#
#
# **Example 2:**
#
#
#
#     Input: root = [0]
#     Output: 0
#
#
# **Example 3:**
#
#
#
#     Input: root = [1]
#     Output: 1
#
#
# **Example 4:**
#
#
#
#     Input: root = [1,1]
#     Output: 3
#
#
#
#
# **Constraints:**
#
#   * The number of nodes in the tree is in the range `[1, 1000]`.
#   * `Node.val` is `0` or `1`.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


"""
    count_triplet_elements(array::Array{Int64,1}, sum::Int64)

Counting the number of three elements in the array, which sum is equal to the reference sum.

# Arguments
- `array::Array{Int64,1}`: Unsorted array
- `array::Array{Int64,1}`: Refernece sum

# Examples
```julia-repl
julia> import ClassicAlgorithmsCollections
julia> arr = [5, 1, 3, 4, 7]
julia> sum = 12
julia> ClassicAlgorithmsCollections.count_triplet_elements(arr, sum)
2

# Notes:
---

This should be implemented recursively instead of using three for-loops because it will
allow using any conditions.

"""
function count_triplet_elements(array::Array{Int64,1}, sum::Int64)

    # Initialize result
    ans = 0
    n = length(array)

    for i in 1:n-2
        for j in i+1:n-1
            for k in j+1:n
                if (array[i] + array[j] + array[k] == sum)
                    ans += 1
                end
            end
        end
    end
    return ans
end


# ==================================== step125(x) ============================================================

"""
    step125(x)

Step used for deviding the number x in steps according to 1-2-5 scheme
#### Examples:
```
step125.([5,10,21.3,50,100.1])
5-element Vector{Int64}:
  1
  2
  5
 10
 20
```
"""
function step125(x::Real)

    m = log10_mantissa(x)
    p = log10_characteristic_power(x)
    v = 10^m
    d = v > 7.9 ? 2.0 : v > 3.9 ? 1.0 : v > 1.49 ? 0.5 : 0.2

    return max(1,round(Int, d *= 10^p))

end

# ==================================== select125(x) ===============================================================

"""
    select125(x)

Select elements of the collection x by index according to 1-2-5 scheme
#### Examples:
```@docs
x = [1,2,4,6,8,10,13,16,18,20,40,60,80,100]
select125(x)
 [2, 6, 10, 16, 20, 60, 100]

x = string.(x)
select125(x)
 ["2", "6", "10", "16", "20", "60", "100"]

x = 1:100
select125(x)
 [20, 40, 60, 80, 100]
```
"""
select125(x) = (n = length(x); return [x[i] for i=step125(n):step125(n):n])

# ==================================== edges(x) ===============================================================

"""
    edges(px [, Δx[, x0]])

Heatmap range transformation from pixel coordinates to physical coordinates,
with pixelsize Δx and offset x0, both in physical units.
#### Examples:
```@docs
px = 1:5
Δx = 2.5
x0 = 2.5
edges(px)
 [0.5, 1.5, 2.5, 3.5, 4.5]

edges(px, Δx)
 [1.25, 3.75, 6.25, 8.75, 11.25]

edges(px, Δx, x0)
 [-1.25, 1.25, 3.75, 6.25, 8.75]
```
"""
edges(px, Δx=1.0, x0=0.0) = collect(px .* Δx) .-(x0 + 0.5Δx)

# =================================== steps(x) =============================================================

"""
    steps(x)

Heatmap range transformation for steplength specification vector x
#### Examples:
```@docs
x = [4,2,6]
steps(x)
 [0, 4, 6, 12]
```
"""
function steps(x::Vector{T} where T<:Real)

    sum(x .< 0) == 0 || error("Error: $x - nagative step length not allowed")

    return (s = append!(eltype(x)[0],x); [Base.sum(s[1:i]) for i ∈ Base.eachindex(s)])

end

# =================================== stepcenters(x) =============================================================

"""
    stepcenters(x)

Stepcenter positions for steplength specification vector x
#### Examples:
```@docs
x = [4,2,6]
stepcenters(x)
 [2.0, 5.0, 9.0]
```
"""
function stepcenters(x::Vector{T} where T<:Real)

    s = append!(eltype(x)[0],x)

    return [Base.sum(s[1:i]) + 0.5x[i] for i ∈ Base.eachindex(x)]

end

# =================================== stepedges(x) =============================================================

"""
    stepedges(x)

Stepedges for steplength specification vector x
#### Examples:
```@docs
x = [4,2,6]
stepedges(x)
 [0, 4, 6, 12]
```
"""
function stepedges(x::Vector{T} where T<:Real)

    return steps(x)

end


using LinearAlgebra
using Optim
using GFX

import StatsFuns: @irrational
@irrational log2π  1.8378770664093454836 log(big(2.)*π)

function KLDivergence(μ0, σ0, μ1, σ1)
    "KL of 2 univariate Normals"
#     return log(σ_q / σ_p) + (σ_p + (μ_p - μ_q)^2)/ (2*σ_q) - 0.5
    return 0.5*( (σ0/σ1)^2 + (μ0 - μ1)^2/σ1^2 +1 + 2*log(σ1/σ0))
end

function minEFE(current_state,
                goal_state,
                model_params;
                Δt=1.0,
                num_iters=100,
                plan_horizon=2)

    # Objective function
    G(π) = EFE(π, current_state, goal_state, model_params, Δt=Δt, time_horizon=plan_horizon)

    # Minimize
    results = optimize(G, rand(plan_horizon,), Optim.Options(iterations=num_iters), LBFGS(); autodiff=:forward)

    return Optim.minimizer(results)
end

function EFE(action, state_k, goal_state, params; Δt = 1., time_horizon=1)
    "Expected Free Energy"

    # Unpack goal state
    μ_star = goal_state[1]
    Σ_star = goal_state[2]

    # Unpack model parameters
    θ, η, τ, ξ = params

    # Process noise
    Σ_z = inv(τ)*[Δt^3/3 Δt^2/2; Δt^2/2 Δt]

    # Utility matrices
    S = [1. Δt; 0. 1.]
    s = [0., Δt]

    # Model matrices
    A = S + s*θ'
    B = s*η
    C = [1., 0.]'

    # Unpack prior state
    μ_tmin1 = mn(state_k)
    Σ_tmin1 = cv(state_k)

    cumEFE = 0
    for t in 1:time_horizon

        # State transition
        μ_t = A*μ_tmin1 + B*action[t]
        Σ_t = A*Σ_tmin1*A' + Σ_z

        # Predicted observation
        y_t = C*μ_t

        # Block covariance matrix of joint
        Σ_11 = Σ_t
        Σ_21 = C*Σ_t
        Σ_12 = Σ_t*C'
        Σ_22 = C*Σ_t*C' .+ inv(ξ)

        # Calculate conditional entropy
        Σ_cond = Σ_22 - Σ_21*inv(Σ_11)*Σ_12
        ambiguity = 0.5(log2π + log(Σ_cond[1]) - 1)

        # Risk as KL between marginal and goal prior
        risk = 0.5*(log(det(Σ_star)/det(Σ_22)) + 1 + (y_t - μ_star)'*inv(Σ_star)*(y_t - μ_star) + tr(inv(Σ_star)*Σ_22))

        # Add to cumulative EFE
        cumEFE += risk + ambiguity

        # Update previous state
        μ_tmin1 = μ_t
        Σ_tmin1 = Σ_t

    end
    return cumEFE
end;


nt = 1000
seed = 99
m = ACQR_simula(nt,seed)
y = m["y"]

############################### starting point
wi = collect(range(0.1, length=2, stop=3))
Ai,Ci,Qi,Ri,Si,m1i,P1i = ACQR_em_starting(y,wi,1,true)

#######################
max_iter=10
tol=1e-6
txo=false
print("Testing ACQR_em: ")
A,C,Q,R,m1,P1,loglik,aic = ACQR_em(y,Ai,Ci,Qi,Ri,m1i,P1i,max_iter,tol,txo)
if sum(A.^2) > 1e-6
    println("OK")
end


using Test
using CubicSplines
using Random
using PyPlot # Plot the curve

@testset "cubic_splines.jl" begin

    # Create data that is symmetric about zero
    xdata = -2.0:1.0:2.0
    ydata = mod.(xdata, 2)
    @assert reverse(xdata) == -xdata
    @assert reverse(ydata) == +ydata

    # Test spline is symmetric about zero
    xs = -2.0:0.5:2.0
    ys = CubicSpline(xdata, ydata)[xs]
    @assert reverse(xs) == -xs
    @test reverse(ys) == +ys

    # Interpolate a sine function with white noise
    xdata = range(0,stop=4π,length=20) .+ 0.5*rand(MersenneTwister(1234),20)
    ydata = sin.(xdata)

    # Create the spline
    spline = CubicSpline(xdata, ydata)
    @test spline[π] ≈ 0.00652422 rtol = 1e-5
    @test spline[0.35] ≈ 0.346190 rtol = 1e-5

    # Non-allocating vector filling
    x = [1.0, 2.0]
    y = zeros(2)
    spline(y,x)
    for i in 1:length(y)
        @test y[i] == spline(x[i])
    end

    # Plot the spline
    # xs = range(xdata[1], stop=xdata[end], length=1000)
    # ys = spline[xs]
    # plt.plot(xdata, ydata, "o")
    # plt.plot(xs, ys)
    # plt.xlabel("x")
    # plt.ylabel("y")
    # plt.close()

    # Extrapolate the spline with a linear curve to the left
    spline = CubicSpline(xdata, ydata; extrapl=[1.0]) # Linear extrapolation with an incline of 1.0

    # Data from outside the interpolation range
    left_value = -1.0
    right_value = 14.0
    @test spline[left_value] ≈ -1.004278 rtol = 1e-5 # Extrapolate linearly to the left
    @test_throws String spline[right_value] # No extrapolation (throw an error)

    # Plot the spline
    # xs = range(left_value, stop=xdata[end], length=1000)
    # ys = spline[xs]
    # plt.plot(xdata, ydata, "o")
    # plt.plot(xs, ys)
    # plt.xlabel("x")
    # plt.ylabel("y")
    # plt.title("With linear extrapolation to the left")
    # plt.close()

    # Extrapolate the spline with a quadratic curve to the left and a cubic curve to the right
    spline = CubicSpline(xdata, ydata; extrapl=[0.0; 0.5], extrapr=[1.0; 0.0; 0.5])
    left_value = -1.0
    right_value = 14.0
    @test spline[left_value] ≈ 1.13020 rtol = 1e-5 # Extrapolate linearly to the left
    @test spline[right_value] ≈ 1.93991 rtol = 1e-5 # Extrapolate linearly to the left

    # Plot the spline
    # xs = range(left_value, stop=right_value, length=2000)
    # ys = spline[xs]
    # plt.plot(xdata, ydata, "o")
    # plt.plot(xs, ys)
    # plt.xlabel("x")
    # plt.ylabel("y")
    # plt.title("With extrapolation on both sides")
    # plt.close()

    # Gradient calculation
    xdata = range(0,stop=4pi,length=20)
    ydata = sin.(xdata)
    spline = CubicSpline(xdata, ydata; extrapl=[2.0; 1.0], extrapr=[1.0; 0.0; 0.5])

    # Plot the spline
    # xs = range(left_value, stop=right_value, length=2000)
    # ys = spline[xs]
    # plt.plot(xdata, ydata, "o")
    # plt.plot(xs, ys)
    # plt.xlabel("x")
    # plt.ylabel("y")
    # plt.title("With extrapolation on both sides")
    # plt.close()

    # Calculate the spline gradient at different points and evaluate it
    @test gradient(spline, -2.0) ≈ -2.0
    @test gradient(spline, -2.0, 2) ≈ 2.0
    @test gradient(spline, -2.0, 3) ≈ 0.0
    @test gradient(spline, pi/2, 1) ≈ 0.01851987 rtol = 1e-5
end


#=
Response surfaces implementantion, following:
"A Taxonomy of Global Optimization Methods Based on Response Surfaces"
by DONALD R. JONES
=#
mutable struct RadialBasis{F,X,Y,B,C} <: AbstractSurrogate
    phi::F
    dim_poly::Int
    x::X
    y::Y
    bounds::B
    coeff::C
end

"""
Calculates current estimate of array value 'val' with respect to RadialBasis object.
"""
function (rad::RadialBasis)(val)
    n = length(rad.x)
    d = length(rad.x[1])
    q = rad.dim_poly
    lb, ub = rad.bounds
    sum_half_diameter = sum((ub[k]-lb[k])/2 for k = 1:d)
    mean_half_diameter = sum_half_diameter/d
    central_point = _center_bounds(first(rad.x), lb, ub)
    approx = sum(rad.coeff[i]*rad.phi(val .- rad.x[i]) for i = 1:n)
    for k = 1:q
        approx += rad.coeff[n+k]*centralized_monomial(val, k-1, mean_half_diameter, central_point)
    end
    return approx
end

function _scaled_chebyshev(x, k, lb, ub)
    return cos(k*acos(-1 + 2*(x-lb)/(ub-lb)))
end

_center_bounds(x::Tuple, lb, ub) = ntuple(i -> (ub[i] - lb[i])/2, length(x))
_center_bounds(x, lb, ub) = (ub .- lb) ./ 2

"""
Calculates current estimate of value 'val' with respect to RadialBasis object.
"""
function (rad::RadialBasis)(val::Number)
    approx = zero(eltype(rad.x[1]))
    n = length(rad.x)
    q = rad.dim_poly
    for i = 1:n
        approx += rad.coeff[i]*rad.phi(val .- rad.x[i])
    end
    for k = 1:q
        approx += rad.coeff[n+k] * _scaled_chebyshev(val, k-1, rad.bounds[1], rad.bounds[2])
    end
    return approx
end

"""
    RadialBasis(x,y,a::Number,b::Number,phi::Function,q::Int)

Constructor for RadialBasis surrogate
- (x,y): sampled points
- '(a,b)': interval of interest
-'phi': radial basis of choice
-'q': number of polynomial elements
"""
function RadialBasis(x, y, lb::Number, ub::Number, phi::Function, q::Int)
    coeff = _calc_coeffs(x, y, lb, ub, phi, q)
    return RadialBasis(phi, q, x, y, (lb, ub), coeff)
end

function _calc_coeffs(x, y, lb, ub, phi, q)
    n = length(x)
    m = n + q
    D = zeros(eltype(x[1]), m, m)
    d = zeros(eltype(x[1]), m)

    @inbounds for i = 1:n
        d[i] = y[i]
        for j = 1:n
            D[i,j] = phi(x[i] .- x[j])
        end
        if i <= n
            for k = 1:q
                D[i,n+k] = _scaled_chebyshev(x[i], k-1, lb, ub)
            end
        end
    end
    D_sym = Symmetric(D, :U)
    coeff = D_sym\d
    return coeff
end

"""
RadialBasis(x,y,bounds,phi::Function,q::Int)

Constructor for RadialBasis surrogate

- (x,y): sampled points
- bounds: region of interest of the form [[a,b],[c,d],...,[w,z]]
- phi: radial basis of choice
- q: number of polynomial elements
"""
function RadialBasis(x, y, bounds, phi::Function, q::Int)
    coeff = _calc_coeffs(x, y, bounds, phi, q)
    return RadialBasis(phi, q, x, y, bounds, coeff)
end

function _calc_coeffs(x, y, bounds, phi, q)
    n = length(x)
    d = length(x[1])
    lb, ub = bounds
    central_point = _center_bounds(first(x), lb, ub)
    sum_half_diameter = sum((ub[k]-lb[k])/2 for k = 1:d)
    mean_half_diameter = sum_half_diameter / d
    m = n+q
    D = zeros(eltype(x[1]), m, m)
    d = zeros(eltype(x[1]), m)

    @inbounds for i = 1:n
        d[i] = y[i]
        for j = 1:n
            D[i,j] = phi(x[i] .- x[j])
        end
        if i < n + 1
            for k = 1:q
                D[i,n+k] = centralized_monomial(x[i], k-1, mean_half_diameter, central_point)
            end
        end
    end
    D_sym = Symmetric(D, :U)
    return coeff = D_sym\d
end

"""
    centralized_monomial(vect,alpha,mean_half_diameter,central_point)

Returns the value at point vect[] of the alpha degree monomial centralized.

#Arguments:
-'vect': vector of points i.e [x,y,...,w]
-'alpha': degree
-'mean_half_diameter': half diameter of the domain
-'central_point': central point in the domain
"""
function centralized_monomial(vect,alpha,mean_half_diameter,central_point)
    if iszero(alpha) return one(eltype(vect)) end
    centralized_product = prod(vect .- central_point)
    return (centralized_product / mean_half_diameter)^alpha
end

"""
    add_point!(rad::RadialBasis,new_x,new_y)

Add new samples x and y and updates the coefficients. Return the new object radial.
"""
function add_point!(rad::RadialBasis,new_x,new_y)
    if (length(new_x) == 1 && length(new_x[1]) == 1) || ( length(new_x) > 1 && length(new_x[1]) == 1 && length(rad.bounds[1])>1)
        push!(rad.x,new_x)
        push!(rad.y,new_y)
        if length(rad.bounds[1]) == 1
            rad.coeff = _calc_coeffs(rad.x,rad.y,rad.bounds[1],rad.bounds[2],rad.phi,rad.dim_poly)
        else
            rad.coeff = _calc_coeffs(rad.x,rad.y,rad.bounds,rad.phi,rad.dim_poly)
        end
    else
        append!(rad.x,new_x)
        append!(rad.y,new_y)
        if length(rad.bounds[1]) == 1
            rad.coeff = _calc_coeffs(rad.x,rad.y,rad.bounds[1],rad.bounds[2],rad.phi,rad.dim_poly)
        else
            rad.coeff = _calc_coeffs(rad.x,rad.y,rad.bounds,rad.phi,rad.dim_poly)
        end
    end
    nothing
end


include("Graph.jl")
include("Tools.jl")

using SparseArrays
using Laplacians

function Exact(G, s) # Returns ci, d, p, idc
	T = time()
	L = getSparseL(G)
	W = getW(G)
	avgs = sum(s)/G.n
	s2 = zeros(G.n)
	for i = 1 : G.n
		s2[i] = s[i] - avgs
	end
	z = W * s
	z2 = W * s2
	# calculate C_I(G)
	lz = L * z
	ci = lz' * lz
	# calculate D(G)
	d = z2' * L * z2
	# calculate P(G)
	p = z2' * z2
	# calculate C(G)
	c = z' * z
	# calculate I_dc(G)
	idc = d + c
	# END CALCULATION
	T = time() - T
	return T, ci, d, p, idc
end

function Approx(G, s; eps = 1e-6) # Returns aci, ad, ap, aidc
	T = time()
	IpL = getSparseIpL(G)
	sL = getSparseL(G)
	f = approxchol_sddm(IpL, tol=0.1*eps)
	avgs = sum(s)/G.n
	s2 = zeros(G.n)
	for i = 1 : G.n
		s2[i] = s[i] - avgs
	end
	z = f(s)
	z2 = f(s2)

	# calculate aC_I(G)
	alz = sL * z
	aci = alz' * alz
	# calculate aD(G)
	ad = z2' * sL * z2
	# calculate aP(G)
	ap = z2' * z2
	# calculate aC(G)
	ac = z' * z
	# calculate aI_dc(G)
	aidc = ad + ac
	# END CALCULATION
	T = time() - T
	return T, aci, ad, ap, aidc
end

function doExp(G, s, lg)
	T, ci, d, p, idc = Exact(G, s)
	T2, aci, ad, ap, aidc = Approx(G, s)
	println(lg, "Exact  Time : ", T)
	println(lg, "Approx Time : ", T2)
	println(lg, "ERROR of ci : ", abs(aci-ci)/ci)
	println(lg, "ERROR of d : ", abs(ad-d)/d)
	println(lg, "ERROR of p  : ", abs(ap-p)/p)
	println(lg, "ERROR of idc : ", abs(aidc-idc)/idc)
	println(lg)
end

function doLarge(G, s, lg)
	T2, aci, ad, ap, aidc = Approx(G, s)
	println(lg, "Approx Time : ", T2)
	println(lg)
end


function PDE_solver(X_domain, X_boundary, max_iter, step_size, sol, L)
    N_domain = size(X_domain)[1]
    N_boundary = size(X_boundary)[1]
    rhs_f = zeros((N_domain,1))
    bdy_g = zeros((N_boundary,1))

    for iter_i = 1:N_domain
        rhs_f[iter_i] = f(X_domain[iter_i,1],X_domain[iter_i,2])
    end
    for iter_i = 1:N_boundary
        bdy_g[iter_i] = g(X_boundary[iter_i,1],X_boundary[iter_i,2])
    end

    J_array = zeros((max_iter+1,1))
    for iter_step = 1:max_iter
        temp_mtx = hcat(alpha*m*Diagonal((sol[:]).^(m-1)), Matrix(1.0I,N_domain,N_domain), zeros((N_domain,N_boundary)));
        temp_vec = [alpha*sol.^m-rhs_f; sol; bdy_g]
        J = L\temp_vec
        J = J'*J
        J_array[iter_step,1]=J[1]
        temp = L\(transpose(Matrix{Float64}(temp_mtx)))
        # J = 1/2*temp_vec'*Theta^{-1}*temp_vec
        grad_now = temp'*(L\temp_vec) #grad(temp_vec,sol)*Theta^{-1}*temp_vec
        GN_hessian_now = temp'*temp #grad(temp_vec,sol)*Theta^{-1}*grad(temp_vec,sol)
        sol = sol - step_size*GN_hessian_now\grad_now;
    end
    temp_vec=[alpha*sol.^m-rhs_f; sol; bdy_g]
    J = L\temp_vec
    J = J'*J
    J_array[max_iter+1,1]=J[1]
    return sol, J_array
end


module DichotomicLocalPolytope

# """
# Module: DichotomicLocalPolytope
#
# Hard-coded computation of local polytope vertices for the CHSH scenario with
# and without auxiliary communication.
# """

using QBase: QMath
# """
# bipartite_no_signaling_strategies(;subspace="p16"):
#   Returns the set of strategies for dichotomic, bi-partite bell test. The
#   joint strategies for Alice and Bob are represented as the kronecker product
#   of their individual strategies α(x)⊗β(y).
#
# Input: subspace, string, the behavior subspace for which results are returned
#        defaults to "p16", the most general and high dimensional bipartite space.
#
# Output: An array of joint strategies which map input x⊗y → a⊗b
# """
function bipartite_no_signaling_strategies(;subspace="p16")
    # Assumption: Alice and Bob have dichotomic setups
    s = dichotomic_strategies()
    n = size(s)[1]

    no_signaling_strategies = [] # initialize empty array

    # Alice and Bob each choose a strategy from the dichotomic strategies.
    for α in s
        for β in s
            if subspace == "p16"
                push!( no_signaling_strategies, kron(α, β) )
            elseif subspace == "p12"
                push!( no_signaling_strategies, kron(α, β)[1:3,:] )
            end
        end
    end

    no_signaling_strategies
end

# """
# deterministic_no_signaling_behaviors(;subspace="p16"):
#   Computes the deterministic behaviors (vertices) of the local polytope for
#   a bi-partitie, dichotomic setup for which no-signaling is applied.
#
# Input: subspace, string, the behavior subspace for which results are returned
#        defaults to "p16", the most general and high dimensional bipartite space.
#
# Output: Column Vectors, which represent deterministic behaviors in the 16-dim
#   basis described by { P(ab|xy) } ∀ a,b,x,y.
# """
function deterministic_no_signaling_behaviors(;subspace="p16")
    # Assumptions: 2-parties with dichotomic setups
    if subspace == "p16"
        s = bipartite_no_signaling_strategies()
    elseif subspace == "p12"
        s = bipartite_no_signaling_strategies(subspace="p12")
    end

    n = size(s)[1]

    behaviors = []
    # looping through distinct strategies
    for i in 1:n
        # initializing an empty probabilistic behavior
        if subspace == "p16"
            p = zeros(Int64,16)
        elseif subspace == "p12"
            p = zeros(Int64,12)
        end

        # columns of identity matrix represent xy ∈ {00, 01, 10, 11}
        identity = QMath.id(size(s[i])[2])

        # Strategy columns represent outputs (a⊗b) for given input (x⊗y). We
        # compute the 16-dim behavior by (a⊗b)⊗(x⊗y) → P(ab|xy)
        for j in 1:size(s[i])[2]
            p = p + kron(s[i][:,j],identity[:,j])
        end

        push!(behaviors,p)
    end

    behaviors
end

# """
# Description:
#   Computes the vertices of the local polytope which bounds the system where
#   Alice sends 1-bit of communication to Bob.
#
# Input:
#   subspace: String (optional), the behavior subspace in which we represent the
#             deterministic behaviors. default, 16-dim
#
# Output:
#   an array of deterministic vertices represented in the specified subspace
# """
function deterministic_fixed_direction_signaling_behaviors(;subspace="p16")
    # Assumption: signaling goes from Alice to Bob

    ds = dichotomic_strategies()

    ts = two_input_strategies()

    binary_inputs = [
        [1;0], # zero
        [0;1]  # one
    ]

    deterministic_behaviors = []
    for α in ds
        for β in ts

            if subspace == "p16"
                behavior = zeros(Int64,16)
            elseif subspace == "p12"
                behavior = zeros(Int64,12)
            elseif subspace == "p10"
                behavior = zeros(Int64,10)
            end

            for x in binary_inputs

                for y in binary_inputs

                    xy = kron(x,y)
                    input = kron(xy,x)

                    if subspace == "p16"
                        ab = kron(α,β)*input
                        behavior += kron(ab,xy)
                    elseif subspace == "p12"
                        ab = kron(α,β)[1:3,:]*input
                        behavior += kron(ab,xy)
                    elseif subspace == "p10"
                        ab = kron(α,β)[[1,3],:]*input

                        behavior += cat(zeros(Int64,2),kron(ab,xy),dims=1)
                    end
                end
                if subspace == "p10"
                    a = α[1,:]'*x # α gets cast as a 1-D column vector
                    behavior += cat(kron(a,x),zeros(Int64,8),dims=1)
                end

            end
            push!(deterministic_behaviors, behavior)
        end
    end

    deterministic_behaviors
end

# """
# deterministic_one_way_signaling_behaviors(;subspace="p16"):
#   Computes the deterministic behaviors (vertices) of the local polytope by
#   executing a finite set of deterministic strategies for all inputs. In this
#   computation it is assumed that 1-bit of information is passed either from
#   Alice to Bob or from Bob to Alice. The case is also considered where there
#   is no-signaling between Alice and Bob.
#
# Input: subspace, string, the behavior subspace for which results are returned
#        defaults to "p16", the most general and high dimensional bipartite space.
#
# Output: An array of p12 behaviors
# """
function deterministic_one_way_signaling_behaviors(;subspace="p16")
    ds = dichotomic_strategies()

    # filter out strategies that do not depend on second input
    ts = filter(f -> (
            (f*[1;0;0;0] != f*[0;1;0;0]) | (f*[0;0;1;0] != f*[0;0;0;1])
        ),
        two_input_strategies()
    )

    binary_inputs = [[1;0],[0;1]]
    deterministic_behaviors = deterministic_no_signaling_behaviors(subspace=subspace)
    # case for when Alice receives 1-bit from Bob
    for α in ts
        for β in ds
            if subspace == "p16"
                strategy_behavior = zeros(Int64,16)
            elseif subspace == "p12"
                strategy_behavior = zeros(Int64,12)
            end

            for x in binary_inputs
                for y in binary_inputs
                    xy = kron(x,y)
                    input = kron(xy,y)

                    if subspace == "p16"
                        ab = kron(α,β)*input
                    elseif subspace == "p12"
                        ab = kron(α,β)[1:3,:]*input
                    end

                    strategy_behavior += kron(ab,xy)
                end
            end
            push!(deterministic_behaviors,strategy_behavior)
        end
    end

    # case for when Bob receives 1-bit from Alice
    for α in ds
        for β in ts
            if subspace == "p16"
                strategy_behavior = zeros(Int64,16)
            elseif subspace == "p12"
                strategy_behavior = zeros(Int64,12)
            end

            for x in binary_inputs
                for y in binary_inputs
                    xy = kron(x,y)
                    input = kron(xy,x)

                    if subspace == "p16"
                        ab = kron(α,β)*input
                    elseif subspace == "p12"
                        ab = kron(α,β)[1:3,:]*input
                    end

                    strategy_behavior += kron(ab,xy)
                end
            end
            push!(deterministic_behaviors,strategy_behavior)
        end
    end

    deterministic_behaviors
end

# """
# dichotomic_strategies():
#   Returns the set of deterministic strategies that map a binary
#   input to a binary output.
#
# Input: None.
#
# Output: An array of 2x2 matrices that represent all possible strategies.
# """
function dichotomic_strategies()
    I2 = [1 0; 0 1]
    PC0 = [1 1; 0 0]
    PC1 = [0 0; 1 1]
    P2 = [0 1; 1 0]

    [I2, PC0, PC1, P2]
end

# """
# two_input_strategies():
#   Returns the set of deterministic strategies that map two binary inputs to
#   a single binary output.
#
# Input: None.
#
# Output: An array of 2x4 matrices that represent all possible strategies
# """
function two_input_strategies()
    s = dichotomic_strategies()
    n = size(s)[1]

    two_input_strategies = []
    for i in 1:n
        for j in 1:n
            push!(two_input_strategies, cat(s[i],s[j],dims=2))
        end
    end

     two_input_strategies
end

end


#Kozachenko-Leonenko estimator
import NearestNeighbors
import Distances
const NN = NearestNeighbors

function nn_entropy{T<:Real}(X::Array{T,2})
	dims = size(X,1)
	N = size(X,2)
    S = spherical_volume(dims)
	t = NN.NaiveNeighborTree(X,NN.Euclidean())
	ee = 0.0
	for i=1:N
		didx,dd = NN.nearest(t,X[:,i],1,i)
		ee += log2(dd[end])
	end
    H = (dims/N)*ee + log2(S*(N-1)/dims) + eulergamma/log(2)
end

function ksg_information{T<:Real}(X1::Array{T,2}, X2::Array{T,2};k::Integer=1)

	dims = size(X1,1)
	N1 = size(X1,2)
	N2 = size(X2,2)
	N = N1 + N2
	#contruct the full matrix
	X = cat(2,X1,X2)
    S = spherical_volume(dims)

	#compute the joint entropy as well as the conditional entropy using the Kozachenko-Leonenko estimator
	#get the distances
	t = NN.NaiveNeighborTree(X,NN.Euclidean())
	eex = 0.0
	eey = 0.0
	ee = 0.0
	for i=1:N
		didx,dd = NN.nearest(t,X[:,i],k,i)
		ee += log2(dd[end])
		#get the number of points within this distance for x and y separately
		qidx,bdd = NN.inball(t,X[:,i],dd[end],1)
		if i <= N1
			n = sum(qidx .<= N1) #x
			eex += digamma(n+1)
		else
			n = sum(qidx .> N1) #y
			eey += digamma(n+1)
		end
	end
	ee = digamma(k) - (eex/N1 + eey/N2) + digamma(N) #Eq. 8
    H = (dims/N)*ee + log2(S*(N-1)/dims) + eulergamma/log(2)
	return H
end

function information(::Type{NSBEntropy},X::Array{Int64,2}, S::Array{Int64,1})
	@assert size(X,ndims(X)) == length(S)
	K = (maximum(X)+1)^size(X,1)
	labels = sort(unique(S))
	Hs = zeros(length(labels))
	y = hash(X)
	n = counts(y)
	H = nsb_entropy(n,K)
	for (i,l) in enumerate(labels)
		n = counts(hash(y[S.==l]))
		Hs[i] = nsb_entropy(n,K)
	end
	return H,Hs
end

function GroupedTemporalEntropy(N::Array{Int64,2}, bins::Array{Float64,1}, trial_labels::Array{Int64,1},word_size::Integer)
	ulabels = sort(unique(trial_labels))
	nlabels = length(ulabels)
	H = NaN*zeros(size(N,1)-word_size)
	dH = NaN*zeros(size(N,1)-word_size)
	Hc = NaN*zeros(size(N,1)-word_size,nlabels)
	dHc = NaN*zeros(size(N,1)-word_size,nlabels)
	trials_per_label = zeros(Int64,nlabels)
	for (k,l) in enumerate(ulabels)
		trials_per_label[k] = sum(trial_labels.==l)
	end
	for i=1:size(N,1)-word_size
		y = Entropy.hash(N[i:i+word_size-1,:])
		n = Entropy.counts(y)
		try
			H[i],dH[i] = Entropy.nsb_entropy(n,maximum(y))
		catch ee
			println("Problem computing entropy for bin $i")
		end

		H[i] /= log(2)
		dH[i] /= log(2)
		for (k,l) in enumerate(ulabels)
			n = Entropy.counts(y[trial_labels.==l])
			try
				Hc[i,k],dHc[i,k] = Entropy.nsb_entropy(n,maximum(y))
				Hc[i,k] /= log(2)
				dHc[i,k] /= log(2)
			catch ee
				println("Problem computing entropy for bin $i location $l")
			end
		end
	end
	return GroupedTemporalEntropy(Hc,dHc,H,dH,bins,word_size, ulabels,trials_per_label)
end

function GroupedCountEntropy(N::Array{Int64,2}, bins::Array{Float64,1}, trial_labels::Array{Int64,1},word_size::Integer)
	ntrials = size(N,2)
	ulabels = sort(unique(trial_labels))
	nlabels = length(ulabels)
	H = NaN*zeros(size(N,1)-word_size)
	dH = NaN*zeros(size(N,1)-word_size)
	Hc = NaN*zeros(size(N,1)-word_size,nlabels)
	dHc = NaN*zeros(size(N,1)-word_size,nlabels)
	trials_per_label = zeros(Int64,nlabels)
	for (k,l) in enumerate(ulabels)
		trials_per_label[k] = sum(trial_labels.==l)
	end
	y = zeros(Int64,ntrials)
	for i=1:size(N,1)-word_size
		fill!(y,0)
		for j=1:ntrials
			for k=1:word_size
				y[j] += N[i+k-1,j]
			end
		end
		n = Entropy.counts(y)
		try
			H[i],dH[i] = Entropy.nsb_entropy(n,maximum(y))
		catch ee
			println("Problem computing entropy for bin $i")
		end
		H[i] /= log(2)
		dH[i] /= log(2)
		for (k,l) in enumerate(ulabels)
			n = Entropy.counts(y[trial_labels.==l])
			try
				Hc[i,k],dHc[i,k] = Entropy.nsb_entropy(n,maximum(y))
				Hc[i,k] /= log(2)
				dHc[i,k] /= log(2)
			catch ee
				println("Problem computing entropy for bin $i location $l")
			end
		end
	end
	return GroupedCountEntropy(Hc,dHc,H,dH,bins,word_size, ulabels,trials_per_label)
end

function GroupedPopulationEntropy(N::Array{Int64,3}, bins::Array{Float64,1}, trial_labels::Array{Int64,1})
	nbins,ntrials,ncells = size(N)
	ulabels = sort(unique(trial_labels))
	nlabels = length(ulabels)
	H = NaN*zeros(nbins)
	dH = NaN*zeros(H)
	Hc = NaN*zeros(nbins,nlabels)
	dHc = NaN*zeros(Hc)
	trials_per_label = zeros(Int64,nlabels)
	for (k,l) in enumerate(ulabels)
		trials_per_label[k] = sum(trial_labels.==l)
	end
	for i=1:size(N,1)
		y = Entropy.hash(squeeze(N[i,:,:],1)')
		n = Entropy.counts(y)
		try
			H[i],dH[i] = Entropy.nsb_entropy(n,maximum(y))
		catch ee
			println("Problem computing entropy for bin $i")
		end

		H[i] /= log(2)
		dH[i] /= log(2)
		for (k,l) in enumerate(ulabels)
			n = Entropy.counts(y[trial_labels.==l])
			try
				Hc[i,k],dHc[i,k] = Entropy.nsb_entropy(n,maximum(y))
				Hc[i,k] /= log(2)
				dHc[i,k] /= log(2)
			catch ee
				println("Problem computing entropy for bin $i location $l")
			end
		end
	end
	I,dI = compute_information(Hc,dHc, H, dH, trials_per_label)
	return GroupedPopulationEntropy(Hc,dHc,H,dH,I,dI,bins,ncells, ulabels,trials_per_label)
end

function compute_information(Hc::Array{Float64,2}, dHc::Array{Float64,2},H::Array{Float64,1},dH::Array{Float64,1}, trials_per_group::Array{Int64,1})
	ps = trials_per_group/sum(trials_per_group)
	_hc = zeros(size(Hc,1))
	_dhc = zeros(size(Hc,1))
	for i=1:size(Hc,2)
		for j=1:size(Hc,1)
			_hc[j] += ps[i]*Hc[j,i]
			_dhc[j] += ps[i]*ps[i]*dHc[j,i]*dHc[j,i] #variance
		end
	end
	I = H - _hc
	dI = sqrt(dH.*dH + _dhc)
	return I, dI
end


using BenchmarkTools, GeometryTypes
import SearchBall

@benchmark SearchBall.rotate2d_left90([1, 1]) # realistic example

a = rand(1_000_000)
b = rand(1_000_000)
@benchmark SearchBall.vector(a, b) # maximum example
@benchmark SearchBall.vector([1.0, 2.0], [2.3, 4.5]) # realistic example

@benchmark SearchBall.intersection(LineSegment(Point2(2.0, 2.0), Point2(3.0, 3.0)), LineSegment(Point2(3.0, 2.0), Point2(2.0, 3.0))) == (true, Point2(2.5, 2.5)) # realistic example

@benchmark SearchBall.intersection(LineSegment(Point2(2.0, 2.0), Point2(3.0, 3.0)), HyperRectangle{2,Float64}([0.0, 0.0], [7.0, 4.0])) == (true, Point2(4.0, 4.0)) # realistic example

@benchmark SearchBall.does_intersect(LineSegment(Point2(2.0, 2.0), Point2(3.0, 3.0)), Circle(Point2(0.0, 0.0), 1.0)) # realistic example

@benchmark SearchBall.polygon_intersection([Point2(0.0, 0.0), Point2(6.0, 0.0), Point2(6.0, 2.0), Point2(3.0, 2.0), Point2(0.0, 2.0)], [Point2(1.0, 1.0), Point2(3.0, 3.0), Point2(5.0, 1.0), Point2(3.0, 4.0)]) # realistic example


## Optimization Model for dtu42380a1
## Strategy I: New production lines
## Mar 11, 2020

using JuMP, Gurobi, CSV

include("./get_data.jl")

model = Model(with_optimizer(Gurobi.Optimizer, Presolve=0, OutputFlag=0));

## PRODUCTION VARIABLES
@variable(model, x[1:L,1:P,1:T] >= 0)
## SOURCE VARIABLES (PREBLENDING AND PACKAGING)
@variable(model, y[1:L,1:S,1:P,1:T] >= 0);
## MACHINE FLOW VARIABLES (ALLOCATION OF REGULAR TIME AND OVERTIME FOR BLENDING AND PACKAGING MACHINES)
@variable(model, mBR[1:L, 1:P, 1:T] >= 0);
@variable(model, mBO[1:L, 1:P, 1:T] >= 0);
@variable(model, mPR[1:L, 1:P, 1:T] >= 0);
@variable(model, mPO[1:L, 1:P, 1:T] >= 0);
## INVENTORY VARIABLES
@variable(model, I[1:L,1:P,1:T] >= 0);
## DEMAND NOT MET VARIABLES
@variable(model, delta[1:L,1:P,1:T] >= 0);

@variable(model, s_b[1:L] >= 1)
@variable(model, s_p[1:L] >= 1)

## New decision variables regarding promotion or not
@variable(model, whe_pro[1:L, 1:2], Bin);

## OBJECTIVE FUNCTION
@objective(model, Min, sum( #=
=#           sum(y[i,s,p,t]*c[s] for s = 1:S)    #=  material cost
=#         + mBR[i,p,t]*cBR                      #= regular time blending machine cost
=#         + mBO[i,p,t]*cBO                      #= overtime blending machine cost
=#         + mPR[i,p,t]*cPR                      #= regular time packaging machine cost
=#         + mPO[i,p,t]*cPO                      #= overtime time packaging machine cost
=#         + I[i,p,t]*h[p]                       #= inventory cost
=#         + PI*delta[i,p,t]                     #= unsatisfied demand penalty
=#         for i = 1:L, p = 1:P, t = 1:T)
           + sum(s_b[i] + s_p[i] for i = 1:L) * 1000000
);

## PROCESS CONSTRAINTS - material and machine inflow must match (according to
##   the requirements) product outflow
@constraint(model, [i = 1:L, p = 1:P, t = 1:T,s = 1:S],
  y[i,s,p,t] == pi[p,s] * x[i,p,t]);

@constraint(model, [i = 1:L, p = 1:P, t = 1:T],
  mBR[i,p,t] + mBO[i,p,t] >=  muB[p] * x[i,p,t] );
@constraint(model, [i = 1:L, p = 1:P, t = 1:T],
  mPR[i,p,t] + mPO[i,p,t] >= muP[p] * x[i,p,t]);

## STORAGE CONSTRAINTS - inflow (production and start-of-period inventory) must
##   equal outflow (demand and end-of-period inventory) at each DC

## Constraints regarding promotion
@constraint(model, [i = 1:L, p = 3, t = 1],
  x[i,p,t] + I0[i,p] == I[i,p,t] + D[i,p,t] +
  0.3 * whe_pro[i, 1] * D[i,p,t] - delta[i,p,t]);
@constraint(model, [i = 1:L, p = 3, t = 2],
  x[i,p,t] + I[i,p,t-1] == I[i,p,t] + D[i,p,t] +
  0.3 * whe_pro[i, 2] * D[i,p,t] - delta[i,p,t]);
@constraint(model, [i = 1:L], whe_pro[i, 1] + whe_pro[i, 2] >= 1)

@constraint(model, [i = 1:L, p = 1:2, t = 1],
  x[i,p,t] + I0[i,p] == I[i,p,t] + D[i,p,t] - delta[i,p,t]);
@constraint(model, [i = 1:L, p = 1:2, t = 2],
  x[i,p,t] + I[i,p,t-1] == I[i,p,t] + D[i,p,t] - delta[i,p,t]);
@constraint(model, [i = 1:L, p = 1:P, t = 3:4],
  x[i,p,t] + I[i,p,t-1] == I[i,p,t] + D[i,p,t] - delta[i,p,t]);

@constraint(model, [i = 1:L, p = 1:P, t = 1:T],
  I[i,p,t] >= ss[i,p,t]);

## Modifications
## CAPACITY CONSTRAINTS
@constraint(model, [i = 1:L, t = 1:T],
  sum(mBR[i,p,t] for p = 1:P) <= 24*5*60*60 * s_b[i]);
@constraint(model, [i = 1:L, t = 1:T],
  sum(mPR[i,p,t] for p = 1:P) <= 24*5*60*60 * s_p[i]);
@constraint(model, [i = 1:L, t = 1:T],
  sum(mBO[i,p,t] for p = 1:P) <= 24*1*60*60 * s_b[i]);
@constraint(model, [i = 1:L, t = 1:T],
  sum(mPO[i,p,t] for p = 1:P) <= 24*1*60*60 * s_p[i]);
@constraint(model, [i = 1:L, p = 1:P, t = 1:T], delta[i,p,t] <= 0.00001);

optimize!(model)
println(value(whe_pro[1, 1]))
println(value(whe_pro[1, 2]))
println(value(whe_pro[2, 1]))
println(value(whe_pro[2, 2]))
println(value(whe_pro[3, 1]))
println(value(whe_pro[3, 2]))

for i = 1:L
  println(value(s_b[i]))
  println(value(s_p[i]))
end
extra = value(s_b[1]) + value(s_p[1]) + value(s_b[2]) +
  value(s_p[2]) + value(s_b[3]) + value(s_p[3])

println(objective_value(model) - extra * 1000000)

include("./export_result.jl")


# Orthonormalize
ortho_qr(φk) = Matrix(qr(φk).Q)


facts("Tensor-CUR") do

context("Small case") do
    T = rand_kruskal3(2, (20, 40, 60), false)
    for i in 1:3
        context("slab axis: $i") do
            @time factors = tensorcur3(T, 3, 15, i)
            @fact sum(factors.Cweight) --> 3
            @fact sum(factors.Rweight) --> 15
            @fact all(factors.error .< 1e-5) --> true
        end
    end
end

context("Large case without reconstruction") do
    T = rand_kruskal3(2, (100, 120, 60), false)
    @time factors = tensorcur3(T, 10, 200, compute_u=false)
    @fact sum(factors.Cweight) --> 10
    @fact sum(factors.Rweight) --> 200
    @fact size(factors.U) --> (0, 0)
    @fact size(factors.error) --> (0,)
end

end


include("../src/RationalFunctionFields.jl")
using .RationalFunctionFields: change_parent_ring, singular2aa, aa2singular,
                        double_singular2aa, double_aa2singular

@testset "Utils tests" begin

    R, xs = Sing.PolynomialRing(Sing.QQ, ["x", "y"])
    x, y = xs
    f = x + y

    faa = singular2aa(f)

    revfaa = aa2singular(faa, new_ring=R)
    @test revfaa == f

    R2, xs2 = Sing.PolynomialRing(R, ["x2", "y2"])
    x2, y2 = xs2
    f2 = x2 + y2

    f2aa = double_singular2aa(f2)

    revf2s = double_aa2singular(f2aa, base=R, new_ring=R2)
    @test revf2s == f2

    AA_R, (x, y) = AA.PolynomialRing(Sing.QQ, ["x", "y"])
    f = x + y
    newparent, (xx, yy, tt) = AA.PolynomialRing(Sing.QQ, ["x", "y", "t"])

    ft, t = change_parent_ring(f, newparent)

    @test parent(ft) == newparent
    @test t == tt

    frev = change_parent_ring(ft, AA_R)

    @test f == frev

end