abstract type Distribution end

function ==(d1::D, d2::D) where D<:Distribution
    ret = true
    for fld in fieldnames(D)
        ret = ret && getfield(d1, fld) == getfield(d2, fld)
    end
    return ret
end

# DISTRIBUTION AS TRAIT

# fallback
isdistribution(::Type{<:Any}) = false

# for distributions in Distributions.jl:
isdistribution(::Type{<:Distributions.Distribution}) = true

# for MLJ custom distibutions defined below:
isdistribution(::Type{Distribution}) = true

isdistribution(d) = isdistribution(typeof(d))

## UNIVARIATE NOMINAL PROBABILITY DISTRIBUTION

"""
    UnivariateNominal(prob_given_label)

A discrete univariate distribution whose finite support is the set of keys of the
provided dictionary, `prob_given_label`. The dictionary values specify
the corresponding probabilities, which must be nonnegative and sum to
one.

    UnivariateNominal(labels, p)

A discrete univariate distribution whose finite support is the
elements of the vector `labels`, and whose corresponding probabilities
are elements of the vector `p`.

````julia
d = UnivariateNominal(["yes", "no", "maybe"], [0.1, 0.2, 0.7])
pdf(d, "no") # 0.2
mode(d) # "maybe"
rand(d, 5) # ["maybe", "no", "maybe", "maybe", "no"]
d = fit(UnivariateNominal, ["maybe", "no", "maybe", "yes"])
pdf(d, "maybe") ≈ 0.5 # true
````

"""
struct UnivariateNominal{L,T<:Real} <: Distribution
    prob_given_label::Dict{L,T}
    function UnivariateNominal{L,T}(prob_given_label::Dict{L,T}) where {L,T<:Real}
        p = values(prob_given_label) |> collect
        Distributions.@check_args(UnivariateNominal, Distributions.isprobvec(p))
        return new{L,T}(prob_given_label)
    end
end
UnivariateNominal(prob_given_label::Dict{L,T}) where {L,T<:Real} =
    UnivariateNominal{L,T}(prob_given_label)

function UnivariateNominal(labels::Vector{L}, p::Vector{T}) where {L,T<:Real}
        Distributions.@check_args(UnivariateNominal, length(labels)==length(p))
        prob_given_label = Dict{L,T}()
        for i in eachindex(p)
            prob_given_label[labels[i]] = p[i]
        end
        return  UnivariateNominal(prob_given_label)
end

function average(dvec::Vector{UnivariateNominal{L,T}}; weights=nothing) where {L,T}

    n = length(dvec)

    Distributions.@check_args(UnivariateNominal, weights == nothing || n==length(weights))

    if weights == nothing
        weights = fill(1/n, n)
    else
        weights = weights/sum(weights)
    end

    # get all labels:
    labels = reduce(union, [keys(d.prob_given_label) for d in dvec])

    z = Dict{L,T}([x => zero(T) for x in labels]...)
    prob_given_label_vec = map(dvec) do d
        merge(z, d.prob_given_label)
    end

    # initialize the prob dictionary for the distribution sum:
    prob_given_label = Dict{L,T}()
    for x in labels
        prob_given_label[x] = zero(T)
    end

    # sum up:
    for x in labels
        for k in 1:n
            prob_given_label[x] += weights[k]*prob_given_label_vec[k][x]
        end
    end

    return UnivariateNominal(prob_given_label)

end

function Distributions.mode(d::UnivariateNominal)
    dic = d.prob_given_label
    p = values(dic)
    max_prob = maximum(p)
    m = first(first(dic)) # mode, just some label for now
    for (x, prob) in dic
        if prob == max_prob
            m = x
            break
        end
    end
    return m
end

Distributions.pdf(d::UnivariateNominal, x) = d.prob_given_label[x]

"""
    _cummulative(d::UnivariateNominal)

Return the cummulative probability vector `[0, ..., 1]` for the
distribution `d`, using whatever ordering is used in the dictionary
`d.prob_given_label`. Used only for to implement random sampling from
`d`.

"""
function _cummulative(d::UnivariateNominal{L,T}) where {L,T<:Real}
    p = collect(values(d.prob_given_label))
    K = length(p)
    p_cummulative = Array{T}(undef, K + 1)
    p_cummulative[1] = zero(T)
    p_cummulative[K + 1] = one(T)
    for i in 2:K
        p_cummulative[i] = p_cummulative[i-1] + p[i-1]
    end
    return p_cummulative
end

"""
_rand(p_cummulative)

Randomly sample the distribution with discrete support `1:n` which has
cummulative probability vector `p_cummulative=[0, ..., 1]` (of length
`n+1`). Does not check the first and last elements of `p_cummulative`
but does not use them either.

"""
function _rand(p_cummulative)
    real_sample = rand()
    K = length(p_cummulative)
    index = K
    for i in 2:K
        if real_sample < p_cummulative[i]
            index = i - 1
            break
        end
    end
    return index
end

function Base.rand(d::UnivariateNominal)
    p_cummulative = _cummulative(d)
    labels = collect(keys(d.prob_given_label))
    return labels[_rand(p_cummulative)]
end

function Base.rand(d::UnivariateNominal, n::Int)
    p_cummulative = _cummulative(d)
    labels = collect(keys(d.prob_given_label))
    return [labels[_rand(p_cummulative)] for i in 1:n]
end

function Distributions.fit(d::Type{<:UnivariateNominal}, v::AbstractVector{L}) where L
    N = length(v)
    prob_given_label = Dict{L,Float64}()
    count_given_label = Distributions.countmap(v)
    for (x, c) in count_given_label
        prob_given_label[x] = c/N
    end
    return UnivariateNominal(prob_given_label)
end

# if fitting to categorical array, must include missing labels with prob zero
function Distributions.fit(d::Type{<:UnivariateNominal}, v::CategoricalVector{L,R,V}) where {L,R,V}
    N = length(skipmissing(v) |> collect)
    prob_given_label = Dict{V,Float64}() # V is the type of levels
    for x in levels(v)
        prob_given_label[x] = 0
    end
    count_given_label = Distributions.countmap(skipmissing(v) |> collect)
    for (x, c) in count_given_label
        prob_given_label[x] = c/N
    end
    return UnivariateNominal(prob_given_label)
end

## NORMAL DISTRIBUTION ARITHMETIC

# *(lambda::Number, d::Distributions.Normal) = Distributions.Normal(lambda*d.μ, lambda*d.σ)
# function +(ds::Distributions.Normal...)
#     μ = sum([d.μ for d in ds])
#     σ = sum([d.σ^2 for d in ds]) |> sqrt
#     return Distributions.Normal(μ, σ)
# end

# hack for issue #809 in Distributions.jl:
Distributions.fit(::Type{Distributions.Normal{T}}, args...) where T = Distributions.fit(Distributions.Normal, args...)


### A Pluto.jl notebook ###
# v0.12.18

using Markdown
using InteractiveUtils

# ╔═╡ c7cd922c-1b76-11eb-2860-2bdefdcbf8b2
begin
	import Pkg
	Pkg.activate(".")

	using SimpleValueGraphs

	# For creating reproducable random matrices
	using Random: MersenneTwister
	using SparseArrays

	# We are going to test our new packages with some functions
	# from Graphs.jl and GraphsRecipes
	using Graphs
	using GraphRecipes, Plots
	pyplot() # gr backend has issues showing edge labels so we use pyplot instead
end

# ╔═╡ 9ce46c16-2085-11eb-076f-256b70803020
md"""
# Creating a new value graph type

This notebook show how one create a new graph type that subclasses `AbstractValGraph`.

The new graph type will be a wrapper type around a matrix `A`, so that the graph has
a directed edge from vertex `i` to vertex `j` with weight `A[i, i]` if that entry is not zero.

We start by importing a few packages that we are going to use later:
"""

# ╔═╡ daf7b178-2087-11eb-23cb-6d4f29edc176
md"""
The definition of `AbstractValGraph` is
```julia
abstract type AbstractValGraph{V<:Integer, V_VALS, E_VALS, G_VALS} <: AbstractGraph{V} end
```
where
* `V` is the type of the vertices
* `V_VALS` are the types of the vertex values
* `E_VALS` are the types of the edge values
* `G_VALS` are the types of the edge values

As this type of `Graphs.AbstractGraph`, it can also be used with Graphs functions as long as we correctly implement the `AbstractGraph` interface.

In our case the vertex type will be `Int` as this type is also used for indexing rows and columns in a matrix.

We don't have any graph or vertex values, so use the empty tuple type for `V_VALS` and `G_VALS`.

For the edge values we have a single type (the type of the values in our matrix). We also want to give the name `:weight` to these values. This can be done by using the named tuple type `NamedTuple{(:weight,), Tuple{T}}` where `T` is the type of the matrix values.

Then our definition is:
"""

# ╔═╡ 9259d936-1b79-11eb-15cc-abaa483a07c0
struct GraphView{T, M<:AbstractMatrix{<:T}} <: SimpleValueGraphs.AbstractValGraph{
		Int,
		Tuple{},
		NamedTuple{(:weight,), Tuple{T}},
		Tuple{}
	}

	matrix::M
end

# ╔═╡ ec4e6c94-2082-11eb-3d82-d799a91863a7
md"""

## Implementing the AbstractValGraph interface

To be able to do anything meaningful with that graph, we have to implement the interface for `Graphs.AbstractGraph` as well as the interface for `SimpleGraphs.AbstractValGraph`. Luckily, SimpleValueGraphs provides already some sensible defaults (that can be overriden for performance or other reasons) for a lot of Graphs.jl functions so that what we will have to implement is less than is usually required by Graphs.jl. Nevertheless we need to implement the following functions:

* `nv(::GraphView)`
* `is_directed(::Type{<:GraphView})`
* `has_edge(::GraphView, s, d)`
* `zero(::Type{<:GraphView{T, M}) where {T, M}`

And because our graphs has edge values, we also need to implement
* `get_edgeval(::GraphView, s, d, :)`

"""

# ╔═╡ 3f533e8e-208e-11eb-090a-6543f23256a2
md"""
##### nv
`nv(g)` should return the number of vertices in our graph. We could make sure in the constructor that the wrapped matrix is square, but instead we just the minimum of the number of rows and columns as the number of vertices so that any additional values in the matrix are simply ignored.
"""

# ╔═╡ 5e162db8-208e-11eb-09e0-5f14ac2f631c
SimpleValueGraphs.nv(g::GraphView) = minimum(size(g.matrix))

# ╔═╡ 5923245a-208e-11eb-0dd9-0fc7c7344c84
md"""
##### is_directed

`is_directed` returns whether the graph is directed or not. This should be a property of the graph type itself, regardless of the values in that graph. As our matrix is not necessarily symmetric (although we could ensure that with a check in the constructor), our graph will be directed.
"""

# ╔═╡ bb9187f8-1b7f-11eb-15c5-d7884b4cca33
SimpleValueGraphs.is_directed(::Type{<:GraphView}) = true

# ╔═╡ a3b59cc2-208f-11eb-32d0-238d11964bf9
md"""
##### has_edge
`has_edge(g, u, v)` should return true if there is an edge between the vertices `u` and `v`. This should be the case when the entry in the wrapped matrix is zero. We can check that by using the `iszero` function. This also means that our graph type does not work for value types that do not have `iszero` defined.

We also verify that `u` and `v` are actually vertices of our graph. For this we use the `vertices` function that alreay has a default implementation through the implementation of `nv`.
"""

# ╔═╡ e28e02c8-1b7f-11eb-2949-1fc7e27f6cc2
function SimpleValueGraphs.has_edge(g::GraphView, u, v)
	u ∉ vertices(g) && return false
	v ∉ vertices(g) && return false

	return !iszero(g.matrix[u, v])
end

# ╔═╡ 10913788-2091-11eb-31a9-8dc219364439
md"""
##### get_edgeval
`get_edgeval(g, u, v, key::Integer)` should return the edge value associated with the edge between `u` and `v` for a  specific key. We only have to implement this method for integer keys. As we only have a single value for each edge, we accept all values for the key - a more sophisticated approach would be to throw an error if the key is not correct.

In our case we do not explicetly verify that the edge exist altought we could do so and throw a specialised error message. But this is not part of the interface and functions that deal with value graphs should not relay on that.
"""

# ╔═╡ 5fce373e-56a0-11eb-015f-e39d395121d4
SimpleValueGraphs.get_edgeval(g::GraphView, u, v, key::Integer) = weight=g.matrix[u, v]

# ╔═╡ ed96451c-5596-11eb-1c39-8b28c0e661af
md"""
##### zero
This function is a bit of an anomaly and should in my opinion not be part of the Graphs.jl interface. It is also rarely used in Graphs.jl, so it might not be a very big issue to omit it. Nevertheless we implement it here for the sake of completeness.

`zero(G)` should create a graph with zero vertices, given a graph type. We do this here by trying to create a matrix of size (0, 0) and the correct matrix type, and then wrap a `GraphView` around it:
"""

# ╔═╡ d594af1c-5690-11eb-3b9d-4f3e1f6384ee
function SimpleValueGraphs.zero(::Type{<:GraphView{T, M}}) where {T, M}
	matrix = convert(M, Matrix{T}(undef, 0, 0))
	return GraphView{T, M}(matrix)
end

# ╔═╡ 455072ea-5692-11eb-1a6b-23f3cfac6a16
zero(GraphView{String, Matrix{String}})

# ╔═╡ a36d032a-5692-11eb-1b38-3772dea5f067
zero(GraphView{Float64, SparseMatrixCSC{Float64}})

# ╔═╡ 45463dee-56a0-11eb-2181-8f0089913ef5
md"## Using our new graph type"

# ╔═╡ 7757542e-2092-11eb-20b4-ff4234b49d32
md"""
Now we are ready to create `GraphView` of a matrix and verify that it indeed works.

We start by creating a sparse 5x5 matrix where each entry is _not_ zero with probability 0.3. For better reproducibility we used a `MersenneTwister` random generator with a fixed seed.

"""

# ╔═╡ 9ef5fe50-1b7d-11eb-1ad4-115e1adeeebe
A = sprand(MersenneTwister(1), Float64, 5, 5, 0.3)

# ╔═╡ 339348dc-2093-11eb-3896-d1e25cbfa433
md"""
Then we wrap this matrix in our `GraphView`. As `A` has 11 non-zero entries, our directed graph should also have 11 edges.
"""

# ╔═╡ bee02bdc-1b7d-11eb-2f87-bb568e3e6b7c
g = GraphView(A)

# ╔═╡ 8d4cc722-2093-11eb-0b98-8741fa511c84
md"Let's verify that our graph has the correct number of edges"

# ╔═╡ bc09149c-2093-11eb-3d18-1b90f05311a8
ne(g)

# ╔═╡ dad3ce14-2093-11eb-076d-e1c784c74c8c
md"We can also look at the list of edges in our graph:"

# ╔═╡ e8b78692-2093-11eb-233b-63061135f033
edges(g) |> collect

# ╔═╡ ecaf3358-2093-11eb-33a9-a1404f9571b0
md"""
This seems to work as it should. By using the `weights` function, we can also get a matrix of the weights in our graph:
"""

# ╔═╡ 4c9dc1f8-2094-11eb-0660-75c6a339a717
weights(g)

# ╔═╡ 598a9a44-2094-11eb-0518-9f2b052d0140
md"We can verify that these are the same values as in our wrapped matrix:"

# ╔═╡ 6f99a4f6-2094-11eb-3987-3d079af43bd8
weights(g) == A

# ╔═╡ 7a02edde-56a0-11eb-28cd-f15320b018ed
md"""
We should also quickly check that `zero` works and then forget about it:
"""

# ╔═╡ 90c970ba-56a0-11eb-039b-45777dda3357
zero(typeof(g))

# ╔═╡ 75a1a2a6-2094-11eb-2989-d5fa04fb8a33
md"""
### Plotting

Using [GraphRecipes.jl](https://github.com/JuliaPlots/GraphRecipes.jl) we can now also plot our graph:
"""

# ╔═╡ a6f47b56-2094-11eb-05ce-a336e6dd028b
graphplot(g)

# ╔═╡ d035e7d4-2094-11eb-2121-3b675521b9db
md"""
Let's add some vertex labels and the eddge weights so that is easier to verify that this corresponds to our matrix:
"""

# ╔═╡ ff2d75f6-2045-11eb-3d9c-218b3f5adfb3
graphplot(g,
	names=vertices(g),
	edgelabel=trunc.(weights(g), digits=2), # truncate weights for better readability
	nodecolor=:lightgreen,
	nodesize=0.2
)

# ╔═╡ 9a2272c8-2096-11eb-349d-91f34702dd1f
md"""
### Shortest paths

Let's use some functions from Graphs.jl to find the shortest path from vertex `2` to `5`:
"""

# ╔═╡ 1147357a-2095-11eb-224e-61ad831db786
path = enumerate_paths(dijkstra_shortest_paths(g, 2), 5)

# ╔═╡ f24f1466-2098-11eb-11ad-479e850f798d
md"""
We want to visualize this path with GraphRecipes. For that we create a `Dict` that assigns to each edge (a tuple of source and destination of that edge) a color.
"""

# ╔═╡ be361882-2097-11eb-3003-a3503eefca35
begin
	edgecolor = Dict()
	# Set all edgecolors to black
	for e in edges(g)
		edgecolor[(src(e), dst(e))] = :black
	end
	# Set the edgecolor for edges on the path to red
	for i in 1:length(path)-1
		edgecolor[(path[i], path[i+1])] = :red
	end
end

# ╔═╡ a33449d8-2099-11eb-3740-a91b14a285f6
md"""
Now we can pass `edgecolor` to the plotting function:
"""

# ╔═╡ 4abed890-2099-11eb-2287-6b1dce2dd4f2
graphplot(g,
	names=vertices(g),
	edgelabel=trunc.(weights(g), digits=2),
	edgecolor=edgecolor,
	nodecolor=:lightgreen,
	nodesize=0.2
)

# ╔═╡ e8acb5ca-1b7d-11eb-3f4e-4f4dfb631517

# ╔═╡ 4daf3fde-1bcb-11eb-2eec-ebe2c0c0f11d

# ╔═╡ a3d03dde-1bcb-11eb-37ff-b558b6ecde1a

# ╔═╡ 6f14c1fa-1bcb-11eb-1ea3-5547297c6fa0

# ╔═╡ Cell order:
# ╟─9ce46c16-2085-11eb-076f-256b70803020
# ╠═c7cd922c-1b76-11eb-2860-2bdefdcbf8b2
# ╟─daf7b178-2087-11eb-23cb-6d4f29edc176
# ╠═9259d936-1b79-11eb-15cc-abaa483a07c0
# ╠═ec4e6c94-2082-11eb-3d82-d799a91863a7
# ╟─3f533e8e-208e-11eb-090a-6543f23256a2
# ╠═5e162db8-208e-11eb-09e0-5f14ac2f631c
# ╟─5923245a-208e-11eb-0dd9-0fc7c7344c84
# ╠═bb9187f8-1b7f-11eb-15c5-d7884b4cca33
# ╟─a3b59cc2-208f-11eb-32d0-238d11964bf9
# ╠═e28e02c8-1b7f-11eb-2949-1fc7e27f6cc2
# ╟─10913788-2091-11eb-31a9-8dc219364439
# ╠═5fce373e-56a0-11eb-015f-e39d395121d4
# ╟─ed96451c-5596-11eb-1c39-8b28c0e661af
# ╠═d594af1c-5690-11eb-3b9d-4f3e1f6384ee
# ╠═455072ea-5692-11eb-1a6b-23f3cfac6a16
# ╠═a36d032a-5692-11eb-1b38-3772dea5f067
# ╟─45463dee-56a0-11eb-2181-8f0089913ef5
# ╟─7757542e-2092-11eb-20b4-ff4234b49d32
# ╠═9ef5fe50-1b7d-11eb-1ad4-115e1adeeebe
# ╟─339348dc-2093-11eb-3896-d1e25cbfa433
# ╠═bee02bdc-1b7d-11eb-2f87-bb568e3e6b7c
# ╟─8d4cc722-2093-11eb-0b98-8741fa511c84
# ╠═bc09149c-2093-11eb-3d18-1b90f05311a8
# ╟─dad3ce14-2093-11eb-076d-e1c784c74c8c
# ╠═e8b78692-2093-11eb-233b-63061135f033
# ╟─ecaf3358-2093-11eb-33a9-a1404f9571b0
# ╠═4c9dc1f8-2094-11eb-0660-75c6a339a717
# ╟─598a9a44-2094-11eb-0518-9f2b052d0140
# ╠═6f99a4f6-2094-11eb-3987-3d079af43bd8
# ╟─7a02edde-56a0-11eb-28cd-f15320b018ed
# ╠═90c970ba-56a0-11eb-039b-45777dda3357
# ╟─75a1a2a6-2094-11eb-2989-d5fa04fb8a33
# ╠═a6f47b56-2094-11eb-05ce-a336e6dd028b
# ╟─d035e7d4-2094-11eb-2121-3b675521b9db
# ╠═ff2d75f6-2045-11eb-3d9c-218b3f5adfb3
# ╟─9a2272c8-2096-11eb-349d-91f34702dd1f
# ╠═1147357a-2095-11eb-224e-61ad831db786
# ╟─f24f1466-2098-11eb-11ad-479e850f798d
# ╠═be361882-2097-11eb-3003-a3503eefca35
# ╟─a33449d8-2099-11eb-3740-a91b14a285f6
# ╠═4abed890-2099-11eb-2287-6b1dce2dd4f2
# ╟─e8acb5ca-1b7d-11eb-3f4e-4f4dfb631517
# ╟─4daf3fde-1bcb-11eb-2eec-ebe2c0c0f11d
# ╟─a3d03dde-1bcb-11eb-37ff-b558b6ecde1a
# ╟─6f14c1fa-1bcb-11eb-1ea3-5547297c6fa0


################################################################################
#
#   bgjt.jl : functions for the BGJT algorithm
#
################################################################################

# Flint functions

function rrefMod!(M::Nemo.fmpz_mat, n::Nemo.fmpz)
#    perm::Array{Int, 1} = collect(1:rows(M))
    perm = C_NULL
    rank = ccall((:fmpz_mat_rref_mod, :libflint), Cint, (Ptr{Int}, Ptr{fmpz_mat},
                                                  Ptr{fmpz}), perm, &M, &n)
    return M, rank, perm
end

function rrefMod!(M::Nemo.fmpz_mat, d::Integer)
    n = Nemo.fmpz(d)
#    perm::Array{Int, 1} = collect(1:rows(M))
    perm = C_NULL
    rank = ccall((:fmpz_mat_rref_mod, :libflint), Cint, (Ptr{Int}, Ptr{fmpz_mat},
                                                  Ptr{fmpz}), perm, &M, &n)
    return M, rank, perm
end

function rrefMod(M::Nemo.fmpz_mat, n::Nemo.fmpz)
    N = deepcopy(M)
    M, rank, perm = rrefMod!(N, n)
    return N, rank, perm
end

function rrefMod(M::Nemo.fmpz_mat, n::Integer)
    N = deepcopy(M)
    M, rank, perm = rrefMod!(N, n)
    return N, rank, perm
end

# Julia functions

export subMatrix
"""
    subMatrix(M::MatElem, nrow::Integer, ncol::Integer)

Return the submatrix consisting of the first `nrow` rows and `ncol` colums of
`M`.
"""
function subMatrix(M::MatElem, nrow::Integer, ncol::Integer)

    # We create the matrix subspace
    S = MatrixSpace(parent(M[1,1]), nrow, ncol)

    # We create a new matrix
    sub = S()

    # And we copy the entries of the given matrix
    for j in 1:ncol
        for i in 1:nrow
            sub[i, j] = M[i, j]
        end
    end
    return sub
end

export pivots
"""
    pivots(M::MatElem)

Find `r` pivots in the row reduced echelon form matrix `M`.
"""
function pivots(M::MatElem, r::Int = 0)

    # There is as many pivots as the rank of
    if r == 0
        r = rank(M)
    end

    # We set an array with the coordinates of the pivots
    piv = Array(Int, r)
    c = 1

    # And we find these coordinates
    for i in 1:r
        while M[i, c] == 0
            c += 1
        end
        piv[i] = c
    end
    return piv
end


struct Point3D <: FieldVector{3, Float64}
    x::Float64
    y::Float64
    z::Float64
end

import Base: +, -, ==, *, ^, <, >, /, !=
import Base: isnan, isapprox, isequal, isless, broadcastable
import Luxor: between, distance, midpoint

Base.broadcastable(x::Point3D) = Ref(x)

function +(p1::Point3D, p2::Point3D)
    Point3D((p2.x + p1.x), (p2.y + p1.y), (p2.z + p1.z))
end
+(p1::Point3D, k::Number)              = Point3D(p1.x + k, p1.y + k, p1.z + k)
+(k::Number, p2::Point3D)              = +(p2::Point3D, k::Number)

function -(p1::Point3D, p2::Point3D)
    Point3D((p1.x - p2.x), (p1.y - p2.y), (p1.z - p2.z))
end
-(p::Point3D)                          = Point3D(-p.x, -p.y, -p.z)
-(k::Number, p1::Point3D)              = Point3D(p1.x - k, p1.y - k, p1.z - k)
-(p1::Point3D, k::Number)              = Point3D(p1.x - k, p1.y - k, p1.z - k)

*(k::Number, p2::Point3D)              = Point3D(k * p2.x, k * p2.y, k * p2.z)
*(p2::Point3D, k::Number)              = Point3D(k * p2.x, k * p2.y, k * p2.z)
*(p1::Point3D, p2::Point3D)            = Point3D(p1.x * p2.x, p1.y * p2.y, p1.z * p2.z)

"""
    distance(p1::Point3D, p2::Point3D)

Return the distance between two points.

"""
function distance(p1::Point3D, p2::Point3D)
    sqrt((p2.x - p1.x)^2 + (p2.y - p1.y)^2 + (p2.z - p1.z)^2)
end

function isnan(pt::Point3D)
    isnan(pt.x) || isnan(pt.y) || isnan(pt.z)
end

"""
    between(p1::Point3D, p2::Point3D, x=0.5)
    between((p1::Point3D, p2::Point3D), x=0.5)

Find a point on a line between two 3D points.
If `x` is 0.5, the returned point should be halfway between them.

"""
function between(p1::Point3D, p2::Point3D, x=0.5)
    return p1 + (x * (p2 - p1))
end

function between(couple::NTuple{2, Point3D}, x=0.5)
    p1, p2 = couple
    return p1 + (x * (p2 - p1))
end

/(p2::Point3D, k::Number)              = Point3D(p2.x/k, p2.y/k, p2.z/k)
^(p::Point3D, e::Integer)              = Point3D(p.x^e,  p.y^e, p.z^e)
^(p::Point3D, e::Float64)              = Point3D(p.x^e,  p.y^e, p.z^e)

# conversion

function Base.convert(type::Type{Point3D}, pt::Point)
    return Point3D(pt.x, pt.y, 0.0)
end

# some refinements
# modifying points with tuples
+(p1::Point3D, shift::NTuple{3, Real}) = Point3D(p1.x + shift[1], p1.y + shift[2], p1.z + shift[3])
-(p1::Point3D, shift::NTuple{3, Real}) = Point3D(p1.x - shift[1], p1.y - shift[2], p1.z - shift[3])
*(p1::Point3D, shift::NTuple{3, Real}) = Point3D(p1.x * shift[1], p1.y * shift[2], p1.z * shift[3])
/(p1::Point3D, shift::NTuple{3, Real}) = Point3D(p1.x / shift[1], p1.y / shift[2], p1.z / shift[3])

# comparisons

isequal(p1::Point3D, p2::Point3D)         = isapprox(p1.x, p2.x, atol=0.00000001) && isapprox(p1.y, p2.y, atol=0.00000001) && isapprox(p1.z, p2.z, atol=0.00000001)
isapprox(p1::Point3D, p2::Point3D)        = isapprox(p1.x, p2.x, atol=0.00000001) && isapprox(p1.y, p2.y, atol=0.00000001) && isapprox(p1.z, p2.z, atol=0.00000001)
isless(p1::Point3D, p2::Point3D)          = (p1.x < p2.x) && (p1.y < p2.y) && (p1.z < p2.z)
!=(p1::Point3D, p2::Point3D)              = !isequal(p1, p2)
<(p1::Point3D, p2::Point3D)               = isless(p1,p2)
>(p1::Point3D, p2::Point3D)               = p2 < p1
==(p1::Point3D, p2::Point3D)              = isequal(p1, p2)

"""
    dotproduct3D(a::Point3D, b::Point3D)

Finds the dot product of a and b
"""
function dotproduct3D(a::Point3D, b::Point3D)
    return a.x * b.x + a.y * b.y + a.z * b.z
end

"""
    magnitude(a::Point3D)

Calculates magnitude of a.
"""
function magnitude(a::Point3D)
    return sqrt(a.x * a.x + a.y * a.y + a.z * a.z)
end

"""
    anglebetweenvectors(v1::Point3D, v2::Point3D)

Calclate the angle between two vectors.
"""
function anglebetweenvectors(v1::Point3D, v2::Point3D)
    intermediate = dotproduct3D(v1, v2)/(magnitude(v1) * magnitude(v2))
    # avoid domain errors
    if isnan(intermediate)
        return 0.0
    else
        return acos(min(max(-1, intermediate), 1))
    end
end

"""
    midpoint(pt1::Point3D, pt2::Point3D)

Find the midpoint between two points. See also `between()`.
"""
function midpoint(pt1::Point3D, pt2::Point3D)
    return (pt1 + pt2)/2
end

"""
    sphericaltocartesian(ρ, θ, ϕ)

Return `Point3D(x, y, z)` corresponding to `(ρ, θ, ϕ)`:

- ρ is the distance from the origin (ie radius)

- θ is the azimuthal angle (the longitude) 0 is +x, π is -x, 2π is +x

- ϕ is the polar angle (the latitude) 0 is North Pole, π is South Pole

There are two major conventions for spherical coordinate notation.

In physics books:

(ρ, θ, φ) gives the radial distance, polar angle (latitude), and azimuthal angle (longitude)

In mathematics books:

(ρ, θ , φ ) gives the radial distance, azimuthal angle (longitude), and polar angle (latitude)

So we're using the mathematics one here.
"""
function sphericaltocartesian(ρ, θ, ϕ)
    x = ρ * sin(ϕ) * cos(θ)
    y = ρ * sin(ϕ) * sin(θ)
    z = ρ * cos(ϕ)
    return Point3D(x, y, z)
end

"""
    cartesiantospherical(x, y, z)

Return `(ρ, θ, ϕ)` (radius, longitude, latitude) of the Point3D(x, y, z).
"""
function cartesiantospherical(x, y, z)
    ϕ = atan(y, x)
    ρ = sqrt(x^2 + y^2 + z^2)
    θ = acos(z/ρ)
    return (ρ, θ, ϕ)
end

# rotations

# old versions just redirect to Rotations now
"""
    rotateX(pt3D::Point3D, rad)

Return a new point resulting from rotating the point around the x axis by an angle in radians.

Rotations are anticlockwise when looking along axis from 0 to +axis.
"""
function rotateX(pt3D::Point3D, rad)
    return RotX(rad) * pt3D
end

"""
    rotateY(pt3D::Point3D, rad)

Return a new point resulting from rotating the point around the y axis by an angle in radians.

Rotations are anticlockwise when looking along axis from 0 to +axis.
"""
function rotateY(pt3D::Point3D, rad)
    return RotY(rad) * pt3D
end

"""
    rotateZ(pt3D::Point3D, rad)

Return a new point resulting from rotating the point around the z axis by an angle in radians.
"""
function rotateZ(pt3D::Point3D, rad)
    return RotZ(rad) * pt3D
end

# rotate around axes

## copies (new points)
"""
    rotateby(pt::Point3D, angleX, angleY, angleZ)
    rotateby(ptlist::Array{Point3D, 1}, angleX, angleY, angleZ)
    rotateby(point::Point3D, r::Rotation)
    rotateby(ptlist::Array{Point3D, 1}, r::Rotation)

Return a new point/list of points resulting from rotating
around the x, y, and z axes by angleX, angleY, angleZ.

The Z rotation is first, then the Y, then the X.

A 3×3 rotation matrix parameterized by the
"Tait-Bryant" XYZ Euler angle convention,
consisting of first a rotation about the Z
axis by theta3, followed by a rotation about
the Y axis by theta2, and finally a rotation
about the X axis by theta1.
"""
function rotateby(point::Point3D, angleX::Float64, angleY::Float64, angleZ::Float64)
    return RotXYZ(angleX, angleY, angleZ) * point
end

function rotateby(ptlist::Array{Point3D, 1}, angleX::Float64, angleY::Float64, angleZ::Float64)
    return rotateby.(ptlist, angleX, angleY, angleZ)
end

function rotateby(point::Point3D, r::Rotation)
    return r * point
end

function rotateby(ptlist::Array{Point3D, 1}, r::Rotation)
    return rotateby.(ptlist, r)
end

## update an array of points
"""
    rotateby!(ptlist::Array{Point3D, 1}, angleX, angleY, angleZ)

Modify a list of points by rotating each one around the
x, y, and z axes by angleX, angleY, angleZ.
"""
function rotateby!(ptlist::Array{Point3D, 1}, angleX::Float64, angleY::Float64, angleZ::Float64)
    for i in eachindex(ptlist)
        ptlist[i] = rotateby(ptlist[i], angleX, angleY, angleZ)
    end
    return ptlist
end

# rotate a point around another point

## copy (new point)

"""
    rotateby(point::Point3D, about::Point3D, angleX, angleY, angleZ)
    rotateby(point::Point3D, about::Point3D, r::Rotation)
    rotateby(ptlist::Array{Point3D, 1}, about::Point3D, r::Rotation)
"""
function rotateby(point::Point3D, about::Point3D, angleX::Float64, angleY::Float64, angleZ::Float64)
    return RotXYZ(angleX, angleY, angleZ) * (point - about) + about
end
function rotateby(point::Point3D, about::Point3D, r::Rotation)
    return r * (point - about) + about
end
function rotateby(ptlist::Array{Point3D, 1}, about::Point3D, r::Rotation)
    return rotateby.(ptlist, about, r)
end

## update an array of points
"""
    rotateby!(ptlist::Array{Point3D, 1}, existingpt::Point3D, angleX, angleY, angleZ)
    rotateby!(ptlist::Array{Point3D, 1}, existingpt::Point3D, r::Rotation)
    rotateby!(ptlist::Array{Point3D, 1}, r::Rotation=RotXYZ{Float64})

Rotate each point in the list by rotation (or angleX, angleY, angleZ) around another point (or origin).
"""
function rotateby!(ptlist::Array{Point3D, 1}, existingpt::Point3D, angleX::Float64, angleY::Float64, angleZ::Float64)
    for i in eachindex(ptlist)
        ptlist[i] = rotateby(ptlist[i], existingpt, angleX, angleY, angleZ)
    end
    return ptlist
end
function rotateby!(ptlist::Array{Point3D, 1}, existingpt::Point3D, r::Rotation)
    for i in eachindex(ptlist)
        ptlist[i] = rotateby(ptlist[i], existingpt, r)
    end
    return ptlist
end

rotateby!(ptlist::Array{Point3D, 1}, r::Rotation=RotXYZ{Float64}) = rotateby!(ptlist, Point3D(0.0, 0.0, 0.0), r)

"""
    moveby(pt::Point3D, d::Point3D)

Return a new point that's the result of moving a point `pt` by a vector `d`.
"""
function moveby(pt::Point3D, d::Point3D)
    return pt + d
end

"""
    moveby!(ptlist::Point3D, x, y, z)
    moveby!(ptlist::Point3D, pt::Point3D)

Move all points in the list by a vector.
"""
function moveby!(ptlist::Array{Point3D, 1}, pt::Point3D)
    return ptlist .+= pt
end

moveby!(ptlist::Array{Point3D, 1}, x, y, z) = moveby!(ptlist, Point3D(x, y, z))

"""
    scaleby!(ptlist::Array{Point3D, 1}, x, y, z)

Scales a list of points by multiplying by `x` in X, `y` in Y, `z` in Z.
"""
function scaleby!(ptlist::Array{Point3D, 1}, x, y, z)
   for n in 1:length(ptlist)
       v = ptlist[n]
       ptlist[n] = Point3D(v.x * x, v.y * y, v.z * z)
   end
   return ptlist
end

"""
    surfacenormal(ptlist::Array{Point3D, 1})

Finds one of these.
"""
function surfacenormal(ptlist::Array{Point3D, 1})
   normal = Point3D(0, 0, 0)
   for i in 1:length(ptlist)
      vertexCurrent = ptlist[i]
      vertexNext    = ptlist[mod1(i + 1, end)]
      x = normal.x + ( (vertexCurrent.y - vertexNext.y) * (vertexCurrent.z + vertexNext.z))
      y = normal.y + ( (vertexCurrent.z - vertexNext.z) * (vertexCurrent.x + vertexNext.x))
      z = normal.z + ( (vertexCurrent.x - vertexNext.x) * (vertexCurrent.y + vertexNext.y))
      normal = Point3D(x, y, z)
   end
   l = magnitude(normal)
   # return normalize(normal)
   return Point3D(normal.x/l, normal.y/l, normal.z/l) / l
end

function Base.unique(pts::Array{Point3D, 1})
    apts = Point3D[]
    for pt in pts
        if pt ∉ apts
            push!(apts, pt)
        end
    end
    return apts
end


function row_row_prime(dd, i, type::Type = eltype(dd[1,1]))
    r = Array{type}(dd[i,:])
    return Hermitian(r * r')
end
function b_bar(dd,S)
    etype = eltype(dd[1,1])
    N = nrow(dd)
    total = 0.0
    for k in 1:N
        total += sqrt(squared_frobenius_distance(row_row_prime.(Ref(dd), k, etype), S))
    end
    return total / (N^2)
end

"""
    identity_regularisation(mat::Hermitian, identity_weight::Real)

Regularisation of the correlation matrix by mixing with the identity matrix.
### Inputs
* `mat` - A matrix to be regularised.
* `identity_weight` - How much weight to give to the identity matrix. Should be between 0 and 1.
### Returns
* A `Hermitian`.

    identity_regularisation(mat::Hermitian, asset_returns::DataFrame) where R<:Real

Regularisation of the correlation matrix by mixing with the identity matrix as per Ledoit & Wolf 2003.
### Inputs
* `mat` - A matrix to be regularised.
* `ts` - Tick data.
### Returns
* A `Hermitian`.

    identity_regularisation(mat::Hermitian, ts::SortedDataFrame,  mat_labels::Vector;
                            spacing::Union{Missing,<:Real} = missing)

Regularisation of the correlation matrix by mixing with the identity matrix as per Ledoit & Wolf 2003.
### Inputs
* `mat` - A matrix to be regularised.
* `ts` - Tick data.
* `mat_labels` - The labels for each asset in the matrix.
* `spacing` A spacing to use to estimate returns. This is used in determining the optimal weight to give to the identity matrix.
### Returns
* A `Hermitian`.

    identity_regularisation(covariance_matrix::CovarianceMatrix, ts::SortedDataFrame;
                            spacing::Union{Missing,<:Real} = missing, apply_to_covariance::Bool = true)

Regularisation of the correlation matrix by mixing with the identity matrix as per Ledoit & Wolf 2003.
### Inputs
* `covariance_matrix` - The `CovarianceMatrix` to be regularised.
* `ts` - Tick data.
* `spacing` A spacing to use to estimate returns. This is used in determining the optimal weight to give to the identity matrix.
* `apply_to_covariance` Should regularisation be applied to the covariance matrix or the correlation matrix.
### Returns
* A `CovarianceMatrix`.

    identity_regularisation(covariance_matrix::CovarianceMatrix, identity_weight::Real;
                            apply_to_covariance = false)

Regularisation of the correlation matrix by mixing with the identity matrix.
### Inputs
* `covariance_matrix` - The `CovarianceMatrix` to be regularised.
* `identity_weight` - How much weight to give to the identity matrix. Should be between 0 and 1.
* `apply_to_covariance` Should regularisation be applied to the covariance matrix or the correlation matrix.
### Returns
* A `CovarianceMatrix`.

### References
Ledoit, O. , Wolf, M. 2003. Improved Estimation of the Covariance Matrix of Stock Returns with an application to portfolio selection. Journal of empirical finance. 10. 603-621.
"""
function identity_regularisation(mat::Hermitian, identity_weight::Real)
    II = I(size(mat)[1])
    # This is a bit different to the paper as we don't have m_n. This is to ensure the diagonal stays as ones.
    mat_prime = (identity_weight .* II) + (1 - identity_weight) .* mat
    return Hermitian(mat_prime)
end
function identity_regularisation(mat::Hermitian, asset_returns::DataFrame) where R<:Real
    II   = I(size(mat)[1])
    m    = squared_frobenius_distance(mat, II) # Lemma 3.2
    d    = sqrt(squared_frobenius_distance(mat, m*II))      # Lemma 3.3
    bbar = b_bar(asset_returns, mat)
    b    = min(bbar, d)
    return identity_regularisation(mat, b/d)
end
function identity_regularisation(mat::Hermitian, ts::SortedDataFrame,  mat_labels::Vector;
                                 spacing::Union{Missing,<:Real} = missing)
    at_times = ismissing(spacing) ? get_all_refresh_times(ts, mat_labels) : collect(0:spacing:maximum(ts.df[:,ts.time]))
    dd_compiled = latest_value(ts, at_times; assets = mat_labels)
    asset_returns = get_returns(dd_compiled; rescale_for_duration = true)
    return identity_regularisation(mat, asset_returns)
end
function identity_regularisation(covariance_matrix::CovarianceMatrix, ts::SortedDataFrame;
                                 spacing::Union{Missing,<:Real} = missing, apply_to_covariance::Bool = true)
     if apply_to_covariance
         actual_covariance = covariance(covariance_matrix)
         regularised_covariance = identity_regularisation(actual_covariance,
                                           ts, covariance_matrix.labels; spacing = spacing)
         corr, vols = cov2cor_and_vol(regularised_covariance, 1)
         return CovarianceMatrix(corr, vols, covariance_matrix.labels, covariance_matrix.time_period_per_unit)
     else
         return CovarianceMatrix(Hermitian(identity_regularisation(covariance_matrix.correlation, ts, covariance_matrix.labels; spacing = spacing)),
                     covariance_matrix.volatility, covariance_matrix.labels, covariance_matrix.time_period_per_unit)
     end
end
function identity_regularisation(covariance_matrix::CovarianceMatrix, identity_weight::Real; apply_to_covariance = false)
     if apply_to_covariance
         regularised_covariance = identity_regularisation(covariance(covariance_matrix,1),identity_weight)
         corr, vols = cov2cor_and_vol(regularised_covariance, 1)
         return CovarianceMatrix(corr, vols, covariance_matrix.labels, covariance_matrix.time_period_per_unit)
     else
         return CovarianceMatrix(Hermitian(identity_regularisation(covariance_matrix.correlation, identity_weight)),
                       covariance_matrix.volatility, covariance_matrix.labels, covariance_matrix.time_period_per_unit)
     end
end


#=
Abstract types
abstract type «name» end
abstract type «name» <: «supertype» end

List of some abstract types and their supertypes in Julia
abstract type Number end
abstract type Real     <: Number end
abstract type AbstractFloat <: Real end
abstract type Integer  <: Real end
abstract type Signed   <: Integer end
abstract type Unsigned <: Integer end

Primitive types
primitive type «name» «bits» end
primitive type «name» <: «supertype» «bits» end

List of primitive types and their supertypes in Julia
primitive type Float16 <: AbstractFloat 16 end
primitive type Float32 <: AbstractFloat 32 end
primitive type Float64 <: AbstractFloat 64 end

primitive type Bool <: Integer 8 end
primitive type Char <: AbstractChar 32 end

primitive type Int8    <: Signed   8 end
primitive type UInt8   <: Unsigned 8 end
primitive type Int16   <: Signed   16 end
primitive type UInt16  <: Unsigned 16 end
primitive type Int32   <: Signed   32 end
primitive type UInt32  <: Unsigned 32 end
primitive type Int64   <: Signed   64 end
primitive type UInt64  <: Unsigned 64 end
primitive type Int128  <: Signed   128 end
primitive type UInt128 <: Unsigned 128 end
=#

primitive type Byte 8 end
# The function reinterpret is used to store the bits of an unsigned integer with 8 bits (UInt8) into the byte.
Byte(val::UInt8) = reinterpret(Byte, val)
b = Byte(0x01)
println(b)

# Parametric typing i.e. Generics
struct Point{T<:Real}
    x::T
    y::T
end

println(Point(0.0, 0.0))
println(Point(1, 3))

struct Pair{T1, T2}
    x::T1
    y::T2
end

println(Pair(3, 1.5))

# Unions
IntOrString = Union{Int64, String}
struct Test
    x::IntOrString
end

println(Test("abc"))
println(Test(123))
# This is invalid:
# println(Test('a'))

a = Vector{T} where T<:Real
# In REPL, type ?a
isintpoint(p::Point{T}) where {T} = (T === Int64)
p = Point(1, 2)
println(isintpoint(p))


ga = Graph(10,20; seed=1)
gb = Graph(10,20; seed=1)
@test sprint(show,edges(ga)) == "EdgeIter 20"
@test sprint(show, start(edges(ga))) == "EdgeIterState [1, 1, false]"

@test length(collect(edges(Graph(0,0)))) == 0

@test edges(ga) == edges(gb)
@test edges(ga) == collect(Edge, edges(gb))
@test collect(Edge, edges(gb)) == edges(ga)
@test Set{Edge}(collect(Edge, edges(gb))) == edges(ga)
@test edges(ga) == Set{Edge}(collect(Edge, edges(gb)))

ga = DiGraph(10,20; seed=1)
gb = DiGraph(10,20; seed=1)
@test edges(ga) == edges(gb)
@test edges(ga) == collect(Edge, edges(gb))
@test collect(Edge, edges(gb)) == edges(ga)
@test Set{Edge}(collect(Edge, edges(gb))) == edges(ga)
@test edges(ga) == Set{Edge}(collect(Edge, edges(gb)))

ga = Graph(10)
add_edge!(ga, 3, 2)
add_edge!(ga, 3, 10)
add_edge!(ga, 5, 10)
add_edge!(ga, 10, 3)

eit = edges(ga)
es = start(eit)

@test es.s == 2
@test es.di == 1

@test [e for e in eit] == [Edge(2, 3), Edge(3, 10), Edge(5,10)]

ga = DiGraph(10)
add_edge!(ga, 3, 2)
add_edge!(ga, 3, 10)
add_edge!(ga, 5, 10)
add_edge!(ga, 10, 3)

eit = edges(ga)
es = start(eit)

@test es.s == 3
@test es.di == 1

@test [e for e in eit] == [Edge(3, 2), Edge(3, 10), Edge(5,10), Edge(10,3)]


### A Pluto.jl notebook ###
# v0.16.0

using Markdown
using InteractiveUtils

# ╔═╡ 0150a3e2-f98e-4412-9e96-1a2db5a9421e
using Combinatorics, Plots

# ╔═╡ 936344da-213a-11ec-3e68-05f0f85cf2f3
md"""
# NP-complete problems

*STMO*

**Michiel Stock**
"""

# ╔═╡ c3b61d51-679c-4aba-957b-105c00037aee
md"In this document, we will go over some simple algorithms to solve the knapsack problem."

# ╔═╡ 62c4926d-0350-4088-8729-0ee5afb306be
md"""
## The knapsack problem

### Problem definition

> Given a set of items, each with a weight and a value, find the subset of items so that the total weight is less than or equal to a given limit and the total value is as large as possible.

Or, in symbols:

Given two sets of cardinality $n$ with the values $\{v_1,\ldots, v_n\}$ and the weights $\{w_1,\ldots, w_n\}$ and a capacity $W>0$ we want to determine $T\subseteq\{1,\ldots,n\}$ such that

$$\max_T \, \sum_{i\in T} v_i$$

$$\text{subject to } \sum_{i\in T} w_i \leq W\,.$$

We can represent this problem in Julia using a list of tuples, representing the items with their reprective values and weights, and a number containing the capacity $W$. Let us however use this opportunity to construct a simple structure to represent knapsack problems.
"""

# ╔═╡ 3c769076-fec9-4730-b993-2fefa09f0c8d
struct Knapsack{T,V<:Real,W<:Real}
  items::Array{Tuple{T,V,W},1}
  capacity::W
end

# ╔═╡ ada8c7c4-f6d8-41e7-a4e0-c137ce7ab151
C = 10

# ╔═╡ 64e7be40-cd0b-40c4-b476-51c6a66d40e1
md"""
Consider the Indiana Jones problem:

| i |  artifact     | $v_i$ |  $w_i$ |
|---|---------------|-------|--------|
| 1 |  statue 1     |   1   |  2     |
| 2 |  statue 2     |   1   |  2     |
| 3 |  statue 3     |   1   |  2     |
| 4 |  tablet 1     |  10   |  5     |
| 5 |  tablet 2     |  10   |  5     |
| 6 |  golden mask  |  13   |  7     |
| 7 |  golden plate |   7   |  3     |
"""

# ╔═╡ be803662-aa08-4ee3-a400-bbc18b7d060f
md"""The capacity is $C kg.

So we can define the instance as (feel free to modify)"""

# ╔═╡ 2ce221f2-5712-440e-9b34-168a3a5af65e
knapsack = Knapsack([("statue 1", 1, 2),
                     ("statue 2", 1, 2),
                     ("statue 2", 1, 2),
                     ("tablet 1", 10, 5),
                     ("tablet 2", 10, 5),
                     ("golden mask", 13, 7),
                     ("golden plate", 7, 3)],
                     C)

# ╔═╡ 2d9598de-dfd6-46c7-bf4f-c2f48134e558
md"We might as well define some useful function that work on `Knapsack` structures."

# ╔═╡ 265ac424-3e7b-4222-ab79-07907544a9fa
"""Getter for the items."""
items(knapsack::Knapsack) = knapsack.items

# ╔═╡ 876bd16d-89ac-4164-8055-e9ec4a4d875f
"""Getter for the capacity."""
capacity(knapsack::Knapsack) = knapsack.capacity

# ╔═╡ cca6890d-b0eb-4154-9b27-be79c0b8ab38
"""Number of items"""
Base.length(knapsack::Knapsack) = length(items(knapsack))

# ╔═╡ 94cb9a4c-aa3f-4ef0-a4e5-170b0d2ad69b
"""Weight of a set of items"""
weight(items::Array) = sum((w for (i, v, w) in items))

# ╔═╡ 1d07e4af-5419-464f-842d-3012920e8a1b
"""Weight of an item"""
weight(item::Tuple) = item[3]

# ╔═╡ f0734b83-dbbc-4d1d-a96e-9209e53b7382
"""Value of a set of items"""
value(items::Array) = sum((v for (i, v, w) in items))

# ╔═╡ 165d0c6a-ec73-46a9-a9f4-5085c65d184c
"""Value of an item"""
value(item::Tuple) = item[2]

# ╔═╡ e1fa7a85-5aab-4e17-b02f-c9a0c62f3f47
md"### Brute force"

# ╔═╡ 38e36108-85cf-4808-8e10-bc6f869522fc
function bruteforce(knapsack::Knapsack{T,V,W}) where {T,V<:Real,W<:Real}
  # number of items
  n = length(knapsack)
  cap = capacity(knapsack)
  knapsack_items = items(knapsack)
  best_value = zero(W)
  best_solution = eltype(knapsack_items)[]
  for solution in combinations(knapsack_items)
    if weight(solution) ≤ cap && value(solution) > best_value
      best_solution = solution
      best_value = value(solution)
    end
  end
  return best_solution, best_value
end

# ╔═╡ 9b555941-5ef2-48d5-8efd-0f4cf7e38af6
bruteforce(knapsack)

# ╔═╡ 96d84e8c-3b11-4b01-a036-e3434804f512
md"### Greedy"

# ╔═╡ f65c49f0-09bf-4b5f-99b9-21b2fe6f5760
function greedy(knapsack::Knapsack{T,V,W},
                    heuristic::Function) where {T,V<:Real,W<:Real}
  items_knapsack = items(knapsack)
  solution = eltype(items_knapsack)[]
  solution_weight = zero(W)
  solution_value = zero(V)
  cap = capacity(knapsack)
  for item in sort(items_knapsack, by=heuristic, rev=true)
    v, w = value(item), weight(item)
    if solution_weight + w ≤ cap
      push!(solution, item)
      solution_weight += w
      solution_value += v
    end
  end
  return solution, solution_value
end

# ╔═╡ 3e95cf39-5d8f-498a-bdb6-df99a51257f3
md"Here, `heuristic` is a function we can provide to guide the greedy search. For example, if we search by value."

# ╔═╡ 97465212-39d3-4b19-8dd0-cf654b80e3e2
greedy(knapsack, value)

# ╔═╡ 834e4c69-5e90-409a-a2ed-ac14609ae728
md"Or, search lightest items first."

# ╔═╡ 02e07bf0-741c-4df2-9a07-55093e2bb55d
greedy(knapsack, item -> -weight(item))

# ╔═╡ e53659a3-e791-4318-a738-678d8c267b86
md"Or, by value-density:"

# ╔═╡ fa75a27d-725f-49df-8527-125c90de1692
greedy(knapsack, item -> value(item) / weight(item))

# ╔═╡ 79be9317-762d-4ce5-85ed-983f4882fa0b
md"### Dynamics programming"

# ╔═╡ d500504b-5cfb-46b9-8b82-1fbfb843b479
function dynamicprogramming(knapsack::Knapsack{T,V,W}) where {T,V<:Real,W<:Int}
  items_knapsack = items(knapsack)
  n = length(knapsack)
  cap = capacity(knapsack)
  DP = zeros(V, n + 1, cap + 1)  # starts from zero
  # fill the DP matrix
  for w in 1:cap
    for (i, (itname, vᵢ, wᵢ)) in enumerate(items_knapsack)
      if wᵢ ≤ w  # this item can fit the bag
        DP[i+1,w+1] = max(DP[i,w+1],  # value without the item
                        DP[i,w-wᵢ+1] + vᵢ)  # value with the item, given room for the item
      else  # no room
        DP[i+1,w+1] = DP[i, w+1]  # take value without the item
      end
    end
  end
  # now, backtrack
  solution = eltype(items_knapsack)[]
  i, w = n, cap
  while i > 0
    if DP[i+1,w+1] > DP[i,w+1]  # item i was added
      item = items_knapsack[i]
      push!(solution, item)
      w -= weight(item)
    end
    i -= 1
    i == 0 || w == 0 && break
  end
  return solution, last(DP)
end

# ╔═╡ a7f61d0a-7706-47d1-8798-264881d032fa
md"Note the restriction on the type of the weigths!"

# ╔═╡ 67ccbd02-d63c-42e3-ae7b-3022d3ec60c0
dynamicprogramming(knapsack)

# ╔═╡ fe8bb7b2-6fd2-4b8f-9ef0-9d2fcc1b744a
md"""
## Exercise: brute-force TSP

Consider the capitals of the provice of Belgium 🇧🇪
"""

# ╔═╡ 66eb2e32-c0bd-42ab-bb67-05dbd01a1d6e
md"**Assignment** Loop over all possible tours to find the shortest one. A tour is just gives the order of the cities indices. For example"

# ╔═╡ 5afd91f6-9e0d-4774-a954-3f00b71dd2e7
md"## Appendix"

# ╔═╡ 8c685b23-0ee2-4027-ad5c-01c1dce6fe3a
stad_coor = Dict(
	"Antwerpen" => (51.219901727372466, 4.413766520738599),
	"Hasselt" => (50.929969726608796, 5.337669056378496),
	"Gent" => (51.046603059146115, 3.7227238267036165),
	"Leuven" => (50.87717306970613, 4.7013098809614045),
	"Brugge"=>(51.20994021593339, 3.2237458378594632),
	"Bergen" => (50.45178175960054, 3.95116527396219),
	"Luik" => (50.63563258400128, 5.58639186132504),
	"Aarlen" => (49.68489470742727, 5.812244206475732),
	"Namen" => (50.45840475199352, 4.858245535731698),
	"Waver" => (50.71402361189344, 4.596358571899753),
	"Brussel" => (50.86323347803335, 4.349845202806156)
);

# ╔═╡ 1a6ded1a-cfbe-4d7b-b70f-b06075747561
cities = keys(stad_coor) |> collect;

# ╔═╡ 8e5dcba4-21dc-4e7c-a3cb-7eec261a270c
cities

# ╔═╡ 15c2faec-655f-4d56-9e4c-5abd4c1d9026
nsteden = ncities = length(cities);

# ╔═╡ 45e7087e-9ad6-49b9-97ac-6a9038d5cb44
a_tour = 1:ncities  # order of the list

# ╔═╡ 827b5a0e-c838-42d2-be03-87a4dd34b24a
dist_sphere((lat1, long1), (lat2, long2); r=6_371) = 2r * asin(√(sin((lat2 -lat1)/2)^2 + cos(lat1)*cos(lat2)*sin((long2-long1)/2)^2));

# ╔═╡ 18e490a3-5823-4862-ae86-07546f556eab
D = [dist_sphere(stad_coor[h1] .* pi ./ 180, stad_coor[h2] .* pi ./ 180) for h1 in cities, h2 in cities];

# ╔═╡ 54db8c49-dee0-4ef1-bd26-78d10b03ae76
D  # distance matrix

# ╔═╡ c59969a6-eb3e-47c5-9bd8-f1af03fce595
function total_distance(route, incomplete=false)
	c = 0.0
	prev = incomplete ? 1 : route[end]
	for next in route
		c += D[prev, next]
		prev = next
	end
	incomplete && (c += D[route[end], 1])
	return c
end

# ╔═╡ b09bae94-4add-4d71-8bbc-5d5e4cdba2f6
total_distance(a_tour)

# ╔═╡ 137dc6ec-72c5-4858-a18a-9fb433899805
function plot_tour(route)
	routec = [route..., route[1]]
	p = plot()

	for h in cities
		lat, long = stad_coor[h]
		scatter!(p, [long], [lat], label="", color="blue")
		annotate!((long, lat, h))
	end

	for (i,j) in zip(routec[1:end-1], routec[2:end])
		h1, h2 = cities[i], cities[j]
		lat1, long1 = stad_coor[h1]
		lat2, long2 = stad_coor[h2]

		plot!(p, [long1, long2], [lat1, lat2], color="red", label="", lw=3, alpha=0.8)
	end
	return p
end;

# ╔═╡ ed475158-07c4-4116-b0eb-d23911bf6531
plot_tour([1 for i in 1:nsteden])

# ╔═╡ cedb1c31-859c-4c6a-a0fd-4c643959c4f7
plot_tour(a_tour)

# ╔═╡ fde5a843-5767-4fc7-a381-eb2ecc1fe801
begin
	myblue = "#304da5"
	mygreen = "#2a9d8f"
	myyellow = "#e9c46a"
	myorange = "#f4a261"
	myred = "#e76f51"
	myblack = "#50514F"

	mycolors = [myblue, myred, mygreen, myorange, myyellow]
end;

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

[compat]
Combinatorics = "~1.0.2"
Plots = "~1.22.3"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"

[[Cairo_jll]]
deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "f2202b55d816427cd385a9a4f3ffb226bee80f99"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+0"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "a851fec56cb73cfdf43762999ec72eff5b86882a"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.15.0"

[[ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[Combinatorics]]
git-tree-sha1 = "08c8b6831dc00bfea825826be0bc8336fc369860"
uuid = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
version = "1.0.2"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "31d0151f5716b655421d9d75b7fa74cc4e744df2"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.39.0"

[[CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"

[[DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[Expat_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b3bfd02e98aedfa5cf885665493c5598c350cd2f"
uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
version = "2.2.10+0"

[[FFMPEG]]
deps = ["FFMPEG_jll"]
git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
version = "0.4.1"

[[FFMPEG_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
git-tree-sha1 = "d8a578692e3077ac998b50c0217dfd67f21d1e5f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.4.0+0"

[[FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "21efd19106a55620a188615da6d3d06cd7f6ee03"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.93+0"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "87eb71354d8ec1a96d4a7636bd57a7347dde3ef9"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.4+0"

[[FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91"
uuid = "559328eb-81f9-559d-9380-de523a88c83c"
version = "1.0.10+0"

[[GLFW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"]
git-tree-sha1 = "dba1e8614e98949abfa60480b13653813d8f0157"
uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89"
version = "3.3.5+0"

[[GR]]
deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test", "UUIDs"]
git-tree-sha1 = "c2178cfbc0a5a552e16d097fae508f2024de61a3"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.59.0"

[[GR_jll]]
deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Pkg", "Qt5Base_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "ef49a187604f865f4708c90e3f431890724e9012"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.59.0+0"

[[GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[Gettext_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
version = "0.21.0+0"

[[Glib_jll]]
deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "7bf67e9a481712b3dbe9cb3dac852dc4b1162e02"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.3+0"

[[Graphite2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
version = "1.3.14+0"

[[Grisu]]
git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
version = "1.0.2"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "24675428ca27678f003414a98c9e473e45fe6a21"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.15"

[[HarfBuzz_jll]]
deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"]
git-tree-sha1 = "8a954fed8ac097d5be04921d595f741115c1b2ad"
uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
version = "2.8.1+0"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d735490ac75c5cb9f1b00d8b5509c11984dc6943"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.0+0"

[[LAME_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c"
uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
version = "3.100.1+0"

[[LZO_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6"
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
version = "2.10.1+0"

[[LaTeXStrings]]
git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.2.1"

[[Latexify]]
deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"]
git-tree-sha1 = "a4b12a1bd2ebade87891ab7e36fdbce582301a92"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
version = "0.15.6"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[Libffi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "761a393aeccd6aa92ec3515e428c26bf99575b3b"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+0"

[[Libgcrypt_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"]
git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae"
uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
version = "1.8.7+0"

[[Libglvnd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
git-tree-sha1 = "7739f837d6447403596a75d19ed01fd08d6f56bf"
uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
version = "1.3.0+3"

[[Libgpg_error_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9"
uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
version = "1.42.0+0"

[[Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[Libmount_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73"
uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
version = "2.35.0+0"

[[Libtiff_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"]
git-tree-sha1 = "340e257aada13f95f98ee352d316c3bed37c8ab9"
uuid = "89763e89-9b03-5906-acba-b20f662cd828"
version = "4.3.0+0"

[[Libuuid_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066"
uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
version = "2.36.0+0"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "5a5bc6bf062f0f95e62d0fe0a2d99699fed82dd9"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.8"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Measures]]
git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f"
uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
version = "0.3.1"

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[Ogg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7937eda4681660b4d6aeeecc2f7e1c81c8ee4e2f"
uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
version = "1.3.5+0"

[[OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "15003dcb7d8db3c6c857fda14891a539a8f2705a"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.10+0"

[[Opus_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
uuid = "91d4177d-7536-5919-b921-800302f37372"
version = "1.3.2+0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[PCRE_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488"
uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc"
version = "8.44.0+0"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "9d8c00ef7a8d110787ff6f170579846f776133a9"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.4"

[[Pixman_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29"
uuid = "30392449-352a-5448-841d-b1acce4e97dc"
version = "0.40.1+0"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PlotThemes]]
deps = ["PlotUtils", "Requires", "Statistics"]
git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d"
uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
version = "2.0.1"

[[PlotUtils]]
deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"]
git-tree-sha1 = "2537ed3c0ed5e03896927187f5f2ee6a4ab342db"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.0.14"

[[Plots]]
deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs"]
git-tree-sha1 = "cfbd033def161db9494f86c5d18fbf874e09e514"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.22.3"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[Qt5Base_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "xkbcommon_jll"]
git-tree-sha1 = "ad368663a5e20dbb8d6dc2fddeefe4dae0781ae8"
uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1"
version = "5.15.3+0"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[RecipesBase]]
git-tree-sha1 = "44a75aa7a527910ee3d1751d1f0e4148698add9e"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.1.2"

[[RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "7ad0dfa8d03b7bcf8c597f59f5292801730c55b8"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.4.1"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Scratch]]
deps = ["Dates"]
git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.1.0"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Showoff]]
deps = ["Dates", "Grisu"]
git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
version = "1.0.3"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3240808c6d463ac46f1c1cd7638375cd22abbccb"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.12"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[StatsAPI]]
git-tree-sha1 = "1958272568dc176a1d881acb797beb909c785510"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.0.0"

[[StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "8cbbc098554648c84f79a463c9ff0fd277144b6c"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.10"

[[StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "2ce41e0d042c60ecd131e9fb7154a3bfadbf50d3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.3"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "1162ce4a6c4b7e31e0e6b14486a6986951c73be9"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.5.2"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[Wayland_jll]]
deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "3e61f0b86f90dacb0bc0e73a0c5a83f6a8636e23"
uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89"
version = "1.19.0+0"

[[Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll"]
git-tree-sha1 = "2839f1c1296940218e35df0bbb220f2a79686670"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.18.0+4"

[[XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"

[[XSLT_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
version = "1.1.34+0"

[[Xorg_libX11_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
git-tree-sha1 = "5be649d550f3f4b95308bf0183b82e2582876527"
uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
version = "1.6.9+4"

[[Xorg_libXau_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e"
uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
version = "1.0.9+4"

[[Xorg_libXcursor_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd"
uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724"
version = "1.2.0+4"

[[Xorg_libXdmcp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4"
uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
version = "1.1.3+4"

[[Xorg_libXext_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3"
uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
version = "1.3.4+4"

[[Xorg_libXfixes_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4"
uuid = "d091e8ba-531a-589c-9de9-94069b037ed8"
version = "5.0.3+4"

[[Xorg_libXi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"]
git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246"
uuid = "a51aa0fd-4e3c-5386-b890-e753decda492"
version = "1.7.10+4"

[[Xorg_libXinerama_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"]
git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123"
uuid = "d1454406-59df-5ea1-beac-c340f2130bc3"
version = "1.1.4+4"

[[Xorg_libXrandr_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631"
uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484"
version = "1.5.2+4"

[[Xorg_libXrender_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96"
uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
version = "0.9.10+4"

[[Xorg_libpthread_stubs_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb"
uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
version = "0.1.0+3"

[[Xorg_libxcb_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
git-tree-sha1 = "daf17f441228e7a3833846cd048892861cff16d6"
uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
version = "1.13.0+3"

[[Xorg_libxkbfile_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "926af861744212db0eb001d9e40b5d16292080b2"
uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a"
version = "1.1.0+4"

[[Xorg_xcb_util_image_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "0fab0a40349ba1cba2c1da699243396ff8e94b97"
uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b"
version = "0.4.0+1"

[[Xorg_xcb_util_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll"]
git-tree-sha1 = "e7fd7b2881fa2eaa72717420894d3938177862d1"
uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5"
version = "0.4.0+1"

[[Xorg_xcb_util_keysyms_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "d1151e2c45a544f32441a567d1690e701ec89b00"
uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7"
version = "0.4.0+1"

[[Xorg_xcb_util_renderutil_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "dfd7a8f38d4613b6a575253b3174dd991ca6183e"
uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e"
version = "0.3.9+1"

[[Xorg_xcb_util_wm_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "e78d10aab01a4a154142c5006ed44fd9e8e31b67"
uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361"
version = "0.4.1+1"

[[Xorg_xkbcomp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxkbfile_jll"]
git-tree-sha1 = "4bcbf660f6c2e714f87e960a171b119d06ee163b"
uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4"
version = "1.4.2+4"

[[Xorg_xkeyboard_config_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xkbcomp_jll"]
git-tree-sha1 = "5c8424f8a67c3f2209646d4425f3d415fee5931d"
uuid = "33bec58e-1273-512f-9401-5d533626f822"
version = "2.27.0+4"

[[Xorg_xtrans_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845"
uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
version = "1.4.0+3"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.0+0"

[[libass_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.15.1+0"

[[libfdk_aac_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55"
uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
version = "2.0.2+0"

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"

[[libvorbis_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
git-tree-sha1 = "c45f4e40e7aafe9d086379e5578947ec8b95a8fb"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+0"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"

[[x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fea590b89e6ec504593146bf8b988b2c00922b2"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2021.5.5+0"

[[x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "ee567a171cce03570d77ad3a43e90218e38937a9"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.5.0+0"

[[xkbcommon_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"]
git-tree-sha1 = "ece2350174195bb31de1a63bea3a41ae1aa593b6"
uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd"
version = "0.9.1+5"
"""

# ╔═╡ Cell order:
# ╟─936344da-213a-11ec-3e68-05f0f85cf2f3
# ╟─c3b61d51-679c-4aba-957b-105c00037aee
# ╠═0150a3e2-f98e-4412-9e96-1a2db5a9421e
# ╟─62c4926d-0350-4088-8729-0ee5afb306be
# ╠═3c769076-fec9-4730-b993-2fefa09f0c8d
# ╠═ada8c7c4-f6d8-41e7-a4e0-c137ce7ab151
# ╟─64e7be40-cd0b-40c4-b476-51c6a66d40e1
# ╟─be803662-aa08-4ee3-a400-bbc18b7d060f
# ╠═2ce221f2-5712-440e-9b34-168a3a5af65e
# ╟─2d9598de-dfd6-46c7-bf4f-c2f48134e558
# ╠═265ac424-3e7b-4222-ab79-07907544a9fa
# ╠═876bd16d-89ac-4164-8055-e9ec4a4d875f
# ╠═cca6890d-b0eb-4154-9b27-be79c0b8ab38
# ╠═94cb9a4c-aa3f-4ef0-a4e5-170b0d2ad69b
# ╠═1d07e4af-5419-464f-842d-3012920e8a1b
# ╠═f0734b83-dbbc-4d1d-a96e-9209e53b7382
# ╠═165d0c6a-ec73-46a9-a9f4-5085c65d184c
# ╟─e1fa7a85-5aab-4e17-b02f-c9a0c62f3f47
# ╠═38e36108-85cf-4808-8e10-bc6f869522fc
# ╠═9b555941-5ef2-48d5-8efd-0f4cf7e38af6
# ╟─96d84e8c-3b11-4b01-a036-e3434804f512
# ╠═f65c49f0-09bf-4b5f-99b9-21b2fe6f5760
# ╟─3e95cf39-5d8f-498a-bdb6-df99a51257f3
# ╠═97465212-39d3-4b19-8dd0-cf654b80e3e2
# ╟─834e4c69-5e90-409a-a2ed-ac14609ae728
# ╠═02e07bf0-741c-4df2-9a07-55093e2bb55d
# ╟─e53659a3-e791-4318-a738-678d8c267b86
# ╠═fa75a27d-725f-49df-8527-125c90de1692
# ╟─79be9317-762d-4ce5-85ed-983f4882fa0b
# ╠═d500504b-5cfb-46b9-8b82-1fbfb843b479
# ╟─a7f61d0a-7706-47d1-8798-264881d032fa
# ╠═67ccbd02-d63c-42e3-ae7b-3022d3ec60c0
# ╟─fe8bb7b2-6fd2-4b8f-9ef0-9d2fcc1b744a
# ╠═ed475158-07c4-4116-b0eb-d23911bf6531
# ╠═8e5dcba4-21dc-4e7c-a3cb-7eec261a270c
# ╠═54db8c49-dee0-4ef1-bd26-78d10b03ae76
# ╟─66eb2e32-c0bd-42ab-bb67-05dbd01a1d6e
# ╠═45e7087e-9ad6-49b9-97ac-6a9038d5cb44
# ╠═cedb1c31-859c-4c6a-a0fd-4c643959c4f7
# ╠═b09bae94-4add-4d71-8bbc-5d5e4cdba2f6
# ╟─5afd91f6-9e0d-4774-a954-3f00b71dd2e7
# ╟─8c685b23-0ee2-4027-ad5c-01c1dce6fe3a
# ╟─1a6ded1a-cfbe-4d7b-b70f-b06075747561
# ╟─15c2faec-655f-4d56-9e4c-5abd4c1d9026
# ╟─827b5a0e-c838-42d2-be03-87a4dd34b24a
# ╟─c59969a6-eb3e-47c5-9bd8-f1af03fce595
# ╟─18e490a3-5823-4862-ae86-07546f556eab
# ╟─137dc6ec-72c5-4858-a18a-9fb433899805
# ╟─fde5a843-5767-4fc7-a381-eb2ecc1fe801
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


@testset "$TEST $G" begin

g = graph(:diamond, G)
@test nv(g) == 4 && ne(g) == 5
@test typeof(g) == G

g = graph(:bull, G)
@test nv(g) == 5 && ne(g) == 5

g = graph(:chvatal, G)
@test nv(g) == 12 && ne(g) == 24

g = graph(:cubical, G)
@test nv(g) == 8 && ne(g) == 12

g = graph(:desargues, G)
@test nv(g) == 20 && ne(g) == 30

g = graph(:dodecahedral, G)
@test nv(g) == 20 && ne(g) == 30

g = graph(:frucht, G)
@test nv(g) == 20 && ne(g) == 18

g = graph(:heawood, G)
@test nv(g) == 14 && ne(g) == 21

g = graph(:house, G)
@test nv(g) == 5 && ne(g) == 6

g = graph(:housex, G)
@test nv(g) == 5 && ne(g) == 8

g = graph(:icosahedral, G)
@test nv(g) == 12 && ne(g) == 30

g = graph(:krackhardtkite, G)
@test nv(g) == 10 && ne(g) == 18

g = graph(:moebiuskantor, G)
@test nv(g) == 16 && ne(g) == 24

g = graph(:octahedral, G)
@test nv(g) == 6 && ne(g) == 12

g = graph(:pappus, G)
@test nv(g) == 18 && ne(g) == 27

g = graph(:petersen, G)
@test nv(g) == 10 && ne(g) == 15

g = graph(:sedgewickmaze, G)
@test nv(g) == 8 && ne(g) == 10

g = graph(:tetrahedral, G)
@test nv(g) == 4 && ne(g) == 6

g = graph(:truncatedcube, G)
@test nv(g) == 24 && ne(g) == 36

g = graph(:truncatedtetrahedron, G)
@test nv(g) == 12 && ne(g) == 18 && !is_directed(g)

g = digraph(:truncatedtetrahedron, DG)
@test nv(g) == 12 && ne(g) == 18 && is_directed(g)
@test typeof(g) == DG

g = graph(:tutte, G)
@test nv(g) == 46 && ne(g) == 69

@test_throws ErrorException g = graph(:nonexistent, G)

end # testset


julia> sum([4,6,8])
18

julia> +((1:10)...)
55

julia +([1,2,3]...)
6

julia> prod([4,6,8])
192


# ---
# title: 502. IPO
# id: problem502
# author: Indigo
# date: 2021-09-08
# difficulty: Hard
# categories: Heap, Greedy
# link: <https://leetcode.com/problems/ipo/description/>
# hidden: true
# ---
#
# Suppose LeetCode will start its IPO soon. In order to sell a good price of its
# shares to Venture Capital, LeetCode would like to work on some projects to
# increase its capital before the IPO. Since it has limited resources, it can
# only finish at most **k** distinct projects before the IPO. Help LeetCode
# design the best way to maximize its total capital after finishing at most
# **k** distinct projects.
#
# You are given several projects. For each project **i** , it has a pure profit
# **P i** and a minimum capital of **C i** is needed to start the corresponding
# project. Initially, you have **W** capital. When you finish a project, you
# will obtain its pure profit and the profit will be added to your total
# capital.
#
# To sum up, pick a list of at most **k** distinct projects from given projects
# to maximize your final capital, and output your final maximized capital.
#
# **Example 1:**
#
#
#
#     Input: k=2, W=0, Profits=[1,2,3], Capital=[0,1,1].
#
#     Output: 4
#
#     Explanation: Since your initial capital is 0, you can only start the project indexed 0.
#                  After finishing it you will obtain profit 1 and your capital becomes 1.
#                  With capital 1, you can either start the project indexed 1 or the project indexed 2.
#                  Since you can choose at most 2 projects, you need to finish the project indexed 2 to get the maximum capital.
#                  Therefore, output the final maximized capital, which is 0 + 1 + 3 = 4.
#
#
# **Note:**
#
#   1. You may assume all numbers in the input are non-negative integers.
#   2. The length of Profits array and Capital array will not exceed 50,000.
#   3. The answer is guaranteed to fit in a 32-bit signed integer.
#
#
## @lc code=start
using LeetCode

function find_maximized_capital(k::Int, w::Int, profits::Vector{Int}, capital::Vector{Int})
    arr = @view(collect(zip(capital, profits))[:])
    sort!(arr)
    hp = Int[]
    for _ in 1:k
        idx = searchsortedlast(arr, (w + 1, -1))
        for item in @view(arr[1:idx])
            heappush!(hp, -item[2])
        end
        arr = @view(arr[idx+1:end])
        w -= isempty(hp) ? 0 : hp[1]
    end
    w
end
## @lc code=end


using JuMP
using Gurobi
using Plasmo
using PlasmoAlgorithms

##Place MP and SP into ModelGraph
mp = Model(solver = GurobiSolver())
sp = Model(solver = GurobiSolver())

@variable(mp, x[1:2], category = :Bin)
@constraint(mp, x[1]+x[2]>=1)
@objective(mp, Min, 2x[1]+3x[2])

@variable(sp, y[1:2], category = :Bin)
@variable(sp, x[1:2], category = :Bin)
@constraint(sp, x[1]+x[2]+y[1]>=2)
@constraint(sp, y[1]+y[2]<=2)
@objective(sp, Min, y[1]+y[2])

## Plasmo Graph
g = ModelGraph()
setsolver(g, GurobiSolver())
n1 = add_node(g)
setmodel(n1,mp)
n2 = add_node(g)
setmodel(n2,sp)

##Set n2 as a child node of n1
edge = Plasmo.add_edge(g,n1,n2)

## Linking constraints between MP and SP
@linkconstraint(g,[i in 1:2], n1[:x][i] == n2[:x][i])

r = bendersolve(g)


# Packages
using Printf
using LinearAlgebra
using Statistics
using Random

# outter functions
include("../utils/byrow.jl")
include("../utils/dare.jl")
include("../utils/dlyap.jl")
include("../utils/simula01.jl")

# inner functions
include("ACQR_simula.jl")
include("ACQR_kfilter.jl")
include("ACQR_kfilter_s.jl")
include("ACQR_ksmoother.jl")
include("ACQR_ksmoother_s.jl")
include("ACQR_em_starting.jl")
include("ACQR_em.jl")
include("ACQR_em_s.jl")


using Test
using PolynomialBasis
using ImplicitDomainQuadrature
# using Revise
using CutCell
include("useful_routines.jl")

x0 = [1.0, 5.0]
L, W = 4.0, 1.0
nelmtsx = 2
nelmtsy = 1
numghostlayers = 2
nodesperelement = 9

mesh = CutCell.Mesh(x0, [L, W], [nelmtsx, nelmtsy], nodesperelement)
paddedmesh = CutCell.BoundaryPaddedMesh(mesh, numghostlayers)

dx = 1.0
dy = 0.5

testbottomcoords = [
    1.0 1.0 2.0 2.0 3.0 3.0 4.0 4.0 5.0 5.0
    4.0 4.5 4.0 4.5 4.0 4.5 4.0 4.5 4.0 4.5
]
testrightcoords = [
    6.0 6.0 6.0 7.0 7.0 7.0
    5.0 5.5 6.0 5.0 5.5 6.0
]
testtopcoords = [
    1.0 1.0 2.0 2.0 3.0 3.0 4.0 4.0 5.0 5.0
    6.5 7.0 6.5 7.0 6.5 7.0 6.5 7.0 6.5 7.0
]
testleftcoords = [
    -1.0 -1.0 -1.0 0.0 0.0 0.0
    5.0 5.5 6.0 5.0 5.5 6.0
]

@test allapprox(paddedmesh.bottomghostcoords, testbottomcoords)
@test allapprox(paddedmesh.rightghostcoords, testrightcoords)
@test allapprox(paddedmesh.topghostcoords, testtopcoords)
@test allapprox(paddedmesh.leftghostcoords, testleftcoords)

x0 = [0.0, 0.0]
L, W = 2.0, 1.0
nelmtsx, nelmtsy = 2, 1
numghostlayers = 1
polyorder = 2

xI = [0.0,0.5]
normal = [0.0,1.0]
tol = 1e-8
boundingradius = 6.0

basis = TensorProductBasis(2, polyorder)
mesh = CutCell.Mesh(x0, [L, W], [nelmtsx, nelmtsy], basis)

levelset = InterpolatingPolynomial(1, basis)
levelsetcoeffs =
    CutCell.levelset_coefficients(x -> plane_distance_function(x,normal,xI), mesh)

cutmesh = CutCell.CutMesh(levelset, levelsetcoeffs, mesh)
paddedmesh = CutCell.BoundaryPaddedMesh(cutmesh, numghostlayers)

refseedpoints, spatialseedpoints, seedcellids =
    CutCell.seed_zero_levelset(2, levelset, levelsetcoeffs, cutmesh)

paddedlevelset = CutCell.BoundaryPaddedLevelset(
    paddedmesh,
    refseedpoints,
    spatialseedpoints,
    seedcellids,
    levelset,
    levelsetcoeffs,
    cutmesh,
    tol,
    boundingradius,
)

Dmx = CutCell.first_order_horizontal_backward_difference(paddedlevelset)
Dpx = CutCell.first_order_horizontal_forward_difference(paddedlevelset)
Dmy = CutCell.first_order_vertical_backward_difference(paddedlevelset)
Dpy = CutCell.first_order_vertical_forward_difference(paddedlevelset)

@test allapprox(Dmy,ones(length(levelsetcoeffs)))
@test allapprox(Dpy,ones(length(levelsetcoeffs)))
@test allapprox(Dmx,zeros(length(levelsetcoeffs)))
@test allapprox(Dpx,zeros(length(levelsetcoeffs)))

x0 = [0.0, 0.0]
L, W = 2.0, 1.0
nelmtsx, nelmtsy = 2, 1
numghostlayers = 1
polyorder = 2

xI = [0.5,0.0]
normal = [1.0,0.0]
tol = 1e-8
boundingradius = 6.0

basis = TensorProductBasis(2, polyorder)
mesh = CutCell.Mesh(x0, [L, W], [nelmtsx, nelmtsy], basis)

levelset = InterpolatingPolynomial(1, basis)
levelsetcoeffs =
    CutCell.levelset_coefficients(x -> plane_distance_function(x,normal,xI), mesh)

cutmesh = CutCell.CutMesh(levelset, levelsetcoeffs, mesh)
paddedmesh = CutCell.BoundaryPaddedMesh(cutmesh, numghostlayers)

refseedpoints, spatialseedpoints, seedcellids =
    CutCell.seed_zero_levelset(2, levelset, levelsetcoeffs, cutmesh)

paddedlevelset = CutCell.BoundaryPaddedLevelset(
    paddedmesh,
    refseedpoints,
    spatialseedpoints,
    seedcellids,
    levelset,
    levelsetcoeffs,
    cutmesh,
    tol,
    boundingradius,
)

Dmx = CutCell.first_order_horizontal_backward_difference(paddedlevelset)
Dpx = CutCell.first_order_horizontal_forward_difference(paddedlevelset)
Dmy = CutCell.first_order_vertical_backward_difference(paddedlevelset)
Dpy = CutCell.first_order_vertical_forward_difference(paddedlevelset)

@test allapprox(Dmx,ones(length(levelsetcoeffs)))
@test allapprox(Dpx,ones(length(levelsetcoeffs)))
@test allapprox(Dmy,zeros(length(levelsetcoeffs)))
@test allapprox(Dpy,zeros(length(levelsetcoeffs)))


# This file includes the plugin for calculation of Lyapunov exponents.

using ChaosTools

"""
    $TYPEDEF

Constructs a `Lyapunov` plugin. The [`process(plg::Lyapunov, x)`](@ref) function computes the maximum numerical Lyapunov
exponents. `m` is the reconstruction dimension, `J` is the amount of delay in reconstruction, `ni` is the number of steps
during transient steps and `ts` is the sampling time between samples of the input data `x`. See also:
(https://juliadynamics.github.io/DynamicalSystems.jl/latest/chaos/nlts/)

# FieldS

    $TYPEDFIELDS
"""
struct Lyapunov <: AbstractPlugin
    "Dimension"
    m::Int
    "Delay amount"
    J::Int
    "Transient nmber"
    ni::Int
    "Samping sampling"
    ts::Float64
end
Lyapunov(;m::Int=15, J::Int=5, ni::Int=300, ts::Float64=0.01) = Lyapunov(m, J, ni, ts)

show(io::IO, plg::Lyapunov) = print(io,
    "Lyapunov(embeddingdim:$(plg.m), numlags:$(plg.J), numiteration:$(plg.ni), samplingtime:$(plg.ts)")

"""
    $SIGNATURES

Computes the maximum Lyapunov exponent of the input data `x`.

# Example
```julia
julia> using Random

julia> rng = MersenneTwister(1234);

julia> x = rand(rng, 1000);

julia> plg = Plugins.Lyapunov()
Lyapunov(embeddingdim:15, numlags:5, numiteration:300, samplingtime:0.01

julia> process(plg, x)
-0.42032928176193973
```
"""
function process(plg::Lyapunov, x)
    ndims(x) == 1 || (x = x[:])
    ntype = FixedMassNeighborhood(5)
    ks = 1 : 4 : plg.ni
    R = reconstruct(x, plg.m, plg.J)
    E = numericallyapunov(R, ks, ntype=ntype)
    val = linear_region(plg.ts .* ks, E)[2]
    return val
end

# using NearestNeighbors using LinearAlgebra using Statistics using LsqFit import Base.log process(plg::Lyapunov, x) =
# lyapunov(x, plg.m, plg.J, plg.ni, plg.ts)[2]

# function reconstruct(x, m, J) N = length(x) M = N - (m - 1) * J X = zeros(m, M) for i = 1 : M data =  x[i : J : i + (m -
#     1)* J] X[:, i] = data end X end

# function knneighbours(X) tree = KDTree(X) [knn(tree, X[:, j], 2)[1][1] for j = 1 : size(X, 2)] end

# distance(Xj, Xjhat) = norm(Xj - Xjhat)

# Base.log(a::Vector) = log.(a)

# function lyapunov(x, m, J, ni, ts) X = reconstruct(x, m, J) M = size(X, 2) js = collect(1:M) jbars = knneighbours(X) m1 =
#     js .+ ni .<= M m2 = jbars .+ ni .<= M m = m1 .& m2 mjs = js[m] mjbars = jbars[m] y = mean(log.([distance.(eachcol(X[:,
#     j : j + ni]), eachcol(X[:, jbar : jbar + ni])) for (j, jbar) in zip(mjs, mjbars)])) / ts @. model(x, p) = p[1]*x ydata
#     = y[round(Int, length(y) * 0.25) : end]   # Discard first transient region and go to the linear region. lambda =
#     coef(curve_fit(model, collect(1:length(ydata)), ydata, rand(1)))[1] return y, lambda end


function derivative(f, ftype::Symbol, dtype::Symbol)
  if ftype == :scalar
    return x::Number -> finite_difference(f, float(x), dtype)
  elseif ftype == :vector
    return x::Vector -> finite_difference(f, float(x), dtype)
  else
    error("ftype must :scalar or :vector")
  end
end
derivative(f, x::Union{T, Vector{T}}, dtype::Symbol = :central) where {T <: Number} = finite_difference(f, float(x), dtype)
derivative(f, dtype::Symbol = :central) = derivative(f, :scalar, dtype)

gradient(f, x::Union{T, Vector{T}}, dtype::Symbol = :central) where {T <: Number} = finite_difference(f, float(x), dtype)
gradient(f, dtype::Symbol = :central) = derivative(f, :vector, dtype)

@static if isdefined(Compat.LinearAlgebra, :gradient)
function Compat.LinearAlgebra.gradient(f, x::Union{T, Vector{T}}, dtype::Symbol = :central) where T <: Number
    Base.depwarn("The finite difference methods from Calculus.jl no longer extend " *
                 "Base.gradient and should be called as Calculus.gradient instead. " *
                 "This usage is deprecated.", :gradient)
    Calculus.gradient(f,x,dtype)
end

function Compat.LinearAlgebra.gradient(f, dtype::Symbol = :central)
    Base.depwarn("The finite difference methods from Calculus.jl no longer extend " *
                 "Base.gradient and should be called as Calculus.gradient instead. " *
                 "This usage is deprecated.", :gradient)
    Calculus.gradient(f,dtype)
end
end

function jacobian(f, x::Vector{T}, dtype::Symbol) where T <: Number
    finite_difference_jacobian(f, x, dtype)
end
function jacobian(f, dtype::Symbol)
    g(x::Vector) = finite_difference_jacobian(f, x, dtype)
    return g
end
jacobian(f) = jacobian(f, :central)

function second_derivative(f, g, ftype::Symbol, dtype::Symbol)
  if ftype == :scalar
    return x::Number -> finite_difference_hessian(f, g, x, dtype)
  elseif ftype == :vector
    return x::Vector -> finite_difference_hessian(f, g, x, dtype)
  else
    error("ftype must :scalar or :vector")
  end
end
function second_derivative(f, g, x::Union{T, Vector{T}}, dtype::Symbol) where T <: Number
  finite_difference_hessian(f, g, x, dtype)
end
function hessian(f, g, x::Union{T, Vector{T}}, dtype::Symbol) where T <: Number
  finite_difference_hessian(f, g, x, dtype)
end
function second_derivative(f, g, x::Union{T, Vector{T}}) where T <: Number
  finite_difference_hessian(f, g, x, :central)
end
function hessian(f, g, x::Union{T, Vector{T}}) where T <: Number
  finite_difference_hessian(f, g, x, :central)
end
function second_derivative(f, x::Number, dtype::Symbol)
  finite_difference_hessian(f, derivative(f), x, dtype)
end
function hessian(f, x::Number, dtype::Symbol)
  finite_difference_hessian(f, derivative(f), x, dtype)
end
function second_derivative(f, x::Vector{T}, dtype::Symbol) where T <: Number
  finite_difference_hessian(f, gradient(f), x, dtype)
end
function hessian(f, x::Vector{T}, dtype::Symbol) where T <: Number
  finite_difference_hessian(f, gradient(f), x, dtype)
end
function second_derivative(f, x::Number)
  finite_difference_hessian(f, derivative(f), x, :central)
end
function hessian(f, x::Number)
  finite_difference_hessian(f, derivative(f), x, :central)
end
function second_derivative(f, x::Vector{T}) where T <: Number
  finite_difference_hessian(f, gradient(f), x, :central)
end
function hessian(f, x::Vector{T}) where T <: Number
  finite_difference_hessian(f, gradient(f), x, :central)
end
second_derivative(f, g, dtype::Symbol) = second_derivative(f, g, :scalar, dtype)
second_derivative(f, g) = second_derivative(f, g, :scalar, :central)
second_derivative(f) = second_derivative(f, derivative(f), :scalar, :central)
hessian(f, g, dtype::Symbol) = second_derivative(f, g, :vector, dtype)
hessian(f, g) = second_derivative(f, g, :vector, :central)
hessian(f) = second_derivative(f, gradient(f), :vector, :central)


@testset "kerneltensorproduct" begin
    rng = MersenneTwister(123456)
    u1 = rand(rng, 10)
    u2 = rand(rng, 10)
    v1 = rand(rng, 5)
    v2 = rand(rng, 5)

    # kernels
    k1 = SqExponentialKernel()
    k2 = ExponentialKernel()
    kernel1 = KernelTensorProduct(k1, k2)
    kernel2 = KernelTensorProduct([k1, k2])

    @test kernel1 == kernel2
    @test kernel1.kernels === (k1, k2) === KernelTensorProduct((k1, k2)).kernels
    @test length(kernel1) == length(kernel2) == 2
    @test string(kernel1) == (
        "Tensor product of 2 kernels:\n\tSquared Exponential Kernel\n\tExponential Kernel"
    )
    @test_throws DimensionMismatch kernel1(rand(3), rand(3))

    @testset "val" begin
        for (x, y) in (((v1, u1), (v2, u2)), ([v1, u1], [v2, u2]))
            val = k1(x[1], y[1]) * k2(x[2], y[2])

            @test kernel1(x, y) == kernel2(x, y) == val
        end
    end

    # Deprecations
    @test TensorProduct === KernelTensorProduct
    @test TensorProduct(k1, k2) == k1 ⊗ k2
    @test TensorProduct((k1, k2)) == k1 ⊗ k2
    @test TensorProduct([k1, k2]) == k1 ⊗ k2

    # Standardised tests.
    TestUtils.test_interface(kernel1, ColVecs{Float64})
    TestUtils.test_interface(kernel1, RowVecs{Float64})
    test_ADs(
        x -> KernelTensorProduct(SqExponentialKernel(), LinearKernel(; c=exp(x[1]))),
        rand(1);
        dims=[2, 2],
    )
    test_params(KernelTensorProduct(k1, k2), (k1, k2))

    @testset "single kernel" begin
        kernel = KernelTensorProduct(k1)
        @test length(kernel) == 1

        @testset "eval" begin
            for (x, y) in (((v1,), (v2,)), ([v1], [v2]))
                val = k1(x[1], y[1])

                @test kernel(x, y) == val
            end
        end
    end
end