using SpecialFunctions
#Copyright (c) 2010, Xin-She Yang
#All rights reserved.
# Trasnlation to Julia language, Fredrik bagge Carlson
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#
#* Redistributions of source code must retain the above copyright notice, this
#  list of conditions and the following disclaimer.
#
#* Redistributions in binary form must reproduce the above copyright notice,
#  this list of conditions and the following disclaimer in the documentation
#  and/or other materials provided with the distribution
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
#FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
#SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# """
#     cuckoo_search(f,X0;
# `lb = -Inf`,
# `ub = Inf`,
# `n = Number of nests (or different solutions)
# `pa = 0.25` Discovery rate of alien eggs/solutions
# `tol = 1.0e-5`,
# `max_iter = 1e5`,
# `timeout = Inf`)
#
# Change this if you want to get better results
# Based on implementation by
# @inproceedings{yang2009cuckoo,
#   title={Cuckoo search via L{\'e}vy flights},
#   author={Yang, Xin-She and Deb, Suash},
#   booktitle={Nature \& Biologically Inspired Computing, 2009. NaBIC 2009. World Congress on},
#   pages={210--214},
#   year={2009},
#   organization={IEEE}
# }
#
# """
function cuckoo_search(f, X0;
    lb=-Inf,
    ub = Inf,
    n = 25,
    pa = 0.25,
    tol = 1.0e-5,
    max_iter = 1e5,
    timeout = Inf)

    nd = length(X0) # n dims

    if lb == -Inf
        lb = X0-0.99999*abs.(X0)
    end
    if ub == Inf
        ub = X0+0.99999*abs.(X0)
    end

    # Random initial solutions
    nest = [copy(X0) for _ = 1:n] # n = num_nests
    nest[1] = X0
    for i=2:n
        nest[i] = @. lb + (ub-lb)*rand()
    end
    # Get the current best
    fitness = fill(Inf,n)
    fmin, bestnest, nest, fitness = update_nests!(f,nest,nest,fitness)
    new_nest = deepcopy(nest)
    N_iter=0
    ## Starting iterations
    t0 = time()
    while fmin > tol && N_iter < max_iter
        # Generate new solutions (but keep the current best)
        move_cuckoos!(nest,new_nest,bestnest,lb,ub)
        fnew, best, nest, fitness = update_nests!(f,nest,new_nest,fitness)
        # Update the counter
        N_iter += n
        if fnew < fmin
            fmin = fnew
            bestnest = best
        end
        # Discovery and randomization
        new_nest = empty_nests(nest,lb,ub,pa)
        # Evaluate this set of solutions
        fnew, best, nest, fitness = update_nests!(f,nest,new_nest,fitness)
        # Update the counter again
        N_iter += n
        # Find the best objective so far
        if fnew < fmin
            fmin = fnew
            bestnest = best
        end
        if time()-t0 > timeout
            @info("Cuckoo search: timeout $(timeout)s reached ($(time()-t0)s)")
            break
        end
    end ## End of iterations
    ## Post-optimization processing
    ## Display all the nests
    bestnest, fmin

end
## --------------- All subfunctions are list below ------------------
## Get cuckoos by ramdom walk
function move_cuckoos!(nest,new_nest,best,lb,ub)
    # Levy flights
    n = length(nest)
    # Levy exponent and coefficient
    # For details, see equation (2.21), Page 16 (chapter 2) of the book
    # X. S. Yang, Nature-Inspired Metaheuristic Algorithms, 2nd Edition, Luniver Press, (2010).
    β = 3/2
    βi = 1/β
    σ = (gamma(1+β)*sin(pi*β/2)/(gamma((1+β)/2)*β*2^((β-1)/2)))^(1/β)
    u,v,step = similar(best),similar(best),similar(best)
    for j = 1:n
        s = nest[j]
        # This is a simple way of implementing Levy flights
        # For standard random walks, use step=1;
        ## Levy flights by Mantegna’s algorithm
        u .= σ.*randn.()
        v .= randn.()
        @. step = u/abs(v)^βi
        # In the next equation, the difference factor (s-best) means that
        # when the solution is the best solution, it remains unchanged.
        @. step = 0.01*step.*(s-best)
        # Here the factor 0.01 comes from the fact that L/100 should the typical
        # step size of walks/flights where L is the typical lenghtscale;
        # otherwise, Levy flights may become too aggresive/efficient,
        # which makes new solutions (even) jump out side of the design domain
        # (and thus wasting evaluations).
        # Now the actual random walks or flights
        s += step .* randn.()
        # Apply simple bounds/limits
        new_nest[j] = clamp(s,lb,ub)
    end
end
## Find the current best nest
function update_nests!(f,nest,new_nest,fitness)
    # Evaluating all new solutions
    for j = 1:length(nest)
        fnew = f(new_nest[j])
        if fnew <= fitness[j]
            fitness[j] = fnew
            nest[j] = new_nest[j]
        end
    end
    # Find the current best
    fmin,K = findmin(fitness)
    best = nest[K]
    fmin,best,nest,fitness
end

## Replace some nests by constructing new solutions/nests
function empty_nests(nest,lb,ub,pa)
    # A fraction of worse nests are discovered with a probability pa
    n = length(nest)
    # Discovered or not -- a status vector
    K = rand(size(nest)) .> pa
    # In the real world, if a cuckoo’s egg is very similar to a host’s eggs, then
    # this cuckoo’s egg is less likely to be discovered, thus the fitness should
    # be related to the difference in solutions. Therefore, it is a good idea
    # to do a random walk in a biased way with some random step sizes.
    ## New solution by biased/selective random walks
    stepsize = rand().*(nest[randperm(n)] .- nest[randperm(n)])
    new_nest = nest .+ stepsize .* K
    for j = 1:length(nest)
        new_nest[j] = clamp(new_nest[j],lb,ub)
    end
    return new_nest
end

function Base.clamp(x::AbstractVector,l::AbstractVector,u::AbstractVector)
    map(x,l,u) do x,l,u
        clamp(x,l,u)
    end
end

using BenchmarkTools, StaticArrays
# 78.619 ms (861606 allocations: 61.70 MiB)
# 6.321 ms (146181 allocations: 12.43 MiB)
rosenbrock(x) =  (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
cuckoo_search(rosenbrock, [10.,10], tol=1e-9)
@btime cuckoo_search(rosenbrock, [10.,10.], lb=[-20,-20], ub=[20,20], tol=1e-9)
@btime cuckoo_search(rosenbrock, @SVector([10.,10.]), lb=@SVector([-20,-20]), ub=@SVector([20,20]), tol=1e-9)

@profiler cuckoo_search(rosenbrock, @SVector([10.,10.]), lb=@SVector([-20,-20]), ub=@SVector([20,20]), tol=1e-9)


### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 6b1079da-c189-48e0-8660-72465e502dc2
begin

	# Setup the Julia environment -
	using PlutoUI
	using Plots
	using DataFrames
	using Luxor

end

# ╔═╡ 12187222-b5b2-464f-926a-87d72f50624b
md"""
### Graphic Analysis of a Flash Separation

Let's revisit the previous flash separation example and explore the solution of the problem graphically.

A liquid feed stream that contains 60% component A (1) and 40% component B (2) enters the first flash drum operating at P = 1.21 MPa and T = 150$^{\circ}$C. The saturation pressures of pure component A and B can be modeled using the [Antoine equation](https://en.wikipedia.org/wiki/Antoine_equation):

$$\begin{eqnarray}
\ln\left(P_{1}^{sat}\right) &=& 15 -\frac{3010}{T+250}\\
\ln\left(P_{2}^{sat}\right) &=& 14 -\frac{2700}{T+205}
\end{eqnarray}$$

where $T$ denotes the temperature in units of celsius $^{\circ}$C and $P_{\star}^{sat}$ denotes the saturation pressure in units of kPa for component $\star$.

#### I'm curious ... what happens if we ...
What happens if the liquid effluent from the first flash drum is fed into a second flash drum for further separation.
What should the pressure be in the second unit to maximize the separation of component A and component B?

#### Assumptions
1. The fluid and vapor in all streams and systems are ideal
1. All systems are at steady-state

#### Strategy:
1. Compute a _Pxy_ diagram for the binary mixture of component A and B at T = 150$^{\circ}$C.
1. Use the _Pxy_ diagram to find the operating pressure with the _widest_ difference between the vapor and liquid composition for component $i$

"""

# ╔═╡ b3d73c72-be93-45c1-a8b2-4a3225c8ce30
md"""
###### Select a Pressure (in units of kPa):
"""

# ╔═╡ 58ad09e0-f241-4c30-ab57-7c3642ebfe65
md"""
##### Calculate $\hat{L}$ and $\hat{V}$ using the Flash Lever Rule (FLR)
The values for $\hat{L}$ and $\hat{V}$ depend upon the input composition $z_{i}$.

The FLR is given as:

$$\frac{\dot{L}}{\dot{V}} = \frac{y^{eq}_{i} - z_{i}}{z_{i} - x^{eq}_{i}}$$

where we defined in the notes: $a\equiv z_{i} - x^{eq}_{i}$ and $b\equiv y^{eq}_{i} - z_{i}$. We can rearrange the total mol balance to give:

$$\frac{b}{a} + 1 = \hat{V}^{-1}$$

from which we can solve for $\hat{V}$:

$$\hat{V} = \left(\frac{b}{a} + 1\right)^{-1}$$

Once we have $\hat{V}$, we can find $\hat{L}$ because we know:

$$\hat{L}+\hat{V} = 1$$

from the total mass balance.

"""

# ╔═╡ 155fc55a-6d48-4cb3-af14-2d3d14699237
md"""
###### Function library is below here (do not change, bad stuff will happen, step slowly away from the keyboard)
"""

# ╔═╡ f4d83604-b048-4759-a357-99bbf4c95bcd
# function to compute staturation pressures for different T
function compute_saturation_pressure(temperature::Float64)::Tuple{Float64,Float64}

	# use the Antoine equation to compute the saturation P -
	P₁ = exp(15 - 3010/(temperature + 250))		# P1_sat units: kPa
	P₂ = exp(14 - 2700/(temperature + 205))		# P2_sat units: kPa

	# return -
	return (P₁,P₂)
end

# ╔═╡ 03fd4f1b-93be-4d75-851e-94de224797ec
function compute_phase_diagram(temperature::Float64; step::Float64=0.01)

	# initailize -
	compostion_array = range(0.0, stop=1.0, step=step) |> collect
	phase_diagram_df = DataFrame(P=Float64[], x₁ = Float64[], y₁ = Float64[])

	# for this T - compute the saturation pressure -
	P_sat_tuple = compute_saturation_pressure(temperature)
	P1_sat = P_sat_tuple[1]
	P2_sat = P_sat_tuple[2]

	# compute the profile -
	for x₁ in compostion_array

		# liquid -
		P = (x₁)*P1_sat + (1.0 - x₁)*P2_sat

		# compute Y -
		y₁ = x₁*(P1_sat)/P

		# package into a row -
		row_data = (P, x₁, y₁)

		# push -
		push!(phase_diagram_df, row_data)
	end

	# return -
	return phase_diagram_df
end

# ╔═╡ 936a10ad-0d29-4d76-b78a-8e392e5cdb3f
begin

	T = 150.0 # units: C
	PS = floor.(compute_saturation_pressure(T))

	Pxy_df = compute_phase_diagram(T; step=0.001)
	(number_of_rows, number_of_cols) = size(Pxy_df)

	nothing
end

# ╔═╡ 1057e9b4-9734-4705-ae85-9aa4b426f79a
@bind P_op Slider(PS[2]:PS[1]; default=1210, show_value=true)

# ╔═╡ 2abed2c4-04b4-4afc-b22c-19afd2a6271a
# make a plot of the Pxy data for binary A and B -
begin

	# compute the pxy - data for the binary A + B mixture -

	# get stuff out of the df -
	P = Pxy_df[!,:P]
	x₁ = Pxy_df[!,:x₁]
	y₁ = Pxy_df[!,:y₁]

	# plot the liquid line -
	plot(x₁,P, lw=2, legend=:topleft, label="x₁",title="Binary A+B at T = 150C",
		ylims=(0,1.1*maximum(P)), color="royalblue1")

	# plot the vapor line -
	plot!(y₁,P,lw=2, label="y₁", color="red1")

	# label the axis -
	xlabel!("Mole fraction component 1 (AU)",fontsize=18)
	ylabel!("Pressure (kPa)",fontsize=18)

	# find intersection and plot the tie-line -
	idx_start = findfirst(x->x>=P_op,P)
	x_eq = x₁[idx_start]
	y_eq = y₁[idx_start]
	tie_line = [x_eq P_op ; y_eq P_op]
	plot!(tie_line[:,1],tie_line[:,2],lw=2, color="gray69", label="")

	# draw lines to x-axis -
	liquid_comp_line = [x_eq P_op ; x_eq 0.0]
	vapor_comp_line = [y_eq P_op ; y_eq 0.0]
	plot!(liquid_comp_line[:,1], liquid_comp_line[:,2], lw=2, color="royalblue1", label="",ls=:dot)
	plot!(vapor_comp_line[:,1], vapor_comp_line[:,2], lw=2, color="red", label="", ls=:dot)

	# draw points -
	scatter!(liquid_comp_line[:,1], liquid_comp_line[:,2], lw=2, color="royalblue1", label="")
	scatter!(vapor_comp_line[:,1], vapor_comp_line[:,2], lw=2, color="red", label="")
end

# ╔═╡ 48a12856-6b7c-44da-91d4-d92946cf9591
begin

	with_terminal() do
		println("x1_eq = $(x_eq) and y1_eq = $(y_eq)")
	end

end

# ╔═╡ 1cba22c8-5ae4-498b-93c2-57b0fb86eac8
begin

	# what is z1 -
	z1 = 0.60

	# What are a and b (and the total width)
	b = (y_eq - z1)
	a = (z1 - x_eq)
	Δ = (y_eq - x_eq)

	N = 1
	D = (b/a+1)
	V_hat = N/D
	L_hat = 1 - V_hat

	with_terminal() do
		println("V_hat = $(V_hat) and L_hat = $(L_hat)")
	end
end

# ╔═╡ 7018a302-2627-11ec-1f6f-4baef5c8f576
html"""
<style>
main {
    max-width: 1200px;
    width: 85%;
    margin: auto;
    font-family: "Roboto, monospace";
}

a {
    color: blue;
    text-decoration: none;
}

.H1 {
    padding: 0px 30px;
}
</style>
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Luxor = "ae8d54c2-7ccd-5906-9d76-62fc9837b5bc"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
DataFrames = "~1.2.2"
Luxor = "~2.15.0"
Plots = "~1.22.4"
PlutoUI = "~0.7.14"
"""

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

[[AbstractFFTs]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "485ee0867925449198280d4af84bdb46a2a404d0"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.0.1"

[[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]]
deps = ["Cairo_jll", "Colors", "Glib_jll", "Graphics", "Libdl", "Pango_jll"]
git-tree-sha1 = "d0b3f8b4ad16cb0a2988c6788646a5e6a17b6b1b"
uuid = "159f3aea-2a34-519c-b102-8c37f9878175"
version = "1.0.5"

[[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"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "a325370b9dd0e6bf5656a6f1a7ae80755f8ccc46"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.7.2"

[[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"

[[ColorVectorSpace]]
deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"]
git-tree-sha1 = "45efb332df2e86f2cb2e992239b6267d97c9e0b6"
uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
version = "0.9.7"

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

[[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"

[[Crayons]]
git-tree-sha1 = "3f71217b538d7aaee0b69ab47d9b7724ca8afa0d"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.0.4"

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

[[DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "d785f42445b63fc86caa08bb9a9351008be9b765"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.2.2"

[[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"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.5"

[[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"

[[FileIO]]
deps = ["Pkg", "Requires", "UUIDs"]
git-tree-sha1 = "3c041d2ac0a52a12a27af2782b34900d9c3ee68c"
uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
version = "1.11.1"

[[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"

[[Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[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"

[[Graphics]]
deps = ["Colors", "LinearAlgebra", "NaNMath"]
git-tree-sha1 = "1c5a84319923bea76fa145d49e93aa4394c73fc2"
uuid = "a2bd30eb-e257-5431-a919-1863eab51364"
version = "1.1.1"

[[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 = "14eece7a3308b4d8be910e265c724a6ba51a9798"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.16"

[[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"

[[HypertextLiteral]]
git-tree-sha1 = "72053798e1be56026b81d4e2682dbe58922e5ec9"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.0"

[[IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[ImageCore]]
deps = ["AbstractFFTs", "ColorVectorSpace", "Colors", "FixedPointNumbers", "Graphics", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "Reexport"]
git-tree-sha1 = "9a5c62f231e5bba35695a20988fc7cd6de7eeb5a"
uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
version = "0.9.3"

[[ImageMagick]]
deps = ["FileIO", "ImageCore", "ImageMagick_jll", "InteractiveUtils", "Libdl", "Pkg", "Random"]
git-tree-sha1 = "5bc1cb62e0c5f1005868358db0692c994c3a13c6"
uuid = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
version = "1.2.1"

[[ImageMagick_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "ea2b6fd947cdfc43c6b8c15cff982533ec1f72cd"
uuid = "c73af94c-d91f-53ed-93a7-00f77d67a9d7"
version = "6.9.12+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"

[[InvertedIndices]]
git-tree-sha1 = "bee5f1ef5bf65df56bdd2e40447590b272a5471f"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.1.0"

[[IrrationalConstants]]
git-tree-sha1 = "f76424439413893a832026ca355fe273e93bce94"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.0"

[[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"

[[Juno]]
deps = ["Base64", "Logging", "Media", "Profile"]
git-tree-sha1 = "07cb43290a840908a771552911a6274bc6c072c7"
uuid = "e5e0dc1b-0480-54bc-9374-aad01c23163d"
version = "0.8.4"

[[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"

[[Libcroco_jll]]
deps = ["Artifacts", "Glib_jll", "JLLWrappers", "Libdl", "Pkg", "XML2_jll"]
git-tree-sha1 = "a8e3b1b67458c8933992b95db9c4b37865906e3f"
uuid = "57eb2189-7eb1-52c8-ac0e-99495f550b14"
version = "0.6.13+2"

[[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"

[[Librsvg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libcroco_jll", "Libdl", "Pango_jll", "Pkg", "gdk_pixbuf_jll"]
git-tree-sha1 = "af3e6dc6747e53a0236fbad80b37e3269cf66a9f"
uuid = "925c91fb-5dd6-59dd-8e8c-345e74382d89"
version = "2.42.2+3"

[[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"

[[LogExpFunctions]]
deps = ["ChainRulesCore", "DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "34dc30f868e368f8a17b728a1238f3fcda43931a"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.3"

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

[[Luxor]]
deps = ["Base64", "Cairo", "Colors", "Dates", "FFMPEG", "FileIO", "ImageMagick", "Juno", "QuartzImageIO", "Random", "Rsvg"]
git-tree-sha1 = "b2b3d49ffb3b89d9e052da47a0b6df13579738ea"
uuid = "ae8d54c2-7ccd-5906-9d76-62fc9837b5bc"
version = "2.15.0"

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

[[MappedArrays]]
git-tree-sha1 = "e8b359ef06ec72e8c030463fe02efe5527ee5142"
uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
version = "0.4.1"

[[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"

[[Media]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "75a54abd10709c01f1b86b84ec225d26e840ed58"
uuid = "e89f7d12-3494-54d1-8411-f7d8b9ae1f27"
version = "0.5.0"

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

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

[[MosaicViews]]
deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"]
git-tree-sha1 = "b34e3bc3ca7c94914418637cb10cc4d1d80d877d"
uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389"
version = "0.3.3"

[[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"

[[OffsetArrays]]
deps = ["Adapt"]
git-tree-sha1 = "c0e9e582987d36d5a61e650e6e543b9e44d9914b"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.10.7"

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

[[OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

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

[[OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+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"

[[PaddedViews]]
deps = ["OffsetArrays"]
git-tree-sha1 = "646eed6f6a5d8df6708f15ea7e02a7a2c4fe4800"
uuid = "5432bcbf-9aad-5242-b902-cca2824c8663"
version = "0.5.10"

[[Pango_jll]]
deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9bc1871464b12ed19297fbc56c4fb4ba84988b0d"
uuid = "36c8627f-9965-5494-a995-c6b170f724f3"
version = "1.47.0+0"

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

[[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 = "b084324b4af5a438cd63619fd006614b3b20b87b"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.0.15"

[[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 = "6841db754bd01a91d281370d9a0f8787e220ae08"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.22.4"

[[PlutoUI]]
deps = ["Base64", "Dates", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "d1fb76655a95bf6ea4348d7197b22e889a4375f4"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.14"

[[PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "a193d6ad9c45ada72c14b731a318bedd3c2f00cf"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.3.0"

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

[[PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "69fd065725ee69950f3f58eceb6d144ce32d627d"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.2.2"

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

[[Profile]]
deps = ["Printf"]
uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"

[[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"

[[QuartzImageIO]]
deps = ["FileIO", "ImageCore", "Libdl"]
git-tree-sha1 = "16de3b880ffdfbc8fc6707383c00a2e076bb0221"
uuid = "dca85d43-d64c-5e67-8c65-017450d5d020"
version = "0.7.4"

[[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"

[[Rsvg]]
deps = ["Cairo", "Glib_jll", "Librsvg_jll"]
git-tree-sha1 = "3d3dc66eb46568fb3a5259034bfc752a0eb0c686"
uuid = "c4c386cf-5103-5370-be45-f3a111cca3b8"
version = "1.0.0"

[[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"

[[SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "793793f1df98e3d7d554b65a107e9c9a6399a6ed"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.7.0"

[[StackViews]]
deps = ["OffsetArrays"]
git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c"
uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15"
version = "0.1.1"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3c76dde64d03699e074ac02eb2e8ba8254d428da"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.13"

[[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 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

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

[[TensorCore]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6"
uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50"
version = "0.1.1"

[[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"

[[gdk_pixbuf_jll]]
deps = ["Artifacts", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg", "Xorg_libX11_jll", "libpng_jll"]
git-tree-sha1 = "0facfc4bfd873c21b83a053bbf182b9ef19c69d8"
uuid = "da03df04-f53b-5353-a52f-6a8b0620ced0"
version = "2.42.6+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:
# ╟─12187222-b5b2-464f-926a-87d72f50624b
# ╟─936a10ad-0d29-4d76-b78a-8e392e5cdb3f
# ╟─b3d73c72-be93-45c1-a8b2-4a3225c8ce30
# ╠═1057e9b4-9734-4705-ae85-9aa4b426f79a
# ╟─2abed2c4-04b4-4afc-b22c-19afd2a6271a
# ╟─48a12856-6b7c-44da-91d4-d92946cf9591
# ╟─58ad09e0-f241-4c30-ab57-7c3642ebfe65
# ╠═1cba22c8-5ae4-498b-93c2-57b0fb86eac8
# ╟─155fc55a-6d48-4cb3-af14-2d3d14699237
# ╠═6b1079da-c189-48e0-8660-72465e502dc2
# ╠═03fd4f1b-93be-4d75-851e-94de224797ec
# ╟─f4d83604-b048-4759-a357-99bbf4c95bcd
# ╟─7018a302-2627-11ec-1f6f-4baef5c8f576
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


#------------------------------------------------------------------------------------------------------------
#   Code to solve elastic net-ish version of covariance estimation
#   min_{X \in \R^{d x r}} F(X) :=  E_{a,b} | <XX^T, aa^T> - b | + (<XX^T, aa^T> - b )^2
#   for a bunch of trials
#------------------------------------------------------------------------------------------------------------
using Random  # for setting the random seed
using PyPlot
using LaTeXStrings, Printf  # for plot labels

include("solve_enet.jl");

#-------------------------------------
#   Set parameters
#-------------------------------------
r = 2;  # rank
dims = [100];  # ambient dimension
dims_colors = ["#1f78b4"]; #, "#33a02c"];
stoch_err = 0.1;  # standard deviation of errors in stochastic measurements b

maxIter = 2000;
dims_steps_subgrad = [1e-6];
dims_steps_mirror = [0.1];

Random.seed!(123);  # for reproducibility

#-------------------------------------
#   Initialize plot
#-------------------------------------
dist_fig = figure(figsize=[10,6]);
xlabel(L"Iteration $k$");
ylabel(L"$\min_U \; \frac{\| X_k U - X_{true} \|_2^2 }{\| X_{true} \|_2^2}$");
title(@sprintf("Distance to solution for elastic net covariance estimation (r=%i)", r));
xlim(0,maxIter)

funval_fig = figure(figsize=[10,6]);
xlabel(L"Iteration $k$");
ylabel("Empirical Function Value");
title(@sprintf("Function values for elastic net covariance estimation (r=%i)", r));
xlim(0,maxIter)

#-----------------------------------------------------------------------------------------
#   Run stochastic subgradient method for each dimension
#-----------------------------------------------------------------------------------------
for i in 1:length(dims)
    d = dims[i];

    # Generate true matrix we are searching for
    Xtrue = randn(d,r);

    # Generate initial point
    radius = 1.0;  # 2-norm measure of relative deviation between Xtrue and Xinit
    pert = randn(d,r);
    Xinit = Xtrue + radius * (norm(Xtrue, 2) / norm(pert, 2)) * pert;

    # Specify step sizes
    steps_subgrad = fill(dims_steps_subgrad[i], maxIter);
    steps_mirror = fill(dims_steps_mirror[i], maxIter);

    # Run the two methods
    (err_hist_subgrad, fun_hist_subgrad) = solve_enet(Xinit, Xtrue, stoch_err, maxIter,
                                                                                steps_subgrad,
                                                                                method="subgradient")
    (err_hist_mirror, fun_hist_mirror) = solve_enet(Xinit, Xtrue, stoch_err, maxIter,
                                                                                steps_mirror,
                                                                                method="mirror")

    # Add distances to appropriate plot
    plt[:figure](dist_fig[:number])
    semilogy(err_hist_subgrad, linestyle="--", color=dims_colors[i],
                    label=@sprintf("SGD, d=%i", d));
    semilogy(err_hist_mirror, color=dims_colors[i], label=@sprintf("SMD, d=%i", d));

    # Add function values to appropriate plot
    plt[:figure](funval_fig[:number])
    semilogy(fun_hist_subgrad, linestyle="--", color=dims_colors[i],
                    label=@sprintf("SGD, d=%i", d));
    semilogy(fun_hist_mirror, color=dims_colors[i], label=@sprintf("SMD, d=%i", d));
end

# Add legends to plots and save final results
plt[:figure](dist_fig[:number])
legend(loc="lower right")
savefig("plots/enet_distances.pdf");

plt[:figure](funval_fig[:number])
legend(loc="lower right")
savefig("plots/enet_values.pdf");


# This file was generated, do not modify it. # hide
# @pipeline PCAPipe(std=Standardizer(), t=PCA(maxoutdim=2))
# pca = machine(PCAPipe(), Xtrain)
# fit!(pca, Xtrain)
# W = transform(pca, Xtrain)

@load PCA

pca = Xc |> Standardizer() |> PCA(maxoutdim=2)
fit!(pca)
W = pca(rows=train);


using ITensors
using ITensors.Ops
using Zygote

s = siteinds("S=1/2", 4)

function f1(x)
  y = ITensor(Op("Ry", 1; θ=x), s)
  return y[1, 1]
end

@show x = 2.0

@show f1(x)
@show f1'(x)

function f2(x)
  y = exp(ITensor(Op("Ry", 1; θ=x), s))
  return y[1, 1]
end

@show x = 2.0

@show f2(x)
@show f2'(x)

function f3(x)
  y = Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x)
  return y[1].params.θ
end

@show x = 2.0

@show f3(x)
@show f3'(x)

function f4(x)
  y = ITensor(Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x), s)
  return y[1, 1]
end

@show x = 2.0

@show f4(x)
@show f4'(x)

function f5(x)
  y = exp(ITensor(Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x), s))
  return y[1, 1]
end

@show x = 2.0

@show f5(x)
@show f5'(x)

function f6(x)
  y = ITensor(exp(Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x)), s)
  return y[1, 1]
end

@show x = 2.0

@show f6(x)
@show f6'(x)

function f7(x)
  y = ITensor(2 * Op("Ry", 1; θ=x), s)
  return y[1, 1]
end

@show x = 2.0

@show f7(x)
@show f7'(x)

function f8(x)
  y = ITensor(2 * (Op("Ry", 1; θ=x) + Op("Ry", 1; θ=x)), s)
  return y[1, 1]
end

@show x = 2.0

@show f8(x)
@show f8'(x)

function f9(x)
  y = ITensor(Op("Ry", 1; θ=x) * Op("Ry", 2; θ=x), s)
  return y[1, 1]
end

@show x = 2.0

@show f9(x)
@show f9'(x)

function f10(x)
  y = ITensor(exp(-x * Op("X", 1) * Op("X", 2)), s)
  return norm(y)
end

@show x = 2.0

@show f10(x)
@show f10'(x)

V = randomITensor(s[1], s[2])

function f11(x)
  y = exp(-x * Op("X", 1) * Op("X", 2))
  y *= exp(-x * Op("X", 1) * Op("X", 2))
  U = Prod{ITensor}(y, s)
  return norm(U(V))
end

@show x = 2.0

@show f11(x)
@show f11'(x)

function f12(x)
  y = exp(-x * (Op("X", 1) + Op("Z", 1) + Op("Z", 1)); alg=Trotter{1}(1))
  U = Prod{ITensor}(y, s)
  return norm(U(V))
end

@show x = 2.0

@show f12(x)
@show f12'(x)

## ## XXX: Error in vcat!
## function f13(x)
##   y = -x * (Op("X", 1) * Op("X", 2) + Op("Z", 1) * Op("Z", 2))
##   U = ITensor(y, s)
##   return norm(U * V)
## end
##
## @show x = 2.0
##
## @show f13(x)
## @show f13'(x)
##
## ## XXX: Error in vcat!
## function f14(x)
##   y = exp(-x * (Op("X", 1) * Op("X", 2) + Op("Z", 1) * Op("Z", 2)); alg=Trotter{1}(1))
##   U = ITensor(y, s)
##   return norm(U * V)
## end
##
## @show x = 2.0
##
## @show f14(x)
## @show f14'(x)


# Model
include_model("double_pendulum")
include_constraints("free_time")

model = free_time_model(model)

function fd(model::DoublePendulum, x⁺, x, u, w, h, t)
    midpoint_implicit(model, x⁺, x, u, w, u[end])
end

# Horizon
T = 50
Tm = convert(Int, floor(T / 2))

# Time step
tf = 5.0
h = tf / (T - 1)

# Bounds

# ul <= u <= uu
ul, uu = control_bounds(model, T,
        [-50.0 * ones(model.m - 1); 0.01],
        [50.0 * ones(model.m - 1); 2.0 * h])

# Initial and final states
θ1 = pi / 3.0
θM = pi / 6.0
x1 = [pi / 2.0 + θ1; -2.0 * θ1; 0.0; 0.0]
xM = [pi / 2.0 + θM; -2.0 * θM; 0.0; 0.0]
xl, xu = state_bounds(model, T,
        x1 = [x1[1:2]; Inf * ones(2)])
xl[Tm][1:2] = copy(xM[1:2])
xu[Tm][1:2] = copy(xM[1:2])

# Trajectory initialization
x0 = [linear_interpolation(x1, xM, Tm)..., linear_interpolation(xM, x1, Tm)...] # linear interpolation on state
u0 = [[0.001 * rand(model.m - 1); h] for t = 1:T-1] # random controls

# Objective
include_objective("nonlinear_stage")
function lz_stage(x, u, t)
    if t > 1
        return 100.0 * (kinematics_ee(model, x)[2])^2.0
    else
        return 0.0
    end
end
lz_terminal(x) = 0.0
obj_z = nonlinear_stage_objective(lz_stage, lz_terminal)

obj_track = quadratic_time_tracking_objective(
        [Diagonal([1.0 * ones(2); zeros(2)]) for t = 1:T],
        [Diagonal(1.0e-1 * ones(model.m)) for t = 1:T-1],
        [x0[t] for t = 1:T],
        [zeros(model.m) for t = 1:T],
        1.0)
obj = MultiObjective([obj_z, obj_track])

# Constraints
include(joinpath(pwd(), "src/constraints/loop.jl"))
con_loop = loop_constraints(model, 1, T)
con_free_time = free_time_constraints(T)
con = multiple_constraints([con_free_time, con_loop])

# Problem
prob = trajectory_optimization_problem(model,
           obj,
           T,
           # h = h,
           xl = xl,
           xu = xu,
           ul = ul,
           uu = uu,
           con = con)

# Pack trajectories into vector
z0 = pack(x0, u0, prob)

# Solve
@time z , info = solve(prob, copy(z0))

# Visualize
using Plots
x, u = unpack(z, prob)
_u = [u[t][1:2] for t = 1:T-1]
tf, t, _h = get_time(u)

plot(hcat(x...)', width = 2.0)
plot(hcat(u...)', width = 2.0, linetype = :steppost)

include(joinpath(pwd(), "models/visualize.jl"))
vis = Visualizer()
render(vis)
visualize!(vis, model, x, Δt = h)

# LQR
Q = [Diagonal(ones(model.n)) for t = 1:T]
R = [Diagonal(0.1 * ones(model.m)) for t = 1:T-1]
K, P = tvlqr(model, x, u, Q, R, 0.0)

plot(hcat([vec(K[t]) for t = 1:T-1]...)',
    linetype = :steppost,
    width = 2.0,
    labels = "",
    title = "LQR gains",
    xlabel = "time step t")

plot(hcat([vec(P[t]) for t = 1:T-1]...)',
    linetype = :steppost,
    width = 2.0,
    labels = "",
    title = "LQR cost-to-go",
    xlabel = "time step t")

N = 5
TN = N * T - (N - 1)
xN = deepcopy(x)
uN = deepcopy(u)
for i = 1:(N - 1)
    xN = [xN..., x[2:end]...]
    uN = [uN..., u...]
end
QN = [Diagonal(ones(model.n)) for t = 1:TN]
RN = [Diagonal(0.1 * ones(model.m)) for t = 1:TN-1]
KN, PN = tvlqr(model, xN, uN, QN, RN, 0.0)

plot(hcat([vec(KN[t]) for t = 1:(TN - 1)]...)',
    linetype = :steppost,
    width = 2.0,
    title = "LQR gains ($N cycles)",
    xlabel = "time step t",
    labels = "")

plot(hcat([vec(PN[t]) for t = 1:(TN - 1)]...)',
    linetype = :steppost,
    width = 2.0,
    title = "LQR cost-to-go ($N cycles)",
    xlabel = "time step t",
    labels = "")

ctg = PN[1:T]
@save joinpath(@__DIR__, "double_pendulum_limit_cycle.jld2") x _u _h ctg


## algebra
#include("fit_ss.jl")

function pow0(z::ComplexF64,k::Int=0)::ComplexF64
  if k==0
    return 1
  elseif k>0
    return 0
  else
    return complex(NaN)
  end
end

pow0(z::Symbol)=""

function pow1(z::ComplexF64,k::Int=0)::ComplexF64
  if k==0
    return z
  elseif k==1
    return 1
  elseif k>1
    return 0
  else
    return complex(NaN)#force NaN if a negative derivative is requested
  end
end

pow1(z::Symbol)="$z"

function pow2(z::ComplexF64,k::Int=0)::ComplexF64
  if k==0
    return z^2
  elseif k==1
    return 2*z
  elseif k==2
    return 2
  elseif k>2
    return 0
  else
    return complex(NaN) #force NaN if a negative derivative is requested
  end
end
pow2(z::Symbol)="$z^2"

#TODO turn this into a macro
function pow(z::ComplexF64,k::Int64,a::Int64)::ComplexF64
  if k>a>0
    f= 0
  elseif k>=0
    f=1
    i=a
    for j=0:k-1
      f*=i
      i-=1
    end
    f*=z^(a-k)
  else
    f=complex(NaN)
  end
  return f
end

function pow(z::ComplexF64,k::Int64,a::Number)::ComplexF64
  if k>=0
    f=1
    i=a
    for j=0:k-1
      f*=i
      i-=1
    end
    f*=z^(a-k)
  else
    f=complex(NaN)
  end
  return f
end

function pow_a(a::Number)

  function f(z::ComplexF64,k::Int64=0)::ComplexF64
    let a=a
      return pow(z,k,a)
    end#let
  end
  #This code fails incremental compilation.
  #TODO: check why
  # if a==0
  #   f(z::Symbol)=""
  # elseif a==1
  #   f(z::Symbol)="$z"
  # else
  #   f(z::Symbol)="$z^$a"
  # end
  function f(z::Symbol)
    let a=a
      if a==0
        return ""
      elseif a==1
        return "$z"
      else
        return
        "$z^$a"
      end
    end
  end
  return f
end

function generate_exp_az(a)
  function f(z::ComplexF64,k::Int)::ComplexF64
    let a=a
      if k>=0
        return a^k*exp(a*z)
      else
        return NaN+NaN*1im
      end
    end
  end

  function f(z::Symbol)::String
    let a=a
      return "exp(a$z)"
    end
  end
  return f
end

function exp_az(z::ComplexF64,a::ComplexF64,k::Int)::ComplexF64
  if k>=0
    return a^k*exp(a*z)
  else
    return nothing
  end
end

function exp_delay(ω::ComplexF64,τ::ComplexF64,m::Int,n::Int)::ComplexF64
  a=-1.0im
  u(z,l)=pow(z,l,m)
  f=0
  for i = 0:n
    f+=binomial(n,i)*u(τ,i)*(a*ω)^(n-i)
  end
  f*=a^m*exp(a*ω*τ)
  return f
end
function exp_delay(ω::Symbol,τ::Symbol)::String
  return "exp(-i$ω$τ)"
end

###

#TODO: improve closures by using let blocks or fastclosure package

function generate_stsp_z(A,B,C,D)
  function stsp_z(z,n)
    f = (-im)^n*factorial(n)*C*(im*z*LinearAlgebra.I-A)^(-n-1)*B
    if n==0
      f = f+D
    end
    return f[1]
  end
  return stsp_z
end

function generate_z_g_z(g)
  function z_g_z(z,n)
    if n == 0
      f = z*g(z,0)
    else
      f = z*g(z,n)+n*g(z,n-1)
    end
    return f
  end
  return z_g_z
end

tau_delay=exp_delay

#TODO create a macro for generating functions

# function powa(z::ComplexF64,k::Int,a::Int)
#   if
# end
function z_exp_iaz(z::ComplexF64, a::ComplexF64,m=0,n=0)::ComplexF64
  if m==n==0
    return z*exp(1im*a*z)
  elseif m==1
    (1im*a*z +1)*exp(1im*a*z)
  elseif n==1
    return 1im*z^2*exp(1im*a*z)
  else
    print("Argh!!!!!")
  end
end

function z_exp__iaz(z::ComplexF64, a::ComplexF64,m=0,n=0)::ComplexF64
  if m==n==0
    return z*exp(-1im*a*z)
  elseif m==1
    (-1im*a*z +1)*exp(-1im*a*z)
  elseif n==1
    return- 1im*z^2*exp(-1im*a*z)
  else
    print("Argh!!!!!")
  end
end

function exp_pm(s)
a=s*1.0im
  function exp_delay(ω,τ,m,n)
    u(z,l)=pow(z,l,m)
    f=0
    for i = 0:n
      f+=binomial(n,i)*u(τ,i)*(a*ω)^(n-i)
    end
    f*=a^m*exp(a*ω*τ)
    return f
  end
return exp_delay
end

function exp_ax2(z,a,n)
  if a==0.0+0im
    if n==0
      return 1.0+0.0im
    else
      return 0.0+0.0im
    end
  end

  f=0.0+0.0im
  A=a^n
  Z=z^n
  cnst=2^n*factorial(n)
  for k=0:n÷2
    coeff=0.0+0.0im
    coeff+=cnst*4.0^(-k)/factorial(k)/factorial(n-2*k)
    coeff*=A
    coeff*=Z
    f+=coeff
    A/=a
    Z/=z^2
  end
  f*=exp(a*z^2)
  return f
end

function exp_az2mzit(z,τ,a,m,n,k)
  f=pow_a(n+2*k)
  g(z,l)=exp_ax2(z,a,l)
  h(z,l)=exp_delay(z,τ,l,0)
  coeff=0.0+0.0im
  multinomialcoeff=factorial(m)
  for ii=0:m
    multi_ii=factorial(ii)
    coeff_ii=h(z,ii)
    for jj=0:m-ii
      multi_jj=factorial(jj)
      coeff_jj=g(z,jj)
      kk=m-jj-ii
      multi=multinomialcoeff/multi_ii/multi_jj/factorial(kk)
      coeff+=multi*f(z,kk)*coeff_jj*coeff_ii
    end
  end
    coeff*=(-1.0im)^n
    return coeff
end

function generate_Σy_exp_ikx(y)
  N=length(y)
  function Σy_exp_ikx(z::ComplexF64,n::Int)
    f=0.0+0.0im
    for (k,y) in enumerate(y)
      k-=1 #zero-based counting of wave-number
      f+=k^n*y*exp(2*pi*1.0im*k/N*z)
    end
    f*=(2*pi*1.0im/N)^n
    return f
  end
  return Σy_exp_ikx
end

function generate_gz_hz(g,h)
  function func(z::ComplexF64,k::Int)
    f=0.0+0.0im
    for i = 0:k
      f+=binomial(k,i)*h(z,k-i)*g(z,i)
    end
    return f
  end
  return func
end

function generate_1_gz(g)
  function func(z::ComplexF64,k::Int)
    if k==0
      return 1-g(z,k)
    else
      return -g(z,k)
    end
  end
  return func
end

function Σnexp_az2mzit(args...)
  J=(length(args)-2)÷6
  f=0.0+0.0im
  z=args[1] #argument is started by eigenfrequency...
  m=args[J*3+1+1]
  i=2:4
  for j=0:J-1
    nn,τ,a=args[i.+j*3] #... and continues with repeated sets of n, τ and a
    l,n,k=args[i.+3*j.+3*J.+1]
    f+=pow1(nn,l)*exp_az2mzit(z,τ,a,m,n,k)
  end
  return f
end


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

"""
weighted sampling: takes (unnormalized) log probs and returns index
"""
function wsample_logprob(logProbs::AbstractVector{<:Real})
  log_p_max = maximum(logProbs)
  p = exp.(logProbs .- log_p_max)
  return Distributions.wsample(p)
end

"""
log 1 minus. For example, `log1m(.3) == log(1 - .3) == log1p(-.3) == log(.7)`.
"""
log1m(x::T) where {T <: Real} = log1p(-x)

"""
Convert a vector of indices to a one-hot matrix, given also the number of
categories.
"""
function to_onehot(indvec::Vector{Int}, categories::Int)
  N = length(indvec)
  onehot_matrix = zeros(Int, N, categories)
  for n in 1:N
    onehot_matrix[n, indvec[n]] = 1
  end
  return onehot_matrix
end

"""
log pdf of gaussian mixture model, for a vector of x of size N and (m: mixture
locations, s: mixture scales, w: mixture weights) each vectors of size K
"""
function gmmlogpdf(m, s, w, x; dims)
  return logsumexp(normlogpdf.(m, s, x) .+ log.(w), dims=dims)
end

"""
log pdf of gaussian mixture model, for a scalar x N and (m: mixture locations,
s: mixture scales, w: mixture weights) each vectors of size K
"""
function gmmlogpdf(m, s, w, x::Real)
  return logsumexp(normlogpdf.(m, s, x) .+ log.(w))
end

"""
Partitions a list `xs` into `(good, bad)` according
to a condition.
"""
function partition(condition::Function, xs::Vector{T}) where T
  good = T[]
  bad = T[]

  for x in xs
    if condition(x)
      append!(good, [x])
    else
      append!(bad, [x])
    end
  end

  return good, bad
end

function quantiles(X, q; dims, drop=false)
  Q = mapslices(x -> quantile(x, q), X, dims=dims)
  out = drop ? dropdims(Q, dims=dims) : Q
  return out
end

function update_mean!(current_mean, x, iter)
  current_mean .+= (x - current_mean) / iter
end

function update_cov!(current_cov, current_mean, x, iter)
  d = x - current_mean
  current_cov .= current_cov * (iter - 1)/iter + (d*d') * (iter - 1)/iter^2
  current_cov .= Matrix(LinearAlgebra.Symmetric(current_cov))
end

"""
x: real vector of dim K - 1
return: simplex of dim K
"""
function simplex_invtransform(x::AbstractVector{<:Real})
  K = length(x) + 1
  z = logistic.(x - log.(K .- collect(1:K-1)))
  p = zeros(K)
  for k in 1:K-1
    p[k] = (1 - sum(p[1:k-1])) * z[k]
  end
  p[K] = 1 - sum(p[1:K-1])
  return p  # simplex
end

"""
Given simplex p (length K), return real vector of length K-1.
"""
function simplex_transform(p::AbstractVector{<:Real})
  K = length(p)
  ks = collect(1:K-1)
  z = [p[k] / (1 - sum(p[1:k-1])) for k in ks]
  return logit.(z) + log.(K .- ks)
end

"""
x: real vector of dim K - 1
return: log abs value of determinant of x
"""
function simplex_logabsdet(x::AbstractVector{<:Real})
  K = length(x) + 1
  ks = collect(1:K-1)
  z = logistic.(x - log.(K .- ks))
  p = simplex_invtransform(x)
  return sum(log1p.(-z) + log.(p[ks]))
end

# DIC.
deviance(loglikelihood::Real) = -2 * loglikelihood
function dic(loglikelihood::AbstractVector{<:Real})
  D = deviance.(loglikelihood)
  return mean(D) + 0.5 * var(D)
end


"""
    struct ReinforceLearner{Tp<:AbstractPolicy}  <: AbstractModelFreeLearner
        π::Tp
        α::Float64
        γ::Float64
    end

See more details at Section (13.3) on Page 326 of the book *Sutton, Richard S., and Andrew G. Barto. Reinforcement learning: An introduction. MIT press, 2018.*
"""
struct ReinforceLearner{Tp<:AbstractPolicy}  <: AbstractModelFreeLearner
    π::Tp
    α::Float64
    γ::Float64
end

(learner::ReinforceLearner)(s) = learner.π(s)
(learner::ReinforceLearner)(s, ::Val{:dist}) = learner.π(s, Val(:dist))

function update!(learner::ReinforceLearner{<:LinearPolicy}, buffer::EpisodeSARDBuffer)
    π, α, γ = learner.π, learner.α, learner.γ
    if isfull(buffer)
        # need to allocate first, because `Reduction` do not support `Iterators.Reverse`
        reversed_G = collect(reverse_discounted_rewards(buffer.reward, γ))
        γᵗ = 1.
        for (G, Sₜ, Aₜ) in zip(Iterators.reverse(reversed_G), @view(buffer.state[1:end-1]), @view(buffer.action[1:end-1]))
            @views error = α * γᵗ * G * (π.features[Sₜ, Aₜ, :] .- sum(b -> π(Sₜ, b) * π.features[Sₜ, b, :],axes(π.features, 2)))
            update!(π, error)
            γᵗ *= γ
        end
    end
end

"""
    mutable struct ReinforceBaselineLearner{Tapp<:AbstractApproximator, Tp<:AbstractPolicy}  <: AbstractModelFreeLearner
        approximator::Tapp
        π::Tp
        αʷ::Float64
        αᶿ::Float64
        γ::Float64
    end

See more details at Section (13.4) on Page 330 of the book *Sutton, Richard S., and Andrew G. Barto. Reinforcement learning: An introduction. MIT press, 2018.*
"""
mutable struct ReinforceBaselineLearner{Tapp<:AbstractApproximator, Tp<:AbstractPolicy}  <: AbstractModelFreeLearner
    approximator::Tapp
    π::Tp
    αʷ::Float64
    αᶿ::Float64
    γ::Float64
end

(learner::ReinforceBaselineLearner)(s) = learner.π(s)
(learner::ReinforceBaselineLearner)(s, ::Val{:dist}) = learner.π(s, Val(:dist))

function update!(learner::ReinforceBaselineLearner{<:AbstractVApproximator, <:LinearPolicy}, buffer::EpisodeSARDBuffer)
    V, π, αᶿ, αʷ, γ = learner.approximator, learner.π, learner.αᶿ, learner.αʷ, learner.γ
    if isfull(buffer)
        # need to allocate first, because `Reduction` do not support `Iterators.Reverse`
        reversed_G = collect(reverse_discounted_rewards(buffer.reward, γ))
        γᵗ = 1.
        for (G, Sₜ, Aₜ) in zip(Iterators.reverse(reversed_G), @view(buffer.state[1:end-1]), @view(buffer.action[1:end-1]))
            δ = G - V(Sₜ)
            update!(V, Sₜ, αʷ * δ)
            @views error = αᶿ * γᵗ * δ * (π.features[Sₜ, Aₜ, :] .- sum(b -> π(Sₜ, b) * π.features[Sₜ, b, :],axes(π.features, 2)))
            update!(π, error)
            γᵗ *= γ
        end
    end
end


using QuasiArrays, LinearAlgebra, LazyArrays, Test

@testset "ldiv" begin
    A = QuasiArray(randn(5,5), (0:0.5:2, 1:0.5:3))
    b = QuasiArray(randn(5), (0:0.5:2,))
    L = Ldiv(A, b)
    @test axes(L) == (axes(A,2),)
    @test similar(L) isa QuasiVector
    @test A\b == copy(L) == QuasiArray(parent(A)\parent(b), (1:0.5:3,))

    B = QuasiArray(randn(5,3), (0:0.5:2,Base.OneTo(3)))
    L = Ldiv(A, B)
    @test axes(L) == (axes(A,2),axes(B,2))
    @test similar(L) isa QuasiMatrix
    @test A\B == copy(L) == QuasiArray(parent(A)\parent(B), (1:0.5:3,Base.OneTo(3)))

    A = QuasiArray(randn(5,5), (Base.OneTo(5), 0:0.5:2))
    v = randn(5)
    @test A\v == QuasiArray(parent(A)\v, (0:0.5:2,))
    @test A' \ b == parent(A)' \ parent(b)
end


using SpecialMatrices

k = 4000
B = Block(k)
for i = 1:k
  B[i] = randn(2,2)
end
#BF = sparse(full(B))

x = randn(k*2,1)

B*x;
Profile.clear()
@time B*x;

#println("Sparse")
#@time BF*x;


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 79b40eaa-f6b6-11ea-05a0-d3a166f4fd39
using OrdinaryDiffEq, LightGraphs, GraphPlot, Random, LinearAlgebra, Plots, PlutoUI

# ╔═╡ 2109877c-f6b5-11ea-1941-a7f99c8bab86
md"# Graph Matching using Gradient Flow in ``\cal{O}_n``"

# ╔═╡ ed92164a-8b01-11eb-031d-7997c875255a
md"""
This demonstrates the findings of [Zavlanos and Pappas, 2008](https://doi.org/10.1016/j.automatica.2008.04.009) on using *Analog Computing* in the form of gradient flows to solve graph matching problems.
"""

# ╔═╡ f0fb46aa-f6d8-11ea-2467-1b2fa447a503
md"## Original and permuted graph"

# ╔═╡ 999b3430-8b02-11eb-1076-354db3137ad1
md"""
We start by constructing a simple graph. Just chose one of the built-in examples.
"""

# ╔═╡ 1c300130-fa76-11ea-3a12-47d1dffd179b
md"""**Select example graph:** $(@bind example_graph Select(
	[ "chain5" => "chain of 5"
	, "chain20" => "chain of 20"
	, "tutte" => "tutte"
	, "housex" => "house"
	]))
"""

# ╔═╡ 12ec0c50-8b04-11eb-0c2d-714d2edc1f4b
md"""
And here is a visualisation of that graph.
(The layouting is non-deterministic, so try rerunning the cell if it looks strange.)
"""

# ╔═╡ 40af3fc4-8b04-11eb-2b1c-9b6658331a25
md"""
We consider `G1` to be the "original" graph.
Let's construct another graph, `G2`, by permuting all vertex labels in `G1`:
"""

# ╔═╡ ca129dcc-8b04-11eb-2f89-7f6855899278
md"""
Our **goal** will be to find a permuation of the vertices in `G2` such that we obtain `G1` again.
"""

# ╔═╡ ba799a4e-fa79-11ea-07fb-4fa60e4829d5
md"### Adjacency matrices"

# ╔═╡ 9eb927fe-8b04-11eb-0699-47c6e029292c
md"""
We will use some linear algebra to solve the graph matching.
To do this, we need canonical matrix representation of a graph: its *adjacency matrix*.

Recall that for an undirected graph ``G = (V, E)``, its adjacency matrix ``A \in \{0, 1\}^{|V| \times |V|}`` has entries
```math
a_{ij} = \begin{cases}
	1, & \text{if } \{i, j\} \in E \\
	0, & \text{else.}
\end{cases}
```
"""

# ╔═╡ be035310-8b08-11eb-26af-2d8fe12b1a29
md"""
## Our Strategy
We briefly restate the strategy that Zavlanos and Pappas suggest:

We want to permute the vertices in `G2`, which is equivalent to permuting the rows and columns in `A2`.
More specifically, we want to find a permuatation matrix ``P`` such that ``A_1`` and ``P^\top A_2 P`` (or equivalently ``P A_1`` and ``A_2 P``) are as similar as possible.
To quantify the approximation error, we pick the squared *Frobenius*-norm as our notion of similarity, which just means that we sum the elementwise squared differences of both matrices:
"""

# ╔═╡ 3a7dd4d2-8b0f-11eb-0658-f3d3779b33f3
md"### Minimise approximation error"

# ╔═╡ c748b142-8b0b-11eb-0199-c5924c5d19cf
md"""
The authors then explain that the permutation matrices are exactly the orthogonal matrices with non-negative entries.
We exploit that by first relaxing our problem to ``P`` being any orthogonal matrix and performing a **gradient flow**.

This is the ODE problem and its solution that describes the gradient flow:
"""

# ╔═╡ 262ef806-f6c4-11ea-1b96-8776b89f8071
function min_approx_err!(dP, P, params, t)
	A1, A2 = params
	dP .= P * (P' * A2 * P * A1 - A1 * P' * A2 * P)
end

# ╔═╡ 9d320016-8b0d-11eb-0d83-45212b00c532
md"Define for how long we want to follow the gradient flow:"

# ╔═╡ 6092784c-f741-11ea-1a0b-3b40c5a88a67
t1 = 20.0

# ╔═╡ 1a9da0ce-f6da-11ea-24a1-398e86eea280
md"""
### Penalising negative entries in ``P``

We now need to make the orthogonal matrix into a permutation matrix, which means we have to get rid of the negative entries.

This function assesses how negative the entries in ``P`` are:
"""

# ╔═╡ ca89c966-f6cf-11ea-05d8-717423c34f3c
negativity(P) = 2/3 * tr(P' * (P .- P .* P))

# ╔═╡ f161ae44-8b0f-11eb-07fc-5364c9e5aa55
md"""
And here we define the corresponding gradient flow:
"""

# ╔═╡ 222dbe04-f6d0-11ea-2997-b5d642fddf4c
function penalise_negative!(dP, P, params, t)
	PP = P .* P
	dP .= P * (P' * PP .- PP' * P)
end

# ╔═╡ 5d6b088e-8b0f-11eb-26f6-dfa9cacb669e
md"""
It turns out that it makes sense to actually combine the gradient flows for the actual objective and for penalising negative entries with some weighting factor `k`.
"""

# ╔═╡ cb658266-fa79-11ea-0db1-7567dea3d069
k = 0.5

# ╔═╡ 490ce0c0-f72f-11ea-1466-3b1aeb47fc11
function combined_gradient!(dP, P, params, t)
	A1, A2, k = params
	dP1 = similar(dP)
	dP2 = similar(dP)
	min_approx_err!(dP1, P, (A1, A2), t)
	penalise_negative!(dP2, P, nothing, t)
	dP .= (1-k) * dP1 .+ k * dP2
end

# ╔═╡ 6767b2d2-8b10-11eb-116c-1972bef114fe
md"""
And this is the overall ODE problem describing this second gradient flow, which starts at the last ``P`` of the first system and evolves until `t2`.
"""

# ╔═╡ cabbd10e-fa79-11ea-30f2-bd1c70613a3c
t2 = 60.0

# ╔═╡ e63643ac-f739-11ea-32ac-d124ec648ff8
md"""
We can summarise the strategy:

**Minimise approximation error until time $t1, then additionally make entries in ``P`` non-negative until time $t2 with $(round(Int, 100k)) % importance.**
"""

# ╔═╡ ba3571ac-8b10-11eb-3654-f3861db16d26
md"""
## Results

This combines both parts of the gradient flow into one time-dependent function:
"""

# ╔═╡ 05f036a2-fa7c-11ea-1902-7592154ca34d
md"### Plotting"

# ╔═╡ a730b14e-f74e-11ea-3ec9-ed8b3a479bdc
trange = 0 : 1.0 : t2;

# ╔═╡ 02cf1af8-8b11-11eb-00a1-2b2e298b4a85
md"""
This is the final solution for ``P`` that the algorithm came up with:
"""

# ╔═╡ 17f5d944-8b11-11eb-0f62-c5e233214748
md"""
Sanity check, that the final solution is still orthogonal:
"""

# ╔═╡ 369c1c8a-f6da-11ea-2069-3fde5a57b953
md"""
## Reconstructed graph

Finally, we can perfom the permutation and find the reconstructed adjacency matrix:
"""

# ╔═╡ 59b29f46-8b11-11eb-2b3c-277d36a0debb
md"""
And this is the corresponding graph.
Does it look at least somewhat like `G1`?
I hope so!
Try playing with the `k` parameter to see how that affects the solution quality.
"""

# ╔═╡ 64e538b2-f6bf-11ea-375e-a5f3e77d6cd0
function permute_vertices(g::AbstractGraph)
	perm_g = SimpleGraph(nv(g))
	perm_v = shuffle(vertices(g))

	for e in edges(g)
		perm_s = perm_v[src(e)]
		perm_d = perm_v[dst(e)]
		add_edge!(perm_g, perm_s, perm_d)
	end

	perm_g
end

# ╔═╡ 12c0e9ee-fa77-11ea-3e61-ed34d1ea848e
function construct_example(id)
	if id == "tutte"
		smallgraph(:tutte)
	elseif id == "housex"
		smallgraph(:housex)
	elseif id == "chain5"
		path_graph(5)
	elseif id == "chain20"
		path_graph(20)
	else
		path_graph(1)
	end
end

# ╔═╡ c548c0b2-f6bc-11ea-1ba0-f3de59ef9987
G1 = construct_example(example_graph)

# ╔═╡ a9ac43ac-f6c2-11ea-2114-338b468c054f
gplot(G1, nodelabel=collect(vertices(G1)))

# ╔═╡ 14eeb4fe-f6c0-11ea-1211-972d5d4f6a4b
G2 = permute_vertices(G1)

# ╔═╡ 78bb17c8-f6c2-11ea-04df-69c5d723ec3c
gplot(G2, nodelabel=collect(vertices(G2)))

# ╔═╡ d8cbb096-f6c2-11ea-1f32-ed95b80b4d8f
A2 = adjacency_matrix(G2);

# ╔═╡ cc0052ea-f6c2-11ea-2710-e175f7ec3f8a
A1 = adjacency_matrix(G1);

# ╔═╡ 637607f8-f6cc-11ea-2a32-a15f4d93c7e7
approx_err(P) = .5 * sum((P*A1 - A2*P) .^ 2)

# ╔═╡ afba6f40-f6c6-11ea-20a3-e16e48da9005
function rand_SO(s::Tuple{Int,Int})
	Q, _ = rand(s...) |> qr
	Q = Matrix(Q)
	if det(Q) < 0
		Q[1,:] .*= -1
	end
	Q
end

# ╔═╡ f4fd59b4-f6c6-11ea-165b-eb798b8fbff5
min_approx_err_solution = let
	P0 = rand_SO(size(A1))
	t0 = 0.0
	min_approx_err_problem = ODEProblem(min_approx_err!, P0, (t0, t1), (A1, A2))

	solve(min_approx_err_problem, Tsit5())
end;

# ╔═╡ 19739754-fa7a-11ea-2b0e-037d4266a5ae
P_min_approx_err = min_approx_err_solution(t1);

# ╔═╡ 7519397a-f6d0-11ea-19ad-f5aaa7f8f228
non_negative_solution = let
	non_negative_problem = ODEProblem(combined_gradient!, P_min_approx_err, (t1, t2), (A1, A2, k))
	solve(non_negative_problem, Tsit5())
end;

# ╔═╡ f6580a7e-f6d1-11ea-2200-f9c80bc69b31
P(t) = if t < t1
		min_approx_err_solution(t)
	else
		non_negative_solution(t)
	end

# ╔═╡ 3358b806-f6d2-11ea-203e-d53ae160ffd6
anim = map(trange) do t
	l = heatmap(P(t) * A1 - A2 * P(t) .|> abs,
		clim=(0,2),
		title="t = $(round(Int, t))",
		xlabel="elem. wise deviation")
	r = heatmap(P(t),
		clim=(-1,1),
		xlabel="permutation matrix")
	plot(l, r, layout=(1,2), size=(600,300), aspect_ratio=:equal)
end;

# ╔═╡ ce19a088-fa7b-11ea-30b3-7b80bfbbdf1f
@bind t_anim Slider(eachindex(anim))

# ╔═╡ ec1ba20c-fa7b-11ea-1238-41e99f6ded6b
anim[t_anim]

# ╔═╡ 507ccbd4-f6d2-11ea-3a8d-1311265cdc54
begin
	plot(trange,
		[[approx_err(P(t)) for t in trange],
		[negativity(P(t)) for t in trange]],
		label=["approximation error"  "negativity of entries"],
		xlabel="time")
	vline!([t1]; linestyle=:dash, label="switching systems")
	vline!([trange[t_anim]]; linestyle=:solid, label="currently displayed")
end

# ╔═╡ 422d4c82-f6d4-11ea-3e87-d7f548a73eb7
final_P = P(t2);

# ╔═╡ e527660c-fc60-11ea-309e-c7372eea3e23
round.(final_P, digits=3)

# ╔═╡ 41f9aea8-f6d5-11ea-0330-af1c77272674
round.(final_P' * final_P, digits=3)

# ╔═╡ a184f726-f6d8-11ea-2c93-8fb47ac719a3
A3 = round.(Int, final_P' * A2 * final_P)

# ╔═╡ 2735a812-f6d8-11ea-16c4-dbbc3b090adb
G3 = SimpleGraph(A3)

# ╔═╡ 7649f264-f6d8-11ea-1712-5dac97376cdc
gplot(G3, nodelabel=collect(vertices(G3)))

# ╔═╡ 97c742fc-f6d5-11ea-1b58-6be1318afa25
begin
	plot(trange,
		[minimum(P(t)) for t in trange],
		ylim=(-1,0.1),
		label="min. entry in P",
		xlabel="time")
	vline!([t1]; linestyle=:dash, label="switching systems", legend=:bottomright)
end

# ╔═╡ Cell order:
# ╟─2109877c-f6b5-11ea-1941-a7f99c8bab86
# ╟─ed92164a-8b01-11eb-031d-7997c875255a
# ╟─f0fb46aa-f6d8-11ea-2467-1b2fa447a503
# ╟─999b3430-8b02-11eb-1076-354db3137ad1
# ╟─1c300130-fa76-11ea-3a12-47d1dffd179b
# ╠═c548c0b2-f6bc-11ea-1ba0-f3de59ef9987
# ╟─12ec0c50-8b04-11eb-0c2d-714d2edc1f4b
# ╠═a9ac43ac-f6c2-11ea-2114-338b468c054f
# ╟─40af3fc4-8b04-11eb-2b1c-9b6658331a25
# ╠═14eeb4fe-f6c0-11ea-1211-972d5d4f6a4b
# ╠═78bb17c8-f6c2-11ea-04df-69c5d723ec3c
# ╟─ca129dcc-8b04-11eb-2f89-7f6855899278
# ╟─ba799a4e-fa79-11ea-07fb-4fa60e4829d5
# ╟─9eb927fe-8b04-11eb-0699-47c6e029292c
# ╠═cc0052ea-f6c2-11ea-2710-e175f7ec3f8a
# ╠═d8cbb096-f6c2-11ea-1f32-ed95b80b4d8f
# ╟─be035310-8b08-11eb-26af-2d8fe12b1a29
# ╠═637607f8-f6cc-11ea-2a32-a15f4d93c7e7
# ╟─3a7dd4d2-8b0f-11eb-0658-f3d3779b33f3
# ╟─c748b142-8b0b-11eb-0199-c5924c5d19cf
# ╠═262ef806-f6c4-11ea-1b96-8776b89f8071
# ╟─9d320016-8b0d-11eb-0d83-45212b00c532
# ╠═6092784c-f741-11ea-1a0b-3b40c5a88a67
# ╠═f4fd59b4-f6c6-11ea-165b-eb798b8fbff5
# ╠═19739754-fa7a-11ea-2b0e-037d4266a5ae
# ╟─1a9da0ce-f6da-11ea-24a1-398e86eea280
# ╠═ca89c966-f6cf-11ea-05d8-717423c34f3c
# ╟─f161ae44-8b0f-11eb-07fc-5364c9e5aa55
# ╠═222dbe04-f6d0-11ea-2997-b5d642fddf4c
# ╟─5d6b088e-8b0f-11eb-26f6-dfa9cacb669e
# ╠═cb658266-fa79-11ea-0db1-7567dea3d069
# ╠═490ce0c0-f72f-11ea-1466-3b1aeb47fc11
# ╟─6767b2d2-8b10-11eb-116c-1972bef114fe
# ╠═cabbd10e-fa79-11ea-30f2-bd1c70613a3c
# ╠═7519397a-f6d0-11ea-19ad-f5aaa7f8f228
# ╟─e63643ac-f739-11ea-32ac-d124ec648ff8
# ╟─ba3571ac-8b10-11eb-3654-f3861db16d26
# ╠═f6580a7e-f6d1-11ea-2200-f9c80bc69b31
# ╟─05f036a2-fa7c-11ea-1902-7592154ca34d
# ╠═a730b14e-f74e-11ea-3ec9-ed8b3a479bdc
# ╟─3358b806-f6d2-11ea-203e-d53ae160ffd6
# ╟─ce19a088-fa7b-11ea-30b3-7b80bfbbdf1f
# ╟─ec1ba20c-fa7b-11ea-1238-41e99f6ded6b
# ╟─507ccbd4-f6d2-11ea-3a8d-1311265cdc54
# ╟─02cf1af8-8b11-11eb-00a1-2b2e298b4a85
# ╠═422d4c82-f6d4-11ea-3e87-d7f548a73eb7
# ╠═e527660c-fc60-11ea-309e-c7372eea3e23
# ╟─17f5d944-8b11-11eb-0f62-c5e233214748
# ╠═41f9aea8-f6d5-11ea-0330-af1c77272674
# ╟─97c742fc-f6d5-11ea-1b58-6be1318afa25
# ╟─369c1c8a-f6da-11ea-2069-3fde5a57b953
# ╠═a184f726-f6d8-11ea-2c93-8fb47ac719a3
# ╟─59b29f46-8b11-11eb-2b3c-277d36a0debb
# ╠═2735a812-f6d8-11ea-16c4-dbbc3b090adb
# ╠═7649f264-f6d8-11ea-1712-5dac97376cdc
# ╟─64e538b2-f6bf-11ea-375e-a5f3e77d6cd0
# ╟─12c0e9ee-fa77-11ea-3e61-ed34d1ea848e
# ╟─afba6f40-f6c6-11ea-20a3-e16e48da9005
# ╠═79b40eaa-f6b6-11ea-05a0-d3a166f4fd39


include("Include.jl")

function main()

    # setup parameters -
    parameters = Dict{String,Any}()
    parameters["K1"] = 20
    parameters["n1"] = 4
    parameters["K2"] = 40
    parameters["n2"] = 8

    # build the Poisson -
    λ = 10
    d = Poisson(λ)

    # S -
    S = [-1 0 ; 0 1]

    # set the system dimensions -
    number_of_chemical_species = 2
    number_of_time_steps = 100
    number_of_sample_paths = 100

    # chemical state -
    A = 100
    B = 0
    chemical_state = Array{Int64,3}(undef, number_of_time_steps, number_of_chemical_species, number_of_sample_paths)

    # main simulation loop -
    for s ∈ 1:number_of_sample_paths

        # machine state -
        machine_state = Vector{Int64}(undef, number_of_time_steps)
        machine_state[1] = 1

        # setup the chemical state -
        chemical_state[1,1,s] = A
        chemical_state[1,2,s] = B

        for t ∈ 2:number_of_time_steps

            # run the machine -
            machine_state[t] = machine(t, machine_state[t-1], chemical_state[:,:,s], parameters)

            # what state are we in?
            current_machine_state = machine_state[t]
            if (current_machine_state == 2)

                # the enzyme has activity if machine in state 2 -
                Δ = ones(2)*rand(d)
                rV = S*Δ

                # do we have enough A?
                if ((chemical_state[t-1,1,s] + rV[1]) > 0)

                    # update the chemical state -
                    chemical_state[t,1,s] = chemical_state[t-1,1,s] + rV[1]
                    chemical_state[t,2,s] = chemical_state[t-1,2,s] + rV[2]

                else

                    # state stays the same -
                    chemical_state[t,1,s] = chemical_state[t-1,1,s]
                    chemical_state[t,2,s] = chemical_state[t-1,2,s]
                end
            else

                # state stays the same -
                chemical_state[t,1,s] = chemical_state[t-1,1,s]
                chemical_state[t,2,s] = chemical_state[t-1,2,s]
            end
        end
    end

    return (chemical_state, number_of_sample_paths)
end

# run the sim -
(chemical_state, number_of_sample_paths) = main();

# make the plots -
for s ∈ 1:number_of_sample_paths

    if (s == 1)
        plot(chemical_state[:, 1,s], legend=false, c=:red)
        plot!(chemical_state[:,2,s], c=:black)
    else
        plot!(chemical_state[:,1,s], legend=false, c=:red)
        plot!(chemical_state[:,2,s], c=:black)
    end
end
xlabel!("Time step index", fontsize=18)
ylabel!("Molecule number", fontsize=18)
current()


"""
    dgtripcolor(x, y, z, t; zmin=nothing, zmax=nothing)

Draw a pseudocolor plot of discontinuous unstructured triangular data. `x`, and `y`, are
one-dimensional input arrays of the same length (the number of points). `t` is an integer array of
size `(3,nt)`, where `nt` is the number of triangles. `z` is an array of the same size as `t`
representing function values on each triangle (discontinuous across triangle edges). The coordinates
of the `i`th vertex of the `j`th triangle are given by `(x[t[i,j]], y[t[i,j]])`. The function value
at the `i`th vertex of the `j`th triangle is given by `z[i,j]`.

The colormap is specified by `cmap`. The colormap bounds can be set by specifying values for `zmin`
and `zmax`. If these parameters are set to `nothing`, then the minimum and maximum values of the `z`
array will be used.

For plotting continuous fields, see [`tripcolor`](@ref).
"""
function dgtripcolor(x, y, z, t, cmap::AbstractVector{T}; px, py, zmin=nothing, zmax=nothing, yflip=false, bg=nothing) where T
    nv = length(x)
    nt = size(t,2)
    @assert (size(x) == (nv,) && size(y) == (nv,) && size(z) == (3,nt) && size(t) == (3,nt)) "Bad input sizes"

    c = isnothing(bg) ? Matrix{T}(undef,px,py) : fill(bg,px,py)
    nc = length(cmap)

    xmin,xmax = extrema(x)
    ymin,ymax = extrema(y)
    w = xmax-xmin
    h = ymax-ymin
    isnothing(zmin) && (zmin = minimum(z))
    isnothing(zmax) && (zmax = maximum(z))

    for it=1:nt
        xt = @views x[t[:,it]]
        yt = @views y[t[:,it]]
        zt = @views z[:,it]

        detT = (yt[2]-yt[3])*(xt[1]-xt[3]) + (xt[3]-xt[2])*(yt[1]-yt[3])

        xmint,xmaxt = extrema(xt)
        ymint,ymaxt = extrema(yt)

        x0 = floor(Int,(xmint-xmin)/w*px)
        x1 = ceil(Int,(xmaxt-xmin)/w*px)
        y0 = floor(Int,(ymint-ymin)/h*py)
        y1 = ceil(Int,(ymaxt-ymin)/h*py)

        # Loop over all pixels in triangle's bounding box
        for i=x0:x1-1, j=y0:y1-1
            # Cartesian coordinates of pixel (i,j) (zero-indexed)
            cx = xmin + i/px*w
            cy = ymin + j/py*h
            # Convert from Cartesian to barycentric coordinates
            λ1 = ((yt[2] -yt[3])*(cx-xt[3]) + (xt[3]-xt[2])*(cy-yt[3]))/detT
            λ2 = ((yt[3] -yt[1])*(cx-xt[3]) + (xt[1]-xt[3])*(cy-yt[3]))/detT
            λ3 = 1 - λ1 - λ2
            tol = 1e-12
            # If pixel is outside of triangle, move on to next pixel
            (λ1>-tol && λ2<1+tol && λ2>-tol && λ2<1+tol && λ3>-tol && λ3<1+tol) || continue
            zval = zt[1]*λ1 + zt[2]*λ2 + zt[3]*λ3
            zval = clamp(zval,zmin,zmax)
            c[i+1,yflip ? py-j : j+1] = cmap[begin+getcolorind(zval,zmin,zmax,nc)]
        end
    end
    c
end

"""
    tripcolor(x, y, z, t; zmin=nothing, zmax=nothing)

Draw a pseudocolor plot of unstructured triangular data. `x`, `y`, and `z` are
one-dimensional input arrays of the same length (the number of points). `t` is an integer
array of size `(3,nt)`, where `nt` is the number of triangles. The coordinates of the `i`th
vertex of the `j`th triangle are given by `(x[t[i,j]], y[t[i,j]])`. The function value at the `j`th
vertex of the `i`th triangle is given by `z[i,j]`.

The colormap is specified by `cmap`. The colormap bounds can be set by specifying values for `zmin`
and `zmax`. If these parameters are set to `nothing`, then the minimum and maximum values of the `z`
array will be used.

For plotting discontinuous fields, see [`dgtripcolor`](@ref).
"""
function tripcolor(x, y, z, t, cmap::AbstractVector{T}; px, py, zmin=nothing, zmax=nothing, yflip=false, bg=nothing) where T
    nv = length(x)
    nt = size(t,2)
    @assert (size(x) == (nv,) && size(y) == (nv,) && size(z) == (nv,) && size(t) == (3,nt)) "Bad input sizes"

    dg_z = similar(t, eltype(z))
    for it=1:nt
        dg_z[:,it] .= @views z[t[:,it]]
    end
    dgtripcolor(x, y, dg_z, t, cmap; px, py, zmin, zmax, bg)
end

function getcolorind(level,lmin,lmax,nc)
    cfrac = (lmin == lmax) ? 0 : (level-lmin)/(lmax-lmin)
    round(Int,cfrac*(nc-1))
end


using LinearAlgebra
function Vectorin(x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4,x5,y5,z5)

    v = zeros(Float64,6,3)
    CD = [x2-x1;y2-y1;z2-z1]
    CH = [x4-x1;y4-y1;z4-z1]
    na = LinearAlgebra.cross(CD,CH)
    BC = [x1-x3;y1-y3;z1-z3]
    j1 = dot(na,BC)
    if j1 > 0
        n1 = na
    else
        n1 = -na
    end
    union1 = n1/sqrt(n1[1]^2+n1[2]^2+n1[3]^2)
    v[1,1] = union1[1]
    v[1,2] = union1[2]
    v[1,3] = union1[3]
    DH = [x4-x2;y4-y2;z4-z2]
    DB = [x3-x2;y3-y2;z3-z2]
    nb = LinearAlgebra.cross(DH,DB)
    CD = [x2-x1;y2-y1;z2-z1]
    j2 = dot(nb,CD)
    if j2 > 0
        n2 = nb
    else
        n2 = -nb
    end
    union2 = n2/sqrt(n2[1]^2+n2[2]^2+n2[3]^2)
    v[2,1] = union2[1]
    v[2,2] = union2[2]
    v[2,3] = union2[3]
    HB = [x3-x4;y3-y4;z3-z4]
    HC = [x1-x4;y1-y4;z1-z4]
    nc = LinearAlgebra.cross(HB,HC)
    DH = [x4-x2;y4-y2;z4-z2]
    j3 = dot(nc,DH)
    if j3 > 0
        n3 = nc
    else
        n3 = -nc
    end
    union3 = n3/sqrt(n3[1]^2+n3[2]^2+n3[3]^2)
    v[3,1] = union3[1]
    v[3,2] = union3[2]
    v[3,3] = union3[3]
    CD = [x2-x1;y2-y1;z2-z1]
    CI = [x5-x1;y5-y1;z5-z1]
    nd = LinearAlgebra.cross(CD,CI)
    BC = [x1-x3;y1-y3;z1-z3]
    j4 = dot(nd,BC)
    if j4 > 0
        n4 = nd
    else
        n4 = -nd
    end
    union4 = n4/sqrt(n4[1]^2+n4[2]^2+n4[3]^2)
    v[4,1] = union4[1]
    v[4,2] = union4[2]
    v[4,3] = union4[3]
    BD = [x2-x3;y2-y3;z2-z3]
    BI = [x5-x3;y5-y3;z5-z3]
    ne = LinearAlgebra.cross(BD,BI)
    CB = [x3-x1;y3-y1;z3-z1]
    j5 = dot(ne,CB)
    if j5 > 0
        n5 = ne
    else
        n5 = -ne
    end
    union5 = n5/sqrt(n5[1]^2+n5[2]^2+n5[3]^2)
    v[5,1] = union5[1]
    v[5,2] = union5[2]
    v[5,3] = union5[3]
    IB = [x3-x5;y3-y5;z3-z5]
    IC = [x1-x5;y1-y5;z1-z5]
    nf = LinearAlgebra.cross(IB,IC)
    DI = [x5-x2;y5-y2;z5-z2]
    j6 = dot(nf,DI)
    if j6 > 0
        n6 = nf
    else
        n6 = -nf
    end
    union6 = n6/sqrt(n6[1]^2+n6[2]^2+n6[3]^2)
    v[6,1] = union6[1]
    v[6,2] = union6[2]
    v[6,3] = union6[3]

    return v
end


using OCToolbox

using Test
using QuantumInformation
using LinearAlgebra

# initial states
ψi = [1.0+0im, 0 + 0]
ψT = [0.0+0im, 1 + 0]

# initial unitary
Ui = Matrix{ComplexF64}(I, 2, 2)
# spin flip unitary
Uf = Matrix{ComplexF64}(I, 2, 2)
Uf[1,1] = Uf[2,2] = cos(π/2)
Uf[1,2] = -sin(π/2)
Uf[2,1] = sin(π/2)

@testset "Test_utils.jl" begin
    @test C1(Ui, Ui, 2) == 0
    @test C1(Ui, Uf, 2) == 1
    @test C2(ψT, ψT) == 0
    @test C2(ψT, ψi) == 1
end


#Wigner 3j-symbol calculator
#17/12/2015

function Wigner3j(aa,bb,cc,alpha,beta,gamma)

# Find triangle coefficient using A. B. Deb (2007) script
triang = triangle_coeff(aa, bb, cc)

#Second term
sqrtterm = sqrt(factorial(aa+alpha)*factorial(aa-alpha)*
    factorial(bb+beta)*factorial(bb-beta)*factorial(cc+gamma)
    *factorial(cc-gamma))

#Finding the range of summation in Racah formula
tmin = zeros(3)
tmin[1] = bb - cc - alpha
tmin[2] = aa + beta - cc
tmin[3] = 0

tmax = zeros(3)
tmax[1] = aa + bb - cc
tmax[2] = aa - alpha
tmax[3] = bb + beta

rangei = maximum(tmin)
rangef = minimum(tmax)

#Sum over the function of t
sumscalar = 0
for tt = rangei:rangef

    sumscalar = sumscalar +
        ((-1)^tt)/tfunction3j(tt,aa,bb,cc,alpha,beta)

end

Wigner = ((-1)^float(aa-bb-gamma))*sqrt(triang)*sqrtterm*sumscalar

return Wigner
end


struct Svmda
    fm
    lev::AbstractVector
    ni::AbstractVector
end

"""
    svmda(X, y; kern = "rbf",
        gamma = 1. / size(X, 2), degree = 3, coef0 = 0., cost = 1.)
Support vector machine for discrimination (C-SVC).
* `X` : X-data.
* `y : y-data (univariate).
* 'kern' : Type of kernel used to compute the Gram matrices.
    Possible values are "krbf", "kpol", "klin" or "ktanh".
* 'gamma' : See below.
* 'degree' : See below.
* 'coef0' : See below.
* 'cost' : Cost of constraints violation C parameter.

Kernel types :
* "krbf" -- radial basis function: exp(-gamma * |x - y|^2)
* "kpol" -- polynomial: (gamma * x' * y + coef0)^degree
* "klin* -- linear: x' * y
* "ktan" -- sigmoid: tanh(gamma * x' * y + coef0)

The function uses LIBSVM.jl (https://github.com/JuliaML/LIBSVM.jl)
that is an interface to library LIBSVM (Chang & Li 2001).

## References

Julia package LIBSVM.jl: https://github.com/JuliaML/LIBSVM.jl

Chang, C.-C. & Lin, C.-J. (2001). LIBSVM: a library for support vector machines.
Software available at http://www.csie.ntu.edu.tw/~cjlin/libsvm.
Detailed documentation (algorithms, formulae, ...) can be found in
http://www.csie.ntu.edu.tw/~cjlin/papers/libsvm.ps.gz

Chih-Chung Chang and Chih-Jen Lin, LIBSVM: a library for support vector machines.
ACM Transactions on Intelligent Systems and Technology, 2:27:1--27:27, 2011.
Software available at http://www.csie.ntu.edu.tw/~cjlin/libsvm

Schölkopf, B., Smola, A.J., 2002. Learning with kernels:
support vector machines, regularization, optimization, and beyond.
Adaptive computation and machine learning. MIT Press, Cambridge, Mass.

"""
function svmda(X, y; kern = "krbf",
    gamma = 1. / size(X, 2), degree = 3, coef0 = 0., cost = 1.)
    gamma = Float64(gamma) ; degree = Int64(degree) ; coef0  = Float64(coef0) ;
    cost  = Float64(cost)
    X = ensure_mat(X)
    y = vec(y)
    ztab = tab(y)
    if kern == "krbf"
        fkern = LIBSVM.Kernel.RadialBasis
    elseif kern == "kpol"
        fkern = LIBSVM.Kernel.Polynomial
    elseif kern == "klin"
        fkern = LIBSVM.Kernel.Linear
    elseif kern == "ktanh"
        fkern = LIBSVM.Kernel.Sigmoid
    end
    nt = 0
    fm = svmtrain(X', y;
        svmtype = SVC,
        kernel = fkern,
        gamma =  gamma,
        coef0 = coef0,
        degree = degree,
        cost = cost,
        nt = nt)
    Svmda(fm, ztab.keys, ztab.vals)
end

"""
    predict(object::Svmda, X)
Compute y-predictions from a fitted model.
* `object` : The fitted model.
* `X` : X-data for which predictions are computed.
"""
function predict(object::Svmda, X)
    pred = svmpredict(object.fm, X')[1]
    n = length(pred)
    pred = reshape(pred, n, 1)
    (pred = pred,)
end


export ## Types,
       Dipole,
       FlipFlop,
       FluxTransmon,
       SemiClassicalDipole,
       ## Methods
       update_params

#Iteractions linearly add new Hamiltonians to the system

type Dipole <: Interaction
    system1::QSystem
    system2::QSystem
    strength::Float64
end

function hamiltonian(f::Dipole)
    return f.strength*kron(X(f.system1), X(f.system2))
end

type FlipFlop <: Interaction
    system1::QSystem
    system2::QSystem
    strength::Float64
end
function hamiltonian(f::FlipFlop)
    return f.strength*(kron(raising(f.system1), lowering(f.system2)) + kron(lowering(f.system1), raising(f.system2)))
end

type SemiClassicalDipole <: Interaction
    system1::Field
    system2::QSystem
    strength::Float64
end
function hamiltonian(scd::SemiClassicalDipole, t::Float64)
    return scd.strength*amplitude(scd.system1, t)*X(scd.system2)
end

# ParametricInteractions are interactions which modify
# parameters of systems rather than linearly add new Hamiltonians

#Flux control
type FluxTransmon <: ParametricInteraction
    flux::Field
    transmon::String #refer to transmon by label in parent CompositeQSystem to avoid broken reference on copy
    strength::Float64
end
FluxTransmon(flux::Field, transmon::TunableTransmon, strength::Float64) = FluxTransmon(flux, label(transmon), strength)

#Set the flux in the associated transmon
function update_params(sys::CompositeQSystem, ft::FluxTransmon, t::Float64)
    myTransmon = sys[ft.transmon]
    myTransmon.flux = myTransmon.fluxBias + ft.strength*amplitude(ft.flux,t)
end