import StateSpaceReconstruction: Embeddings

function marginal_NN(points, dists_to_kth)
    D = pairwise(Chebyshev(), points, dims = 2)

    npts = size(points, 2)
    N = zeros(Int, npts)

    for i = 1:npts
        N[i] = sum(D[:, i] .<= dists_to_kth[i])
    end

    return N
end

"""
    transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        v::TEVars; metric = Chebyshev(), b = 2)

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments

- `points`: The set of points representing the embedding for which to compute
    transfer entropy. Must be provided as an array of size `dim`-by-`n` points.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.

## Keyword arguments

- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b=2` gives the transfer entropy in bits.

## References

1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
    mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        v::TEVars; metric = Chebyshev(), normalise = false, b = 2) where T

    # Make sure that the array contains points as columns.
    if size(points, 1) > size(points, 2)
        error("The dimension of the dataset exceeds the number of points.")
    end
    # The total number of points
    N = size(points, 2)

    # Create some dummy variable names to avoid cluttering the code too much
    X = v.target_future
    Y = v.target_presentpast
    Z = vcat(v.source_presentpast, v.conditioned_presentpast)
    XY = vcat(X, Y)
    YZ = vcat(Y, Z)

    pts_X = points[X, :]
    pts_Y = points[Y, :]
    pts_XY = points[XY, :]
    pts_YZ = points[YZ, :]
    pts_XYZ = points

    # Create trees to search for nearest neighbors
    tree_XYZ = KDTree(pts_XYZ, metric)
    tree_XY = KDTree(pts_XY, metric)

    # Find the k nearest neighbors to all of the points in each of the trees
    idxs_XYZ, dists_XYZ = knn(tree_XYZ, pts_XYZ, k1, true)
    idxs_XY, dists_XY   = knn(tree_XY,  pts_XY, k2, true)

    # In each of the trees, find the index of the k-th nearest neighbor to all
    # of the points.
    kth_NN_idx_XYZ = [idx[k1] for idx in idxs_XYZ]
    kth_NN_idx_XY  = [idx[k2] for idx in idxs_XY]

    # Distances between points in the XYZ and XY spaces and their
    # kth nearest neighbour, along marginals X, YZ (for the XYZ space)
    # and X, Y (for the XY space).
    ϵ_XYZ_X = colwise(metric, pts_X, pts_X[:, kth_NN_idx_XYZ])
    ϵ_XYZ_YZ = colwise(metric, pts_YZ, pts_YZ[:, kth_NN_idx_XYZ])
    ϵ_XY_X = colwise(metric, pts_X, pts_X[:, kth_NN_idx_XY])
    ϵ_XY_Y = colwise(metric, pts_Y, pts_Y[:, kth_NN_idx_XY])

    NXYZ_X  = marginal_NN(pts_X,  ϵ_XYZ_X)
    NXYZ_YZ = marginal_NN(pts_YZ, ϵ_XYZ_YZ)
    NXY_X   = marginal_NN(pts_X,  ϵ_XY_X)
    NXY_Y   = marginal_NN(pts_Y,  ϵ_XY_Y)

    if normalise
        warn("Normalisation not properly tested yet")
        te = sum(digamma.(NXY_X) + digamma.(NXY_Y) -
            digamma.(NXYZ_X) - digamma.(NXYZ_YZ)) / N

        # Distiances between points in the XY space and their k-th nearest beighbour, along
        # both marginals (so, effectively, the joint distribution)
        ϵ_XY_XY = colwise(metric, pts_XY, pts_XY[:, kth_NN_idx_XY])
        NXY_XY   = marginal_NN(pts_XY,  ϵ_XY_XY)

        te = te / (sum(digamma.(NXY_XY) - digamma.(NXY_Y)) / N)
    else
        te = sum(digamma.(NXY_X) + digamma.(NXY_Y) -
            digamma.(NXYZ_X) - digamma.(NXYZ_YZ)) / N
    end

    # Convert from nats to to the desired unit (b^x = e^1 => 1/ln(b))
    te / log(b)
end

"""
    transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        conditioned_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments
- `points`: The set of points representing the embedding for which to compute
    transfer entropy. Must be provided as an array of size `dim`-by-`n` points.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.
- `target_future`: Which rows of `points` correspond to future values of the
    target variable?
- `target_presentpast`: Which rows of `points` correspond to present and past
    values of the target variable?
- `source_presentpast`: Which rows of `points` correspond to present and past
    values of the source variable?
- `conditioned_presentpast`: Which rows of `points` correspond to present and
    past values of conditional variables?

## Keyword arguments
- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b=2` gives the transfer entropy in bits.

# References
1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
    mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        conditioned_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

    # Make sure that the array contains points as columns.
    if size(points, 1) > size(points, 2)
        error("The dimension of the dataset exceeds the number of points.")
    end

    # Create some dummy variable names to avoid cluttering the code too much
    v = TEVars(target_future, target_presentpast,
                source_presentpast, conditioned_presentpast)

    transferentropy_kraskov(points, k1, k2, v, b = b)
end

"""
    transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments

- `points`: The set of points representing the embedding for which to compute
    transfer entropy. Must be provided as an array of size `dim`-by-`n` points.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.
- `target_future`: Which rows of `points` correspond to future values of the
    target variable?
- `target_presentpast`: Which rows of `points` correspond to present and past
    values of the target variable?
- `source_presentpast`: Which rows of `points` correspond to present and past
    values of the source variable?

This version of the function assumes there is no conditioning.

## Keyword arguments

- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b=2` gives the transfer entropy in bits.

# References
1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
    mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(points::AbstractArray{T, 2}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

    # Make sure that the array contains points as columns.
    if size(points, 1) > size(points, 2)
        error("The dimension of the dataset exceeds the number of points.")
    end

    # Create some dummy variable names to avoid cluttering the code too much
    v = TEVars(target_future, target_presentpast,
                source_presentpast, Int[])

    transferentropy_kraskov(points, k1, k2, v, b = b)
end

"""
    transferentropy_kraskov(E::StateSpaceReconstruction.AbstractEmbedding,
            k1::Int, k2::Int, v::TEVars; metric = Chebyshev(), b = 2)

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments

- `E`: The embedding for which to compute transfer entropy.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.

## Keyword arguments

- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b=2` gives the transfer entropy in bits.

## References

1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(E::Embeddings.AbstractEmbedding{D, T},
            k1::Int, k2::Int, v::TEVars; metric = Chebyshev(), b = 2) where {D, T}

    # Make sure that the array contains points as columns.
    if size(E.points, 1) > size(E.points, 2)
        error("The dimension exceeds the number of points.")
    end

    transferentropy_kraskov(E.points, k1, k2, v, metric = metric, b = b)
end

"""
    transferentropy_kraskov(E::AbstractEmbedding, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        conditioned_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where T

Compute transfer entropy decomposed as the sum of mutual informations,
using an adapted version of the Kraskov estimator for mutual information [1].

## Arguments

- `points`: The set of points representing the embedding for which to compute
    transfer entropy. Must be provided as an array of size `dim`-by-`n` points.
- `k1`: The number of nearest neighbours for the highest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `k2`: The number of nearest neighbours for the lowest-dimensional mutual
    information estimate. To minimize bias, choose ``k_1 < k_2`` if
    if ``min(k_1, k_2) < 10`` (see fig. 16 in [1]). Beyond dimension 5, choosing
    ``k_1 = k_2`` results in fairly low bias, and a low number of nearest
    neighbours, say `k1 = k2 = 4`, will suffice.
- `v`: A `TEVars` instance, indicating which variables of the embedding should
    be grouped as what when computing the marginal entropies that go into the
    transfer entropy expression.
- `target_future`: Which rows of `points` correspond to future values of the
    target variable?
- `target_presentpast`: Which rows of `points` correspond to present and past
    values of the target variable?
- `source_presentpast`: Which rows of `points` correspond to present and past
    values of the source variable?
- `conditioned_presentpast`: Which rows of `points` correspond to present and
    past values of conditional variables?

## Keyword arguments

- `metric`: The distance metric. Must be a valid metric from `Distances.jl`.
- `b`: The base of the logarithm, dictating the unit of the transfer entropy.
        The default `b = 2` gives the transfer entropy in bits.

# References

1. Kraskov, Alexander, Harald Stögbauer, and Peter Grassberger. "Estimating
    mutual information." Physical review E 69.6 (2004): 066138.
"""
function transferentropy_kraskov(E::Embeddings.AbstractEmbedding{D, T}, k1::Int, k2::Int,
        target_future::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        target_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        source_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}},
        conditioned_presentpast::Union{Int, UnitRange{Int}, Vector{Int}, Tuple{Int}};
        metric = Chebyshev(), b = 2) where {D, T}

    # Make sure that the array contains points as columns.
    if size(E.points, 1) > size(E.points, 2)
        error("The dimension of the dataset exceeds the number of points.")
    end

    # Create some dummy variable names to avoid cluttering the code too much
    v = TEVars(target_future, target_presentpast,
                source_presentpast, conditioned_presentpast)

    transferentropy_kraskov(E.points, k1, k2, v, b = b)
end

tekraskov = transferentropy_kraskov
tekNN = transferentropy_kraskov

export
transferentropy_kraskov,
tekraskov,
tekNN


using NearestNeighbors
using MNIST

X_train, y_train = traindata()
X_test, y_test = testdata()

@elapsed t1 = NaiveNeighborTree(X_train)
@elapsed t2 = BallTree(X_train) # VERY SLOW!!

v = X_train[:, 1]

@elapsed k_nearest(v, t1, 30)
@elapsed k_nearest(v, t2, 30)

errors = 0
n = length(y_test)

for i in 1:25
	is, ds = k_nearest(X_test[:, i], t2, 3)
	if mode(y_train[is]) != y_test[i]
		errors += 1
	end
end

errors / 25


# ---
# title: 543. Diameter of Binary Tree
# id: problem543
# author: Tian Jun
# date: 2020-11-14
# difficulty: Easy
# categories: Tree
# link: <https://leetcode.com/problems/diameter-of-binary-tree/description/>
# hidden: true
# ---
#
# Given a binary tree, you need to compute the length of the diameter of the
# tree. The diameter of a binary tree is the length of the **longest** path
# between any two nodes in a tree. This path may or may not pass through the
# root.
#
# **Example:**
# Given a binary tree
#
#
#
#               1
#              / \
#             2   3
#            / \
#           4   5
#
#
# Return **3** , which is the length of the path [4,2,1,3] or [5,2,1,3].
#
# **Note:** The length of path between two nodes is represented by the number of
# edges between them.
#
#
## @lc code=start
using LeetCode

function diameter_of_binary_tree(root::TreeNode)::Int
    return max(
        diameter_of_binary_tree(root.left),
        diameter_of_binary_tree(root.right),
        _max_depth_to_leaf(root.left) + _max_depth_to_leaf(root.right) + 1,
    ) - 1
end

diameter_of_binary_tree(root::Nothing) = 0

function _max_depth_to_leaf(root::TreeNode)
    return max(1 + _max_depth_to_leaf(root.left), 1 + _max_depth_to_leaf(root.right))
end

_max_depth_to_leaf(::Nothing) = 0
## @lc code=end


# Test cross-tabulation

# simple ClusteringResult subtype for testing
struct SimpleCluRes{T} <: ClusteringResult
    assignments::Vector{T}
    counts::Vector{Int}

    SimpleCluRes(assignments::AbstractVector{T}) where {T<:Integer} =
        new{T}(assignments,
               isempty(assignments) ? Vector{Int}() :
               counts(assignments, (1:maximum(assignments))))
end

@testset "counts() (contingency matrix)" begin

# StatsBase's counts() doesn't allow empty inputs
@test_throws ArgumentError counts(Int[], Int[])
# Clustering's counts()
@test counts(SimpleCluRes(Int[]), Int[]) == Matrix{Int}(undef, 0, 0)
@test_throws DimensionMismatch counts(SimpleCluRes([1]), Int[])
@test_throws DimensionMismatch counts(SimpleCluRes([1]), [2, 1])
#@test_throws ArgumentError counts([1, 1], [0, 1]) # doesn't throw as StatsBase.counts() is called
#@test_throws ArgumentError counts([1, -1], [2, 1]) # doesn't throw as StatsBase.counts() is called
@test_throws ArgumentError counts(SimpleCluRes([1, 1]), [0, 1])
@test_throws ArgumentError counts(SimpleCluRes([1, -1]), [2, 1])

# supports different Integer types
@test counts(SimpleCluRes(Int32[1, 2]), Int16[1, 2]) == Matrix{Int}(I, 2, 2)

@test counts([1, 2, 3], [1, 2, 3]) == Matrix{Int}(I, 3, 3)
@test counts([2, 3, 1], [1, 2, 3]) == [0 0 1; 1 0 0; 0 1 0]

# 3rd cluster in A missing
@test counts([2, 4, 1], [1, 2, 3]) == [0 0 1; 1 0 0; 0 0 0; 0 1 0]
@test counts(SimpleCluRes([2, 4, 1]), [1, 2, 3]) == [0 0 1; 1 0 0; 0 0 0; 0 1 0]

# 1st cluster in B missing (StatsBase.counts() and Clustering.counts() give different results)
@test counts([2, 3, 1], [2, 2, 3]) == [0 1; 1 0; 1 0]
@test counts(SimpleCluRes([2, 3, 1]), [2, 2, 3]) == [0 0 1; 0 1 0; 0 1 0]

@test counts([2, 2, 1], [1, 1, 1]) == reshape([1; 2], (2, 1))
@test counts([1, 1, 1], [2, 2, 1]) == [1 2]

@testset "with ClusteringResult objects" begin
    Random.seed!(34568)
    X = rand(3, 25)

    clu3 = kmeans(X, 3)
    clu5 = kmeans(X, 5)

    Y = rand(3, 20)
    cluY = kmeans(Y, 3)

    @test_throws DimensionMismatch counts(clu3, cluY)
    @test counts(clu3, clu5) == counts(clu5, clu3)'
    @test size(counts(clu3, clu5)) == (3, 5)
    @test sum(counts(clu3, clu5)) == 25
    @test counts(clu3, clu5) == counts(clu3, assignments(clu5))
    @test counts(clu3, clu5) == counts(assignments(clu3), clu5)
    @test counts(clu3, clu5) == counts(assignments(clu3), assignments(clu5))
end

end


import Base: rand,
             ∈,
             isempty,
             convert

export HalfSpace, LinearConstraint,
       an_element,
       constrained_dimensions,
       halfspace_left, halfspace_right

"""
    HalfSpace{N<:Real, VN<:AbstractVector{N}} <: AbstractPolyhedron{N}

Type that represents a (closed) half-space of the form ``a⋅x ≤ b``.

### Fields

- `a` -- normal direction (non-zero)
- `b` -- constraint

### Examples

The set ``y ≥ 0`` in the plane:

```jldoctest
julia> HalfSpace([0, -1.], 0.)
HalfSpace{Float64,Array{Float64,1}}([0.0, -1.0], 0.0)
```
"""
struct HalfSpace{N<:Real, VN<:AbstractVector{N}} <: AbstractPolyhedron{N}
    a::VN
    b::N

    function HalfSpace{N, VN}(a::VN, b::N
                             ) where {N<:Real, VN<:AbstractVector{N}}
        @assert !iszero(a) "a half-space needs a non-zero normal vector"
        return new{N, VN}(a, b)
    end
end

isoperationtype(::Type{<:HalfSpace}) = false

# convenience constructor without type parameter
HalfSpace(a::VN, b::N) where {N<:Real, VN<:AbstractVector{N}} =
    HalfSpace{N, VN}(a, b)

"""
    LinearConstraint

Alias for `HalfSpace`
"""
const LinearConstraint = HalfSpace

# --- LazySet interface functions ---

"""
    dim(hs::HalfSpace)::Int

Return the dimension of a half-space.

### Input

- `hs` -- half-space

### Output

The ambient dimension of the half-space.
"""
function dim(hs::HalfSpace)::Int
    return length(hs.a)
end

"""
    ρ(d::AbstractVector{N}, hs::HalfSpace{N})::N where {N<:Real}

Evaluate the support function of a half-space in a given direction.

### Input

- `d`  -- direction
- `hs` -- half-space

### Output

The support function of the half-space.
If the set is unbounded in the given direction, the result is `Inf`.
"""
function ρ(d::AbstractVector{N}, hs::HalfSpace{N})::N where {N<:Real}
    v, unbounded = σ_helper(d, Hyperplane(hs.a, hs.b); error_unbounded=false,
                            halfspace=true)
    if unbounded
        return N(Inf)
    end
    return dot(d, v)
end

"""
    σ(d::AbstractVector{N}, hs::HalfSpace{N}) where {N<:Real}

Return the support vector of a half-space.

### Input

- `d`  -- direction
- `hs` -- half-space

### Output

The support vector in the given direction, which is only defined in the
following two cases:
1. The direction has norm zero.
2. The direction is the half-space's normal direction.
In both cases the result is any point on the boundary (the defining hyperplane).
Otherwise this function throws an error.
"""
function σ(d::AbstractVector{N}, hs::HalfSpace{N}) where {N<:Real}
    v, unbounded = σ_helper(d, Hyperplane(hs.a, hs.b); error_unbounded=true,
                            halfspace=true)
    return v
end

"""
    isbounded(hs::HalfSpace)::Bool

Determine whether a half-space is bounded.

### Input

- `hs` -- half-space

### Output

`false`.
"""
function isbounded(::HalfSpace)::Bool
    return false
end

"""
    isuniversal(hs::HalfSpace{N}, [witness]::Bool=false
               )::Union{Bool, Tuple{Bool, Vector{N}}} where {N<:Real}

Check whether a half-space is universal.

### Input

- `P`       -- half-space
- `witness` -- (optional, default: `false`) compute a witness if activated

### Output

* If `witness` option is deactivated: `false`
* If `witness` option is activated: `(false, v)` where ``v ∉ P``

### Algorithm

Witness production falls back to `isuniversal(::Hyperplane)`.
"""
function isuniversal(hs::HalfSpace{N}, witness::Bool=false
                    )::Union{Bool, Tuple{Bool, Vector{N}}} where {N<:Real}
    if witness
        return isuniversal(Hyperplane(hs.a, hs.b), true)
    else
        return false
    end
end

"""
    an_element(hs::HalfSpace{N})::Vector{N} where {N<:Real}

Return some element of a half-space.

### Input

- `hs` -- half-space

### Output

An element on the defining hyperplane.
"""
function an_element(hs::HalfSpace{N})::Vector{N} where {N<:Real}
    return an_element_helper(Hyperplane(hs.a, hs.b))
end

"""
    ∈(x::AbstractVector{N}, hs::HalfSpace{N})::Bool where {N<:Real}

Check whether a given point is contained in a half-space.

### Input

- `x` -- point/vector
- `hs` -- half-space

### Output

`true` iff ``x ∈ hs``.

### Algorithm

We just check if ``x`` satisfies ``a⋅x ≤ b``.
"""
function ∈(x::AbstractVector{N}, hs::HalfSpace{N})::Bool where {N<:Real}
    return dot(x, hs.a) <= hs.b
end

"""
    rand(::Type{HalfSpace}; [N]::Type{<:Real}=Float64, [dim]::Int=2,
         [rng]::AbstractRNG=GLOBAL_RNG, [seed]::Union{Int, Nothing}=nothing
        )::HalfSpace{N}

Create a random half-space.

### Input

- `HalfSpace` -- type for dispatch
- `N`         -- (optional, default: `Float64`) numeric type
- `dim`       -- (optional, default: 2) dimension
- `rng`       -- (optional, default: `GLOBAL_RNG`) random number generator
- `seed`      -- (optional, default: `nothing`) seed for reseeding

### Output

A random half-space.

### Algorithm

All numbers are normally distributed with mean 0 and standard deviation 1.
Additionally, the constraint `a` is nonzero.
"""
function rand(::Type{HalfSpace};
              N::Type{<:Real}=Float64,
              dim::Int=2,
              rng::AbstractRNG=GLOBAL_RNG,
              seed::Union{Int, Nothing}=nothing
             )::HalfSpace{N}
    rng = reseed(rng, seed)
    a = randn(rng, N, dim)
    while iszero(a)
        a = randn(rng, N, dim)
    end
    b = randn(rng, N)
    return HalfSpace(a, b)
end

"""
    isempty(hs::HalfSpace)::Bool

Return if a half-space is empty or not.

### Input

- `hs` -- half-space

### Output

`false`.
"""
function isempty(hs::HalfSpace)::Bool
    return false
end

"""
    constraints_list(hs::HalfSpace{N}) where {N<:Real}

Return the list of constraints of a half-space.

### Input

- `hs` -- half-space

### Output

A singleton list containing the half-space.
"""
function constraints_list(hs::HalfSpace{N}) where {N<:Real}
    return [hs]
end

"""
    constraints_list(A::AbstractMatrix{N}, b::AbstractVector{N}) where {N<:Real}

Convert a matrix-vector representation to a linear-constraint representation.

### Input

- `A` -- matrix
- `b` -- vector

### Output

A list of linear constraints.
"""
function constraints_list(A::AbstractMatrix{N}, b::AbstractVector{N}
                         ) where {N<:Real}
    m = size(A, 1)
    @assert m == length(b) "a matrix with $m rows is incompatible with a " *
                           "vector of length $(length(b))"

    return [LinearConstraint(A[i, :], b[i]) for i in 1:m]
end

"""
    constrained_dimensions(hs::HalfSpace{N})::Vector{Int} where {N<:Real}

Return the indices in which a half-space is constrained.

### Input

- `hs` -- half-space

### Output

A vector of ascending indices `i` such that the half-space is constrained in
dimension `i`.

### Examples

A 2D half-space with constraint ``x1 ≥ 0`` is constrained in dimension 1 only.
"""
function constrained_dimensions(hs::HalfSpace{N})::Vector{Int} where {N<:Real}
    return nonzero_indices(hs.a)
end

"""
    halfspace_left(p::AbstractVector{N},
                   q::AbstractVector{N})::HalfSpace{N} where {N<:Real}

Return a half-space describing the 'left' of a two-dimensional line segment
through two points.

### Input

- `p` -- first point
- `q` -- second point

### Output

The half-space whose boundary goes through the two points `p` and `q` and which
describes the left-hand side of the directed line segment `pq`.

### Algorithm

The implementation is simple: the half-space ``a⋅x ≤ b`` is calculated as
`a = [dy, -dx]`, where ``d = (dx, dy)`` denotes the line segment
`pq`, that is, ``\\vec{d} = \\vec{p} - \\vec{q}``, and `b = dot(p, a)`.

### Examples

The left half-space of the "east" and "west" directions in two-dimensions are
the upper and lower half-spaces:

```jldoctest halfspace_left
julia> using LazySets: halfspace_left

julia> halfspace_left([0.0, 0.0], [1.0, 0.0])
HalfSpace{Float64,Array{Float64,1}}([0.0, -1.0], 0.0)

julia> halfspace_left([0.0, 0.0], [-1.0, 0.0])
HalfSpace{Float64,Array{Float64,1}}([0.0, 1.0], 0.0)
```

We create a box from the sequence of line segments that describe its edges:

```jldoctest halfspace_left
julia> H1 = halfspace_left([-1.0, -1.0], [1.0, -1.0]);

julia> H2 = halfspace_left([1.0, -1.0], [1.0, 1.0]);

julia> H3 = halfspace_left([1.0, 1.0], [-1.0, 1.0]);

julia> H4 = halfspace_left([-1.0, 1.0], [-1.0, -1.0]);

julia> H = HPolygon([H1, H2, H3, H4]);

julia> B = BallInf([0.0, 0.0], 1.0);

julia> B ⊆ H && H ⊆ B
true
```
"""
function halfspace_left(p::AbstractVector{N},
                        q::AbstractVector{N})::HalfSpace{N} where {N<:Real}
    @assert length(p) == length(q) == 2 "the points must be two-dimensional"
    @assert p != q "the points must not be equal"
    a = [q[2] - p[2], p[1] - q[1]]
    return HalfSpace(a, dot(p, a))
end

"""
    halfspace_right(p::AbstractVector{N},
                    q::AbstractVector{N})::HalfSpace{N} where {N<:Real}

Return a half-space describing the 'right' of a two-dimensional line segment
through two points.

### Input

- `p` -- first point
- `q` -- second point

### Output

The half-space whose boundary goes through the two points `p` and `q` and which
describes the right-hand side of the directed line segment `pq`.

### Algorithm

See the documentation of `halfspace_left`.
"""
function halfspace_right(p::AbstractVector{N},
                         q::AbstractVector{N})::HalfSpace{N} where {N<:Real}
    return halfspace_left(q, p)
end

"""
    is_tighter_same_dir_2D(c1::LinearConstraint{N},
                           c2::LinearConstraint{N}) where {N<:Real}

Check if the first of two two-dimensional constraints with equivalent normal
direction is tighter.

### Input

- `c1`     -- first linear constraint
- `c2`     -- second linear constraint
- `strict` -- (optional; default: `false`) check for strictly tighter
              constraints?

### Output

`true` iff the first constraint is tighter.
"""
function is_tighter_same_dir_2D(c1::LinearConstraint{N},
                                c2::LinearConstraint{N};
                                strict::Bool=false) where {N<:Real}
    @assert dim(c1) == dim(c2) == 2 "this method requires 2D constraints"
    @assert c1.a <= c2.a <= c1.a "the constraints must have the same " *
        "normal direction"

    lt = strict ? (<) : (<=)
    if c1.a[1] == zero(N)
        @assert c2.a[1] == zero(N)
        return lt(c1.b, c1.a[2] / c2.a[2] * c2.b)
    end
    return lt(c1.b, c1.a[1] / c2.a[1] * c2.b)
end

function _linear_map_hrep(M::AbstractMatrix{N}, P::HalfSpace{N}, use_inv::Bool;
                          inverse::Union{Nothing, AbstractMatrix{N}}=nothing
                         ) where {N<:Real}
    constraint = _linear_map_hrep_helper(M, P, use_inv; inverse=inverse)[1]
    return HalfSpace(constraint.a, constraint.b)
end

"""
    translate(hs::HalfSpace{N}, v::AbstractVector{N}; share::Bool=false
             ) where {N<:Real}

Translate (i.e., shift) a half-space by a given vector.

### Input

- `hs`    -- half-space
- `v`     -- translation vector
- `share` -- (optional, default: `false`) flag for sharing unmodified parts of
             the original set representation

### Output

A translated half-space.

### Notes

The normal vectors of the halfspace (vector `a` in `a⋅x ≤ b`) is shared with the
original halfspace if `share == true`.

### Algorithm

A half-space ``a⋅x ≤ b`` is transformed to the half-space ``a⋅x ≤ b + a⋅v``.
In other words, we add the dot product ``a⋅v`` to ``b``.
"""
function translate(hs::HalfSpace{N}, v::AbstractVector{N}; share::Bool=false
                  ) where {N<:Real}
    @assert length(v) == dim(hs) "cannot translate a $(dim(hs))-dimensional " *
                                 "set by a $(length(v))-dimensional vector"
    a = share ? hs.a : copy(hs.a)
    b = hs.b + dot(hs.a, v)
    return HalfSpace(a, b)
end


puzzleinput = map(collect, readlines("inputs/day11.txt"))
puzzleinput = hcat(puzzleinput...)
puzzleinput = map(c -> c == 'L' ? 1 : 0, puzzleinput)
puzzleinput = permutedims(puzzleinput)

# First Half

shiftl(a) = hcat(a[:, 2:end], fill(0, (size(a)[1], 1)))
shiftr(a) = hcat(fill(0, (size(a)[1], 1)), a[:, 1:end - 1])
shiftu(a) = vcat(a[2:end, :], fill(0, (1, size(a)[2])))
shiftd(a) = vcat(fill(0, (1, size(a)[2])), a[1:end - 1, :])
# Shift left, right, up and down an array a filling the new cells with 0

function newoccupied1(o, l)
    # Starting from occupied o, returns new occupied after one round of the rules
    # The seat layout is l

    ao =
        shiftl(o) +
        shiftr(o) +
        shiftu(o) +
        shiftd(o) +
        shiftl(shiftd(o)) +
        shiftr(shiftd(o)) +
        shiftr(shiftu(o)) +
        shiftl(shiftu(o))
    # Calculates how many adjacent are occupied for all the places

    function rule(i)
        if o[i] == 0 && l[i] == 1 && ao[i] == 0
            # If a seat is empty (0) and there are no occupied seats adjacent to it,
            # the seat becomes occupied.
            1
        elseif o[i] == 1 && ao[i] > 3
            # If a seat is occupied (1) and four or more seats adjacent to it are
            # also occupied, the seat becomes empty.
            0
        else
            # Otherwise, the seat's state does not change.
            o[i]
        end
    end

    map(rule, CartesianIndices(l))
    # Apply the rule to all the places
end

occupied = fill(0, size(puzzleinput))
while true
    no = newoccupied1(occupied, puzzleinput)
    if no == occupied
        # Exit when further applications of the rules cause no seats to change state
        break
    end
    global occupied = no
end

sum(occupied)

# Second Half

function newoccupied2(o, l)
    # Starting from occupied o, returns new occupied after one round of the rules
    # The seat layout is l
    function sees(i)
        # Calculates how many first seats in each direction from place i
        # are occupied
        ij = [0; 0]
        ij[1] = i[1]
        ij[2] = i[2]
        m = [size(o)[1]; size(o)[2]]
        occ = 0
        for di = -1:1
            for dj = -1:1
                if di == 0 && dj == 0
                    continue
                end
                d = [di; dj]
                dij = ij + [di; dj]
                while dij[1] > 0 && dij[2] > 0 && dij[1] <= m[1] && dij[2] <= m[2]
                    if l[dij[1], dij[2]] == 1 # There is a seat in the layout
                        if o[dij[1], dij[2]] == 1 # And is occupied
                            occ = occ + 1
                        end
                        break
                    end
                    dij = dij + d
                end
            end
        end
        occ
    end

    ao = map(sees, CartesianIndices(l))
    # Calculates how many first seat in each direction are occupied
    # for all the places

    function rule(i)
        if o[i] == 0 && l[i] == 1 && ao[i] == 0
            # If a seat is empty (0) and there are no occupied seats adjacent to it,
            # the seat becomes occupied.
            1
        elseif o[i] == 1 && ao[i] > 4
            # If a seat is occupied (1) and five or more visible occupied seats are
            # also occupied, the seat becomes empty.
            0
        else
            # Otherwise, the seat's state does not change.
            o[i]
        end
    end

    map(rule, CartesianIndices(l))
    # Apply the rule to all the places
end

occupied = fill(0, size(puzzleinput))
while true
    no = newoccupied2(occupied, puzzleinput)
    if no == occupied
        # Exit when further applications of the rules cause no seats to change state
        break
    end
    global occupied = no
end

sum(occupied)


# # Phillips model of Baroclinic Instability
#
#md # This example can be viewed as a Jupyter notebook via [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/literated/multilayerqg_2layer.ipynb).
#
# A simulation of the growth of barolinic instability in the Phillips 2-layer model
# when we impose a vertical mean flow shear as a difference ``\Delta U`` in the
# imposed, domain-averaged, zonal flow at each layer.
#
# ## Install dependencies
#
# First let's make sure we have all required packages installed.

# ```julia
# using Pkg
# pkg"add GeophysicalFlows, Plots, Printf"
# ```

# ## Let's begin
# Let's load `GeophysicalFlows.jl` and some other needed packages.
#
using NPZ

using GeophysicalFlows, Plots, Printf

using Random: seed!

# ## Choosing a device: CPU or GPU

dev = CPU()     # Device (CPU/GPU)
nothing # hide

# ## Numerical parameters and time-stepping parameters

n = 128                  # 2D resolution = n²
stepper = "FilteredRK4"  # timestepper
     dt = 2.5e-3           # timestep
 nsteps = 200000          # total number of time-steps
 nsubs  = 100             # number of time-steps for plotting (nsteps must be multiple of nsubs)
nothing # hide

# ## Physical parameters
L = 2π                   # domain size
μ = 5e-2                 # bottom drag
β = 5                    # the y-gradient of planetary PV
ν = 0                 # hyperviscosity

nlayers = 2              # number of layers
f₀, g = 1, 1             # Coriolis parameter and gravitational constant
 H = [0.2, 0.8]          # the rest depths of each layer
 ρ = [4.0, 5.0]          # the density of each layer

 U = zeros(nlayers) # the imposed mean zonal flow in each layer
 U[1] = 1.0
 U[2] = 0.0
nothing # hide

# ## Problem setup
# We initialize a `Problem` by providing a set of keyword arguments. In this example we don't
# do any dealiasing to our solution by providing `aliased_fraction=0`.
prob = MultiLayerQG.Problem(nlayers, dev;
                            nx=n, Lx=L, f₀=f₀, g=g, H=H, ρ=ρ, U=U, μ=μ, β=β, ν=ν,
			    dt=dt, stepper=stepper, aliased_fraction=0)
nothing # hide

# and define some shortcuts.
sol, clock, params, vars, grid = prob.sol, prob.clock, prob.params, prob.vars, prob.grid
x, y = grid.x, grid.y
nothing # hide

# ## Setting initial conditions

# Our initial condition is some small-amplitude random noise. We smooth our initial
# condidtion using the `timestepper`'s high-wavenumber `filter`.
#
# `ArrayType()` function returns the array type appropriate for the device, i.e., `Array` for
# `dev = CPU()` and `CuArray` for `dev = GPU()`.

seed!(1234) # reset of the random number generator for reproducibility
q₀  = 1e-2 * ArrayType(dev)(randn((grid.nx, grid.ny, nlayers)))
q₀h = prob.timestepper.filter .* rfft(q₀, (1, 2)) # apply rfft  only in dims=1, 2
q₀  = irfft(q₀h, grid.nx, (1, 2))                 # apply irfft only in dims=1, 2

MultiLayerQG.set_q!(prob, q₀)
nothing # hide

# ## Diagnostics

# Create Diagnostics -- `energies` function is imported at the top.
E = Diagnostic(MultiLayerQG.energies, prob; nsteps=nsteps)
diags = [E] # A list of Diagnostics types passed to "stepforward!" will  be updated every timestep.
nothing # hide

# ## Output

# We choose folder for outputing `.jld2` files and snapshots (`.png` files).
filepath = "."
plotpath = "./plots_2layer"
plotname = "snapshots"
filename = joinpath(filepath, "2layer.jld2")
nothing # hide

# Do some basic file management
if isfile(filename); rm(filename); end
if !isdir(plotpath); mkdir(plotpath); end
nothing # hide

# And then create Output
get_sol(prob) = sol # extracts the Fourier-transformed solution
function get_u(prob)
  sol, params, vars, grid = prob.sol, prob.params, prob.vars, prob.grid

  @. vars.qh = sol
  streamfunctionfrompv!(vars.ψh, vars.qh, params, grid)
  @. vars.uh = -im * grid.l * vars.ψh
  invtransform!(vars.u, vars.uh, params)

  return vars.u
end

out = Output(prob, filename, (:sol, get_sol), (:u, get_u))
nothing # hide

startwalltime = time()

u_data = zeros(round(Int, nsteps / nsubs)+1, n, n, nlayers)
v_data = zeros(round(Int, nsteps / nsubs)+1, n, n, nlayers)
q_data = zeros(round(Int, nsteps / nsubs)+1, n, n, nlayers)
ψ_data = zeros(round(Int, nsteps / nsubs)+1, n, n, nlayers)

for j = 0:round(Int, nsteps / nsubs)

  u_data[j+1, :, :, :] = prob.vars.u
  v_data[j+1, :, :, :] = prob.vars.v
  q_data[j+1, :, :, :] = prob.vars.q
  ψ_data[j+1, :, :, :] = prob.vars.ψ

  if j % (1000 / nsubs) == 0
    cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy])

    log = @sprintf("step: %04d, t: %.1f, cfl: %.2f, KE₁: %.3e, KE₂: %.3e, PE: %.3e, walltime: %.2f min", clock.step, clock.t, cfl, E.data[E.i][1][1], E.data[E.i][1][2], E.data[E.i][2][1], (time()-startwalltime)/60)

    println(log)
  end

  stepforward!(prob, diags, nsubs)
  MultiLayerQG.updatevars!(prob)
end

npzwrite("u_data.npy", u_data)
npzwrite("v_data.npy", v_data)
npzwrite("q_data.npy", q_data)
npzwrite("psi_data.npy", ψ_data)

# ## Save
# Finally, we can save, e.g., the last snapshot via
# ```julia
# savename = @sprintf("%s_%09d.png", joinpath(plotpath, plotname), clock.step)
# savefig(savename)
# ```


module TestTools
using DataFrames, CSV

using Test

include("../src/Tools.jl")
import .Tools
export runtests

function test_make_Y_bus()

    VN = 12500
    Sb = 1e6
    Yb = Sb / (VN^2)

    target_pu = [
        20-40im -20+40im 0+0im
        -20+40im 48.5714-97.1429im -28.5714+57.1429im
        0+0im -28.5714+57.1429im 28.5714-56.8429im
    ]
    target = target_pu * Yb

    data = DataFrame(CSV.File("case3_dist.csv"))

    result = Tools.make_Y_bus(data, VN)
    @testset "make_Y_bus" begin
        @testset "Y[$i, $j]" for i = 1:3, j = 1:3
            @test target[i, j] ≈ result[i, j] atol = 1e-4
        end
    end
end

function runtests()
    @testset "Tools" begin
        test_make_Y_bus()
    end
end

end

TestTools.runtests()


@testset "Conjugate Gradient" begin
    # TODO: Investigate the exceptions (could be they just need more iterations?)
    # Test Optim.cg for all differentiable functions in MultivariateProblems.UnconstrainedProblems.examples
    # Trigonometric gets stuck in a local minimum?
    skip = ("Trigonometric", "Extended Powell")
    run_optim_tests(ConjugateGradient(),
                    skip=skip,
                    convergence_exceptions = (("Powell", 1), ("Powell", 2), ("Polynomial", 1),
                                              ("Extended Rosenbrock", 1),
                                              ("Extended Powell", 1),
                                              ("Extended Powell", 2)),
                    minimum_exceptions = (("Paraboloid Diagonal", 1)),
                    minimizer_exceptions = (("Paraboloid Diagonal", 1),
                                            ("Extended Powell", 1),
                                            ("Extended Powell", 2)),
                    f_increase_exceptions = (("Hosaki"),),
                    iteration_exceptions = (("Paraboloid Diagonal", 10000),),
                    show_name = debug_printing)

    @testset "matrix input" begin
        cg_objective(X, B) = sum(abs2, X .- B)/2

        function cg_objective_gradient!(G, X, B)
            for i = 1:length(G)
                G[i] = X[i]-B[i]
            end
        end

        Random.seed!(1)
        B = rand(2,2)
        results = Optim.optimize(X -> cg_objective(X, B), (G, X) -> cg_objective_gradient!(G, X, B), rand(2,2), ConjugateGradient())
        @test Optim.converged(results)
        @test Optim.minimum(results) < 1e-8
    end

    @testset "Undefined beta_k behaviour" begin
        # Ref #669
        # If y_k is zero, then betak is undefined.
        # Check that we don't produce NaNs

        f(x) = 100 - x[1] + exp(x[1] - 100)
        g!(grad, x) = grad[1] = -1 + exp(x[1] - 100)
        res = optimize(f, g!, [0.0], ConjugateGradient(alphaguess=LineSearches.InitialStatic(alpha=1.0), linesearch=LineSearches.BackTracking()))
        @test Optim.converged(res)
        @test Optim.minimum(res) ≈ 1.0
    end
end


# solve the KS function and store data for plotting
include("kuramoto-sivashinsky.jl")
using .kuramoto_sivashinsky

# define integrand functions needed for time averages
function ident(val)
    return val
end
function square(val)
    return val*val
end

# set the parameters
tp = Float64             # floating point type to use
num_nodes = 255 #127 #511          # number of (interior) nodes to use
#num_spin = 5000          # number of steps to use during spin up
#num_steps = 20000        # number of time steps to use during simulation
Lx = convert(tp, 128)    # domain size
order = 6                # order of accuracy
Spin = convert(tp, 100) #convert(tp, 1000) # spin-up period
Time = convert(tp, 400) #convert(tp, 2000) # statistics gathering period for simulation
dt = 0.01
num_spin = convert(Int, Spin/dt)
num_steps = convert(Int, Time/dt)

# set initial condition and set-up the KSData structure
dx = Lx/(num_nodes+1)
x = Array{tp}(dx:dx:Lx-dx)
#u = exp.(-((x-64).^2)./512)
u = 2*rand(num_nodes) .- 1
ks = kuramoto_sivashinsky.buildKSData(order, num_nodes, tp)

# solve the problem and get the time averages
println("Starting spin-up portion of simulation")
sol = kuramoto_sivashinsky.solveUsingMidpoint(ks, Spin, num_spin, u)
u = sol[2:end-1,end]
println("Starting statistics gathering simulation")
@elapsed sol = kuramoto_sivashinsky.solveUsingMidpoint(ks, Time, num_steps, u)
u_avg_ref = kuramoto_sivashinsky.calcSolutionAverage(order, sol, ident)
u2_avg_ref = kuramoto_sivashinsky.calcSolutionAverage(order, sol, square)
println("u averaged = ", u_avg_ref)
println("u^2 averaged = ", u2_avg_ref)

# output to file for visualization
file = open("ks_solution.dat", "w")
for n = 1:num_steps+1
    for val in sol[:,n]
        print(file, val, " ")
    end
    println(file)
end
close(file)


export WeightedMeasure, AbstractWeightedMeasure

"""
    struct WeightedMeasure{R,M} <: AbstractMeasure
        logweight :: R
        base :: M
    end
"""

abstract type AbstractWeightedMeasure <: AbstractMeasure end

logweight(μ::AbstractWeightedMeasure) = μ.logweight
basemeasure(μ::AbstractWeightedMeasure) = μ.base

@inline function logdensity(sm::AbstractWeightedMeasure, x)
    logdensity(sm.base, x) + sm.logweight
end

###############################################################################

struct WeightedMeasure{R,M} <: AbstractWeightedMeasure
    logweight::R
    base::M
end

basemeasure_type(::WeightedMeasure{R,M}) where {R,M} = M

function Base.show(io::IO, μ::WeightedMeasure)
    io = IOContext(io, :compact => true)
    print(io, exp(μ.logweight), " ⊙ ", μ.base)
end

function Base.show_unquoted(io::IO, μ::WeightedMeasure, indent::Int, prec::Int)
    io = IOContext(io, :compact => true)
    if Base.operator_precedence(:*) ≤ prec
        print(io, "(")
        show(io, μ)
        print(io, ")")
    else
        show(io, μ)
    end
    return nothing
end

function ⊙(k::T, m::AbstractMeasure) where {T<:Number}
    logk = log(k)
    return weightedmeasure(logk, m)
end

⊙(m::AbstractMeasure, k::Real) = k ⊙ m

weightedmeasure(ℓ, μ) = WeightedMeasure(ℓ, μ)


using Base.Test

using NURBS

include("BSplineBasis.jl")
include("NURBSBasis.jl")


using Test
using LowRankApprox
using LinearAlgebra, Random, SparseArrays

Random.seed!(0)

include("linop.jl")
include("permute.jl")
include("snorm.jl")
include("trapezoidal.jl")

println()

include("sketch.jl")
include("id.jl")
include("pheig.jl")
include("pqr.jl")
include("prange.jl")
include("psvd.jl")
include("cur.jl")
include("lowrankmatrix.jl")


"""
    sumr(;binfile::AbstractString="", outdir::AbstractString=".", pseudocount::Number=1.0)
Extract the summary information of data matrix.
`binfile` is a Julia Binary file generated by `csv2bin` function.
`outdir` is specified the directory you want to save the result.
`pseudocount` is specified to avoid NaN by log10(0) and used when `Row_LogMeans.csv` <log10(mean+pseudocount) value of each Row> is generated.
Output Files
---------
- `Col_Means.csv` : Mean in each column.
- `Col_LogMeans.csv` : Mean of Log10(count+pseudocount) in each column.
- `Col_FTTMeans.csv` : Mean of sqrt(count) + sqrt(count+1) in ech column.
- `Col_Vars.csv` : Variance in each column.
- `Col_LogVars.csv` : Variance of Log10(count+pseudocount) in each column.
- `Col_FTTVars.csv` : Variance of sqrt(count) + sqrt(count+1) in ech column.
- `Col_SDs.csv` : Standard deviation in each column.
- `Col_LogSDs.csv` : Standard deviation of Log10(count+pseudocount) in each column.
- `Col_FTTSDs.csv` : Standard deviation of sqrt(count) + sqrt(count+1) in ech column.
- `Col_CV2s.csv` : Coefficient of Variation in each column.
- `Col_NoZeros.csv` : Number of zero-elements in each column.
- `Col_NoCounts.csv` : Sum of counts in each column.
- `Row_Means.csv` : Mean in each row.
- `Row_LogMeans.csv` : Mean of Log10(count+pseudocount) in each row.
- `Row_FTTMeans.csv` : Mean of sqrt(count) + sqrt(count+1) in ech row.
- `Row_Vars.csv` : Variance in each row.
- `Row_LogVars.csv` : Variance of Log10(count+pseudocount) in each row.
- `Row_FTTVars.csv` :  Variance of sqrt(count) + sqrt(count+1) in ech row.
- `Row_SDs.csv` : Standard deviation in each row.
- `Row_LogSDs.csv` : Standard deviation of Log10(count+pseudocount) in each row.
- `Row_FTTSDs.csv` : Standard deviation of sqrt(count) + sqrt(count+1) in ech row.
- `Row_CV2s.csv` : Coefficient of Variation in each row.
- `Row_NoZeros.csv` : Number of zero-elements in each row.
- `Row_NoCounts.csv` : Sum of counts in each row.
"""
function sumr(;binfile::AbstractString="", outdir::AbstractString=".", pseudocount::Number=1.0)
    # 1 / 2 : Column-wise statistics
    println("1 / 2 : Column-wise statistics are calculated...")
    Col_Means, Col_LogMeans, Col_FTTMeans, Col_Vars, Col_LogVars, Col_FTTVars, Col_SDs, Col_LogSDs, Col_FTTSDs, Col_CV2s, Col_NoZeros, Col_NoCounts = colstats(binfile, pseudocount)

    # 2 / 2 : Row-wise statistics
    println("2 / 2 : Row-wise statistics are calculated...")
    Row_Means, Row_LogMeans, Row_FTTMeans, Row_Vars, Row_LogVars, Row_FTTVars, Row_SDs, Row_LogSDs, Row_FTTSDs,  Row_CV2s, Row_NoZeros, Row_NoCounts = rowstats(binfile, pseudocount)

    # Save
    # Col-wise
    writecsv(joinpath(outdir, "Col_Means.csv"), Col_Means)
    writecsv(joinpath(outdir, "Col_LogMeans.csv"), Col_LogMeans)
    writecsv(joinpath(outdir, "Col_FTTMeans.csv"), Col_FTTMeans)
    writecsv(joinpath(outdir, "Col_Vars.csv"), Col_Vars)
    writecsv(joinpath(outdir, "Col_LogVars.csv"), Col_LogVars)
    writecsv(joinpath(outdir, "Col_FTTVars.csv"), Col_FTTVars)
    writecsv(joinpath(outdir, "Col_SDs.csv"), Col_SDs)
    writecsv(joinpath(outdir, "Col_LogSDs.csv"), Col_LogSDs)
    writecsv(joinpath(outdir, "Col_FTTSDs.csv"), Col_FTTSDs)
    writecsv(joinpath(outdir, "Col_CV2s.csv"), Col_CV2s)
    writecsv(joinpath(outdir, "Col_NoZeros.csv"), Col_NoZeros)
    writecsv(joinpath(outdir, "Col_NoCounts.csv"), Col_NoCounts)

    # Row-wise
    writecsv(joinpath(outdir, "Row_Means.csv"), Row_Means)
    writecsv(joinpath(outdir, "Row_LogMeans.csv"), Row_LogMeans)
    writecsv(joinpath(outdir, "Row_FTTMeans.csv"), Row_FTTMeans)
    writecsv(joinpath(outdir, "Row_Vars.csv"), Row_Vars)
    writecsv(joinpath(outdir, "Row_LogVars.csv"), Row_LogVars)
    writecsv(joinpath(outdir, "Row_FTTVars.csv"), Row_FTTVars)
    writecsv(joinpath(outdir, "Row_SDs.csv"), Row_SDs)
    writecsv(joinpath(outdir, "Row_LogSDs.csv"), Row_LogSDs)
    writecsv(joinpath(outdir, "Row_FTTSDs.csv"), Row_FTTSDs)
    writecsv(joinpath(outdir, "Row_CV2s.csv"), Row_CV2s)
    writecsv(joinpath(outdir, "Row_NoZeros.csv"), Row_NoZeros)
    writecsv(joinpath(outdir, "Row_NoCounts.csv"), Row_NoCounts)
end

# Column-wise statistics
function colstats(binfile::AbstractString, pseudocount::Number)
    N, M = nm([binfile])
    N = N[]
    M = M[]
    tmpN = zeros(UInt32, 1)
    tmpM = zeros(UInt32, 1)
    x = zeros(UInt32, M)
    m = zeros(M)
    lm = zeros(M)
    fttm = zeros(M)
    v = zeros(M)
    lv = zeros(M)
    fttv = zeros(M)
    sd = zeros(M)
    lsd = zeros(M)
    fttsd = zeros(M)
    c = zeros(M)
    nz = zeros(M)
    nc = zeros(M)
    # Mean Calculator
    progress = Progress(N)
    open(binfile) do file
        stream = ZstdDecompressorStream(file)
        read!(stream, tmpN)
        read!(stream, tmpM)
        for n = 1:N
            # Data Import
            read!(stream, x)
            # Update
            m = m .+ (x .- m) / n
            lm = lm .+ (log10.(x .+ pseudocount) .- lm) / n
            fttm = fttm .+ (sqrt.(x) .+ sqrt.(x .+ 1) .- lm) / n
            # Non-zero counts
            nc = nc .+ x
            tmpx = x
            for mm = 1:M
                if tmpx[mm] != 0
                    tmpx[mm] = 1
                end
            end
            nz = nz .+ tmpx
            # Progress Bar
            next!(progress)
        end
        close(stream)
    end
    # Sum of squared deviation Calculator
    progress = Progress(N)
    open(binfile) do file
        stream = ZstdDecompressorStream(file)
        read!(stream, tmpN)
        read!(stream, tmpM)
        for n = 1:N
            # Data Import
            read!(stream, x)
            # Update
            tmpv = x .- m
            tmplv = log10.(x .+ pseudocount) .- lm
            tmpfttv = sqrt.(x) .+ sqrt.(x .+ 1) .- fttm
            v = v .+ tmpv .* tmpv
            lv = lv .+ tmplv .* tmplv
            fttv = fttv .+ tmpfttv .* tmpfttv
            # Progress Bar
            next!(progress)
        end
        close(stream)
    end
    # Variance
    v = v ./ N
    lv = lv ./ N
    fttv = fttv ./ N
    # Var → SD
    sd = sqrt.(v)
    lsd = sqrt.(lv)
    fttsd = sqrt.(fttv)
    # CV2
    c = v ./ m .* m
    return m, lm, fttm, v, lv, fttv, sd, lsd, fttsd, c, nz, nc
end

# Row-wise statistics
function rowstats(binfile::AbstractString, pseudocount::Number)
    N, M = nm([binfile])
    N = N[]
    M = M[]
    tmpN = zeros(UInt32, 1)
    tmpM = zeros(UInt32, 1)
    x = zeros(UInt32, M)
    m = zeros(N)
    lm = zeros(N)
    fttm = zeros(N)
    v = zeros(N)
    lv = zeros(N)
    fttv = zeros(N)
    sd = zeros(N)
    lsd = zeros(N)
    fttsd = zeros(N)
    c = zeros(N)
    nz = zeros(UInt32, N)
    nc = zeros(UInt32, N)
    progress = Progress(N)
    open(binfile) do file
        stream = ZstdDecompressorStream(file)
        read!(stream, tmpN)
        read!(stream, tmpM)
        for n = 1:N
            # Data Import
            read!(stream, x)
            # Update
            m[n] = mean(x)
            lm[n] = mean(log10.(x .+ pseudocount))
            fttm[n] = mean(sqrt.(x) .+ sqrt.(x .+ 1))
            v[n] = var(x)
            lv[n] = var(log10.(x .+ pseudocount))
            fttv[n] = var(sqrt.(x) .+ sqrt.(x .+ 1))
            sd[n] = var(x)^(1/2)
            lsd[n] = var(log10.(x .+ pseudocount))^(1/2)
            fttsd[n] = var(sqrt.(x) .+ sqrt.(x .+ 1))^(1/2)
            c[n] = v[n] ./ (m[n] .* m[n])
            for mm = 1:M
                if x[mm] != 0
                    nz[n] = nz[n] + 1
                end
            end
            nc[n] = sum.(x)[1]
            # Progress Bar
            next!(progress)
        end
        close(stream)
    end
    return m, lm, fttm, v, lv, fttv, sd, lsd, fttsd, c, nz, nc
end


# MTs with eltypes which have units from the Unitful package

using Test
using MomentTensors
using Unitful

@testset "Units" begin
    m = Unitful.m
    N = Unitful.N
    Nm = N*m

    @testset "Construction" begin
        @test MT((1, 2, 3, 4, 5, 6).*u"N*m") == MT(1Nm, 2Nm, 3Nm, 4Nm, 5Nm, 6Nm) == MT((1:6)*Nm)
        @test MT(1:6) != MT((1:6)*Nm)
        let mt = MT((1:6)*Nm)
            @test eltype(mt) == typeof(float(1Nm))
        end
    end

    @testset "getindex/setindex!" begin
        let mt = MT((1:6)*Nm)
            @test mt[1,1] == 1Nm
        end
    end

    @testset "Arithmetic" begin
        @test MT(ones(6).*u"N*m/s") + MT(2*ones(6).*u"N*m/s") == MT(3*ones(6).*u"N*m/s")
        let m1 = MT((11, 22, 33, 12, 13, 23).*Nm),
            m2 = MT((-11, -22, -33, -12, -13, -23).*Nm)
            @test m1 + m2 == MT((0, 0, 0, 0, 0, 0).*Nm)
            @test m1 == -m2
            @test -m1 == m2
            @test m1 + 1Nm == MT((12, 23, 34, 13, 14, 24).*Nm)
            @test m1 - 1Nm == MT((10, 21, 32, 11, 12, 22).*Nm)
            @test m1 + 3Nm == 3Nm + m1
            @test m1 - 4Nm == -(4Nm - m1)
            @test 2m1 == MT((22, 44, 66, 24, 26, 46).*Nm)
            @test 2m1 == m1*2
            @test 1/m1 == MT(1 ./ ((11, 22, 33, 12, 13, 23).*Nm)...)
            @test m1/2 == MT((5.5, 11, 16.5, 6, 6.5, 11.5).*Nm)
        end
    end

    @testset "Conversion" begin
        @test Array(MT((1:6)*Nm)) == Float64[1 4 5; 4 2 6; 5 6 3].*Nm
    end

    @testset "Decomposition" begin
        let m = MT([ 3 -5 10
                    -5  1  4
                    10  4  1].*Nm)
            eltp = typeof(float(1Nm))
            d = decompose(m)

            @testset "Unitful MT $f" for f in (:iso, :dev, :dc, :clvd)
                @test eltype(getfield(d, f)) == eltp
            end
            @testset "Unitful scalar $f" for f in (:iso_m0, :dev_m0, :m0)
                @test typeof(getfield(d, f)) == eltp
            end
            @testset "Dimensionless scalar $f" for f in (:prop_iso, :prop_dev, :prop_dc, :prop_clvd)
                @test typeof(getfield(d, f)) == typeof(ustrip(m[1,1]))
            end
        end
    end

    @testset "amplitude_v_azimuth" begin
        eltp = typeof(Float32(1N))
        p, sv, sh, j = amplitude_v_azimuth(MT(rand(Float32, 6).*N), 0, 0)
        for v in (p, sv, sh)
            @test eltype(v) == eltp
        end
        @test eltype(j) == Float32
    end

    @testset "radiation_pattern" begin
        eltp = typeof(Float32(1Nm))
        p, sv, sh, j = radiation_pattern(MT(rand(Float32, 6).*Nm), 0, 0)
        for v in (p, sv, sh)
            @test v isa eltp
        end
        @test j isa Float32
    end

    @testset "rotate" begin
        @test rotate(MT(rand(Float32, 6).*Nm), 1, 2, 3) isa MT{typeof(1f0*Nm)}
    end

    @testset "eps_non_dc" begin
        @test eps_non_dc(MT(rand(Float32, 6).*Nm)) isa typeof(ustrip(1f0*Nm))
    end
end


include("euler/euler.jl")
using .Calculus: get_fibonacci

compute(n::Int)::Int = get_fibonacci(n + 2)


# Utility functions for CliffordAlgebras.jl

"""
    nextselector(s::NTuple{N,<:Integer},M) where N

Helper function for enumerating base vectors.
"""
function nextselector(s::NTuple{N,<:Integer}, M) where {N}
    @assert issorted(s)
    @assert all(s .<= M) && all(s .>= 1)
    for n = 1:(N-1)
        if s[n] + 1 < s[n+1]
            return ntuple(i -> i < n ? i : i == n ? s[i] + 1 : s[i], N)
        end
    end
    if s[N] < M
        return ntuple(i -> i < N ? i : i == N ? s[i] + 1 : s[i], N)
    else
        return nothing
    end
end

"""
    enumeratebase(N::Integer)

Generates the basis vector product combinations for the Clifford algebra of an N dimensional vector space.
"""
function enumeratebase(N::Integer)
    result = Tuple[]
    push!(result, ())
    for n = 1:N
        s = ntuple(identity, n)
        while true
            push!(result, s)
            s = nextselector(s, N)
            !isnothing(s) || break
        end
    end
    Tuple(result)
end

"""
    adaptbasefordual(base)

Shuffles the base vector products so that the Poincaré dual of every basis vector is another basis vector.
"""
function adaptbasefordual(base)
    N = length(base)
    Nh = N ÷ 2
    Tuple(
        n > Nh && length(base[n]) >= 2 && levicivita([base[N-n+1]..., base[n]...]) < 0 ?
        Tuple(
            k == length(base[n]) ? base[n][k-1] :
            k + 1 == length(base[n]) ? base[n][k+1] : base[n][k] for k = 1:length(base[n])
        ) : base[n] for n = 1:N
    )
end

"""
    findpermutation(a,b)

Find the permutation that takes the a to b.
"""
function findpermutation(a, b)
    @assert allunique(a) "Permutations can only be found in sequences of unique elements."
    @assert length(a) == length(b) "Permutations can only be found between sequences of identical length."
    @assert length(intersect(a, b)) == length(a) "Permutations can only be found between sequences with the same elements."
    [findfirst(isequal(a[n]), b) for n = 1:length(a)]
end


using Sundials

## f routine. Compute function f(t,y).

function f(t, y)
    ydot[1] = -0.04*y[1] + 1.0e4*y[2]*y[3]
    ydot[3] = 3.0e7*y[2]*y[2]
    ydot[2] = -ydot[1] - ydot[3]
    return ydot
end

## g routine. Event function whose roots cause integration to stop.

function g(t, y)
    gout[1] = y[1] - 0.0001
    gout[2] = y[3] - 0.01
    return(gout, [false,false], [0,0])
end

## Jacobian routine. Compute J(t,y) = df/dy.

# Using StrPack
# -- it works
# -- it's clunky and dependent on Sundials version
# Note: this is for Sundials v 2.4; the structure changed for v 2.5
# Because of this, I'm commenting this out.
## using StrPack
## @struct type J_DlsMat
##     typ::Int32
##     M::Int32
##     N::Int32
##     ldim::Int32
##     mu::Int32
##     ml::Int32
##     s_mu::Int32
##     data::Ptr{Float64}
##     ldata::Int32
##     cols::Ptr{Ptr{Float64}}
## end
# Note: Here is the (untested) structure for v 2.5
## using StrPack
## @struct type J_DlsMat
##     typ::Int32
##     M::Int
##     N::Int
##     ldim::Int
##     mu::Int
##     ml::Int
##     s_mu::Int
##     data::Ptr{Float64}
##     ldata::Int
##     cols::Ptr{Ptr{Float64}}
## end

# The following works if the code above is uncommented.
function Jac(N, t, y, fy, Jptr, user_data,
             tmp1, tmp2, tmp3)
    y = Sundials.asarray(y)
    dlsmat = unpack(IOString(pointer_to_array(convert(Ptr{Uint8}, Jptr),
                                              (sum(map(sizeof, J_DlsMat.types))+10,))),
                    J_DlsMat)
    J = pointer_to_array(unsafe_ref(dlsmat.cols), (int(neq), int(neq)), false)
    J[1,1] = -0.04
    J[1,2] = 1.0e4*y[3]
    J[1,3] = 1.0e4*y[2]
    J[2,1] = 0.04
    J[2,2] = -1.0e4*y[3] - 6.0e7*y[2]
    J[2,3] = -1.0e4*y[2]
    J[3,2] = 6.0e7*y[2]
    return int32(0)
end

y = [1.0,0.0,0.0]
reltol = 1e-4
abstol = [1e-8, 1e-14, 1e-6]

#cvode_mem = Sundials.CVodeCreate(Sundials.CV_BDF, Sundials.CV_NEWTON)
#flag = Sundials.CVodeInit(cvode_mem, f, t0, y)
#flag = Sundials.CVodeSVtolerances(cvode_mem, reltol, abstol)
#flag = Sundials.CVodeRootInit(cvode_mem, int32(2), g)
#flag = Sundials.CVDense(cvode_mem, neq)
## flag = Sundials.CVDlsSetDenseJacFn(cvode_mem, Jac)  # works, but clunky, see above

t = [0, 4*10 .^(-1.:10.)]
sol = Sundials.cvodej(f, t, y, events=g, reltol=reltol,abstol=abstol)


@testset "Nodes initialization algorithm entry  dimension 1 output dimension 1" begin
    # Comprobamos que las hipótesis de selección son correctas
    M = 1
    @test RampFunction(M) == 1
    @test RampFunction(-M) == 0
    # Bien definido para tamaño n = 2 y salida de dimensión 1
    f_regression(x)=(x<=1) ? exp(-x) : log(x)
    data_set_size =  5
    entry_dimension = 1
    output_dimension = 1
    # Número de neuronas
    n = data_set_size  # Debe de ser mayor que 1 para que no de error
    X_train= map(
        x-> (x-0.5)*10, # reescalamos al intervalo [-5,5]
        rand(Float64, data_set_size)
    )

    Y_train = map(f_regression, X_train)
    h = nn_from_data(X_train, Y_train, n, M)

    # veamos que el tamaño de la salida es la adecuada
    @test size(h.W1) == (n,2)
    @test size(h.W2) == (1,n)

    # Si ha sido bien construida:
    # Evaluar la red neuronal en los datos con los que se construyó
    # debería de resultar el valor de Y_train respectivo
    evaluar(x)=forward_propagation(h,
     RampFunction,x)

    for (x,y) in zip(X_train,Y_train)
        @test evaluar([x]) ≈ [y]
    end

end


#############################################################################
#############################################################################
#
# This file defines the polynomial type with several operations
#
#############################################################################
#############################################################################

####################################
# Polynomial and PolynomialModP Type #
####################################

    """
    A Polynomial type - designed to be for polynomials with integer coefficients.
    """
    struct Polynomial
        terms::Array{Term}
        Polynomial() = new(Term[])
        #Inner constructor
        Polynomial(h::Array{Term}, clean::Bool) = clean && new(h)
    end

    """
    Construct a polynomial with a single term.
    """
    function Polynomial(t::Term)
        terms = Term[]
        t.coeff != 0 && push!(terms, t)
        return Polynomial(terms,true)
    end

    """
    Construct a polynomial with a vector of terms.
    """
    function Polynomial(tv::Vector{Term})
        n = length(tv)
       sort!(tv, by = x -> -x.degree) #sorts
        if n == 1
            return Polynomial(tv, true)
        else
            terms = Term[]
            i = 1
            while i < n+1 #removes like terms
                if tv[i].coeff != 0
                    if i == n
                        push!(terms,tv[i])

                    elseif tv[i].degree == tv[i+1].degree
                        tv[i+1] = tv[i]+tv[i+1]
                    else
                        push!(terms,tv[i])
                    end
                end
                i = i + 1
            end
        end

        if length(terms)== 0 || terms[1].coeff == 0
            return Polynomial()
        end

        return Polynomial(terms, true)
        #Once made pretty (no repreated factors and ordered), the vector is sent to the inner constructor to
        #made into a polynomial
    end

    """
    Polymonials mod P
    """
    struct PolynomialModP
        polynomial::Polynomial
        mod::Int
        PolynomialModP(p) = new(Polynomial(Term[]),p)
        PolynomialModP(poly::Polynomial, mod::Int, modded::Bool) = modded && new(poly,mod) #see 109 for the explination of the bool
    end

    function PolynomialModP(t::Term,p)
        terms = Term[]
        modcoeff = mod(t.coeff,p)
        modcoeff != 0 && push!(terms,Term(modcoeff,t.degree))
        return PolynomialModP(Polynomial(terms),p,true)
    end

    function PolynomialModP(tv::Vector{Term},p::Int)
        n = length(tv)
        sort!(tv, by = x -> -x.degree) #it has to be sorted in order, soo this does that
        tv = mod.(tv,p)
        if n == 1
            terms = tv
        else
            terms = Term[]
            i = 1
            while i < n+1 # This adds like terms together
                if tv[i].coeff != 0
                    tv[i].coeff
                    if i == n
                        push!(terms,tv[i])
                    elseif tv[i].degree == tv[i+1].degree
                        tv[i+1] = tv[i]+tv[i+1]
                        tv[i+1].coeff = tv[i+1].coeff%p
                    else
                        push!(terms,tv[i])
                    end
                end
                i = i + 1
            end
        end

        if length(terms)== 0 || terms[1].coeff == 0
            return PolynomialModP(p)
        end

        terms = Polynomial(terms,true) #The true signifies that polynomial is odered and has no repeats,
        #as such can just be turned into a poly straight away
        return PolynomialModP(terms, p, true)
    end

    PolynomialModP(poly::Polynomial, p::Int) = PolynomialModP(mod(poly,p), p, true)

####################################
# Polynomial Construction #
####################################
    """
    Construct a polynomial of the form x^p-x.
    """
        cyclotonic_polynomial(p::Int) = Polynomial([Term(1,p), Term(-1,0)])

    """
    Construct a polynomial of the form x-n.
    """
        linear_monic_polynomial(n::Int) = Polynomial([Term(1,1), Term(-n,0)])

    """
    Construct a polynomial of the form x.
    """
    x_poly() = Polynomial(Term(1,1))
    x_poly(p::Int) = PolynomialModP(Term(1,1),p)

    """
    Creates the zero polynomial.
    """
    zero(::Type{Polynomial})::Polynomial = Polynomial()
    zero(::Type{PolynomialModP},n::Int)::PolynomialModP = PolynomialModP(n)
    """
    Creates the unit polynomial.
    """
    one(::Type{Polynomial})::Polynomial = Polynomial(Term(1,0))
    one(p::Polynomial) = one(typeof(p))

    one(::Type{PolynomialModP},p)::PolynomialModP = PolynomialModP(one(Term),p)
    one(p::PolynomialModP) = one(typeof(p),p.mod)

    """
    Generates a random polynomial.
    """
    function rand(::Type{Polynomial} ;
                    degree::Int = -1,
                    terms::Int = -1,
                    max_coeff::Int = 100,
                    mean_degree::Float64 = 5.0,
                    prob_term::Float64  = 0.7,
                    monic = false,
                    condition = (p)->true)

        while true
            _degree = degree == -1 ? rand(Poisson(mean_degree)) : degree
            _terms = terms == -1 ? rand(Binomial(_degree,prob_term)) : terms
            degrees = vcat(sort(sample(0:_degree-1,_terms,replace = false)),_degree)
            coeffs = rand(1:max_coeff,_terms+1)
            monic && (coeffs[end] = 1)
            p = Polynomial( [Term(coeffs[i],degrees[i]) for i in 1:length(degrees)] )
            condition(p) && return p
        end
    end

    function rand(::Type{PolynomialModP}, mod::Int ;
        degree::Int = -1,
        terms::Int = -1,
        mean_degree::Float64 = 5.0,
        prob_term::Float64  = 0.7,
        monic = false,
        condition = (p)->true)

        while true
            max_coeff = mod
            _degree = degree == -1 ? rand(Poisson(mean_degree)) : degree
            _terms = terms == -1 ? rand(Binomial(_degree,prob_term)) : terms
            degrees = vcat(sort(sample(0:_degree-1,_terms,replace = false)),_degree)
            coeffs = rand(1:max_coeff-1,_terms+1)
            monic && (coeffs[end] = 1)
            p = [Term(coeffs[i],degrees[i]) for i in 1:length(degrees)]
            poly = PolynomialModP(p,mod)
            condition(poly) && return poly
        end
    end

###########
# Display #
###########

    """
    Show a polynomial.
    """
    function show(io::IO, p::Polynomial)
        p = deepcopy(p)

        if iszero(p)
            print(io,"0")
        else
            n = length(p.terms)
            for (i,t) in enumerate(p.terms)

                if t.coeff < 0
                    print(io,i != 1 ? " - " : "-")
                    print(io,-t)
                else
                    print(io,i != 1 ? " + " : "")
                    print(io,t)
                end
            end
        end
    end

    show(io::IO,poly::PolynomialModP) = print(io, poly.polynomial, " (mod ", poly.mod, ")")

##############################################
# Iteration over the terms of the polynomial #
##############################################

    """
    Allows to do iteration over the terms of the polynomial. The iteration is in an arbitrary order.
    """
    iterate(p::Polynomial, state=1) = iterate(p.terms, state)
    iterate(p::PolynomialModP, state=1) = iterate(p.polynomial.terms, state)

    function coeffvector(p::Polynomial, size::Int = -1)::Vector{Int}
        size == -1 && (size = degree(p)+1)
        a = zeros(size)
        if length(p.terms) != size
            for t in p.terms
                a[size - t.degree] = t.coeff
            end
        else
            a = coeffs(p)
        end
        return a
    end

    coeffvector(p::PolynomialModP,size::Int = -1) = coeffvector(p.polynomial, size)

##############################
# Queries about a polynomial #
##############################

    """
    The number of terms of the polynomial.
    """
    length(p::Polynomial) = length(p.terms)
    length(p::PolynomialModP) = length(p.polynomial)

    """
    The leading term of the polynomial.
    """
    leading(p::Polynomial)::Term = isempty(p.terms) ? zero(Term) : first(p.terms)
    leading(p::PolynomialModP)::Term = leading(p.polynomial)

    """
    Returns the coefficients of the polynomial.
    """
    coeffs(p::Polynomial)::Vector{Int} = [t.coeff for t in p]
    coeffs(p::PolynomialModP)::Vector{Int} = coeffs(p.polynomial)

    """
    The degree of the polynomial.
    """
    degree(p::Polynomial)::Int = leading(p).degree
    degree(p::PolynomialModP)::Int = leading(p.polynomial).degree

    """
    The content of the polynomial is the GCD of its coefficients.
    """
    content(p::Polynomial)::Int = euclid_alg(coeffs(p))
    content(p::PolynomialModP)::Int = content(p.polynomial)

    """
    Evaluate the polynomial at a point `x`.
    """
    evaluate(f::Polynomial, x::T) where T <: Number = sum(evaluate(t,x) for t in f)
    evaluate(f::PolynomialModP, x::T) where T <: Number = evaluate(f.polynomial,x)%f.mod

################################
# Pushing and popping of terms #
################################

    """
    Push a new term into the polynomial.
    """
    #Note that ideally this would throw and error if pushing another term of degree that is already in the polynomial
    function push!(p::Polynomial, t::Term)
        iszero(t) && return #don't push a zero
        Polynomial(push!(p.terms,t))
    end

    function push!(p::PolynomialModP, t::Term)
        iszero(t) && return #don't push a zero
        PolynomialModP(push!(p.polynomial,t),p.mod)
    end

    """
    Pop the leading term out of the polynomial.
    """
    pop!(p::Polynomial)::Term = pop!(p.terms)
    pop!(p::PolynomialModP)::Term = pop!(p.polynomial.terms)

    prepop!(p::Polynomial)::Term = popat!(p.terms,1) #I needed a way to "pop" the first term (When changing to array from heap)
    prepop!(p::PolynomialModP)::Term = popat!(p.polynomial.terms,1)
    """
    Check if the polynomial is zero.
    """
    iszero(p::Polynomial)::Bool = isempty(p.terms)
    iszero(p::PolynomialModP)::Bool = iszero(p.polynomial)

#################################################################
# Transformation of the polynomial to create another polynomial #
#################################################################

    """
    The negative of a polynomial.
    """
    -(p::Polynomial) = Polynomial(map((pt)->-pt, p.terms))
    -(p::PolynomialModP) = PolynomialModP(-p.polynomial,p.mod)

    """
    Create a new polynomial which is the derivative of the polynomial.
    """
    function derivative(p::Polynomial)::Polynomial
        der_p = Polynomial()
        for term in p
            der_term = derivative(term)
            !iszero(der_term) && push!(der_p,der_term)
        end
        return der_p
    end

    derivative(p::PolynomialModP)::PolynomialModP = PolynomialModP(derivative(p.polynomial),p.mod)

    """
    The prim part (multiply a polynomial by the inverse of its content).
    """
    prim_part(p::Polynomial) = p ÷ content(p)
    prim_part(p::PolynomialModP) = p ÷ content(p)

    """
    A square free polynomial.
    """

    square_free(p::PolynomialModP)::PolynomialModP = p ÷ gcd(p,derivative(p))

#################################
# Queries about two polynomials #
#################################

    """
    Check if two polynomials are the same
    """
    ==(p1::Polynomial, p2::Polynomial)::Bool = p1.terms == p2.terms
    ==(p1::PolynomialModP, p2::PolynomialModP)::Bool = p1.polynomial.terms == p2.polynomial.terms

    """
    Check if a polynomial is equal to 0.
    """
    #Note that in principle there is a problem here. E.g The polynomial 3 will return true to equalling the integer 2.
    ==(p::Polynomial, n::T) where T <: Real = iszero(p) == iszero(n)
    ==(p::PolynomialModP, n::T) where T <: Real = ==(p.polynomial,n)

##################################################################
# Operations with two objects where at least one is a polynomial #
##################################################################
    """
    Subtraction of two polynomials.
    """
    -(p1::Polynomial, p2::Polynomial)::Polynomial = p1 + (-p2)
    -(p1::PolynomialModP, p2::PolynomialModP)::PolynomialModP = p1 + (-p2)

    -(p::PolynomialModP, n::Int) = p + PolynomialModP(Term(-n,0),p.mod)
    #Subtract an integer from a Polynomial
    -(p::Polynomial, n::Int)::Polynomial = p + Polynomial(Term(-n,0))

    """
    Multiplication of polynomial and term.
    """
    *(t::Term,p1::Polynomial)::Polynomial = iszero(t) ? Polynomial() : Polynomial(map((pt)->t*pt, p1.terms))
    *(p1::Polynomial, t::Term)::Polynomial = t*p1

    *(t::Term,p1::PolynomialModP) = iszero(t) ? PolynomialModP(p1.mod) : PolynomialModP(map((pt)->mod(t*pt,p1.mod), p1.polynomial.terms),p1.mod)
    *(p1::PolynomialModP, t::Term) = t*p1

    """
    Multiplication of polynomial and an integer.
    """
    *(n::Int,p::Polynomial)::Polynomial = p*Term(n,0)
    *(p::Polynomial,n::Int)::Polynomial = n*p

    *(n::Int,p::PolynomialModP) = p*Term(n,0)
    *(p::PolynomialModP,n::Int) = n*p

    """
    Integer division of a polynomial by an integer.

    Warning this may not make sense if n does not divide all the coefficients of p.
    """
    ÷(p::Polynomial,n::Int) = (prime)->Polynomial(map((pt)->((pt ÷ n)(prime)), p.terms))
    ÷(p::PolynomialModP, n::Int) = PolynomialModP(÷(p.polynomial,n)(p.mod),p.mod)

    """
    Take the s of a polynomial with an integer.
    """
    function mod(f::Polynomial, p::Int)::Polynomial
        p_out = Polynomial()
        for t in f
            push!(p_out, mod(t, p)) #if coeff reduced to zero, push! will handle it
        end
        return p_out
    end

" ---- Polynomial Run ----"


using LinearAlgebra

struct ValueOne; end
ValueOne()

# Compute X <- a X + b I.
function matfun_axpby!(X,a,b,Y::UniformScaling)
    m,n=size(X)
    if ~(a isa ValueOne)
        rmul!(X,a)
    end
    @inbounds for i=1:n
        X[i,i]+=(b isa ValueOne) ? 1 : b
    end
end

# Compute X <- a X + b Y.
function matfun_axpby!(X,a,b,Y)
    m,n=size(X)
    if ~(a isa ValueOne)
        rmul!(X,a)
    end
    @inbounds for i=1:m
        @inbounds for j=1:n
            if (b isa ValueOne)
                X[i,j]+=Y[i,j]
            else
                X[i,j]+=b*Y[i,j]
            end
        end
    end
end

@inline function denman_beavers(A)
    return denman_beavers!(copy(A))
end

@inline function denman_beavers!(A)
    T=promote_type(eltype(A),Float64) # Make it work for many 'bigger' types (matrices and scalars)
    max_memslots=4
    memslots=Vector{Matrix{T}}(undef,max_memslots)
    n=size(A,1)
    for j=1:max_memslots
        memslots[j]=Matrix{T}(undef,n,n)
    end
    # The first slots are precomputed nodes [:A]
    memslots[1]=A # overwrite A
    # Uniform scaling is exploited.
    # No matrix I explicitly allocated.
    value_one=ValueOne()
    # Computation order: A_shift Xinv0 Y1 X1 Yinv1 Xinv1 Y2 Yinv2 X3
    # Computing A_shift = x*A+x*I
    coeff1=1.0
    coeff2=1.0
    # Smart lincomb recycle A
    memslots[1] .= coeff1.*memslots[1]
    mul!(memslots[1],true,I*coeff2,true,true)
    # Computing Xinv0 with operation: ldiv
    memslots[2]=inv(memslots[1])
    # Computing Y1 = x*I+x*Xinv0
    coeff1=0.5
    coeff2=0.5
    # Smart lincomb recycle Xinv0
    memslots[2] .= coeff2.*memslots[2]
    mul!(memslots[2],true,I*coeff1,true,true)
    # Computing X1 = x*A_shift+x*I
    coeff1=0.5
    coeff2=0.5
    # Smart lincomb recycle A_shift
    memslots[1] .= coeff1.*memslots[1]
    mul!(memslots[1],true,I*coeff2,true,true)
    # Computing Yinv1 with operation: ldiv
    memslots[3]=inv(memslots[2])
    # Computing Xinv1 with operation: ldiv
    memslots[4]=inv(memslots[1])
    # Computing Y2 = x*Y1+x*Xinv1
    coeff1=0.5
    coeff2=0.5
    # Smart lincomb recycle Y1
    memslots[2] .= coeff1.*memslots[2] .+ coeff2.*memslots[4]
    # Deallocating Xinv1 in slot 4
    # Computing Yinv2 with operation: ldiv
    memslots[4]=inv(memslots[2])
    # Deallocating Y2 in slot 2
    # Computing X3 = x*X1+x*Yinv1+x*Yinv2
    coeff1=0.25
    coeff2=0.25
    coeff3=0.5
    # Smart lincomb recycle X1
    memslots[1] .= coeff1.*memslots[1] .+ coeff2.*memslots[3] .+ coeff3.*memslots[4]
    # Deallocating Yinv1 in slot 3
    # Deallocating Yinv2 in slot 4
    return memslots[1] # Returning X3
end


struct DoubleQuadRule{P,Q}
  outer_quad_points::P
  inner_quad_points::Q
end

"""
    regularcellcellinteractions!(biop, tshs, bshs, tcell, bcell, interactions, strat)

Function for the computation of moment integrals using simple double quadrature.
"""
function momintegrals!(biop, tshs, bshs, tcell, bcell, z, strat::DoubleQuadRule)

    # memory allocation here is a result from the type instability on strat
    # which is on purpose, i.e. the momintegrals! method is chosen based
    # on dynamic polymorphism.
    womps = strat.outer_quad_points
    wimps = strat.inner_quad_points

    for womp in womps
        tgeo = womp.point
        tvals = womp.value
        M = length(tvals)
        jx = womp.weight

        for wimp in wimps
            bgeo = wimp.point
            bvals = wimp.value
            N = length(bvals)
            jy = wimp.weight

            j = jx * jy
            kernel = kernelvals(biop, tgeo, bgeo)

            for n in 1 : N
                bval = bvals[n]
                for m in 1 : M
                    tval = tvals[m]
                    igd = integrand(biop, kernel, tval, tgeo, bval, bgeo)
                    z[m,n] += j * igd
                end
            end
        end
    end

    return z
end


include("euler/euler.jl")
using .BigIntegers: BigInteger
using .Numbers: is_palindrome

function compute(n::Int)::Int
    result = 0
    for i ∈ 1:n
        number = BigInteger(string(i))
        number += BigInteger(reverse(number.str))
        j = 1
        while j ≤ 50 && !is_palindrome(number)
            number += BigInteger(reverse(number.str))
            j += 1
        end
        if j > 50
            result += 1
        end
    end
    return result
end


## Scratch pad

using Polygons

P = [ .8 .2; .2 .8 ]
U = [ .5 -.5 ; -.5 .5 ]
bet = 0.9
vlow = ( 1 - bet ) * ( ( eye(2) - bet * P ) \ U )

N=40
dirs = zeros( N, 2 )
dirs[:,1] = [ cos(2 * pi * (i-1)/N) for i in 1:N ]
dirs[:,2] = [ sin(2 * pi * (i-1)/N) for i in 1:N ]

# Search directions
U1(s) = [ 2 -1; 3+s s ]
U2(s) = [ 2 3-s; -1 -s ]
# The period payoff functions

function T_unc( W::Array{ Polygon, 1 }, s::Float64, a::Vector, outer = true)
    # s is the value for the state and a is a vector containing the action indices
    u = vec( [ U1(s)[a[1],a[2]] ; U2(s)[a[1],a[2]] ] )
        # Period payoff
    s_idx = ( s == .5 ) ? 1 : 2
        # The index of s.  Required for selecting the right transition probability row
    return ( ( 1 - bet ) * u ) + ( bet * ( weightedSum( W, vec( P[ s_idx, :] ), dirs, outer ) ) )
end

function T_a( W::Array{ Polygon, 1 }, s::Float64, a::Vector, outer = true)
    # calcultes the incentive-compatible value set conditional on the action a
    unc = T_unc( W, s, a, outer )
        # The unconstrained set
    pddev = [ maximum( U1(s)[ :, a[2] ] ), maximum( U2(s)[ a[1], : ] ) ]
        # The deviating period payoffs
    s_idx = ( s == .5 ) ? 1 : 2
        # The index of s.  Required for selecting the right transition probability row
    dev = ( 1 - bet ) * pddev + bet * vlow[ s_idx, : ]'
        # The deviating payoff
    ic = crop( crop( unc, 1, dev[1] ), 2, dev[2] )
        # The incentive compatible set of payoffs\
    return ic
end

A_idx = [ 1 1; 1 2 ; 2 1; 2 2 ]
    # The matrix of action combinations

function T_operator( W::Array{ Polygon, 1 }, states::Vector, outer = true)
    # Forms the T operator
    out = [ union( [ T_a( W, s, vec(A_idx[j,:]))::Polygon for j in 1:size(A_idx)[1] ] )::Polygon for s in states ]
    return out
end

# polyPlot( [ winit1, T_operator( winit, [ .5 , -.5 ] ) ] )

winit1 = grahamScan( [ 2 2; -1 3.5; 3.5 -1; -0.5 0.5 ; 0.5 -0.5 ] )
winit = [ winit1, winit1 ]
aa = [ T_a( winit, -.5, vec(A_idx[j,:]))::Polygon for j in 1:size(A_idx)[1] ]
ff = union( aa )
polyPlot( [ aa, ff ])
polyPlot( aa[2] )

s = -.5
a = vec(A_idx[2,:])
nn=T_a( winit, s, a)::Polygon
unc = T_unc( winit, s, a)
unc2 = grahamScan(unc)
pddev = [ maximum( U1(s)[ :, a[2] ] ), maximum( U2(s)[ a[1], : ] ) ]
    # The deviating period payoffs
s_idx = ( s == .5 ) ? 1 : 2
    # The index of s.  Required for selecting the right transition probability row
dev = ( 1 - bet ) * pddev + bet * vlow[ s_idx, : ]'
    # The deviating payoff
ic = crop( crop( unc, 1, dev[1] ), 2, dev[2] )

polyPlot([unc, nn])


#####
##### Diffusive fluxes
#####

@inline diffusive_flux_x(i, j, k, grid, clock, κᶠᶜᶜ::Number, c) = κᶠᶜᶜ * Axᵃᵃᶠ(i, j, k, grid) * ∂xᶠᵃᵃ(i, j, k, grid, c)
@inline diffusive_flux_y(i, j, k, grid, clock, κᶜᶠᶜ::Number, c) = κᶜᶠᶜ * Ayᵃᵃᶠ(i, j, k, grid) * ∂yᵃᶠᵃ(i, j, k, grid, c)
@inline diffusive_flux_z(i, j, k, grid, clock, κᶜᶜᶠ::Number, c) = κᶜᶜᶠ * Azᵃᵃᵃ(i, j, k, grid) * ∂zᵃᵃᶠ(i, j, k, grid, c)

# Diffusivities-as-functions

@inline diffusive_flux_x(i, j, k, grid, clock, κ::Function, c) =
        diffusive_flux_x(i, j, k, grid, clock, κ(xnode(Face, i, grid), ynode(Center, j, grid), znode(Center, k, grid), clock.time), c)

@inline diffusive_flux_y(i, j, k, grid, clock, κ::Function, c) =
        diffusive_flux_y(i, j, k, grid, clock, κ(xnode(Center, i, grid), ynode(Face, j, grid), znode(Center, k, grid), clock.time), c)

@inline diffusive_flux_z(i, j, k, grid, clock, κ::Function, c) =
        diffusive_flux_z(i, j, k, grid, clock, κ(xnode(Center, i, grid), ynode(Center, j, grid), znode(Face, k, grid), clock.time), c)

#####
##### Laplacian diffusion operator
#####

"""
    ∂ⱼκᵢⱼ∂ᵢc(i, j, k, grid, κˣ, κʸ, κᶻ, c)

Calculates diffusion for a tracer c via

    1/V * [δxᶜᵃᵃ(κˣ * Ax * ∂xᶠᵃᵃ(c)) + δyᵃᶜᵃ(κʸ * Ay * ∂yᵃᶠᵃ(c)) + δzᵃᵃᶜ(κᶻ * Az * ∂zᵃᵃᶠ(c))]

which will end up at the location `ccc`.
"""
@inline function ∂ⱼκᵢⱼ∂ᵢc(i, j, k, grid, clock, κˣ, κʸ, κᶻ, c)
    return 1/Vᵃᵃᶜ(i, j, k, grid) * (δxᶜᵃᵃ(i, j, k, grid, diffusive_flux_x, clock, κˣ, c) +
                                    δyᵃᶜᵃ(i, j, k, grid, diffusive_flux_y, clock, κʸ, c) +
                                    δzᵃᵃᶜ(i, j, k, grid, diffusive_flux_z, clock, κᶻ, c))
end

@inline ∂ⱼκᵢⱼ∂ᵢc(i, j, k, grid, clock, κ, c) = ∂ⱼκᵢⱼ∂ᵢc(i, j, k, grid, clock, κ, κ, κ, c)


metric(x :: AbstractArray, y :: AbstractArray) =
    mapreduce((x, y) -> (x - y)^2, +, x, y)

function sumcosts(tracker1 :: AbstractArray{T, N},
                  tracker2 :: AbstractArray{T, N},
                  costfn   :: Function) where {T, N}
    @assert tracked_data(tracker1) == tracked_data(tracker2)
    return mapreduce(data -> costfn(tracker1 |> data,
                                    tracker2 |> data),
                     +, tracked_data(tracker1))
end

function sumcosts(tracker1 :: AbstractArray{T, N},
                  tracker2 :: AbstractArray{T, N},
                  costfn   :: Function,
                  weights  :: Dict{AbstractTracker{T}, Float64}) where {T, N}
    @assert tracked_data(tracker1) == tracked_data(tracker2)
    return mapreduce(data -> costfn(tracker1 |> data,
                                    tracker2 |> data) / weights[data],
                     +, tracked_data(tracker1))
end

# All cost functions accept two systems as their arguments: and return
# a distance between those systems. We will then try to minimize that
# distance.

"""
    euclid_mean(data1 :: CorrelationData, data2 :: CorrelationData)
    euclid_mean(data1 :: AbstractArray, data2 :: AbstractArray)

Calculate squared euclidean distance between values of two correlation
functions (represented as `CorrelationData` object) or two systems
(represented as `AbstractArray` object). The values are averaged
along all directions before calculation.
"""
function euclid_mean end

euclid_mean(data1 :: CorrelationData,
            data2 :: CorrelationData) =
                metric(data1 |> mean, data2 |> mean)

euclid_mean(data1 :: AbstractArray{T, N},
            data2 :: AbstractArray{T, N}) where {T, N} =
                sumcosts(data1, data2, euclid_mean)

"""
    euclid_directional(data1 :: CorrelationData, data2 :: CorrelationData)
    euclid_directional(data1 :: AbstractArray, data2 :: AbstractArray)

Calculate squared euclidean distance between values of two correlation
functions (represented as `CorrelationData` object) or two systems
(represented as `AbstractArray` object). The values calculated
along different directions are not averaged and treated separately.
"""
function euclid_directional end

function euclid_directional(data1 :: CorrelationData,
                            data2 :: CorrelationData)
    @assert directions(data1) == directions(data2)
    sum(metric(data1[dir], data2[dir]) for dir in directions(data1))
end

function euclid_directional(data1 :: AbstractArray{T, N},
                            data2 :: AbstractArray{T, N}) where {T, N}
    sumcosts(data1, data2, euclid_directional)
end

"""
    euclid_mean_weighted(data1 :: AbstractArray, data2 :: AbstractArray)

Calculate squared euclidean distance between values of two correlation
functions (represented as `CorrelationData` object) or two systems
(represented as `AbstractArray` object). The values are averaged
along all directions before calculation.

Each correlation function has its own weight as described in
Kirill M. Gerke and Marina V. Karsanina 2015 EPL 111 56002
"""
function euclid_mean_weighted(data1 :: AbstractArray{T, N},
                              data2 :: AbstractArray{T, N}) where {T, N}
    @assert tracked_data(data1) == tracked_data(data2)
    # At first, calculate weights for all correlation functions
    weights = Dict(data => euclid_mean(data1 |> data,
                                       data2 |> data)
                   for data in tracked_data(data1))
    f(:: Any, :: Any) = sumcosts(data1, data2, euclid_mean, weights)
    return f
end

"""
    euclid_directional_weighted(data1 :: AbstractArray, data2 :: AbstractArray)

Calculate squared euclidean distance between values of two correlation
functions (represented as `CorrelationData` object) or two systems
(represented as `AbstractArray` object). The values calculated
along different directions are not averaged and treated separately.

Each correlation function has its own weight as described in
Kirill M. Gerke and Marina V. Karsanina 2015 EPL 111 56002
"""
function euclid_directional_weighted(data1 :: AbstractArray{T, N},
                                     data2 :: AbstractArray{T, N}) where {T, N}
    @assert tracked_data(data1) == tracked_data(data2)
    # At first, calculate weights for all correlation functions
    weights = Dict(data => euclid_directional(data1 |> data,
                                              data2 |> data)
                   for data in tracked_data(data1))
    f(:: Any, :: Any) = sumcosts(data1, data2, euclid_directional, weights)
    return f
end

"""
    generalized_čapek_cost(tracker1, tracker2, dict)

Returns a function which calculates the cost based on two-point and
lineal-path functions for solid phase and any other functions present
in `keys(dict)`. `dict` is a dictionary which includes key-value pairs
`AbstractTracker => Float64`. Values of `dict` must be in range
`[0, 1]`. They control an initial contribution of the corresponding
correlation function. The smaller these values are the smaller is
the initial contribution. Contributions of all correlation functions
equivalizes when the cost function becomes small.
"""
function generalized_čapek_cost(data1 :: AbstractArray{T, N},
                                data2 :: AbstractArray{T, N},
                                dict  :: Dict{<:AbstractTracker{T}, Float64}) where {T, N}
    # Initial S2
    s2cost_init  = euclid_directional(Directional.s2(data1, 0),
                                      Directional.s2(data2, 0))
    # Initial L2 for solid phase
    l2scost_init = euclid_directional(Directional.l2(data1, 1),
                                      Directional.l2(data2, 1))

    # Rescale coefficients as in čapek_cost
    sdict = Dict(k => v*(s2cost_init + l2scost_init) for (k, v) in dict)

    local function f(data1 :: AbstractArray, data2 :: AbstractArray)
        # S2
        s2cost  = euclid_directional(Directional.s2(data1, 0),
                                     Directional.s2(data2, 0))
        # L2 for solid phase
        l2scost = euclid_directional(Directional.l2(data1, 1),
                                     Directional.l2(data2, 1))
        # All other cost functions
        other = sum(η/(η + s2cost + l2scost) *
                    euclid_directional(corrfn(data1),
                                       corrfn(data2))
                    for (corrfn, η) in sdict)

        return s2cost + l2scost + other
    end

    return f
end

"""
    čapek_cost(data1 :: AbstractArray, data2 :: AbstractArray, η = 0.6)

Returns a function which calculates the cost based on two-point
correlation function for void phase  and lineal-path function for
solid and void phases where contribution of the latter increases with
time.
"""
čapek_cost(data1 :: AbstractArray{T, N},
           data2 :: AbstractArray{T, N},
           η     :: Float64 = 0.6) where {T, N} =
               generalized_čapek_cost(data1, data2, Dict(T |> zero |> L2Tracker => η))


using FunManifolds
using Test
using StaticArrays

include("../utils.jl")

@testset "CurveWarpingSRSFSpace" begin
    for N in [21, 50]
        nodes = range(0.0, 1.0, length=N)
        M = CurveWarpingSpace(nodes)
        MS = CurveWarpingSRSFSpace(nodes)

        test_manifold(
            MS,
            [srsf(M, t -> t^2), srsf(M, t -> t^3), srsf(M, t -> t^(0.7))];
            exp_log_atol_multiplier=N % 2 == 1 ? 1e8 : 10,
            test_default_vector_transport=true,
            test_reverse_diff=false,
            test_forward_diff=false,
            test_injectivity_radius=false,
        )
    end

    @testset "reverse SRSF" begin
        N = 11
        nodes = range(0.0, 1.0, length=N)
        M = CurveWarpingSpace(nodes)
        MS = CurveWarpingSRSFSpace(nodes)

        γ = FunManifolds.make_warping(M, [t^2 for t in nodes])
        γSRSF = srsf(M, γ)
        @test isapprox(M, γ, reverse_srsf(M, γSRSF), atol=1e-15)
    end
end


include("setupInversionTest.jl");

@everywhere begin
	using jInv.InverseSolve
	using jInv.LinearSolvers
end

using Test

println("Running inversion test");

Dobs, Wd = solveForwardProblem(m ,pForp, omega, nrec, nsrc, nfreq);
mc, Dc, pInv, Iact, mback = solveInverseProblem(pForp, Dobs, Wd, nfreq, nx, nz, Mr);

fullMc = reshape(Iact*pInv.modelfun(mc)[1] + mback,tuple((pInv.MInv.n)...));
println("Model error:");
println(norm(fullMc.-m));
@test norm(fullMc.-m) < 1.4

for k=1:length(Dc)
	wait(Dc[k]);
end
Dinv = Array{Array}(undef, nfreq);
for k=1:length(Dc)
	Dinv[k] = fetch(Dc[k]);
end
println("Data error:");
println(norm(Dobs.-Dinv));
@test norm(Dobs.-Dinv) < 5.4


function softabs(hessian::RealMatrix, a::Float64=1000.0)
  λ, Q = eig(hessian)
  Q*diagm(λ./tanh.(a*λ))*Q'
end


# r[3,Npoints]
# atpos[3,Natoms]
# A[Natoms]
# R0[Natoms]
function to_curvilinear( r::Array{Float64,2}, atpos, A, R0)
    #
    Npoints = size(r)[2]
    Natoms = size(atpos)[2]
    #
    ξ = zeros(Float64,3,Npoints)
    #
    for ip = 1:Npoints
        ξ[:,ip] = r[:,ip]
        for ia = 1:Natoms
            xx = norm( (r[:,ip] - atpos[:,ia])/R0[ia] )
            ξ[:,ip] = ξ[:,ip] + A[ia]*( r[:,ip] - atpos[:,ia] )/R0[ia] * sech(xx)
        end
    end
end


include("../src/advection.jl")
include("../src/spline.jl")
include("../src/bspline.jl")
include("../src/bsplinelu.jl")
include("../src/bsplinefft.jl")
include("../src/lagrange.jl")

function aff_graph(ind, fp)
    println("ind=$ind begin")
    minval = 0
    maxval = 0
    for (i, val) in enumerate(fp)
        #        println("$i\t$val")
        minval = min(val, minval)
        maxval = max(val, maxval)
    end
    println("minval=$minval maxval=$maxval")
    println("ind=$ind end")
end
function test_dirac(bsp::InterpolationType, order, len, nb, modval)
    #        println("lag=$lag")
    n = len
    # whennot circuar coef != 1 set the function unperiodic
    fp = zeros(BigFloat, n)
    fp[div(n, 2)] = big"1.0"
    fi = zeros(BigFloat, n)
    value = big"0.30529810681113334445566767713091"
    normax = 0.0
    aff_graph(1, fp)
    for i = 1:nb
        fi .= fp
        interpolate!(missing, fp, fi, value, bsp)
        if i % modval == 0
            aff_graph(i, fp)
        end
    end
end
function test_dirac_lagrange(order, len, nb, modval)
    lag = Lagrange(BigFloat, order; iscirc = true)
    test_dirac(lag, order, len, nb, modval)
end
function test_dirac_splu(order, len, nb, modval)
    bsp = B_SplineLU(order, len, BigFloat; iscirc = true)
    test_dirac(bsp, order, len, nb, modval)
end
function test_dirac_spfft(order, len, nb, modval)
    bsp = B_SplineFFT(order, len, BigFloat)
    test_dirac(bsp, order, len, nb, modval)
end

test_dirac_lagrange(31, 256, 500, 50)
test_dirac_splu(31, 256, 500, 50)
test_dirac_spfft(31, 256, 500, 50)


using Base.Threads

function getInput()
    println("Enter start, end, n")
    collect([parse(Int64, x) for x in split(readline())])
end

function trap(
    left_endpt::Float64,
    right_endpt::Float64,
    trap_count::Int64,
    base_len::Float64)

    f(x) = x^2

    estimate = (f(left_endpt) + f(right_endpt)) / 2.0
    for i=1:trap_count - 1
        estimate += f(left_endpt + i * base_len) # type instability
    end
    return estimate * base_len
end

function main(a=0, b=131072, n=32768)
    # a, b, n = getInput()
    h = (b - a) / n

    n_threads = nthreads()
    if n_threads == 1
        println("
        Number of threads detected to be =1\n
        environment variables may not be properly set up!"
        )
    end
    total = Atomic{Float64}(0)
    local_n::Int64 = div(n, n_threads)
    @threads for rank in 0:n_threads - 1
        local_a = a + rank * local_n * h
        local_b = local_a + local_n * h
        local_ans = trap(local_a, local_b, local_n, h)
        println("local_a = $local_a, local_b = $local_b, ans = $local_ans")
        atomic_add!(total, local_ans)
    end

    println("the answer for a = $a, b = $b, n = $n: $(total[])")
end

for i=1:10000
    main()
end


using ProximalOperators: ProximableFunction
using RecursiveArrayTools: ArrayPartition
import ProximalOperators: gradient!, gradient

struct ComposeAffine <: ProximableFunction
    f
    A
    b
end

function compose_affine_gradient!(y, g::ComposeAffine, x)
    res = g.A * x .+ g.b
    gradres, v = gradient(g.f, res)
    mul!(y, adjoint(g.A), gradres)
    return v
end

gradient!(y, g::ComposeAffine, x) = compose_affine_gradient!(y, g, x)
gradient!(y::ArrayPartition, g::ComposeAffine, x::ArrayPartition) = compose_affine_gradient!(y, g, x)

function ProximalOperators.gradient(h::ComposeAffine, x::ArrayPartition)
    grad_fx = similar(x)
    fx = gradient!(grad_fx, h, x)
    return grad_fx, fx
end