Base.one(::Type{P}) where {C, T, P <: Polynomial{C, T}} = Polynomial(one(T))
Base.one(p::Polynomial) = one(typeof(p))

Base.zero(::Type{Polynomial{C, T, A}}) where {C, T, A} = Polynomial(A())
Base.zero(t::PolynomialLike) = zero(typeof(t))

combine(t1::Term, t2::Term) = combine(promote(t1, t2)...)
combine(t1::T, t2::T) where {T <: Term} = Term(t1.coefficient + t2.coefficient, t1.monomial)
compare(t1::Term, t2::Term) = monomial(t1) > monomial(t2)

# Graded Lexicographic order
# First compare total degree, then lexicographic order
function isless(m1::Monomial{V}, m2::Monomial{V}) where {V}
    d1 = degree(m1)
    d2 = degree(m2)
    if d1 < d2
        return true
    elseif d1 > d2
        return false
    else
        return exponents(m1) < exponents(m2)
    end
end

jointerms(terms1::AbstractArray{<:Term}, terms2::AbstractArray{<:Term}) = mergesorted(terms1, terms2, compare, combine)

(+)(p1::Polynomial, p2::Polynomial) = Polynomial(jointerms(terms(p1), terms(p2)))
(-)(p1::Polynomial, p2::Polynomial) = Polynomial(jointerms(terms(p1), (-).(terms(p2))))

(==)(::Variable{N}, ::Variable{N}) where {N} = true
(==)(::Variable, ::Variable) = false
(==)(m1::Monomial{V}, m2::Monomial{V}) where {V} = exponents(m1) == exponents(m2)

# Multiplication is handled as a special case so that we can write these
# definitions without resorting to promotion:
MP.multconstant(α, v::Monomial) = Term(α, v)
MP.multconstant(α, v::Variable) = Term(α, Monomial(v))

(*)(v1::V, v2::V) where {V <: Variable} = Monomial{(V(),), 1}((2,))
(*)(v1::Variable, v2::Variable) = (*)(promote(v1, v2)...)

function MP.divides(m1::Monomial{V, N}, m2::Monomial{V, N}) where {V, N}
    reduce((d, exp) -> d && (exp[1] <= exp[2]), zip(m1.exponents, m2.exponents), init=true)
end
MP.divides(m1::Monomial, m2::Monomial) = divides(promote(m1, m2)...)
function MP.mapexponents(op, m1::M, m2::M) where M<:Monomial
    M(map(op, m1.exponents, m2.exponents))
end
MP.mapexponents(op, m1::Monomial, m2::Monomial) = mapexponents(op, promote(m1, m2)...)

# TODO: this could be faster with an in-place summation
function (*)(p1::Polynomial{S}, p2::Polynomial{T}) where {S, T}
    C = Base.promote_op(*, S, T)
    M = promote_type(monomialtype(p1), monomialtype(p2))
    result = Polynomial(termtype(M, C)[])
    for t1 in terms(p1)
        for t2 in terms(p2)
            result += t1 * t2
        end
    end
    result
end

^(v::V, x::Integer) where {V <: Variable} = Monomial{(V(),), 1}((x,))

# dot(v1::AbstractVector{<:TermLike}, v2::AbstractVector) = dot(v1, v2)
# dot(v1::AbstractVector, v2::AbstractVector{<:TermLike}) = dot(v1, v2)
# dot(v1::AbstractVector{<:TermLike}, v2::AbstractVector{<:TermLike}) = dot(v1, v2)

# All of these types are immutable, so there's no need to copy anything to get
# a shallow copy.
Base.copy(x::TermLike) = x

Base.copy(p::Polynomial) = Polynomial(copy(terms(p)))

adjoint(v::Variable) = v
adjoint(m::Monomial) = m
adjoint(t::Term) = Term(adjoint(coefficient(t)), monomial(t))
adjoint(x::Polynomial) = Polynomial(adjoint.(terms(x)))


using FastBEAST
using StaticArrays
using LinearAlgebra

# Do a 3D test with Laplace kernel

function OneoverRkernel(testpoint::SVector{3,T}, sourcepoint::SVector{3,T}) where T
    if isapprox(testpoint, sourcepoint, rtol=eps()*1e-4)
        return 0.0
    else
        return 1.0 / (norm(testpoint - sourcepoint))
    end
end

function assembler(kernel, testpoints, sourcepoints)
    kernelmatrix = zeros(promote_type(eltype(testpoints[1]),eltype(sourcepoints[1])),
                length(testpoints), length(sourcepoints))

    for i = 1:length(testpoints)
        for j = 1:length(sourcepoints)
            kernelmatrix[i,j] = kernel(testpoints[i], sourcepoints[j])
        end
    end
    return kernelmatrix
end

function assembler(kernel, matrix, testpoints, sourcepoints)
    for i = 1:length(testpoints)
        for j = 1:length(sourcepoints)
            matrix[i,j] = kernel(testpoints[i], sourcepoints[j])
        end
    end
end

##

N =  1000
NT = N

spoints = [@SVector rand(3) for i = 1:N]
tpoints = 0.1*[@SVector rand(3) for i = 1:NT] + [1.0*SVector(3.5, 3.5, 3.5) for i = 1:NT]

@views OneoverRkernelassembler(matrix, tdata, sdata) = assembler(OneoverRkernel, matrix, tpoints[tdata], spoints[sdata])
stree = create_tree(spoints, BoxTreeOptions(nmin=50))
ttree = create_tree(tpoints, BoxTreeOptions(nmin=50))
kmat = assembler(OneoverRkernel, tpoints, spoints)
hmat = HMatrix(OneoverRkernelassembler, ttree, stree, compressor=:naive, T=Float64)

@test estimate_reldifference(hmat,kmat) ≈ 0 atol=1e-4
@test compressionrate(hmat)*100 ≈ 99 atol=1

##
N = 4000
NT = N

spoints = [@SVector rand(3) for i = 1:N]
##
@views OneoverRkernelassembler(matrix, tdata, sdata) = assembler(OneoverRkernel, matrix, spoints[tdata], spoints[sdata])
stree = create_tree(spoints, BoxTreeOptions(nmin=400))
kmat = assembler(OneoverRkernel, spoints, spoints)
hmat = HMatrix(OneoverRkernelassembler, stree, stree, compressor=:aca, T=Float64)

@test estimate_reldifference(hmat,kmat) ≈ 0 atol=1e-4
@test compressionrate(hmat)*100 ≈ 54 atol=1


using StochasticDiffEq, DiffEqNoiseProcess, Test, DiffEqDevTools, Random
Random.seed!(1)

u0 = zeros(2)
v0 = ones(2)
γ = 1

f1_harmonic(v,u,p,t) = -u
f2_harmonic(v,u,p,t) = v
g(u,p,t) = 1

f1_harmonic_iip(dv,v,u,p,t) = dv .= f1_harmonic(v,u,p,t)
f2_harmonic_iip(du,v,u,p,t) = du .= f2_harmonic(v,u,p,t)
g_iip(du,u,p,t) = du .= g(u,p,t)

ff_harmonic = DynamicalSDEFunction(f1_harmonic,f2_harmonic,g)
prob1 = DynamicalSDEProblem(ff_harmonic,g,v0,u0,(0.0,5.0))
sol1 = solve(prob1,BAOAB(gamma=γ);dt=1/10,save_noise=true)

prob2 = DynamicalSDEProblem(f1_harmonic_iip,f2_harmonic_iip,g_iip,v0,u0,(0.0,5.0); noise=NoiseWrapper(sol1.W))
sol2 = solve(prob2,BAOAB(gamma=γ);dt=1/10)

@test sol1[:] ≈ sol2[:]

dts = (1/2) .^ (8:-1:4)

# Can't use NoiseGrid as noise is not generated with the correct size in convergence.jl. We require noise with shape of v.
sim1  = analyticless_test_convergence(dts,prob1,BAOAB(gamma=γ),(1/2)^10;trajectories=Int(1e2),use_noise_grid=false)
@test abs(sim1.𝒪est[:weak_final]-1) < 0.3


# Sets

# Note: When adding a new set, also add it to Utilities.Model.

"""
    AbstractSet

Abstract supertype for set objects used to encode constraints. A set object
should not contain any [`VariableIndex`](@ref) or [`ConstraintIndex`](@ref)
as the set is passed unmodifed during [`copy_to`](@ref).
"""
abstract type AbstractSet end

"""
    dimension(s::AbstractSet)

Return the [`output_dimension`](@ref) that an [`AbstractFunction`](@ref) should have to be used with the set `s`.

### Examples

```julia-repl
julia> dimension(Reals(4))
4

julia> dimension(LessThan(3.0))
1

julia> dimension(PositiveSemidefiniteConeTriangle(2))
3
```

"""
function dimension end

"""
    dual_set(s::AbstractSet)

Return the dual set of `s`, that is the dual cone of the set. This follows the
definition of duality discussed in [Duality](@ref).

See [Dual cone](https://en.wikipedia.org/wiki/Dual_cone_and_polar_cone) for more
information.

If the dual cone is not defined it returns an error.

### Examples

```jldocstest
julia> dual_set(Reals(4))
Zeros(4)

julia> dual_set(SecondOrderCone(5))
SecondOrderCone(5)

julia> dual_set(ExponentialCone())
DualExponentialCone()
```
"""
function dual_set end

dual_set(s::AbstractSet) = error("Dual of $s is not implemented.")

"""
    dual_set_type(S::Type{<:AbstractSet})

Return the type of dual set of sets of type `S`, as returned by
[`dual_set`](@ref). If the dual cone is not defined it returns an error.

### Examples

```jldocstest
julia> dual_set_type(Reals)
Zeros

julia> dual_set_type(SecondOrderCone)
SecondOrderCone

julia> dual_set_type(ExponentialCone)
DualExponentialCone
```
"""
function dual_set_type end

function dual_set_type(S::Type{<:AbstractSet})
    return error("Dual type of $S is not implemented.")
end

"""
    AbstractScalarSet

Abstract supertype for subsets of ``\\mathbb{R}``.
"""
abstract type AbstractScalarSet <: AbstractSet end

Base.broadcastable(set::AbstractScalarSet) = Ref(set)

dimension(s::AbstractScalarSet) = 1

"""
    AbstractVectorSet

Abstract supertype for subsets of ``\\mathbb{R}^n`` for some ``n``.
"""
abstract type AbstractVectorSet <: AbstractSet end

dimension(s::AbstractVectorSet) = s.dimension # .dimension field is conventional, overwrite this method if not applicable

"""
    Reals(dimension)

The set ``\\mathbb{R}^{dimension}`` (containing all points) of dimension `dimension`.
"""
struct Reals <: AbstractVectorSet
    dimension::Int
end

dual_set(s::Reals) = Zeros(dimension(s))
dual_set_type(::Type{Reals}) = Zeros

"""
    Zeros(dimension)

The set ``\\{ 0 \\}^{dimension}`` (containing only the origin) of dimension `dimension`.
"""
struct Zeros <: AbstractVectorSet
    dimension::Int
end

dual_set(s::Zeros) = Reals(dimension(s))
dual_set_type(::Type{Zeros}) = Reals

"""
    Nonnegatives(dimension)

The nonnegative orthant ``\\{ x \\in \\mathbb{R}^{dimension} : x \\ge 0 \\}`` of dimension `dimension`.
"""
struct Nonnegatives <: AbstractVectorSet
    dimension::Int
end

dual_set(s::Nonnegatives) = copy(s)
dual_set_type(::Type{Nonnegatives}) = Nonnegatives

"""
    Nonpositives(dimension)

The nonpositive orthant ``\\{ x \\in \\mathbb{R}^{dimension} : x \\le 0 \\}`` of dimension `dimension`.
"""
struct Nonpositives <: AbstractVectorSet
    dimension::Int
end

dual_set(s::Nonpositives) = copy(s)
dual_set_type(::Type{Nonpositives}) = Nonpositives

"""
    GreaterThan{T <: Real}(lower::T)

The set ``[lower,\\infty) \\subseteq \\mathbb{R}``.
"""
struct GreaterThan{T<:Real} <: AbstractScalarSet
    lower::T
end

# guimarqu
struct ExtendedGreaterThan{T<:Real} <: AbstractScalarSet
    lower::T
end

"""
    LessThan{T <: Real}(upper::T)

The set ``(-\\infty,upper] \\subseteq \\mathbb{R}``.
"""
struct LessThan{T<:Real} <: AbstractScalarSet
    upper::T
end

# guimarqu
struct ExtendedLessThan{T<:Real} <: AbstractScalarSet
    lower::T
end

"""
    EqualTo{T <: Number}(value::T)

The set containing the single point ``x \\in \\mathbb{R}`` where ``x`` is given by `value`.
"""
struct EqualTo{T<:Number} <: AbstractScalarSet
    value::T
end

function Base.:(==)(
    set1::S,
    set2::S,
) where {S<:Union{GreaterThan,LessThan,EqualTo}}
    return constant(set1) == constant(set2)
end

"""
    Interval{T <: Real}(lower::T,upper::T)

The interval ``[lower, upper] \\subseteq \\mathbb{R}``.
If `lower` or `upper` is `-Inf` or `Inf`, respectively, the set is interpreted as a one-sided interval.

    Interval(s::GreaterThan{<:AbstractFloat})

Construct a (right-unbounded) `Interval` equivalent to the given [`GreaterThan`](@ref) set.

    Interval(s::LessThan{<:AbstractFloat})

Construct a (left-unbounded) `Interval` equivalent to the given [`LessThan`](@ref) set.

    Interval(s::EqualTo{<:Real})

Construct a (degenerate) `Interval` equivalent to the given [`EqualTo`](@ref) set.
"""
struct Interval{T<:Real} <: AbstractScalarSet
    lower::T
    upper::T
end
Interval(s::GreaterThan{<:AbstractFloat}) = Interval(s.lower, typemax(s.lower))
Interval(s::LessThan{<:AbstractFloat}) = Interval(typemin(s.upper), s.upper)
Interval(s::EqualTo{<:Real}) = Interval(s.value, s.value)
Interval(s::Interval) = s

"""
    constant(s::Union{EqualTo, GreaterThan, LessThan})

Returns the constant of the set.
"""
constant(s::EqualTo) = s.value
constant(s::GreaterThan) = s.lower
constant(s::LessThan) = s.upper

"""
    NormInfinityCone(dimension)

The ``\\ell_\\infty``-norm cone ``\\{ (t,x) \\in \\mathbb{R}^{dimension} : t \\ge \\lVert x \\rVert_\\infty = \\max_i \\lvert x_i \\rvert \\}`` of dimension `dimension`.
"""
struct NormInfinityCone <: AbstractVectorSet
    dimension::Int
end

dual_set(s::NormInfinityCone) = NormOneCone(dimension(s))
dual_set_type(::Type{NormInfinityCone}) = NormOneCone

"""
    NormOneCone(dimension)

The ``\\ell_1``-norm cone ``\\{ (t,x) \\in \\mathbb{R}^{dimension} : t \\ge \\lVert x \\rVert_1 = \\sum_i \\lvert x_i \\rvert \\}`` of dimension `dimension`.
"""
struct NormOneCone <: AbstractVectorSet
    dimension::Int
end

dual_set(s::NormOneCone) = NormInfinityCone(dimension(s))
dual_set_type(::Type{NormOneCone}) = NormInfinityCone

"""
    SecondOrderCone(dimension)

The second-order cone (or Lorenz cone or ``\\ell_2``-norm cone) ``\\{ (t,x) \\in \\mathbb{R}^{dimension} : t \\ge \\lVert x \\rVert_2 \\}`` of dimension `dimension`.
"""
struct SecondOrderCone <: AbstractVectorSet
    dimension::Int
end

dual_set(s::SecondOrderCone) = copy(s)
dual_set_type(::Type{SecondOrderCone}) = SecondOrderCone

"""
    RotatedSecondOrderCone(dimension)

The rotated second-order cone ``\\{ (t,u,x) \\in \\mathbb{R}^{dimension} : 2tu \\ge \\lVert x \\rVert_2^2, t,u \\ge 0 \\}`` of dimension `dimension`.
"""
struct RotatedSecondOrderCone <: AbstractVectorSet
    dimension::Int
end

dual_set(s::RotatedSecondOrderCone) = copy(s)
dual_set_type(::Type{RotatedSecondOrderCone}) = RotatedSecondOrderCone

"""
    GeometricMeanCone(dimension)

The geometric mean cone ``\\{ (t,x) \\in \\mathbb{R}^{n+1} : x \\ge 0, t \\le \\sqrt[n]{x_1 x_2 \\cdots x_n} \\}`` of dimension `dimension```{}=n+1``.

### Duality note

The dual of the geometric mean cone is
``\\{ (u, v) \\in \\mathbb{R}^{n+1} : u \\le 0, v \\ge 0, -u \\le n \\sqrt[n]{\\prod_i v_i} \\}`` of dimension `dimension```{}=n+1``.
"""
struct GeometricMeanCone <: AbstractVectorSet
    dimension::Int
end

"""
    ExponentialCone()

The 3-dimensional exponential cone ``\\{ (x,y,z) \\in \\mathbb{R}^3 : y \\exp (x/y) \\le z, y > 0 \\}``.
"""
struct ExponentialCone <: AbstractVectorSet end

dual_set(s::ExponentialCone) = DualExponentialCone()
dual_set_type(::Type{ExponentialCone}) = DualExponentialCone

"""
    DualExponentialCone()

The 3-dimensional dual exponential cone ``\\{ (u,v,w) \\in \\mathbb{R}^3 : -u \\exp (v/u) \\le \\exp(1) w, u < 0 \\}``.
"""
struct DualExponentialCone <: AbstractVectorSet end

dual_set(s::DualExponentialCone) = ExponentialCone()
dual_set_type(::Type{DualExponentialCone}) = ExponentialCone

"""
    PowerCone{T <: Real}(exponent::T)

The 3-dimensional power cone ``\\{ (x,y,z) \\in \\mathbb{R}^3 : x^{exponent} y^{1-exponent} \\ge |z|, x \\ge 0, y \\ge 0 \\}`` with parameter `exponent`.
"""
struct PowerCone{T<:Real} <: AbstractVectorSet
    exponent::T
end

dual_set(s::PowerCone{T}) where {T<:Real} = DualPowerCone{T}(s.exponent)
dual_set_type(::Type{PowerCone{T}}) where {T<:Real} = DualPowerCone{T}

"""
    DualPowerCone{T <: Real}(exponent::T)

The 3-dimensional power cone ``\\{ (u,v,w) \\in \\mathbb{R}^3 : (\\frac{u}{exponent})^{exponent} (\\frac{v}{1-exponent})^{1-exponent} \\ge |w|, u \\ge 0, v \\ge 0 \\}`` with parameter `exponent`.
"""
struct DualPowerCone{T<:Real} <: AbstractVectorSet
    exponent::T
end

dual_set(s::DualPowerCone{T}) where {T<:Real} = PowerCone{T}(s.exponent)
dual_set_type(::Type{DualPowerCone{T}}) where {T<:Real} = PowerCone{T}

function dimension(
    s::Union{ExponentialCone,DualExponentialCone,PowerCone,DualPowerCone},
)
    return 3
end

function Base.:(==)(set1::S, set2::S) where {S<:Union{PowerCone,DualPowerCone}}
    return set1.exponent == set2.exponent
end

"""
    RelativeEntropyCone(dimension)

The relative entropy cone ``\\{ (u, v, w) \\in \\mathbb{R}^{1+2n} : u \\ge \\sum_{i=1}^n w_i \\log (\\frac{w_i}{v_i}), v_i \\ge 0, w_i \\ge 0 \\}`` of dimension `dimension```{}=2n+1``.

### Duality note

The dual of the relative entropy cone is
``\\{ (u, v, w) \\in \\mathbb{R}^{1+2n} : \\forall i, w_i \\ge u (\\log (\\frac{u}{v_i}) - 1), v_i \\ge 0, u > 0 \\}`` of dimension `dimension```{}=2n+1``.
"""
struct RelativeEntropyCone <: AbstractVectorSet
    dimension::Int
end

"""
    NormSpectralCone(row_dim, column_dim)

The epigraph of the matrix spectral norm (maximum singular value function) ``\\{ (t, X) \\in \\mathbb{R}^{1 + row_dim \\times column_dim} : t \\ge \\sigma_1(X) \\}`` where ``\\sigma_i`` is the ``i``th singular value of the matrix ``X`` of row dimension `row_dim` and column dimension `column_dim`.
The matrix X is vectorized by stacking the columns, matching the behavior of Julia's `vec` function.
"""
struct NormSpectralCone <: AbstractVectorSet
    row_dim::Int
    column_dim::Int
end

dual_set(s::NormSpectralCone) = NormNuclearCone(s.row_dim, s.column_dim)
dual_set_type(::Type{NormSpectralCone}) = NormNuclearCone

"""
    NormNuclearCone(row_dim, column_dim)

The epigraph of the matrix nuclear norm (sum of singular values function) ``\\{ (t, X) \\in \\mathbb{R}^{1 + row_dim \\times column_dim} : t \\ge \\sum_i \\sigma_i(X) \\}`` where ``\\sigma_i`` is the ``i``th singular value of the matrix ``X`` of row dimension `row_dim` and column dimension `column_dim`.
The matrix X is vectorized by stacking the columns, matching the behavior of Julia's `vec` function.
"""
struct NormNuclearCone <: AbstractVectorSet
    row_dim::Int
    column_dim::Int
end

dual_set(s::NormNuclearCone) = NormSpectralCone(s.row_dim, s.column_dim)
dual_set_type(::Type{NormNuclearCone}) = NormSpectralCone

function dimension(s::Union{NormSpectralCone,NormNuclearCone})
    return 1 + s.row_dim * s.column_dim
end

"""
    abstract type AbstractSymmetricMatrixSetTriangle <: AbstractVectorSet end

Abstract supertype for subsets of the (vectorized) cone of symmetric matrices,
with [`side_dimension`](@ref) rows and columns. The entries of the upper-right
triangular part of the matrix are given column by column (or equivalently, the
entries of the lower-left triangular part are given row by row). A vectorized
cone of [`dimension`](@ref) ``n`` corresponds to a square matrix with side
dimension ``\\sqrt{1/4 + 2 n} - 1/2``. (Because a ``d \\times d`` matrix has
``d(d + 1) / 2`` elements in the upper or lower triangle.)

### Examples

The matrix
```math
\\begin{bmatrix}
  1 & 2 & 4\\\\
  2 & 3 & 5\\\\
  4 & 5 & 6
\\end{bmatrix}
```
has [`side_dimension`](@ref) 3 and vectorization ``(1, 2, 3, 4, 5, 6)``.

### Note

Two packed storage formats exist for symmetric matrices, the respective orders
of the entries are:
- upper triangular column by column (or lower triangular row by row);
- lower triangular column by column (or upper triangular row by row).

The advantage of the first format is the mapping between the `(i, j)` matrix
indices and the `k` index of the vectorized form. It is simpler and does not
depend on the side dimension of the matrix.
Indeed,
- the entry of matrix indices `(i, j)` has vectorized index
  `k = div((j - 1) * j, 2) + i` if ``i \\leq j`` and
  `k = div((i - 1) * i, 2) + j` if ``j \\leq i``;
- and the entry with vectorized index `k` has matrix indices
  `i = div(1 + isqrt(8k - 7), 2)` and `j = k - div((i - 1) * i, 2)` or
  `j = div(1 + isqrt(8k - 7), 2)` and `i = k - div((j - 1) * j, 2)`.

### Duality note

The scalar product for the symmetric matrix in its vectorized form is the sum of
the pairwise product of the diagonal entries plus twice the sum of the pairwise
product of the upper diagonal entries; see [p. 634, 1].
This has important consequence for duality.

Consider for example the following problem
([`PositiveSemidefiniteConeTriangle`](@ref) is a subtype of
[`AbstractSymmetricMatrixSetTriangle`](@ref))
```math
\\begin{align*}
    & \\max_{x \\in \\mathbb{R}} & x
    \\\\
    & \\;\\;\\text{s.t.} &
    (1, -x, 1) & \\in \\text{PositiveSemidefiniteConeTriangle}(2).
\\end{align*}
```
The dual is the following problem
```math
\\begin{align*}
    & \\min_{x \\in \\mathbb{R}^3} & y_1 + y_3
    \\\\
    & \\;\\;\\text{s.t.} & 2y_2 & = 1\\\\
    & & y & \\in \\text{PositiveSemidefiniteConeTriangle}(2).
\\end{align*}
```
Why do we use ``2y_2`` in the dual constraint instead of ``y_2`` ?
The reason is that ``2y_2`` is the scalar product between ``y`` and the symmetric
matrix whose vectorized form is ``(0, 1, 0)``. Indeed, with our modified scalar
products we have
```math
\\langle
(0, 1, 0),
(y_1, y_2, y_3)
\\rangle
=
\\mathrm{trace}
\\begin{pmatrix}
  0 & 1\\\\
  1 & 0
\\end{pmatrix}
\\begin{pmatrix}
  y_1 & y_2\\\\
  y_2 & y_3
\\end{pmatrix}
= 2y_2.
```

### References

[1] Boyd, S. and Vandenberghe, L.. *Convex optimization*. Cambridge university press, 2004.
"""
abstract type AbstractSymmetricMatrixSetTriangle <: AbstractVectorSet end

function dimension(set::AbstractSymmetricMatrixSetTriangle)
    d = side_dimension(set)
    return div(d * (d + 1), 2)
end

"""
    abstract type AbstractSymmetricMatrixSetSquare <: AbstractVectorSet end

Abstract supertype for subsets of the (vectorized) cone of symmetric matrices,
with [`side_dimension`](@ref) rows and columns. The entries of the matrix are
given column by column (or equivalently, row by row). The matrix is both
constrained to be symmetric and to have its [`triangular_form`](@ref) belong
to the corresponding set. That is, if the functions in entries ``(i, j)`` and
``(j, i)`` are different, then a constraint will be added to make sure that the
entries are equal.

### Examples

[`PositiveSemidefiniteConeSquare`](@ref) is a subtype of
[`AbstractSymmetricMatrixSetSquare`](@ref) and constraining the matrix
```math
\\begin{bmatrix}
  1 & -y\\\\
  -z & 0\\\\
\\end{bmatrix}
```
to be symmetric positive semidefinite can be achieved by constraining the vector
``(1, -z, -y, 0)`` (or ``(1, -y, -z, 0)``) to belong to the
`PositiveSemidefiniteConeSquare(2)`. It both constrains ``y = z`` and
``(1, -y, 0)`` (or ``(1, -z, 0)``) to be in
`PositiveSemidefiniteConeTriangle(2)`, since
`triangular_form(PositiveSemidefiniteConeSquare)` is
`PositiveSemidefiniteConeTriangle`.
"""
abstract type AbstractSymmetricMatrixSetSquare <: AbstractVectorSet end

dimension(set::AbstractSymmetricMatrixSetSquare) = side_dimension(set)^2

"""
    side_dimension(set::Union{AbstractSymmetricMatrixSetTriangle,
                              AbstractSymmetricMatrixSetSquare})

Side dimension of the matrices in `set`. By convention, it should be stored in
the `side_dimension` field but if it is not the case for a subtype of
[`AbstractSymmetricMatrixSetTriangle`](@ref), the method should be implemented
for this subtype.
"""
function side_dimension(
    set::Union{
        AbstractSymmetricMatrixSetTriangle,
        AbstractSymmetricMatrixSetSquare,
    },
)
    return set.side_dimension
end

"""
    triangular_form(S::Type{<:AbstractSymmetricMatrixSetSquare})
    triangular_form(set::AbstractSymmetricMatrixSetSquare)

Return the [`AbstractSymmetricMatrixSetTriangle`](@ref) corresponding to the
vectorization of the upper triangular part of matrices in the
[`AbstractSymmetricMatrixSetSquare`](@ref) set.
"""
function triangular_form end
function triangular_form(set::AbstractSymmetricMatrixSetSquare)
    return triangular_form(typeof(set))(side_dimension(set))
end

"""
    PositiveSemidefiniteConeTriangle(side_dimension) <: AbstractSymmetricMatrixSetTriangle

The (vectorized) cone of symmetric positive semidefinite matrices, with
`side_dimension` rows and columns. See
[`AbstractSymmetricMatrixSetTriangle`](@ref) for more details on the vectorized
form.
"""
struct PositiveSemidefiniteConeTriangle <: AbstractSymmetricMatrixSetTriangle
    side_dimension::Int
end

dual_set(s::PositiveSemidefiniteConeTriangle) = copy(s)
function dual_set_type(::Type{PositiveSemidefiniteConeTriangle})
    return PositiveSemidefiniteConeTriangle
end

"""
    PositiveSemidefiniteConeSquare(side_dimension) <: AbstractSymmetricMatrixSetSquare

The cone of symmetric positive semidefinite matrices, with side length `side_dimension`.
 See [`AbstractSymmetricMatrixSetSquare`](@ref) for more details on the vectorized
form.

The entries of the matrix are given column by column (or equivalently, row by row).
The matrix is both constrained to be symmetric and to be positive semidefinite.
That is, if the functions in entries ``(i, j)`` and ``(j, i)`` are different, then a constraint will be added to make sure that the entries are equal.

### Examples

Constraining the matrix
```math
\\begin{bmatrix}
  1 & -y\\\\
  -z & 0\\\\
\\end{bmatrix}
```
to be symmetric positive semidefinite can be achieved by constraining the vector ``(1, -z, -y, 0)`` (or ``(1, -y, -z, 0)``)
to belong to the `PositiveSemidefiniteConeSquare(2)`.
It both constrains ``y = z`` and ``(1, -y, 0)`` (or ``(1, -z, 0)``) to be in `PositiveSemidefiniteConeTriangle(2)`.
"""
struct PositiveSemidefiniteConeSquare <: AbstractSymmetricMatrixSetSquare
    side_dimension::Int
end

function _dual_set_square_error()
    return error(
        """Dual of `PositiveSemidefiniteConeSquare` is not defined in MathOptInterface.
           For more details see the comments in `src/Bridges/Constraint/square.jl`.""",
    )
end
function dual_set(::PositiveSemidefiniteConeSquare)
    return _dual_set_square_error()
end
function dual_set_type(::Type{PositiveSemidefiniteConeSquare})
    return _dual_set_square_error()
end

function triangular_form(::Type{PositiveSemidefiniteConeSquare})
    return PositiveSemidefiniteConeTriangle
end

"""
    LogDetConeTriangle(side_dimension)

The log-determinant cone ``\\{ (t, u, X) \\in \\mathbb{R}^{2 + d(d+1)/2} : t \\le u \\log(\\det(X/u)), u > 0 \\}`` where the matrix `X` is represented in the same symmetric packed format as in the `PositiveSemidefiniteConeTriangle`.
The argument `side_dimension` is the side dimension of the matrix `X`, i.e., its number of rows or columns.
"""
struct LogDetConeTriangle <: AbstractVectorSet
    side_dimension::Int
end

function dimension(s::LogDetConeTriangle)
    return 2 + div(s.side_dimension * (s.side_dimension + 1), 2)
end

"""
    LogDetConeSquare(side_dimension)

The log-determinant cone ``\\{ (t, u, X) \\in \\mathbb{R}^{2 + d^2} : t \\le u \\log(\\det(X/u)), X \\text{ symmetric}, u > 0 \\}`` where the matrix `X` is represented in the same format as in the `PositiveSemidefiniteConeSquare`.
Similarly to `PositiveSemidefiniteConeSquare`, constraints are added to ensures that `X` is symmetric.
The argument `side_dimension` is the side dimension of the matrix `X`, i.e., its number of rows or columns.
"""
struct LogDetConeSquare <: AbstractVectorSet
    side_dimension::Int
end

dimension(s::LogDetConeSquare) = 2 + s.side_dimension^2

"""
    RootDetConeTriangle(side_dimension)

The root-determinant cone ``\\{ (t, X) \\in \\mathbb{R}^{1 + d(d+1)/2} : t \\le \\det(X)^{1/d} \\}`` where the matrix `X` is represented in the same symmetric packed format as in the `PositiveSemidefiniteConeTriangle`.
The argument `side_dimension` is the side dimension of the matrix `X`, i.e., its number of rows or columns.
"""
struct RootDetConeTriangle <: AbstractVectorSet
    side_dimension::Int
end

function dimension(s::RootDetConeTriangle)
    return 1 + div(s.side_dimension * (s.side_dimension + 1), 2)
end

"""
    RootDetConeSquare(side_dimension)

The root-determinant cone ``\\{ (t, X) \\in \\mathbb{R}^{1 + d^2} : t \\le \\det(X)^{1/d}, X \\text{ symmetric} \\}`` where the matrix `X` is represented in the same format as in the `PositiveSemidefiniteConeSquare`.
Similarly to `PositiveSemidefiniteConeSquare`, constraints are added to ensure that `X` is symmetric.
The argument `side_dimension` is the side dimension of the matrix `X`, i.e., its number of rows or columns.
"""
struct RootDetConeSquare <: AbstractVectorSet
    side_dimension::Int
end

dimension(s::RootDetConeSquare) = 1 + s.side_dimension^2

"""
    Integer()

The set of integers ``\\mathbb{Z}``.
"""
struct Integer <: AbstractScalarSet end

"""
    ZeroOne()

The set ``\\{ 0, 1 \\}``.
"""
struct ZeroOne <: AbstractScalarSet end

"""
    Semicontinuous{T <: Real}(lower::T,upper::T)

The set ``\\{0\\} \\cup [lower,upper]``.
"""
struct Semicontinuous{T<:Real} <: AbstractScalarSet
    lower::T
    upper::T
end

"""
    Semiinteger{T <: Real}(lower::T,upper::T)

The set ``\\{0\\} \\cup \\{lower,lower+1,\\ldots,upper-1,upper\\}``.
"""
struct Semiinteger{T<:Real} <: AbstractScalarSet
    lower::T
    upper::T
end

function Base.:(==)(
    set1::S,
    set2::S,
) where {S<:Union{Interval,Semicontinuous,Semiinteger}}
    return set1.lower == set2.lower && set1.upper == set2.upper
end

"""
    SOS1{T <: Real}(weights::Vector{T})

The set corresponding to the special ordered set (SOS) constraint of type 1.
Of the variables in the set, at most one can be nonzero.
The `weights` induce an ordering of the variables; as such, they should be unique values.
The *k*th element in the set corresponds to the *k*th weight in `weights`.
See [here](http://lpsolve.sourceforge.net/5.5/SOS.htm) for a description of SOS constraints and their potential uses.
"""
struct SOS1{T<:Real} <: AbstractVectorSet
    weights::Vector{T}
end

"""
    SOS2{T <: Real}(weights::Vector{T})

The set corresponding to the special ordered set (SOS) constraint of type 2.
Of the variables in the set, at most two can be nonzero, and if two are nonzero, they must be adjacent in the ordering of the set.
The `weights` induce an ordering of the variables; as such, they should be unique values.
The *k*th element in the set corresponds to the *k*th weight in `weights`.
See [here](http://lpsolve.sourceforge.net/5.5/SOS.htm) for a description of SOS constraints and their potential uses.
"""
struct SOS2{T<:Real} <: AbstractVectorSet
    weights::Vector{T}
end

Base.:(==)(a::T, b::T) where {T<:Union{SOS1,SOS2}} = a.weights == b.weights
function Base.isapprox(a::T, b::T; kwargs...) where {T<:Union{SOS1,SOS2}}
    return isapprox(a.weights, b.weights; kwargs...)
end

dimension(s::Union{SOS1,SOS2}) = length(s.weights)

"""
	ActivationCondition

Activation condition for an indicator constraint.
The enum value is used as first type parameter of `IndicatorSet{A,S}`.
"""
@enum ActivationCondition begin
    ACTIVATE_ON_ZERO
    ACTIVATE_ON_ONE
end

"""
    IndicatorSet{A, S <: AbstractScalarSet}(set::S)

``\\{(y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^n : y = 0 \\implies x \\in set\\}``
when `A` is `ACTIVATE_ON_ZERO` and
``\\{(y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^n : y = 1 \\implies x \\in set\\}``
when `A` is `ACTIVATE_ON_ONE`.

`S` has to be a sub-type of `AbstractScalarSet`.
`A` is one of the value of the `ActivationCond` enum.
`IndicatorSet` is used with a `VectorAffineFunction` holding
the indicator variable first.

Example: ``\\{(y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^2 : y = 1 \\implies x_1 + x_2 \\leq 9 \\} ``

```julia
f = MOI.VectorAffineFunction(
    [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)),
     MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x1)),
     MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)),
    ],
    [0.0, 0.0],
)

indicator_set = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0))

MOI.add_constraint(model, f, indicator_set)
```
"""
struct IndicatorSet{A,S<:AbstractScalarSet} <: AbstractVectorSet
    set::S
    IndicatorSet{A}(set::S) where {A,S<:AbstractScalarSet} = new{A,S}(set)
end

dimension(::IndicatorSet) = 2

function Base.copy(set::IndicatorSet{A,S}) where {A,S}
    return IndicatorSet{A}(copy(set.set))
end

function Base.:(==)(
    set1::IndicatorSet{A,S},
    set2::IndicatorSet{A,S},
) where {A,S}
    return set1.set == set2.set
end

"""
    Complements(dimension::Int)

The set corresponding to a mixed complementarity constraint.

Complementarity constraints should be specified with an
[`AbstractVectorFunction`](@ref)-in-`Complements(dimension)` constraint.

The dimension of the vector-valued function `F` must be `2 * dimension`. This
defines a complementarity constraint between the scalar function `F[i]` and the
variable in `F[i + dimension]`. Thus, `F[i + dimension]` must be interpretable
as a single variable `x_i` (e.g., `1.0 * x + 0.0`).

The mixed complementarity problem consists of finding `x_i` in the interval
`[lb, ub]` (i.e., in the set `Interval(lb, ub)`), such that the following holds:

  1. `F_i(x) == 0` if `lb_i < x_i < ub_i`
  2. `F_i(x) >= 0` if `lb_i == x_i`
  3. `F_i(x) <= 0` if `x_i == ub_i`

Classically, the bounding set for `x_i` is `Interval(0, Inf)`, which recovers:
`0 <= F_i(x) ⟂ x_i >= 0`, where the `⟂` operator implies `F_i(x) * x_i = 0`.

### Examples

The problem:

    x -in- Interval(-1, 1)
    [-4 * x - 3, x] -in- Complements(1)

defines the mixed complementarity problem where the following holds:

  1. `-4 * x - 3 == 0` if `-1 < x < 1`
  2. `-4 * x - 3 >= 0` if `x == -1`
  3. `-4 * x - 3 <= 0` if `x == 1`

There are three solutions:

  1. `x = -3/4` with `F(x) = 0`
  2. `x = -1` with `F(x) = 1`
  3. `x = 1` with `F(x) = -7`

The function `F` can also be defined in terms of single variables. For example,
the problem:

    [x_3, x_4] -in- Nonnegatives(2)
    [x_1, x_2, x_3, x_4] -in- Complements(2)

defines the complementarity problem where `0 <= x_1 ⟂ x_3 >= 0` and
`0 <= x_2 ⟂ x_4 >= 0`.
"""
struct Complements <: AbstractVectorSet
    dimension::Int
end

dimension(set::Complements) = 2 * set.dimension

# isbits types, nothing to copy
function Base.copy(
    set::Union{
        Reals,
        Zeros,
        Nonnegatives,
        Nonpositives,
        GreaterThan,
        LessThan,
        EqualTo,
        Interval,
        NormInfinityCone,
        NormOneCone,
        SecondOrderCone,
        RotatedSecondOrderCone,
        GeometricMeanCone,
        ExponentialCone,
        DualExponentialCone,
        PowerCone,
        DualPowerCone,
        RelativeEntropyCone,
        NormSpectralCone,
        NormNuclearCone,
        PositiveSemidefiniteConeTriangle,
        PositiveSemidefiniteConeSquare,
        LogDetConeTriangle,
        LogDetConeSquare,
        RootDetConeTriangle,
        RootDetConeSquare,
        Complements,
        Integer,
        ZeroOne,
        Semicontinuous,
        Semiinteger,
    },
)
    return set
end
Base.copy(set::S) where {S<:Union{SOS1,SOS2}} = S(copy(set.weights))

"""
    supports_dimension_update(S::Type{<:MOI.AbstractVectorSet})

Return a `Bool` indicating whether the elimination of any dimension of
`n`-dimensional sets of type `S` give an `n-1`-dimensional set `S`.
By default, this function returns `false` so it should only be implemented
for sets that supports dimension update.

For instance, `supports_dimension_update(MOI.Nonnegatives}` is `true` because
the elimination of any dimension of the `n`-dimensional nonnegative orthant
gives the `n-1`-dimensional nonnegative orthant. However
`supports_dimension_update(MOI.ExponentialCone}` is `false`.
"""
function supports_dimension_update(::Type{<:AbstractVectorSet})
    return false
end
function supports_dimension_update(
    ::Type{<:Union{Reals,Zeros,Nonnegatives,Nonpositives}},
)
    return true
end

"""
    update_dimension(s::AbstractVectorSet, new_dim)

Returns a set with the dimension modified to `new_dim`.
"""
function update_dimension end
function update_dimension(
    set::Union{Reals,Zeros,Nonnegatives,Nonpositives},
    new_dim,
)
    return typeof(set)(new_dim)
end


using DataInterpolations, Test
using StableRNGs
import ForwardDiff

@testset "Linear Interpolation" begin
    u = 2.0collect(1:10)
    t = 1.0collect(1:10)
    A = LinearInterpolation(u,t)

    for (_t, _u) in zip(t, u)
        @test A(_t) == _u
    end
    @test A(0)        == 0.0
    @test A(5.5)      == 11.0
    @test A(11)       == 22

    u = vcat(2.0collect(1:10)', 3.0collect(1:10)')
    A = LinearInterpolation(u,t)

    for (_t, _u) in zip(t, eachcol(u))
        @test A(_t) == _u
    end
    @test A(0)        == [0.0, 0.0]
    @test A(5.5)      == [11.0, 16.5]
    @test A(11)       == [22, 33]

    x = 1:10
    y = 2:4
    u_= x' .* y
    u = [u_[:,i] for i = 1:size(u_,2)]
    A = LinearInterpolation(u,t)
    @test A(0)        == [0.0, 0.0, 0.0]
    @test A(5.5)      == [11.0, 16.5, 22.0]
    @test A(11)       == [22.0, 33.0, 44.0]

    u = [u_[:,i:i+1] for i = 1:2:10]
    t = 1.0collect(2:2:10)
    A = LinearInterpolation(u,t)

    @test A(0)        == [-2.0 0.0; -3.0 0.0; -4.0 0.0]
    @test A(3)        == [4.0 6.0; 6.0 9.0; 8.0 12.0]
    @test A(5)        == [8.0 10.0; 12.0 15.0; 16.0 20.0]

    # with NaNs (#113)
    u = [NaN, 1.0, 2.0, 3.0]
    t = 1:4
    A = LinearInterpolation(u, t)
    @test isnan(A(1.0))
    @test A(2.0) == 1.0
    @test A(2.5) == 1.5
    @test A(3.0) == 2.0
    @test A(4.0) == 3.0

    u = [0.0, NaN, 2.0, 3.0]
    A = LinearInterpolation(u, t)
    @test A(1.0) == 0.0
    @test isnan(A(2.0))
    @test isnan(A(2.5))
    @test A(3.0) == 2.0
    @test A(4.0) == 3.0

    u = [0.0, NaN, 2.0, 3.0]
    A = LinearInterpolation(u, t)
    @test A(1.0) == 0.0
    @test isnan(A(2.0))
    @test isnan(A(2.5))
    @test A(3.0) == 2.0
    @test A(4.0) == 3.0

    u = [0.0, 1.0, NaN, 3.0]
    A = LinearInterpolation(u, t)
    @test A(1.0) == 0.0
    @test A(2.0) == 1.0
    @test isnan(A(2.5))
    @test isnan(A(3.0))
    @test A(4.0) == 3.0

    u = [0.0, 1.0, 2.0, NaN]
    A = LinearInterpolation(u, t)
    @test A(1.0) == 0.0
    @test A(2.0) == 1.0
    @test A(3.0) == 2.0
    @test isnan(A(3.5))
    @test isnan(A(4.0))

    # Test type stability
    u = Float32.(1:5)
    t = Float32.(1:5)
    A1 = LinearInterpolation(u, t)
    u = 1:5
    t = 1:5
    A2 = LinearInterpolation(u, t)
    u = [1//i for i in 1:5]
    t = (1:5)
    A3 = LinearInterpolation(u, t)
    u = [1//i for i in 1:5]
    t = [1//(6-i) for i in 1:5]
    A4 = LinearInterpolation(u, t)

    F32 = Float32(1)
    F64 = Float64(1)
    I32 = Int32(1)
    I64 = Int64(1)
    R32 = Int32(1)//Int32(1)
    R64 = 1//1
    for A in [A1, A2, A3, A4]
        @test @inferred(A(F32)) === A(F32)
        @test @inferred(A(F64)) === A(F64)
        @test @inferred(A(I32)) === A(I32)
        @test @inferred(A(I64)) === A(I64)
        @test @inferred(A(R32)) === A(R32)
        @test @inferred(A(R64)) === A(R64)
    end

    # Nan time value:
    t = 0.0:3;  # Floats
    u = [0, -2, -1, -2];
    A = LinearInterpolation(u, t);
    dA = t -> ForwardDiff.derivative(A, t)
    @test isnan(dA(NaN))

    t = 0:3;  # Integers
    u = [0, -2, -1, -2];
    A = LinearInterpolation(u, t);
    dA = t -> ForwardDiff.derivative(A, t)
    @test isnan(dA(NaN))

    # Test derivative at point gives derivative to the right (except last is to left):
    ts = t[begin:end-1]
    @test dA.(ts) == dA.(ts .+ 0.5)
    # Test last derivitive is to the left:
    @test dA(last(t)) == dA(last(t) - 0.5)
end

@testset "Quadratic Interpolation" begin
    u = [1.0, 4.0, 9.0, 16.0]
    t = [1.0, 2.0, 3.0, 4.0]
    A = QuadraticInterpolation(u,t)

    for (_t, _u) in zip(t, u)
        @test A(_t) == _u
    end
    @test A(0.0) == 0.0
    @test A(1.5) == 2.25
    @test A(2.5) == 6.25
    @test A(3.5) == 12.25
    @test A(5.0) == 25

    u = [1.0 4.0 9.0 16.0; 1.0 4.0 9.0 16.0]
    A = QuadraticInterpolation(u,t)

    for (_t, _u) in zip(t, eachcol(u))
        @test A(_t) == _u
    end
    @test A(0.0) == [0.0  ,  0.0 ]
    @test A(1.5) == [2.25 ,  2.25]
    @test A(2.5) == [6.25 ,  6.25]
    @test A(3.5) == [12.25, 12.25]
    @test A(5.0) == [25.0 , 25.0 ]

    u_= [1.0, 4.0, 9.0, 16.0]' .* ones(5)
    u = [u_[:,i] for i = 1:size(u_,2)]
    A = QuadraticInterpolation(u,t)
    @test A(0)   == zeros(5)
    @test A(1.5) == 2.25 * ones(5)
    @test A(2.5) == 6.25 * ones(5)
    @test A(3.5) == 12.25 * ones(5)
    @test A(5.0) == 25.0 * ones(5)

    u = [repeat(u[i], 1, 3) for i=1:4]
    A = QuadraticInterpolation(u,t)
    @test A(0)   == zeros(5, 3)
    @test A(1.5) == 2.25 * ones(5, 3)
    @test A(2.5) == 6.25 * ones(5, 3)
    @test A(3.5) == 12.25 * ones(5, 3)
    @test A(5.0) == 25.0 * ones(5, 3)
end

@testset "Lagrange Interpolation" begin
    u = [1.0, 4.0, 9.0]
    t = [1.0, 2.0, 3.0]
    A = LagrangeInterpolation(u,t)

    @test A(2.0) == 4.0
    @test A(1.5) == 2.25

    u = [1.0, 8.0, 27.0, 64.0]
    t = [1.0, 2.0, 3.0, 4.0]
    A = LagrangeInterpolation(u,t)

    @test A(2.0) == 8.0
    @test A(1.5) ≈ 3.375
    @test A(3.5) ≈ 42.875

    u = [1.0 4.0 9.0 16.0; 1.0 4.0 9.0 16.0]
    A = LagrangeInterpolation(u,t)

    @test A(2.0) == [4.0,4.0]
    @test A(1.5) ≈ [2.25,2.25]
    @test A(3.5) ≈ [12.25,12.25]

    u_= [1.0, 4.0, 9.0]' .* ones(4)
    u = [u_[:,i] for i = 1:size(u_,2)]
    t = [1.0, 2.0, 3.0]
    A = LagrangeInterpolation(u,t)

    @test A(2.0) == 4.0 * ones(4)
    @test A(1.5) == 2.25 * ones(4)

    u_= [1.0, 8.0, 27.0, 64.0]' .* ones(4)
    u = [u_[:,i] for i = 1:size(u_,2)]
    t = [1.0, 2.0, 3.0, 4.0]
    A = LagrangeInterpolation(u,t)

    @test A(2.0) == 8.0 * ones(4)
    @test A(1.5) ≈ 3.375 * ones(4)
    @test A(3.5) ≈ 42.875 * ones(4)

    u = [repeat(u[i], 1, 3) for i=1:4]
    A = LagrangeInterpolation(u,t)

    @test A(2.0) == 8.0 * ones(4, 3)
    @test A(1.5) ≈ 3.375 * ones(4, 3)
    @test A(3.5) ≈ 42.875 * ones(4, 3)
end

@testset "Akima Interpolation" begin
    u = [0.0, 2.0, 1.0, 3.0, 2.0, 6.0, 5.5, 5.5, 2.7, 5.1, 3.0]
    t = collect(0.0:10.0)
    A = AkimaInterpolation(u, t)

    @test A(0.0) ≈ 0.0
    @test A(0.5) ≈ 1.375
    @test A(1.0) ≈ 2.0
    @test A(1.5) ≈ 1.5
    @test A(2.5) ≈ 1.953125
    @test A(3.5) ≈ 2.484375
    @test A(4.5) ≈ 4.1363636363636366866103344
    @test A(5.1) ≈ 5.9803623910336236590978842
    @test A(6.5) ≈ 5.5067291516462386624652936
    @test A(7.2) ≈ 5.2031367459745245795943447
    @test A(8.6) ≈ 4.1796554159017080820603951
    @test A(9.9) ≈ 3.4110386597938129327189927
    @test A(10.0) ≈ 3.0
end

@testset "ConstantInterpolation" begin

    t = [1.0, 2.0, 3.0, 4.0]

    @testset "Vector case" for u in
        [[1.0, 2.0, 0.0, 1.0], ["B", "C", "A", "B"]]

        A = ConstantInterpolation(u, t, dir=:right)
        @test A(0.5) == u[1]
        @test A(1.0) == u[1]
        @test A(1.5) == u[2]
        @test A(2.0) == u[2]
        @test A(2.5) == u[3]
        @test A(3.0) == u[3]
        @test A(3.5) == u[1]
        @test A(4.0) == u[1]
        @test A(4.5) == u[1]

        A = ConstantInterpolation(u, t) # dir=:left is default
        @test A(0.5) == u[1]
        @test A(1.0) == u[1]
        @test A(1.5) == u[1]
        @test A(2.0) == u[2]
        @test A(2.5) == u[2]
        @test A(3.0) == u[3]
        @test A(3.5) == u[3]
        @test A(4.0) == u[1]
        @test A(4.5) == u[1]
    end

    @testset "Matrix case" for u in
        [[1.0 2.0 0.0 1.0; 1.0 2.0 0.0 1.0], ["B" "C" "A" "B"; "B" "C" "A" "B"]]

        A = ConstantInterpolation(u, t, dir=:right)
        @test A(0.5) == u[:,1]
        @test A(1.0) == u[:,1]
        @test A(1.5) == u[:,2]
        @test A(2.0) == u[:,2]
        @test A(2.5) == u[:,3]
        @test A(3.0) == u[:,3]
        @test A(3.5) == u[:,1]
        @test A(4.0) == u[:,1]
        @test A(4.5) == u[:,1]

        A = ConstantInterpolation(u, t) # dir=:left is default
        @test A(0.5) == u[:,1]
        @test A(1.0) == u[:,1]
        @test A(1.5) == u[:,1]
        @test A(2.0) == u[:,2]
        @test A(2.5) == u[:,2]
        @test A(3.0) == u[:,3]
        @test A(3.5) == u[:,3]
        @test A(4.0) == u[:,1]
        @test A(4.5) == u[:,1]
    end

    @testset "Vector of Vectors case" for u in
        [[[1.0, 2.0], [0.0, 1.0], [1.0, 2.0], [0.0, 1.0]],
        [["B", "C"], ["A", "B"], ["B", "C"], ["A", "B"]]]

        A = ConstantInterpolation(u, t, dir=:right)
        @test A(0.5) == u[1]
        @test A(1.0) == u[1]
        @test A(1.5) == u[2]
        @test A(2.0) == u[2]
        @test A(2.5) == u[3]
        @test A(3.0) == u[3]
        @test A(3.5) == u[4]
        @test A(4.0) == u[4]
        @test A(4.5) == u[4]

        A = ConstantInterpolation(u, t) # dir=:left is default
        @test A(0.5) == u[1]
        @test A(1.0) == u[1]
        @test A(1.5) == u[1]
        @test A(2.0) == u[2]
        @test A(2.5) == u[2]
        @test A(3.0) == u[3]
        @test A(3.5) == u[3]
        @test A(4.0) == u[4]
        @test A(4.5) == u[4]
    end

    @testset "Vector of Matrices case" for u in
        [[[1.0 2.0; 1.0 2.0], [0.0 1.0; 0.0 1.0], [1.0 2.0; 1.0 2.0], [0.0 1.0; 0.0 1.0]],
        [["B" "C"; "B" "C"], ["A" "B"; "A" "B"], ["B" "C"; "B" "C"], ["A" "B"; "A" "B"]]]

        A = ConstantInterpolation(u, t, dir=:right)
        @test A(0.5) == u[1]
        @test A(1.0) == u[1]
        @test A(1.5) == u[2]
        @test A(2.0) == u[2]
        @test A(2.5) == u[3]
        @test A(3.0) == u[3]
        @test A(3.5) == u[4]
        @test A(4.0) == u[4]
        @test A(4.5) == u[4]

        A = ConstantInterpolation(u, t) # dir=:left is default
        @test A(0.5) == u[1]
        @test A(1.0) == u[1]
        @test A(1.5) == u[1]
        @test A(2.0) == u[2]
        @test A(2.5) == u[2]
        @test A(3.0) == u[3]
        @test A(3.5) == u[3]
        @test A(4.0) == u[4]
        @test A(4.5) == u[4]
    end
end

@testset "QuadraticSpline Interpolation" begin
    u = [0.0, 1.0, 3.0]
    t = [-1.0, 0.0, 1.0]

    A = QuadraticSpline(u,t)

    # Solution
    P₁ = x -> (x + 1)^2 # for x ∈ [-1, 0]
    P₂ = x -> 2*x + 1   # for x ∈ [ 0, 1]

    for (_t, _u) in zip(t, u)
        @test A(_t) == _u
    end
    @test A(-2.0) == P₁(-2.0)
    @test A(-0.5) == P₁(-0.5)
    @test A(0.7)  == P₂( 0.7)
    @test A(2.0)  == P₂( 2.0)

    u_= [0.0, 1.0, 3.0]' .* ones(4)
    u = [u_[:,i] for i = 1:size(u_,2)]
    A = QuadraticSpline(u,t)
    @test A(-2.0) == P₁(-2.0) * ones(4)
    @test A(-0.5) == P₁(-0.5) * ones(4)
    @test A(0.7)  == P₂( 0.7) * ones(4)
    @test A(2.0)  == P₂( 2.0) * ones(4)

    u = [repeat(u[i], 1, 3) for i=1:3]
    A = QuadraticSpline(u,t)
    @test A(-2.0) == P₁(-2.0) * ones(4, 3)
    @test A(-0.5) == P₁(-0.5) * ones(4, 3)
    @test A(0.7)  == P₂( 0.7) * ones(4, 3)
    @test A(2.0)  == P₂( 2.0) * ones(4, 3)
end

@testset "CubicSpline Interpolation" begin
    u = [0.0, 1.0, 3.0]
    t = [-1.0, 0.0, 1.0]

    A = CubicSpline(u,t)

    # Solution
    P₁ = x -> 1 + 1.5x + x^2 + 0.5x^3 # for x ∈ [-1.0, 0.0]
    P₂ = x -> 1 + 1.5x + x^2 - 0.5x^3 # for x ∈ [0.0, 1.0]

    for (_t, _u) in zip(t, u)
        @test A(_t) == _u
    end
    for x in (-1.5, -0.5, -0.7)
        @test A(x) ≈ P₁(x)
    end
    for x in (0.3, 0.5, 1.5)
        @test A(x) ≈ P₂(x)
    end

    u_= [0.0, 1.0, 3.0]' .* ones(4)
    u = [u_[:,i] for i = 1:size(u_,2)]
    A = CubicSpline(u,t)
    for x in (-1.5, -0.5, -0.7)
        @test A(x) ≈ P₁(x) * ones(4)
    end
    for x in (0.3, 0.5, 1.5)
        @test A(x) ≈ P₂(x) * ones(4)
    end

    u = [repeat(u[i], 1, 3) for i=1:3]
    A = CubicSpline(u,t)
    for x in (-1.5, -0.5, -0.7)
        @test A(x) ≈ P₁(x) * ones(4, 3)
    end
    for x in (0.3, 0.5, 1.5)
        @test A(x) ≈ P₂(x) * ones(4, 3)
    end
end

# BSpline Interpolation and Approximation
t = [0,62.25,109.66,162.66,205.8,252.3]
u = [14.7,11.51,10.41,14.95,12.24,11.22]

A = BSplineInterpolation(u,t,2,:Uniform,:Uniform)

@test [A(25.0), A(80.0)] == [13.454197730061425, 10.305633616059845]
@test [A(190.0), A(225.0)] == [14.07428439395079, 11.057784141519251]
@test [A(t[1]), A(t[end])] == [u[1], u[end]]

A = BSplineInterpolation(u,t,2,:ArcLen,:Average)

@test [A(25.0), A(80.0)] == [13.363814458968486, 10.685201117692609]
@test [A(190.0), A(225.0)] == [13.437481084762863, 11.367034741256463]
@test [A(t[1]), A(t[end])] == [u[1], u[end]]

A = BSplineApprox(u,t,2,4,:Uniform,:Uniform)

@test [A(25.0), A(80.0)] ≈ [12.979802931218234, 10.914310609953178]
@test [A(190.0), A(225.0)] ≈ [13.851245975109263, 12.963685868886575]
@test [A(t[1]), A(t[end])] ≈ [u[1], u[end]]

# Curvefit Interpolation
rng = StableRNG(12345)
model(x, p) = @. p[1]/(1 + exp(x - p[2]))
t = range(-10, stop=10, length=40)
u = model(t, [1.0, 2.0]) + 0.01*randn(rng, length(t))
p0 = [0.5, 0.5]

A = Curvefit(u, t, model, p0, LBFGS())

ts = [-7.0, -2.0, 0.0, 2.5, 5.0]
vs = [1.0013468217936277, 0.9836755196317837, 0.8833959853995836, 0.3810348276782708, 0.048062978598861855]
us = A.(ts)

@test vs ≈ us

# missing values handling tests
u = [1.0, 4.0, 9.0, 16.0, 25.0, missing, missing]
t = [1.0, 2.0, 3.0, 4.0, missing, 6.0, missing]
A = QuadraticInterpolation(u,t)

@test A(2.0) == 4.0
@test A(1.5) == 2.25
@test A(3.5) == 12.25
@test A(2.5) == 6.25

u = copy(hcat(u, u)')
A = QuadraticInterpolation(u,t)

@test A(2.0) == [4.0, 4.0]
@test A(1.5) == [2.25, 2.25]
@test A(3.5) == [12.25,12.25]
@test A(2.5) == [6.25, 6.25]


# ---
# title: "`closing`"
# id: op_closing
# cover: assets/closing.png
# ---

# `closing` operator is defined as `dilate(erode(img))`. Intuitively, closing operation
# fills the black holes in the image.

using ImageMorphology
using TestImages
using ImageBase
using ImageShow

img = restrict(testimage_dip3e("fig0940")) # rice
img01 = @. Gray(img > 0.5)
mosaic(img, img01; nrow=1)

#-

out1 = closing(img) # default: all spatial dimensions, r=1, a box-shape SE
out2 = closing(img; dims=(2,)) # only apply to the second dimension
out3 = closing(img; r=5) # half-size r=5

## also to the binary version
out1_01 = closing(img01)
out2_01 = closing(img01; dims=(2,))
out3_01 = closing(img01; r=5)

mosaic(out1, out2, out3, out1_01, out2_01, out3_01; nrow=2, rowmajor=true)

# ## See also

# [`closing`](@ref op_closing) is the dual operator of `closing` in the following sense:

complement.(closing(img)) == closing(complement.(img))

# For a comprehensive and more accurate documentation, please check the [`closing`](@ref)
# reference page.

# save cover image #src
using FileIO #src
mkpath("assets") #src
img = Gray.(restrict(testimage("blobs"))) #src
save("assets/closing.png", closing(img; r=2)) #src


###
# This file contains a detailed example, demonstrating the typical workflow
# Graft aims to support.
# The dataset used here was constructed by splicing together two separate datasets:
#
# 1. `SOCR Data MLB HeightsWeights`: Heights, ages and weights of Baseball players (Vertex Data). References:
#   * Jarron M. Saint Onge, Patrick M. Krueger, Richard G. Rogers. (2008) Historical trends in height,
#   weight, and body mass: Data from U.S. Major League Baseball players, 1869-1983, Economics & Human
#   Biology, Volume 6, Issue 3, Symposium on the Economics of Obesity, December 2008, Pages 482-488,
#   ISSN 1570-677X, DOI: 10.1016/j.ehb.2008.06.008.
#   * Jarron M. Saint Onge, Richard G. Rogers, Patrick M. Krueger. (2008) Major League Baseball Players'
#   Life Expectancies, Southwestern Social Science Association, Volume 89, Issue 3, pages 817–830,
#   DOI: 10.1111/j.1540-6237.2008.00562.x.
# 2. Advogato Trust Network : Edge weights between 0 and 1. References:
#   * Advogato network dataset -- KONECT, July 2016. [http](http://konect.uni-koblenz.de/networks/advogato)
#   * Paolo Massa, Martino Salvetti, and Danilo Tomasoni. Bowling alone and trust decline in social network
#   sites. In Proc. Int. Conf. Dependable, Autonomic and Secure Computing, pages 658--663, 2009.
#
# The dataset has 6541 vertices, 51127 edges.
# Vertex properties: Age, Height(cm), Weight(kg)
# Edge properties  : Trust(float)
###

using Graft
using StatsBase
import LightGraphs

## Load and summarize the graph.

# Load the graph
download(
 "https://raw.githubusercontent.com/pranavtbhat/Graft.jl/gh-pages/Datasets/baseball.txt",
 joinpath(Pkg.dir("Graft"), "examples/baseball.txt")
)
g = loadgraph(joinpath(Pkg.dir("Graft"), "examples/baseball.txt"))

# Get the graph's size
size(g)

# Get an edge iterator for g
edges(g)

# List vertex labels
encode(g)

# Split the graph into vertex and edge descriptors
V,E = g;

# Display the vertex table
V

# Display the edge table
E

## Run some metadata queries

# Find the average BMI of baseball players
@query(g |> eachvertex(v.Weight / (v.Height / 100) ^ 2)) |> mean

# Find the median height of baseball players in their 20s
@query(g |> filter(v.Age < 30,v.Age >= 20) |> eachvertex(v.Height * 0.0328084)) |> median

# Find the mean age difference in strong relationships
@query(g |> filter(e.Trust > 0.8) |> eachedge(s.Age - t.Age)) |> abs |> mean

# Find fred's 3 hop neighborhood (friends and friends-of-friends and so on)
fred_nhood = hopgraph(g, "fred", 3)

# See how well younger players in fred's neighborhood trust each other
@query(fred_nhood |> filter(v.Age > 30) |> eachedge(e.Trust)) |> mean

## Export data to LightGraphs.jl

# Find the 2 hop neighborhood of 2 separate vertices (multi seed traversal)
sg = hopgraph(g, ["nikolay", "jbert"], 3)

# Generate an edge distance property on the inverse of normalized-trust
dists = @query(sg |> eachedge(1 / e.Trust ));
seteprop!(sg, :, dists, :Dist);

# Trim edges of very high distance
sg = @query(sg |> filter(e.Dist < 10))

# Export the graph's adjacency matrix
M = export_adjacency(sg)
lg = LightGraphs.DiGraph(M)

# Export the edge distance property
D = export_edge_property(sg, :Dist)

# Compute betweenness centrailty
centrality = LightGraphs.betweenness_centrality(lg)

# Set the centrality as a vertex property
setvprop!(sg, :, centrality, :Centrality)

# Apply all pair shortest paths on the graph
apsp = LightGraphs.floyd_warshall_shortest_paths(lg, D).dists;

# Add the new shortest paths as a property to the graph
eit = edges(sg);
seteprop!(sg, :, [apsp[e.second,e.first] for e in eit], :Shortest_Dists);

# Show new vertex descriptor
VertexDescriptor(sg)

# Show the new edge descriptor
EdgeDescriptor(sg)


function my_f(x::Float64, y::Float64)
        return 2x + 3y
end


using Plots, SeisNoise, DTWDT, PlotlyJS
"""
Allen and Smith autoregressive lag-1 autocorrelation coefficient.
In an AR(1) model
    x(t) - <x> = γ(x(t-1) - <x>) + α z(t)
where <x> is the process mean, γ and α are process parameters and z(t) is a
Gaussian unit-variance white noise

References
    ----------
    [1] Allen, M. R. and Smith, L. A. Monte Carlo SSA: detecting
        irregular oscillations in the presence of colored noise.
        *Journal of Climate*, **1996**, 9(12), 3373-3404.
        <http://dx.doi.org/10.1175/1520-0442(1996)009<3373:MCSDIO>2.0.CO;2>
    [2] http://www.madsci.org/posts/archives/may97/864012045.Eg.r.html

Original function translated from https://github.com/regeirk/pycwt/blob/master/pycwt/helpers.py
"""
function ar1(x::AbstractArray)
    N = size(x)
    xm = mean(x)
    x = x .- xm

    # estimate the lag zero and one covariance
    c0 = (x' .* x) / N
    c1 = (x[0:N-1]' .* x[1:N]) / (N-1)

    # according to A. Grinsteds' substitutions
    A = c0 * N^2
    B = -c1 .* N .- A - 2*c0 .+ 2*c1 .- c1 * N^2 .+ c0*N
    C = N * (c0 .+ c1*N .- c1)
    D = B^2 - 4*A .* C

    if D > 0
        g = (-B - sqrt(D) / (2*A))
    else
        @warn "Cannot place an upperbound on the unbiased AR(1). Series is
               too short or trend too large."
    end

    # according to Allen & Smith (1996), footnote 4
    μ2 = -1 / N + (2 / N^2) * ((N - g^N) / (1-g) - g * (1 - g^(N-1)) / (1-g)^2)
    c0t = c0 / (1-μ2)
    a = sqrt((1-g^2) * c0t)

    return g, a, μ2
end

function smooth(W::AbstractArray, dt::Float64, dj::Float64, scales::Array{Float64,1})
    # The smoothing is performed by using a filter given by the absolute
    # value of the wavelet function at each scale, normalized to have a
    # total weight of unity, according to suggestions by Torrence &
    # Webster (1999) and by Grinsted et al. (2004).
    m, n = size(W)

    # Filter in time
    k = 2π*fftfreq(length(W[1,:]))
    k2 = k.^2
    snorm = scales ./ dt

    # Smoothing by Gaussian window (absolute value of wavelet function)
    # using the convolution theorem: multiplication by Gaussian curve in
    # Fourier domain for each scale, outer product of scale and frequency
    F = exp.(-0.5 .* (snorm.^2) .* k2') # outer product
    smooth = (ifft(F .* fft(W,2), 2))

    T = smooth[:, 1:n] # Remove possible padded region due to FFTW

    # Filter in scale. For the Morlet wavelet, this is simply a boxcar with 0.6 width
    # construct boxcar
    wsize = convert(Int64, round(0.60 / dj * 2))
    if wsize % 2 == 0
        wsize+=1
    end
    halfWin = div(wsize,2)

    # iterate over 'horizontal' and smooth in the 'vertical'
    # this could also be done by adding an axis to the transpose and performing a 2d convolution
    for i=1:size(T,2)
        # pad signal for 'same' padding
        paddedT = vcat(T[1, i]*ones(halfWin), T[:, i], T[end, i]*ones(halfWin))
        # smooth signal
        win = Array{eltype(paddedT), 1}(undef, wsize)
        win.=ones(wsize)/wsize
        smoothT = conv(paddedT, win)
        # trim signal
        T[:, i] = smoothT[2*halfWin+1:end-2*halfWin]
    end

    return T
end

"""
Modified wavelet coherence transform
Modified from original by Congcong Yuan
"""
function wct_modified(y1, y2, dt, dj, s0, J; wavelet::String="morlet", norm::Bool=true, sig::Bool=false, siglvl::Float64=0.95)
    # define wavelet
    if wavelet=="morlet"
        wav = WT.Morlet(6)
    end

    fλ = (4*π) / (wav.σ + sqrt(2 + wav.σ^2))

    # calculate std dev of both input signals (std(y1), std(y2))
    std1 = std(y1)
    std2 = std(y2)
    # normalize
    if norm
        y1 = (y1 .- mean(y1)) ./ std1
        y2 = (y2 .- mean(y2)) ./ std2
    end

    # calculate the CWT of the time series, using identical parameters for both calculations
    W1, sj, freqs, coi = cwt(y1, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)
    W2, sj, freqs, coi = cwt(y2, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)

    # fourier frequencies corresponding to wavelet scales
    freqs = 1 ./ (fλ .* sj)

    scales1 = Array{Float64,2}(undef, size(W1))
    scales2 = Array{Float64,2}(undef, size(W1))
    for i=1:size(scales1, 2)
        scales1[:,i] .= sj
        scales2[:,i] .= sj
    end

    # smooth wavelet spectra before truncating
    S1 = smooth(abs.(W1).^2 ./ scales1, dt, dj, sj)
    S2 = smooth(abs.(W2).^2 ./ scales2, dt, dj, sj)

    # compute wavelet transform coherence
    W12 = W1 .* conj(W2)

    scales = scales1
    S12 = smooth(W12 ./ scales, dt, dj, sj)
    WCT = abs.(S12).^2 ./ (S1 .* S2)
    aWCT = angle.(W12)

    # calculate cross spectrum and its amplitude
    WXS = W12
    WXA = abs.(S12)

    # calculate the significance using a Monte Carlo simulation with 95%
    # confidence as a function of scale
    if sig
        # TODO: implement and test the wct_significance and ar1 functions
        a1, b1, c1 = ar1(y1)
        a2, b2, c2 = ar1(y2)
        sig = wct_significance(a1, a2, dt=dt, dj=dj, s0=s0, J=J, significance_level=siglvl, wavelet=wavelet)
    else
        sig = [0]
    end

    return WXS, WXA, real.(WCT), aWCT, coi, freqs, sig
end

"""
Compute wavelet cross spectrum given an array of frequency band minima and maxima.
"""
function wxs_freqbands(cur, ref, t, twindow, freqbands, dj, s0, J; wvn::String="morlet", unwrapflag::Bool=false, sig::Bool=false, siglvl::Float64=0.95, windowflag::Bool=true, nwindow::Float64=1.5)
    # define sample frequency
    dt = t[2] - t[1]
    fs = 1/dt

    # perform wavelet coherence transform
    WXS, WXA, WCT, aWCT, coi, freqs, sig = wct_modified(cur, ref, dt, dj, s0, J, wavelet=wvn, sig=sig, siglvl=siglvl)

    # do inverse cwt for different frequency bands
    if unwrapflag==true
        phase = unwrap(aWCT, dims=ndims(aWCT))
    else
        phase = aWCT
    end

    (nband, nfreq) = size(freqbands)
    if nband==0 || nfreq!=2
        println("Error: please check if inputs in freqbands is right!")
    else
        dtt = zeros(nband)
        err = zeros(nband)

        # iterate over frequency bands
        for iband=1:nband
            fmin = freqbands[iband, 1]
            fmax = freqbands[iband, 2]
            if (fmax > maximum(freqs)) || (fmax <= fmin)
                println("Error: please ensure columns 1 and 2 are right frequency limits in freqbands!")
            else
                freq_ind = findall(f->(f>=fmin && f<=fmax), freqs)

                iphase = []
                iphase = phase[freq_ind, :]

                if windowflag
                    tmin = twindow[1]
                    tmax = twindow[2]

                    # time checks
                    if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                        println("Error: please input right time limits in the time window!")
                    else
                        # truncate data with the time window
                        t_ind = findall(x->(x≤tmax && x≥tmin), t)
                        wt = t[t_ind]
                    end
                else # using dynamic time window
                    tmin = twindow[1]
                    tmax = twindow[1] + nwindow*(1/fmin + 1/fmax)/2.

                    if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                        println("Error: please input right time limits in the time window!")
                    else
                        # truncate data with the time window
                        t_ind = findall(x->(x≤tmax && x≥tmin), t)
                        wt = t[t_ind]
                    end
                end

                delta_t = zeros(nband, length(t))
                delta_t_err = zeros(nband, length(t))
                # do regression to get slope dt_m between phase delays and frequency band
                for itime=1:length(wt)
                    # get weights
                    # w = 1 ./ WXA[freq_ind,itime]
                    w = 1 ./ WCT[freq_ind, itime]
                    infNaN = findall(x->(isnan.(x) || isinf.(x)), w)
                    if length(infNaN)!=0
                        w[infNaN] .= 1.0
                    end

                    # perform a WLS inversion
                    # This does NOT force the best fit line through the origin
                    model = glm(@formula(Y ~ X),DataFrame(X=freqs[freq_ind]*2π,Y=iphase[:,itime]),Normal(),
                    IdentityLink(),wts=w)

                    delta_t[iband, itime] = coef(model)[2]
                    delta_t_err[iband, itime] = stderror(model)[2]
                end

                # do regression in time to get time shift
                # w2 = 1/np.mean(WXA[freq_ind,:],axis=0)
                w2 = 1 ./ mean(WCT[freq_ind,:], dims=1)
                infNaN =findall(x->(isnan.(x) || isinf.(x)), w2[:])
                if length(infNaN)!=0
                    w2[:, infNaN] .= 1.0
                end
            end

            if length(wt)>2
                if delta_t == nothing
                    continue
                end

                # perform a WLS inversion
                # This DOES force the best fit line through the origin
                model = glm(@formula(Y ~0 + X),DataFrame(X=wt,Y=delta_t[iband, :]),Normal(),
                IdentityLink(),wts=w2[:])
                dtt[iband] = coef(model)[1]
                err[iband] = stderror(model)[1]
            else
                print("not enough points to estimate dv/v")
                dtt[iband]=NaN
                err[iband]=NaN
            end
        end
    end

    return freqbands, dtt.*100, err.*100
end

"""
Compute wavelet cross spectrum given an array of frequencies
"""
function wxs_allfreq(cur, ref, t, twindow, fwindow, dj, s0, J; wvn::String="morlet", unwrapflag::Bool=false, sig::Bool=false, siglvl::Float64=0.95, windowflag::Bool=true, nwindow::Float64=1.5)
    # Part 1 ----- Cross spectrum analysis
    # define sample frequency
    dt = t[2] - t[1]
    fs = 1/dt
    (fmin, fmax) = fwindow

    # perform wavelet coherence transform
    WXS, WXA, WCT, aWCT, coi, freqs, sig = wct_modified(cur, ref, dt, dj, s0, J, wavelet=wvn, sig=sig, siglvl=siglvl)

    # do inverse cwt for different frequency bands
    if unwrapflag==true
        phase = unwrap(aWCT, dims=2)
    else
        phase = aWCT
    end

    delta_t = Array{Float64,2}(undef, size(phase))
    for i=1:size(phase,2)
        delta_t[:, i] = phase[:, i] ./ (2π.*freqs)
    end

    # zero out cone of influence and data outside frequency band
    if (fmax > maximum(freqs) || fmax<=fmin)
        println("Error: please input correct frequency limits in the frequency window!")
    else
        freq_indout = findall(f->(f < fmin || f > fmax), freqs)
        delta_t[freq_indout, :] .= 0.
        freq_indin = findall(f->(f >= fmin && f <= fmax), freqs)
    end

    # Part 2 ------ dv/v
    dvv = zeros(size(freqs))
    err = zeros(size(freqs))

    if windowflag
        (tmin, tmax) = twindow

        # time checks
        if tmin < minimum(twindow) || tmax > maximum(twindow) || tmax <= tmin
            println("Error: please input right time limits in the time window!")
        else
            # trim time vector
            t_ind = findall(x->(x≤tmax && x≥tmin), t)
            # trim vectors to correspond to time
            wt = t[t_ind]
        end

        # perform linear regression
        for ifreq in freq_indin
            if length(wt) > 2
                if delta_t[ifreq, :] == nothing
                    continue
                end

                # use WXA as weight for regression
                w = 1 ./ WCT[ifreq, :]
                infNaN = findall(x->(isnan.(x) || isinf.(x)), w)
                if length(infNaN)!=0
                    w[infNaN] .= 1.0
                end
                # perform linear regression
                model = glm(@formula(Y ~0 + X),DataFrame(X=wt,Y=delta_t[ifreq,:]),Normal(),
                            IdentityLink(),wts=w)

                dvv[ifreq] = coef(model)[1]
                err[ifreq] = stderror(model)[1]
            else
                println("Not enough points to estimate dv/v")
                dvv[ifreq] = NaN
                err[ifreq] = NaN
            end
        end
    else # using dynamic time window
        for ifreq in freq_indin
            tmin = twindow[1]
            tmax = twindow[1] + nwindow*(1.0 / freqs[ifreq])

            # time checks
            if tmin < minimum(twindow) || tmax > maximum(twindow) || tmax <= tmin
                println("Error: please input right time limits in the time window!")
            else
                # trim time vector
                t_ind = findall(x->(x≤tmax && x≥tmin), t)
                # trim vectors to correspond to time
                wt = t[t_ind]
            end

            if length(wt) > 2
                if delta_t[ifreq, :] == nothing
                    continue
                end

                # use WXA as weight for regression
                w = 1 ./ WCT[ifreq, :]
                infNaN = findall(x->(isnan.(x) || isinf.(x)), w)
                if length(infNaN)!=0
                    w[infNaN] .= 1.0
                end
                # perform linear regression
                model = glm(@formula(Y ~0 + X),DataFrame(X=wt,Y=delta_t[ifreq,:]),Normal(),
                            IdentityLink(),wts=w)

                dvv[ifreq] = coef(model)[1]
                err[ifreq] = stderror(model)[1]
            else
                println("Not enough points to estimate dv/v")
                dvv[ifreq] = NaN
                err[ifreq] = NaN
            end
        end
    end

    return freqs, dvv.*100, err.*100
end

function wts_freqbands(cur, ref, t, twindow, freqbands, dj, s0, J; dvmax::Float64=0.1, ndv::Int64=100, wvn::String="morlet", normalize::Bool=true, windowflag::Bool=true, nwindow::Float64=1.5)
    # define sample frequency
    dt = t[2] - t[1]
    fs = 1/dt

    # apply cwt to two traces
    # define wavelet
    if wvn=="morlet"
        wav = WT.Morlet(6)
    end
    # calculate the CWT of the time series, using identical parameters for both calculations
    cwt1, sj, freqs, coi = cwt(cur, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)
    cwt2, sj, freqs, coi = cwt(ref, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)

    (nband, nfreq) = size(freqbands)
    if nband==0 || nfreq != 2
        println("Error: please check if inputs in freqbands is right!")
    else
        dvv = zeros(Float32, nband)
        cc = zeros(Float32, nband)
        cdp = zeros(Float32, nband)
        err = zeros(Float32, nband)

        # iterate over frequency bands
        for iband=1:nband
            (fmin, fmax) = freqbands[iband, :]

            if (fmax > maximum(freqs) || fmax <= fmin)
                println("Error: please ensure columns 1 and 2 are right frequency limits in freqbands!")
            else
                freq_ind = findall(f->(f>=fmin && f<=fmax), freqs)

                # perform icwt
                icwt1 = icwt(cwt1[freq_ind, :], CFW(wav,1/dj), sj[freq_ind], dt=dt, dj=dj)
                icwt2 = icwt(cwt2[freq_ind, :], CFW(wav,1/dj), sj[freq_ind], dt=dt, dj=dj)

                if windowflag
                    tmin = twindow[1]
                    tmax = twindow[2]

                    # time checks
                    if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                        println("Error: please input right time limits in the time window!")
                    else
                        # trim time vector
                        t_ind = findall(x->(x≤tmax && x≥tmin), t)
                    end
                else
                    tmin = twindow[1]
                    tmax = twindow[1] + nwindow*(1/fmin + 1/fmax)/2.

                    # time checks
                    if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                        println("Error: please input right time limits in the time window!")
                    else
                        # trim time vector
                        t_ind = findall(x->(x≤tmax && x≥tmin), t)
                    end
                end
                # trim vectors to correspond to time
                wt = t[t_ind]
                window = collect(1:length(wt))
                rcwt1 = real.(icwt1)
                rcwt2 = real.(icwt2)
                wcwt1 = rcwt1[t_ind]
                wcwt2 = rcwt2[t_ind]

                # normalize both signals, if appropriate
                if normalize
                    ncwt1 = ((wcwt1 .- mean(wcwt1)) ./ std(wcwt1))[:]
                    ncwt2 = ((wcwt2 .- mean(wcwt2)) ./ std(wcwt2))[:]
                else
                    ncwt1 = wcwt1[:]
                    ncwt2 = wcwt2[:]
                end

                # use stretching to extract dv/v
                (dvv[iband], cc[iband], cdp[iband], eps, err[iband], allC) = stretching(ncwt1, ncwt2, wt, window, fmin, fmax, dvmin=-dvmax, dvmax=dvmax)
            end
        end
    end

    return freqbands, dvv, cc, cdp, err
end

function wts_allfreqs(cur, ref, t, twindow, fwindow, dj, s0, J; dvmax::Float64=0.1, ndv::Int64=100, wvn::String="morlet", normalize::Bool=true, windowflag::Bool=true, nwindow::Float64=1.5)
    # define sample frequency
    dt = t[2] - t[1]
    fs = 1/dt
    (fmin, fmax) = fwindow

    # apply cwt to two traces
    # define wavelet
    if wvn=="morlet"
        wav = WT.Morlet(6)
    end
    # calculate the CWT of the time series, using identical parameters for both calculations
    cwt1, sj, freqs, coi = cwt(cur, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)
    cwt2, sj, freqs, coi = cwt(ref, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)

    # extract real part of cwt
    rcwt1 = real.(cwt1)
    rcwt2 = real.(cwt2)

    # zero out cone of influence and data outside frequency band
    if (fmax > maximum(freqs) || fmax<=fmin)
        println("Error: please input correct frequency limits in the frequency window!")
    else
        freq_indout = findall(f->(f < fmin || f > fmax), freqs)
        rcwt1[freq_indout, :] .= 0.
        rcwt2[freq_indout, :] .= 0.
        freq_indin = findall(f->(f >= fmin && f <= fmax), freqs)

        # Use stretching method to extract dvv
        nfreq=length(freqs)

        dvv = zeros(Float32, nfreq)
        cc = zeros(Float32, nfreq)
        cdp = zeros(Float32, nfreq)
        err = zeros(Float32, nfreq)

        for ifreq in freq_indin
            if windowflag
                tmin = twindow[1]
                tmax = twindow[2]

                # time checks
                if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                    println("Error: please input right time limits in the time window!")
                else
                    # trim time vector
                    t_ind = findall(x->(x≤tmax && x≥tmin), t)
                end
            else
                tmin = twindow[1]
                tmax = twindow[1] + nwindow*(1/fmin + 1/fmax)/2.

                # time checks
                if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                    println("Error: please input right time limits in the time window!")
                else
                    # trim time vector
                    t_ind = findall(x->(x≤tmax && x≥tmin), t)
                end
            end

            # prepare time axis and its indices
            wt = t[t_ind]
            it = collect(1:length(wt))
            # prepare windowed data
            wcwt1 = rcwt1[ifreq, t_ind]
            wcwt2 = rcwt2[ifreq, t_ind]

            # normalize both signals if appropriate
            # normalize both signals, if appropriate
            if normalize
                ncwt1 = ((wcwt1 .- mean(wcwt1)) ./ std(wcwt1))[:]
                ncwt2 = ((wcwt2 .- mean(wcwt2)) ./ std(wcwt2))[:]
            else
                ncwt1 = wcwt1[:]
                ncwt2 = wcwt2[:]
            end

            # compute the stretching
            window = collect(1:length(wt))
            # use stretching to extract dv/v
            (dvv[ifreq], cc[ifreq], cdp[ifreq], eps, err[ifreq], allC) = stretching(ncwt1, ncwt2, wt, window, fmin, fmax, dvmin=-dvmax, dvmax=dvmax)
        end
    end

    return freqs, dvv, cc, cdp, err
end

function wtdtw_freqbands(cur, ref, t, twindow, freqbands, maxLag, b, direction, dj, s0, J; wvn::String="morlet", normalize::Bool=true, windowflag::Bool=true, nwindow::Float64=1.5)
    # define sample frequency
    dt = t[2] - t[1]
    fs = 1/dt

    # apply cwt to two traces
    # define wavelet
    if wvn=="morlet"
        wav = WT.Morlet(6)
    end
    # calculate the CWT of the time series, using identical parameters for both calculations
    cwt1, sj, freqs, coi = cwt(cur, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)
    cwt2, sj, freqs, coi = cwt(ref, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)

    (nband, nfreq) = size(freqbands)
    if nband==0 || nfreq != 2
        println("Error: please check if inputs in freqbands is right!")
    else
        dvv = zeros(Float32, nband)
        err = zeros(Float32, nband)

        # iterate over frequency bands
        for iband=1:nband
            (fmin, fmax) = freqbands[iband, :]

            if (fmax > maximum(freqs) || fmax <= fmin)
                println("Error: please ensure columns 1 and 2 are right frequency limits in freqbands!")
            else
                freq_ind = findall(f->(f>=fmin && f<=fmax), freqs)

                # perform icwt
                icwt1 = icwt(cwt1[freq_ind, :], CFW(wav,1/dj), sj[freq_ind], dt=dt, dj=dj)
                icwt2 = icwt(cwt2[freq_ind, :], CFW(wav,1/dj), sj[freq_ind], dt=dt, dj=dj)

                if windowflag
                    tmin = twindow[1]
                    tmax = twindow[2]

                    # time checks
                    if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                        println("Error: please input right time limits in the time window!")
                    else
                        # trim time vector
                        t_ind = findall(x->(x≤tmax && x≥tmin), t)
                    end
                else
                    tmin = twindow[1]
                    tmax = twindow[1] + nwindow*(1/fmin + 1/fmax)/2.

                    # time checks
                    if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                        println("Error: please input right time limits in the time window!")
                    else
                        # trim time vector
                        t_ind = findall(x->(x≤tmax && x≥tmin), t)
                    end
                end
                # trim vectors to correspond to time
                wt = t[t_ind]
                window = collect(1:length(wt))
                rcwt1 = real.(icwt1)
                rcwt2 = real.(icwt2)
                wcwt1 = rcwt1[t_ind]
                wcwt2 = rcwt2[t_ind]

                # normalize both signals, if appropriate
                if normalize
                    ncwt1 = ((wcwt1 .- mean(wcwt1)) ./ std(wcwt1))[:]
                    ncwt2 = ((wcwt2 .- mean(wcwt2)) ./ std(wcwt2))[:]
                else
                    ncwt1 = wcwt1[:]
                    ncwt2 = wcwt2[:]
                end

                (stbarTime, stbar, dist, error) = dtwdt(ncwt1, ncwt2, dt, maxLag=maxLag, b=b, direction=direction)

                # perform linear regression
                model = glm(@formula(Y ~0 + X),DataFrame(X=wt,Y=stbarTime),Normal(),
                            IdentityLink(),wts=ones(length(wt)))

                dvv[iband] = coef(model)[1]
                err[iband] = stderror(model)[1]
            end
        end
    end
    return freqs, dvv, err
end

function wtdtw_allfreqs(cur, ref, t, twindow, fwindow, maxLag, b, direction, dj, s0, J; wvn::String="morlet", normalize::Bool=true, windowflag::Bool=true, nwindow::Float64=1.5)
    # define sample frequency
    dt = t[2] - t[1]
    fs = 1/dt
    (fmin, fmax) = fwindow

    # apply cwt to two traces
    # define wavelet
    if wvn=="morlet"
        wav = WT.Morlet(6)
    end
    # calculate the CWT of the time series, using identical parameters for both calculations
    cwt1, sj, freqs, coi = cwt(cur, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)
    cwt2, sj, freqs, coi = cwt(ref, CFW(wav,1/dj), J1=J, dt=dt, s0=s0)

    # extract real part of cwt
    rcwt1 = real.(cwt1)
    rcwt2 = real.(cwt2)

    # zero out cone of influence and data outside frequency band
    if (fmax > maximum(freqs) || fmax<=fmin)
        println("Error: please input correct frequency limits in the frequency window!")
    else
        freq_indout = findall(f->(f < fmin || f > fmax), freqs)
        rcwt1[freq_indout, :] .= 0.
        rcwt2[freq_indout, :] .= 0.
        freq_indin = findall(f->(f >= fmin && f <= fmax), freqs)

        # Use stretching method to extract dvv
        nfreq=length(freqs)

        dvv = zeros(Float32, nfreq)
        err = zeros(Float32, nfreq)

        for ifreq in freq_indin
            if windowflag
                tmin = twindow[1]
                tmax = twindow[2]

                # time checks
                if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                    println("Error: please input right time limits in the time window!")
                else
                    # trim time vector
                    t_ind = findall(x->(x≤tmax && x≥tmin), t)
                end
            else
                tmin = twindow[1]
                tmax = twindow[1] + nwindow*(1/fmin + 1/fmax)/2.

                # time checks
                if tmin < minimum(t) || tmax > maximum(t) || tmax <= tmin
                    println("Error: please input right time limits in the time window!")
                else
                    # trim time vector
                    t_ind = findall(x->(x≤tmax && x≥tmin), t)
                end
            end

            # prepare time axis and its indices
            wt = t[t_ind]
            it = collect(1:length(wt))
            # prepare windowed data
            wcwt1 = rcwt1[ifreq, t_ind]
            wcwt2 = rcwt2[ifreq, t_ind]

            # normalize both signals if appropriate
            # normalize both signals, if appropriate
            if normalize
                ncwt1 = ((wcwt1 .- mean(wcwt1)) ./ std(wcwt1))[:]
                ncwt2 = ((wcwt2 .- mean(wcwt2)) ./ std(wcwt2))[:]
            else
                ncwt1 = wcwt1[:]
                ncwt2 = wcwt2[:]
            end

            (stbarTime, stbar, dist, error) = dtwdt(ncwt1, ncwt2, dt, maxLag=maxLag, b=b, direction=direction)

            # perform linear regression
            model = glm(@formula(Y ~0 + X),DataFrame(X=wt,Y=stbarTime),Normal(),
                        IdentityLink(),wts=ones(length(wt)))

            dvv[ifreq] = coef(model)[1]
            err[ifreq] = stderror(model)[1]
        end
    end

    return freqs, dvv, err
end


#!/usr/bin/env julia

using Test

function toMaiden(lat::Real, lon::Real; precision::Int=3)
    #Returns a maidenloc for specified lat-lon tuple at specified level.

  a = divrem(lon+180, 20)
  b = divrem(lat+90, 10)

  astring = string(Char('A' + Int(a[1])), Char('A'+ Int(b[1])))
  lon = a[2] / 2.
  lat = b[2]
  i = 1

  while i < precision
    i += 1
    a = divrem(lon, 1)
    b = divrem(lat, 1)

    if (i % 2) == 0
      astring *= string(Int(a[1])) * string(Int(b[1]))
      lon = 24 * a[2]
      lat = 24 * b[2]
    else
      astring *= string('A' + Int(a[1]), 'A' + Int(b[1]))
      lon = 10 * a[2]
      lat = 10 * b[2]
    end
  end

  if length(astring) >= 6
    astring = astring[1:4] * lowercase(astring[5:6]) * astring[7:end]
  end

  return astring

end

function toLoc(maiden::String)

    maiden = uppercase(maiden)

    N = length(maiden)

    Oa = Char('A')
    lon = -180.
    lat = -90.
# %% first pair
    lon += (Char(maiden[1])-Oa)*20
    lat += (Char(maiden[2])-Oa)*10
# %% second pair
    if N >= 4
        lon += parse(Int, maiden[3])*2
        lat += parse(Int, maiden[4])*1
    end
    if N >= 6
        lon += (Char(maiden[5])-Oa) * 5.0/60
        lat += (Char(maiden[6])-Oa) * 2.5/60
    end
    if N >= 8
        lon += parse(Int, maiden[7]) * 5.0/600
        lat += parse(Int, maiden[8]) * 2.5/600
    end

    return (lat, lon)
end

if basename(PROGRAM_FILE) == basename(@__FILE__)

  if length(ARGS) == 2
    println(toMaiden(ARGS[1], ARGS[2], precision=3))
  elseif length(ARGS) == 1
    println(toLoc(ARGS[1]))
  else
    @test toMaiden(40, -50) == "GN50aa"

    lat, lon = toLoc("JO32ii09")
    @test isapprox(lat, 52.37083333333334)
    @test isapprox(lon, 6.666666666666667)
    println("OK: Maidenhead self-test")
  end
end


# using Revise
using PiecewiseDeterministicMarkovProcesses,DifferentialEquations, LinearAlgebra, Random
const PDMP = PiecewiseDeterministicMarkovProcesses

function F_eva!(xcdot, xc, xd, parms::Vector{Float64}, t::Float64)
	# vector field used for the continuous variable
	xcdot[1] = -(xc[1] - 1.5)
	nothing
end

function R(x)
	return x^4
end

function R_eva(rate, xc, xd, parms, t::Float64, issum::Bool)
	# rate function
	rate_print = parms[1]
	if issum == false
		if xd[1] == 0
			rate[1] = R(xc[1])
			rate[2] = 0.0
			rate[3] = rate_print
			return 0.0, 4.95 #transition 0->1
		else
			rate[1] = 0.0
			rate[2] = 1.0
			rate[3] = rate_print
			return 0.0, 4.95 #transition 1->0
		end
	else
		if xd[1] == 0
			return R(xc[1]) + rate_print, 5. #transition 0->1
		else
			return 1.0 + rate_print, 5. #transition 1->0
		end
	end
end

function Delta_xc_eva(xc, xd, parms, t::Float64, ind_reaction::Int64)
	# this function return the jump in the continuous component
	if ind_reaction == 2
		xc[1] = 0.0
	end
	return true
end

xc0 = [0.0]
xd0 = [0, 1]

nu_eva = [1 0;-1 0;0 1]
parms = [1.]
tf = 100.

println("--> Case simple chv:")
	Random.seed!(1234)
	problem = PDMP.PDMPProblem(F_eva!,R_eva,Delta_xc_eva,nu_eva, xc0, xd0, parms, (0.0, tf))
	dummy_t =  @time PDMP.solve(problem, CHV(Tsit5()); n_jumps = 200)

println("--> For simulations rejection (Tsit5):")
	Random.seed!(123)
	problem = PDMP.PDMPProblem(F_eva!,R_eva,Delta_xc_eva,nu_eva, xc0, xd0, parms, (0.0, tf))
	result1 =  @time PDMP.solve(problem, Rejection(:lsoda); n_jumps = 200)

println("--> Simulation using save_at to see sampling behaviour")
	nj = 51
	parms = [10.0]
	Random.seed!(1234)
	problem = PDMP.PDMPProblem(F_eva!,R_eva,Delta_xc_eva,nu_eva, xc0, xd0, parms, (0.0, tf))
	result3 =  @time PDMP.solve(problem, CHV(Tsit5()); n_jumps = 200, save_positions = (false,true))


"""
This module calculates (generalized) hypergeometric functions:

    pFq(α, β; z) = Σ_{k=0}^∞ (α_1)ₖ ⋯ (α_p)ₖ / (β_1)ₖ ⋯ (β_q)ₖ zᵏ/k!
"""
module HypergeometricFunctions

using DualNumbers, LinearAlgebra, SpecialFunctions

export _₁F₁, _₂F₁, _₃F₂, pFq

include("specialfunctions.jl")
include("gauss.jl")
include("confluent.jl")
include("generalized.jl")
include("drummond.jl")
include("weniger.jl")

end #module


function getTableauSPARKGLRK(s)
    g = getCoefficientsGLRK(s)
    δ = zeros(0, s)

    return TableauSPARK(Symbol("sparkglrk", s), g.o,
                        g.a, g.a, g.a, g.a,
                        g.a, g.a, g.a, g.a,
                        g.b, g.b, g.b, g.b,
                        g.c, g.c, g.c, g.c,
                        get_GLRK_ω_matrix(s), δ)
end


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

ufofdm is parametrized by its FFT size, the filter length (in samples) and the allocatedSubcarriers vector. Optional parameters are carrier size in subcarrier (by default RB size which is 12)
# --- Syntax
qamRx	= genereSignalufofdm(sigRx,nFFT,nCp,allocatedSubcarriers;sizeRB=12)
# ---  Input parameters
- sigRx	  : Time UF-OFDM recevied signal [Array{Complex{Float64},nbEch}]
- nFFT	  : FFT size [Int]
- L		  : Dolph Chebyshev filter length [Int]
- allocatedSubcarriers : Vector of index of allocated subcarriers [Array{Int,nbSubcarriers}]
- sizeRB  : Carrier size in subcarriers (default : LTE RB size: 12) [Int]
# ---  Output parameters
- qamRx  : Time frequency matrix : [Array{Complex{Float64},nbSubcarriers,nbSymb}]
- nbSymb			: Number of OFDM symbol tro be transmitted
- nbSubcarriers	: Number of allocated subcarriers (shall be < nFFT)
# ---
# v 1.0 - Robin Gerzaguet.
"""

function ufofdmSigDecode(sigRx,nFFT,L,allocatedSubcarriers;sizeRB=12,window=0)
	# ----------------------------------------------------
	# --- UF-OFDM parameters
	# ----------------------------------------------------
	# --- Symbol size (classic convolution)
	sizeSymb	  = nFFT + L - 1;
	nbSymbDecode		  = Int( floor( length(sigRx)/sizeSymb  ));
	# --- Number of physical RB (i.e carriers)
	nbRB		  = Int(floor(length(allocatedSubcarriers) / sizeRB));
	nbDataSubcarrier = length(allocatedSubcarriers);
	if window == 1
		# TODO Create windowing on UFOFDM Rx
	end
	qamRx	= zeros(Complex{Float64},nbDataSubcarrier,nbSymbDecode);
	# --- Iterative decoding
	for iB = 1 : 1 :nbSymbDecode
		# --- Extract subsignal
		subSig	  = sigRx[ 1+(iB-1)*sizeSymb:iB*sizeSymb ];
		# --- Apodisation
		if window != 0
			subSig	= subSig .* filterTimeDomain;
		end
		# --- Zero Padding to get a 2nFFT
		sigZP	  = [subSig;zeros(Complex{Float64},nFFT-L+1)];
		# --- Apply FFT
		sigFFT	  = fft(sigZP);
		# --- Getting only odd subcarrier
		sigFFT	  = sigFFT[1:2:end];
		# --- Getting received QPSK symbols
		qamRx[ :,iB ]  = sigFFT[allocatedSubcarriers];
	end
	return qamRx;
end
function ufofdmSigDecode(sigChan,ufofdm::StrucUFOFDM;window=0)
	return ufofdmSigDecode(sigChan,ufofdm.nFFT,ufofdm.L,ufofdm.allocatedSubcarriers,sizeRB=ufofdm.sizeRB,window=window);
end


using Gnuplot, ColorSchemes
#first plot
ψ1 = ψ2 = LinRange(0, 4π, 100)
function tα_qubit(β, ψ1, ψ2, fα, f)
    term1 = 2 .+ 2 .*β .- cos.(ψ1) .- cos.(ψ2)
    term2 = 2*β*cos.(π*fα) .* cos.(2*π*f .+ π*fα .- ψ1 .- ψ2)
    term1 .- term2
end
tα_qubitarr = [tα_qubit(0.61, i, j, 0.2, 0.1) for i in ψ1, j in ψ2]
#second plot
x = y = -15:0.33:15
fz(x,y) = sin.(sqrt.(x.^2 + y.^2))./sqrt.(x.^2+y.^2)
fxy = [fz(x,y) for x in x, y in y]
@gsp xlabel = "ψ1" ylabel = "ψ2" title = "tunnel α_{q}" :-
@gsp :- ψ1 ψ2 tα_qubitarr "w pm3d" "set view map" palette(:viridis)
@gsp :- "set size square" "set auto fix" :-
@gsp :- "set multiplot layout 1,2 title 'multiplot layout'" :-
@gsp :- 2 xlab="x" ylab="y" title = "Mexican hat" :-
@gsp :- x y fxy "w l lc palette" "unset view" "set key off"
@gsp :- "set view 65,30" palette(:lajolla) "set size square"
@gsp :- "set auto fix"
save(term="pngcairo size 1200,800", output="./code/pltf_ex1.png") #hide


using JuSwarm, StaticArrays, Test
import Optim

function ackleyfunc(x::AbstractVector)
    a = 20
    b = 0.2
    c = 2*π
    d = length(x)

    sum1 = 0.0
    sum2 = 0.0
    for i in 1:d
        sum1 += x[i]^2
        sum2 += cos(c*x[i])
    end
    return -a*exp(-b*sqrt(sum1/d))  - exp(sum2/d) + a + exp(1)
end

N = 20
opts = PSOOptions(N; SwarmSize = 200,
                     LowerBounds = SVector{N}(fill(-32.768, N, 1)),
                     UpperBounds = SVector{N}(fill(32.768, N, 1)),
                     HybridOptimizer = Optim.LBFGS(),
                     Display = "none",
                     UseParallel = true)

sol = psoptimize(ackleyfunc,opts)

@test sol.fbest <= sol.fbestPSO

@test sol.fbestPSO <= 1.0e-6

@test sol.fbest <= 1.0e-10


##
# New environments

struct BoxND
	# create one with e.g. BoxND(2, [0.0, 0.0], [1.0, 1.0])
	dim::Int64
    min::Vector{Float64}
    max::Vector{Float64}
end

function Base.rand(rng::AbstractRNG, s::Random.SamplerTrivial{BoxND})
	box = s[]
	v_range = box.max - box.min

	return v_range .* rand(rng, box.dim) + box.min
end

##
# Different definitions for action distances and proposals

# Box actions
function action_dist(a1::Vector{Float64}, a2::Vector{Float64})
	return euclidean(a1, a2)
end

function action_sample_centered(best_a::Vector{Float64}, sampler, a_space::BoxND)
	in_bounds = false
	a_new = Vector{Float64}

	# make sure new sample is in bounds
	while !in_bounds
		a_new = rand(sampler) + best_a
		in_bounds = minimum(a_new .> a_space.min) && minimum(a_new .< a_space.max)
	end

	return a_new
end

# TagAction hybrid actions
function action_dist(a1::TagAction, a2::TagAction)
	if a1.look == a2.look
		angle_diff = euclidean(a1.angle, a2.angle)
		return minimum([angle_diff, 2 * pi - angle_diff])
	else
		return Inf
	end
end

function action_sample_centered(best_a::TagAction, sampler, a_space)
	look_temp = best_a.look
	angle_temp = mod(rand(sampler)[1] + best_a.angle, 2 * pi)
	return TagAction(look_temp, angle_temp)
end


#=
215 = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26.

What is the sum of the digits of the number 21000?
=#
function powersum()
  x = BigInt(1000)
  sum(digits(2^x))
end
@time println(powersum())


###############################################
# Finding Nash Equlibria through optimization #
###############################################

"""
vFunction(game::Dict{String,<:AbstractArray},
                s::Vector{Vector{T}}) where T<:Real

Computes numerically for a given game and a given profile V - function crucial
to implement finding NE through optimization, if it equal to 0 - given
profile is a Nash Equilibrium, if greater - not

**Input Parameters**
* `game::Dict{String,<:AbstractArray}` - dictionary containing a game
* `s::Vector{Vector{T}}` - strategy profile for which we want to compute V - function
"""
function vFunction(game::Dict{String,<:AbstractArray},
                s::Vector{Vector{T}}) where T<:Real

    if !all(@. round(sum(s), digits = 2) == 1)
        error("Provided vectors are not a strategy profile")
    end

v = 0.0

for i in 1:length(s)
    PlayerActions = Matrix(1I, length(s[i]), length(s[i]))
    for j in 1:length(s[i])
        temp = deepcopy(s)
        temp[i] = PlayerActions[j,:]
        for k in 1:length(s)
        v = v + max(0, get_payoff(game, temp)[string("player",k)] - get_payoff(game, s)[string("player",k)])^2
end
    end
end

return v

end

"""
cart_prod_simplices(s::Vector{Vector{T}}) where T<:Real

Creates cartesian product of probability simplices for a given vector of vectors (especially - for a strategy profile)

from Manifold.jl
function(ProbabilitySimplex(n)) creates n-probability simplex
× - creates product

**Input Parameters**
* `s::Vector{Vector{T}}` - vector of vectors for which we create cartesian product of probability simplices
"""

function cart_prod_simplices(s::Vector{Vector{T}}) where T<:Real

vec = ProbabilitySimplex(length(s[1]))

for i in 2:length(s)
vec = vec × ProbabilitySimplex(length(s[i]))
end

return vec

end

"""
It is not possible to fully implement finding NE through optimization
without heavily modifing Julia optimization packages

The most promising at the time being is Manopt.jl (Manifold Optimization)
https://github.com/JuliaManifolds/Manopt.jl using certain data types from
Manifolds.jl (https://github.com/JuliaManifolds/Manifolds.jl).

However, at the time being (06.09.2020) not every manifold type from Manifolds.jl
is supported/imported to that library
https://github.com/JuliaManifolds/Manopt.jl/blob/master/src/Manopt.jl
- especially not Probability Simplex
https://github.com/JuliaManifolds/Manifolds.jl/blob/master/src/manifolds/ProbabilitySimplex.jl
crucial to finding NE through vFunction optimization

Other packages are far less advanced - JuMP
https://jump.dev/JuMP.jl/v0.21.1/installation/index.html
is a Julia frontend for various solvers and solver syntax is the best for
that library.

Optim.jl at the time being supports only very simple manifold constraints and
only for first-order algorithms
https://julianlsolvers.github.io/Optim.jl/stable/#algo/manifolds/
"""


# p6u.jl - variable coefficient wave equation - UNSTABLE VARIANT

# Grid, variable coefficient, and initial data:
N = 128; h = 2*pi/N; x = h*(1:N);
c = @. .2 + sin(x-1)^2;
t = 0; dt = 1.9/N;
v = @. exp(-100*(x-1)^2);
vold = @. exp(-100*(x-.2*dt-1)^2);

# Time-stepping by leap frog formula:
tmax = 8; tplot = .15; clf();
plotgap = round(Int,tplot/dt);
nplots = round(Int,tmax/tplot);
data = [v zeros(N,nplots)]; tdata = t;
for i = 1:nplots
    for n = 1:plotgap
        t = t+dt;
        v_hat = fft(v);
        w_hat = 1im*[0:N/2-1; 0; -N/2+1:-1] .* v_hat;
        w = real.(ifft(w_hat));
        vnew = vold - 2*dt*c.*w;        # leap frog formula
        vold = v; v = vnew;
    end
    data[:,i+1] = v; tdata = [tdata; t];
    if norm(v,Inf)>2.5
        data = data[:,1:i+1];
        break
    end
end

# Plot results:
mesh(x,tdata,data',ccount=0);
xlim(0,2*pi); ylim(0,tmax); zlim(-3,3);
xlabel("x"); ylabel("t"); zlabel("u"); gca()[:view_init](70,10-90);


using Plots, Distributions

value_α = [0.5, 1.0, 2.0, 4.0]
value_β = [0.5, 1.0, 2.0, 4.0]

l = @layout [grid(4, 4)]
plots = []
for a in value_α, b in value_β
    if a == 0.5
        title = "β=$b"
    else
        title = ""
    end
    if b == 0.5
        ylabel = "α=$a"
        yticks = [0.0, 2.5]
    else
        ylabel = ""
        yticks = nothing
    end
    if a == 4.0
        xticks = [0.0, 0.5, 1.0]
    else
        xticks = nothing
    end
    p = plot(Beta(a, b), label=false, xlims=(0.0, 1.0), ylims=(0.0, 4.5), title=title, ylabel=ylabel, xticks=xticks, yticks=yticks, grid = false)
    push!(plots, p)
end
plot(plots..., layout=l)


using ControlSystems
using LinearAlgebra

"""
    ntf, stf = calculateTF(ABCD, k=1)

Calculate the NTF and STF of a delta-sigma modulator whose loop filter
is described by the `ABCD` matrix, assuming a quantizer gain of `k`.

The `ntf` and `stf` are TransferFunction objects.
"""
function calculateTF(ABCD, k=[1.0])
    nq = length(k)
    n = size(ABCD, 1) - nq
    nu = size(ABCD, 2) - size(ABCD, 1)
    A, B, C, D = partitionABCD(ABCD, nu+nq)
    B1 = B[:,1:nu]
    B2 = B[:,nu+1:end]
    D1 = D[:,1:nu]
    if any(x -> x != 0, D[:,nu+1:end])
        error("D must be zero")
    end
    K = diagm(k)

    # Find the noise transfer function by forming the closed-loop
    # system (sys_cl) in state-space form.
    Acl = A + B2*K*C
    Bcl = [(B1 + B2*K*D1) B2]
    Ccl = K*C
    Dcl = [K*D1 diagm(ones(nq))]

    sys_cl = ss(Acl, Bcl, Ccl, Dcl, 1)
    tfs = zpk(sys_cl)
    tol = min(1e-3, max(1e-6, eps(Float64)^(1/(size(ABCD,1)))))
    mtfs = minreal(tfs, tol)
    stf = TransferFunction(mtfs.matrix[:,1:nu], mtfs.Ts)
    ntf = TransferFunction(mtfs.matrix[:,nu+1:nu+nq], mtfs.Ts)

    return ntf, stf
end


using Manifolds
@testset "Subgradient Plan" begin
    M = Euclidean(2)
    x = [4., 2.]
    x0 = [5.,2.]
    o = SubGradientMethodOptions(M, x0, StopAfterIteration(200), DecreasingStepsize(0.1))
    o.∂ = [1., 0.]
    f = y -> distance(M,y,x)
    ∂f = y -> distance(M,x,y) == 0 ? zero_tangent_vector(M,y) : -2*log(M,y,x)/distance(M,x,y)
    p = SubGradientProblem(M,f, ∂f)
    oR = solve(p,o)
    xHat = get_solver_result(oR)
    @test get_initial_stepsize(p,o) == 0.1
    @test get_stepsize(p,o,1) == 0.1
    @test get_last_stepsize(p,o,1) == 0.1
    # Check Fallbacks of Problen
    @test get_cost(p,x) == 0.
    @test norm(M,x,get_subgradient(p,x)) == 0
    @test_throws ErrorException get_gradient(p,o.x)
    @test_throws ErrorException getProximalMap(p,1.,o.x,1)
end