### A Pluto.jl notebook ###
# v0.17.3

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 iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ a858ec12-ae05-433c-8850-84630ae10896
begin
	using PlutoUI, CoordinateTransformations, Rotations, Viznet, Compose, StaticArrays
	# left right layout
	function leftright(a, b; width=600)
		HTML("""

	$(html(a))
	$(html(b))

""")
	end

	# up down layout
	function updown(a, b; width=nothing)
		HTML("""

	$(html(a))

	$(html(b))

""")
	end

	function highlight(str)
		HTML("""$(str)""")
	end
end;

# ╔═╡ 9c3b4290-138c-406c-a980-966dd93b11d2
using SymEngine # install with `] add SymEngine`

# ╔═╡ 7209ba2c-94d9-4102-9772-68e11c9f9fa5
using OMEinsum

# ╔═╡ a5ca245f-d67b-48a5-a70e-afdc3ee840ec
using Zygote

# ╔═╡ 62e98e87-800d-4780-9fd7-70b82f2a0c3e
using CUDA

# ╔═╡ 70cd5e7d-9200-444f-b974-0238c74c6013
using OMEinsumContractionOrders

# ╔═╡ bb3d0c31-787c-44d6-a415-4f85a20eac5d
html"<button onclick='present();'>present</button>"

# ╔═╡ b526ffe6-5c4a-450e-99ba-fc44fc4f6f72
html"""

"""

# ╔═╡ bac7b9bb-8d9a-418e-b8d5-ffb00796dd22
md"## Your animal friends"

# ╔═╡ ab5c94d0-a69e-476c-9548-b522c5c48fc3
rand_animal(size...) = Basic.(Symbol.('🐀' .+ rand(0:60, size...)))

# ╔═╡ e08fdfd1-a521-4434-af61-bfed3e42c61a
md"This is a vector"

# ╔═╡ dddaf055-f769-4b80-8a97-ff1e76ad99a8
v1 = rand_animal(3)

# ╔═╡ b3cba07a-84cd-4796-aa01-01e4719e8ff0
md"This is a matrix"

# ╔═╡ 1aef6d34-41f0-4717-9b13-f81f19e8825f
m1 = rand_animal(3, 3)

# ╔═╡ 399afc64-039f-43da-9994-05f4cc44d633
md"This is a tensor of rank 4"

# ╔═╡ 7a9e9466-03fd-4955-8e5d-5cba4544e7c7
t1 = rand_animal(2, 2, 2, 2)

# ╔═╡ b4088ca7-fadd-4491-a15b-9648004b238c
md"## Matrix-matrix multiplication"

# ╔═╡ 39149c34-76c1-46eb-9c1c-a0e7e2e51d17
md"This is an einsum notation, defined by `@ein_str` string literal."

# ╔═╡ ae4ef95d-0a65-4a80-8abd-1a684c60b30d
code_mm = ein"ij,jk->ik"

# ╔═╡ b3240fff-de5d-475b-9875-9440e61a858a
mm_1 = rand_animal(3, 3)

# ╔═╡ 700874d1-1dbf-4ac8-91d7-523733a176e7
mm_2 = rand_animal(3, 3)

# ╔═╡ a1b9e48d-7bb5-461c-b161-aae154fcb799
md"You can call it"

# ╔═╡ 1e9ca8b5-81bc-4946-a8c4-c08afe903d8b
ein"ij,jk->ik"(mm_1, mm_2)

# ╔═╡ 31e06d5b-2efa-41a8-987f-5e75c87a3eff
md"It is equivalent to"

# ╔═╡ 7a7dd9a1-fed7-428c-899e-90cbe3b6d699
let
	mm_out = zeros(Basic, 3, 3)
	for i=1:3
		for j=1:3
			for k=1:3
				mm_out[i,k] += mm_1[i,j] * mm_2[j, k]
			end
		end
	end
	mm_out
end

# ╔═╡ ed6fa023-43b1-4f5c-be3f-f93f16dfe285
md"It can also be constructed as"

# ╔═╡ 778d16ea-67f1-4903-9767-28682b99527c
EinCode([['i', 'j'], ['j', 'k']], ['i', 'k'])

# ╔═╡ 5cf51764-3138-4fd9-8bcd-c399ba4306f7
md"or"

# ╔═╡ f23efdd0-47dd-49b8-88c5-3debd35cd55a
EinCode([[1, 2], [2, 3]], [1, 3])

# ╔═╡ 005fddc2-1378-426c-b4b7-80a09baf8ec7
let n = Basic(:n)
	# NOTE: `flop` counts the number of iterations!
	flop(code_mm, Dict('i'=>n, 'j'=>n, 'k'=>n))
end

# ╔═╡ dfcb8259-7f6f-40b1-ae75-15eaaabe7279
md"or, for convenience"

# ╔═╡ db6ba19e-2425-4ddc-a67a-d68ebadfd0b2
flop(code_mm, uniformsize(code_mm, Basic(:n)))

# ╔═╡ 3dbb855a-4045-4433-93c8-43997161807b
md"##  This is summation"

# ╔═╡ fbb23079-6b77-412e-8648-e0bf10fabab5
sum_1 = rand_animal(3)

# ╔═╡ 7ae59add-d3d4-42bf-83fd-989c2848225a
ein"i->"(sum_1)

# ╔═╡ 14ad6f1c-cabf-47c8-ad39-81bcd92bf840
sum_2 = rand_animal(2, 2, 3)

# ╔═╡ d14e10af-5179-476d-ab8a-63d1837e866d
ein"ijk->k"(sum_2)

# ╔═╡ 61545cc3-f486-4dea-83f8-448eb23646fb
flop(ein"ijk->k", uniformsize(ein"ijk->k", Basic(:n)))

# ╔═╡ 2790a9b1-f706-4167-8b7c-8d8432b2511a
md"## Repeating a vector"

# ╔═╡ f8cc753b-8ba0-4212-80ef-1ced60866389
rp_1 = rand_animal(3)

# ╔═╡ 051de93e-f189-4a75-bd82-ebd646eb7eda
ein"i->ij"(rp_1; size_info=Dict('j'=>4))

# ╔═╡ 68e643ba-f852-4b9e-9799-13f193df3bfe
let
	rp_out = zeros(Basic, 3, 4)
	for i=1:3
		for j=1:4
			rp_out[i, j] += rp_1[i]
		end
	end
	rp_out
end

# ╔═╡ d00afc01-ecb0-454e-8cac-342d5b75101e
md"## Star contraction"

# ╔═╡ b7b777bc-3f38-4a4f-a438-454187602962
star_1 = rand_animal(2, 2)

# ╔═╡ e4ffef2b-9b19-40ed-8547-63673a3ea649
star_2 = rand_animal(2, 2)

# ╔═╡ 6c2955de-be35-4fd9-a5c8-ef430ed0013a
star_3 = rand_animal(2, 2)

# ╔═╡ 579222f1-da51-4afa-9087-2c0799bbe6ad
ein"ai, aj, ak->ijk"(star_1, star_2, star_3)

# ╔═╡ b22796e6-d163-4bbb-b4d4-bbaf57e63f8f
let
	star_out = zeros(Basic, 2, 2, 2)
	for i=1:2
		for j=1:2
			for k=1:2
				for a=1:2
					star_out[i, j, k] += star_1[a,i] * star_2[a,j] * star_3[a,k]
				end
			end
		end
	end
	star_out
end

# ╔═╡ 955d364d-5225-4833-95d5-18265e319523
md"## Automatic differentiation"

# ╔═╡ d7c25437-2bc9-473d-846e-d9f8a4ca7d9c
a, b = randn(2, 2), randn(2);

# ╔═╡ 1f57a827-c57b-4d1e-ac73-e41d6206052a
Zygote.gradient(x->ein"i->"(ein"ij,j->i"(x, b))[], a)[1]

# ╔═╡ 5dbedaab-48d8-4abe-8046-eee0e4cd4093
let
	A, B, C = randn(2,2,2,2), randn(2,2,2,2,2), randn(2,2,2,2,2)
	size_dict = uniformsize(ein"ijkl,lmkcd,asedf->dfas", 2)
	O = ein"ijkl,lmkcd,asedf->dfas"(A, B, C; size_info=size_dict)

	Ō = randn(2,2,2,2)
	# exchange input/output labels and tensors
	Ā = ein"dfas,lmkcd,asedf->ijkl"(Ō, B, C; size_info=size_dict)
	B̄ = ein"ijkl,dfas,asedf->lmkcd"(A, Ō, C; size_info=size_dict)
	C̄ = ein"ijkl,lmkcd,dfas->asedf"(A, B, Ō; size_info=size_dict)
	Ā, B̄, C̄
end;

# ╔═╡ 015b2b71-b6aa-4b6e-b6bf-75776c0f3c90
md"## Speed up your code with GPU"

# ╔═╡ 7f8a6c77-75a6-4063-9fc6-409397bb6306
md"""
* step 1: import CUDA library.
* step 2: upload your array to GPU with `CuArray` function.
"""

# ╔═╡ dcc74a07-e7e4-48a5-9942-4f7a9714db9b
let
	cuarr1, cuarr2 = CuArray(randn(2, 2)), CuArray(randn(2))
	result = ein"ij,j->i"(cuarr1, cuarr2)
	typeof(result)
end

# ╔═╡ 6f9df5fe-70de-4bab-8fd6-e0173f06d6e4
md"Note: if you do not have a Nvidia GPU, the above code will give you an error, please do not panic."

# ╔═╡ f3b5656d-5608-4325-a884-65bceddb4963
md"""## Summary
* Einsum can be defined as: iterating over unique indices, accumulate product of corresponding input tensor elements to the output tensor.
* Einsum's representation power
    * ein"ij,jk->ik" is matrix multiplication
    * ein"i->" and ein"ijk->k" is summation
    * ein"i->ij" is repeating axis
    * ein"ai,aj,ak->ijk" is a star contraction
* The time complexity of an einsum notation is ``O(n^{(\#~ of ~ unique ~ labels)})``
* Features in OMEinsum
    * Automatic differentiation
    * GPU
    * Generic programming
"""

# ╔═╡ a9d59880-0cc1-441b-9329-f58d39c23f16
md"# Contraction order matters"

# ╔═╡ 3c50281e-ae20-40ea-85ea-dcccfbd2756e
md"## Multiplying a sequence of matrices"

# ╔═╡ 7f6c0836-7c9e-4726-9512-dc2e5ad86e1f
code_seq_1 = ein"ij,jk,kl,lm->im"

# ╔═╡ 0efbdbad-a1e1-4aab-a9e7-143a05c934eb
seq_1 = rand_animal(2,2)

# ╔═╡ e2275f15-a57b-4a74-9826-2c723aad096c
seq_2 = rand_animal(2,2)

# ╔═╡ ebfe52bb-76c1-4b39-8f8c-0dec56cc80f3
seq_3 = rand_animal(2,2)

# ╔═╡ 744576a6-6d31-48a6-a71c-cb6246b76bef
seq_4 = rand_animal(2,2)

# ╔═╡ e92b78f1-8ac6-421f-930b-e092402845cd
ein"ij,jk,kl,lm->im"(seq_1, seq_2, seq_3, seq_4)

# ╔═╡ 623551e9-48bb-4976-920e-12396a596975
flop(code_seq_1, uniformsize(code_seq_1, Basic(:n)))

# ╔═╡ d4a3557a-9de2-4500-b09e-5b3c249906fc
code_seq_2 = ein"(ij,jk),(kl,lm)->im"

# ╔═╡ 5ff2bdd9-a708-4ee3-92fa-b8fbae6bd3e3
flop(code_seq_2, uniformsize(code_seq_2, Basic(:n)))

# ╔═╡ 484b3561-6ed3-40bf-b94d-526f508c4755
md"## The Song Shan Lake Spring School (SSSS) Challenge"

# ╔═╡ d4040b3f-5595-4fe9-9356-6438017820fa
md"[Song Shan Lake Spring School Github](https://github.com/QuantumBFS/SSSS)"

# ╔═╡ ca083761-51d9-4144-ad79-5281e3ca4522
md"In 2019, Lei Wang, Pan Zhang, Roger and me released a challenge in the Song Shan Lake Spring School, the one gives the largest number of solutions to the challenge quiz can take a macbook home ([@LinuxDaFaHao](https://github.com/LinuxDaFaHao)). Students submitted many [solutions to the problem](https://github.com/QuantumBFS/SSSS/blob/master/Challenge.md). The second part of the quiz is"

# ╔═╡ 8482d4d2-b377-4d36-a395-5db79ccb136c
md"""
θ = $(@bind θ2 Slider(0.0:0.01:π; default=0.5))

ϕ = $(@bind ϕ2 Slider(0.0:0.01:2π; default=0.3))
"""

# ╔═╡ 7288ba08-f845-428e-a02c-cccfe4d160cb
md"""
In the $(highlight("Buckyball")) structure shown in the figure, we attach an ising spin ``s_i=\pm 1`` on each vertex. The neighboring spins interact with an $(highlight("anti-ferromagnetic")) coupling of unit strength. Count the $(highlight("degeneracy")) of configurations that minimizes the energy
```math
E(\{s_1,s_2,\ldots,s_n\}) = \sum_{i,j \in edges}s_i s_j
```
"""

# ╔═╡ 87613668-8585-49fe-b43a-8bb4be430287
# returns atom locations
function fullerene()
	φ = (1+√5)/2
	res = NTuple{3,Float64}[]
	for (x, y, z) in ((0.0, 1.0, 3φ), (1.0, 2 + φ, 2φ), (φ, 2.0, 2φ + 1.0))
		for (α, β, γ) in ((x,y,z), (y,z,x), (z,x,y))
			for loc in ((α,β,γ), (α,β,-γ), (α,-β,γ), (α,-β,-γ), (-α,β,γ), (-α,β,-γ), (-α,-β,γ), (-α,-β,-γ))
				if loc ∉ res
					push!(res, loc)
				end
			end
		end
	end
	return res
end;

# ╔═╡ dec4a621-8d5c-4c20-a9ae-d7f765852950
let
	tb = textstyle(:default)
	Compose.set_default_graphic_size(14cm, 8cm)
	cam_position = SVector(0.0, 0.0, 0.5)
	rot = RotY(θ2)*RotX(ϕ2)
	cam_transform = PerspectiveMap() ∘ inv(AffineMap(rot, rot*cam_position))
	Nx = Ny = Nz = 4
	nb = nodestyle(:circle; r=0.01)
	eb = bondstyle(:default; r=0.01)
	x(i,j,k) = cam_transform(SVector(i,j,k) .* 0.03).data
	fl = fullerene()
	fig = canvas() do
		for (i,j,k) in fl
			nb >> x(i,j,k)
			for (i2,j2,k2) in fl
				(i2-i)^2+(j2-j)^2+(k2-k)^2 < 5.0 && eb >> (x(i,j,k), x(i2,j2,k2))
			end
		end
		tb >> ((0.4, 0.2), "60 vertices\n90 edges")
		nb >> (0.4, -0.1)
		tb >> ((0.55, -0.1), "Ising spin (s=±1)")
		eb >> ((0.37, -0.05), (0.43, -0.05))
		tb >> ((0.54, -0.05), "AFM coupling")
	end
	img = Compose.compose(Compose.context(0.3,0.5, 1.2/1.4, 1.5), fig)
	img
end

# ╔═╡ 004ebf76-0bce-4e4d-99f6-ef60816ecc07
c60_xy = fullerene();

# ╔═╡ f3ad225a-0b42-460e-a82c-b799d5da8f13
c60_edges = [[i,j] for (i,(i2,j2,k2)) in enumerate(c60_xy), (j,(i1,j1,k1)) in enumerate(c60_xy) if i<j && (i2-i1)^2+(j2-j1)^2+(k2-k1)^2 < 5.0];

# ╔═╡ 21c55a1d-018f-480a-91e6-c46f3a516fe3
c60_code = EinCode(c60_edges, Int[])

# ╔═╡ e731f0ed-948d-49ea-81ad-5bb8d66802ae
length(getixsv(c60_code))  # number of input tensors

# ╔═╡ 6746d834-9c40-4e32-b0f4-a6d716df277a
getiyv(c60_code)  # labels for the output tensor

# ╔═╡ 7107b506-58e3-4d5a-8899-4dccff1c6591
length(uniquelabels(c60_code))  # number of unique labels

# ╔═╡ ce134b9f-d4be-4728-b883-01b1c2ec9158
flop(c60_code, uniformsize(c60_code, Basic(:n)))

# ╔═╡ 738e9ed9-c6b3-4b90-8475-1923a3485447
md"## Find a good contraction order"

# ╔═╡ 41f07437-6f81-4da7-b4f2-037d9f36daff
# optimize use the `TreeSA` optimizer
c60_optcode = optimize_code(c60_code, uniformsize(c60_code, 2), TreeSA())

# ╔═╡ 647b241c-b08c-4a0c-9b40-c0398ad6b6ca
c60_elimination_order = OMEinsum.label_elimination_order(c60_optcode)

# ╔═╡ 0739e08b-0873-415e-a77b-07b83eae9fb6
md"contraction step = $(@bind nstep_c60 Slider(0:60; show_value=true, default=0))"

# ╔═╡ 739e3777-b8fc-4cd4-a92e-960f355a74ad
md"The resulting contraction order produces time complexity = $(flop(c60_optcode, uniformsize(c60_optcode, Basic(:n))))"

# ╔═╡ 78da91e7-1b76-46df-ae9a-6d611c2638e1
let
	θ2 = 0.5
	ϕ2 = 0.8
	mask = zeros(Bool,length(c60_elimination_order))
	mask[c60_elimination_order[1:nstep_c60]] .= true
	Compose.set_default_graphic_size(12cm, 12cm)
	cam_position = SVector(0.0, 0.0, 0.5)
	rot = RotY(θ2)*RotX(ϕ2)
	cam_transform = PerspectiveMap() ∘ inv(AffineMap(rot, rot*cam_position))
	Nx = Ny = Nz = 4
	tb = textstyle(:default)
	nb1 = nodestyle(:circle, fill("red"); r=0.01)
	nb2 = nodestyle(:circle, fill("white"), stroke("black"); r=0.01)
	eb = bondstyle(:default; r=0.01)
	x(i,j,k) = cam_transform(SVector(i,j,k) .* 0.03).data

	fig = canvas() do
		for (s, (i,j,k)) in enumerate(c60_xy)
			(mask[s] ? nb1 : nb2) >> x(i,j,k)
		end
		for (i, j) in c60_edges
			eb >> (x(c60_xy[i]...), x(c60_xy[j]...))
		end
		nb1 >> (-0.1, 0.45)
		tb >> ((-0.0, 0.45), "contracted")
		nb2 >> (-0.1, 0.50)
		tb >> ((-0.0, 0.50), "remaining")
	end
	Compose.compose(Compose.context(0.5,0.35, 1.0, 1.0), fig)
end

# ╔═╡ 2bff8d2e-838e-4581-8d2a-1dbde609eccc
md"The partition function"

# ╔═╡ 174b14a9-91fe-4c6e-861d-b669fe791138
Z = c60_optcode([(J = 1.0; β = 1.0; expJ = exp(β*J); [1/expJ expJ; expJ 1/expJ]) for i=1:90]...)[]

# ╔═╡ 63001407-5b10-4b0d-b4ec-98138d24b927
log(Z) / 60

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
Compose = "a81c6b42-2e10-5240-aca2-a61377ecd94b"
CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298"
OMEinsum = "ebe7aa44-baf0-506c-a96f-8464559b3922"
OMEinsumContractionOrders = "6f22d1fd-8eed-4bb7-9776-e7d684900715"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8"
Viznet = "52a3aca4-6234-47fd-b74a-806bdf78ede9"
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"

[compat]
CUDA = "~3.5.0"
Compose = "~0.9.2"
CoordinateTransformations = "~0.6.2"
OMEinsum = "~0.6.5"
OMEinsumContractionOrders = "~0.6.1"
PlutoUI = "~0.7.23"
Rotations = "~1.1.0"
StaticArrays = "~1.2.13"
SymEngine = "~0.8.7"
Viznet = "~0.3.3"
Zygote = "~0.6.32"
"""

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

julia_version = "1.7.0"
manifest_format = "2.0"

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

[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "abb72771fd8895a7ebd83d5632dc4b989b022b5b"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.2"

[[deps.AbstractTrees]]
git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.3.4"

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

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

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

[[deps.BFloat16s]]
deps = ["LinearAlgebra", "Printf", "Random", "Test"]
git-tree-sha1 = "a598ecb0d717092b5539dbbe890c98bac842b072"
uuid = "ab4f0b2a-ad5b-11e8-123f-65d77653426b"
version = "0.2.0"

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

[[deps.BatchedRoutines]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "8ee75390ba4bbfaf9aa48c121857b0da9a914265"
uuid = "a9ab73d0-e05c-5df1-8fde-d6a4645b8d8e"
version = "0.2.1"

[[deps.BetterExp]]
git-tree-sha1 = "dd3448f3d5b2664db7eceeec5f744535ce6e759b"
uuid = "7cffe744-45fd-4178-b173-cf893948b8b7"
version = "0.1.0"

[[deps.CEnum]]
git-tree-sha1 = "215a9aa4a1f23fbd05b92769fdd62559488d70e9"
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
version = "0.4.1"

[[deps.CUDA]]
deps = ["AbstractFFTs", "Adapt", "BFloat16s", "CEnum", "CompilerSupportLibraries_jll", "ExprTools", "GPUArrays", "GPUCompiler", "LLVM", "LazyArtifacts", "Libdl", "LinearAlgebra", "Logging", "Printf", "Random", "Random123", "RandomNumbers", "Reexport", "Requires", "SparseArrays", "SpecialFunctions", "TimerOutputs"]
git-tree-sha1 = "2c8329f16addffd09e6ca84c556e2185a4933c64"
uuid = "052768ef-5323-5732-b1bb-66c8b64840ba"
version = "3.5.0"

[[deps.Calculus]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad"
uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
version = "0.5.1"

[[deps.ChainRules]]
deps = ["ChainRulesCore", "Compat", "LinearAlgebra", "Random", "RealDot", "Statistics"]
git-tree-sha1 = "feeac82d7ef2bc0e531433a1f1bd65b4d8dd53c8"
uuid = "082447d4-558c-5d27-93f4-14fc19e9eca2"
version = "1.16.0"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "4c26b4e9e91ca528ea212927326ece5918a04b47"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.11.2"

[[deps.ChangesOfVariables]]
deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.2"

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

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

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

[[deps.CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"

[[deps.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 = "dce3e3fea680869eaa0b774b2e8343e9ff442313"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.40.0"

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

[[deps.Compose]]
deps = ["Base64", "Colors", "DataStructures", "Dates", "IterTools", "JSON", "LinearAlgebra", "Measures", "Printf", "Random", "Requires", "Statistics", "UUIDs"]
git-tree-sha1 = "c6461fc7c35a4bb8d00905df7adafcff1fe3a6bc"
uuid = "a81c6b42-2e10-5240-aca2-a61377ecd94b"
version = "0.9.2"

[[deps.CoordinateTransformations]]
deps = ["LinearAlgebra", "StaticArrays"]
git-tree-sha1 = "681ea870b918e7cff7111da58791d7f718067a19"
uuid = "150eb455-5306-5404-9cee-2592286d6298"
version = "0.6.2"

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.11"

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

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

[[deps.Dierckx]]
deps = ["Dierckx_jll"]
git-tree-sha1 = "5fefbe52e9a6e55b8f87cb89352d469bd3a3a090"
uuid = "39dd38d3-220a-591b-8e3c-4c3a8c710a94"
version = "0.5.1"

[[deps.Dierckx_jll]]
deps = ["CompilerSupportLibraries_jll", "Libdl", "Pkg"]
git-tree-sha1 = "a580560f526f6fc6973e8bad2b036514a4e3b013"
uuid = "cd4c43a9-7502-52ba-aa6d-59fb2a88580b"
version = "0.0.1+0"

[[deps.DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"

[[deps.DiffRules]]
deps = ["LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "9bc5dac3c8b6706b58ad5ce24cffd9861f07c94f"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.9.0"

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

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

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

[[deps.DualNumbers]]
deps = ["Calculus", "NaNMath", "SpecialFunctions"]
git-tree-sha1 = "84f04fe68a3176a583b864e492578b9466d87f1e"
uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
version = "0.6.6"

[[deps.ExprTools]]
git-tree-sha1 = "b7e3d17636b348f005f11040025ae8c6f645fe92"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.6"

[[deps.FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "8756f9935b7ccc9064c6eef0bff0ad643df733a3"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.12.7"

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

[[deps.ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "2b72a5624e289ee18256111657663721d59c143e"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "0.10.24"

[[deps.GMP_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "781609d7-10c4-51f6-84f2-b8444358ff6d"

[[deps.GPUArrays]]
deps = ["Adapt", "LinearAlgebra", "Printf", "Random", "Serialization", "Statistics"]
git-tree-sha1 = "7772508f17f1d482fe0df72cabc5b55bec06bbe0"
uuid = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7"
version = "8.1.2"

[[deps.GPUCompiler]]
deps = ["ExprTools", "InteractiveUtils", "LLVM", "Libdl", "Logging", "TimerOutputs", "UUIDs"]
git-tree-sha1 = "2cac236070c2c4b36de54ae9146b55ee2c34ac7a"
uuid = "61eb1bfa-7361-4325-ad38-22787b887f55"
version = "0.13.10"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

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

[[deps.IRTools]]
deps = ["InteractiveUtils", "MacroTools", "Test"]
git-tree-sha1 = "006127162a51f0effbdfaab5ac0c83f8eb7ea8f3"
uuid = "7869d1d1-7146-5819-86e3-90919afe41df"
version = "0.4.4"

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

[[deps.InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.2"

[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[deps.IterTools]]
git-tree-sha1 = "fa6287a4469f5e048d763df38279ee729fbd44e5"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.4.0"

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

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

[[deps.LLVM]]
deps = ["CEnum", "LLVMExtra_jll", "Libdl", "Printf", "Unicode"]
git-tree-sha1 = "7cc22e69995e2329cc047a879395b2b74647ab5f"
uuid = "929cbde3-209d-540e-8aea-75f648917ca0"
version = "4.7.0"

[[deps.LLVMExtra_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c5fc4bef251ecd37685bea1c4068a9cfa41e8b9a"
uuid = "dad2f222-ce93-54a1-a47d-0025e8a3acab"
version = "0.0.13+0"

[[deps.LazyArtifacts]]
deps = ["Artifacts", "Pkg"]
uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"

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

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

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

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

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

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

[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "be9eef9f9d78cecb6f262f3c10da151a6c5ab827"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.5"

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

[[deps.MPC_jll]]
deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "MPFR_jll", "Pkg"]
git-tree-sha1 = "9618bed470dcb869f944f4fe4a9e76c4c8bf9a11"
uuid = "2ce0c516-f11f-5db3-98ad-e0e1048fbd70"
version = "1.2.1+0"

[[deps.MPFR_jll]]
deps = ["Artifacts", "GMP_jll", "Libdl"]
uuid = "3a97d323-0669-5f0c-9066-3539efd106a3"

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

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

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

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

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

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

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

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

[[deps.OMEinsum]]
deps = ["AbstractTrees", "BatchedRoutines", "CUDA", "ChainRulesCore", "Combinatorics", "LinearAlgebra", "MacroTools", "Requires", "Test", "TupleTools"]
git-tree-sha1 = "c172922074434ef8dda952da7178208ad832637a"
uuid = "ebe7aa44-baf0-506c-a96f-8464559b3922"
version = "0.6.5"

[[deps.OMEinsumContractionOrders]]
deps = ["BetterExp", "OMEinsum", "Requires", "SparseArrays", "Suppressor"]
git-tree-sha1 = "68fd9479e9c0a8a161787b2c0d05368444de1b87"
uuid = "6f22d1fd-8eed-4bb7-9776-e7d684900715"
version = "0.6.1"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

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

[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

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

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

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

[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "5152abbdab6488d5eec6a01029ca6697dff4ec8f"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.23"

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

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

[[deps.Quaternions]]
deps = ["DualNumbers", "LinearAlgebra"]
git-tree-sha1 = "adf644ef95a5e26c8774890a509a55b7791a139f"
uuid = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0"
version = "0.4.2"

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

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

[[deps.Random123]]
deps = ["Libdl", "Random", "RandomNumbers"]
git-tree-sha1 = "0e8b146557ad1c6deb1367655e052276690e71a3"
uuid = "74087812-796a-5b5d-8853-05524746bad3"
version = "1.4.2"

[[deps.RandomNumbers]]
deps = ["Random", "Requires"]
git-tree-sha1 = "043da614cc7e95c703498a491e2c21f58a2b8111"
uuid = "e6cf234a-135c-5ec9-84dd-332b85af5143"
version = "1.5.3"

[[deps.RealDot]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "9f0a1b71baaf7650f4fa8a1d168c7fb6ee41f0c9"
uuid = "c1ae055f-0cd5-4b69-90a6-9a35b1a98df9"
version = "0.1.0"

[[deps.RecipesBase]]
git-tree-sha1 = "6bf3f380ff52ce0832ddd3a2a7b9538ed1bcca7d"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.2.1"

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

[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "8f82019e525f4d5c669692772a6f4b0a58b06a6a"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.2.0"

[[deps.Rotations]]
deps = ["LinearAlgebra", "Quaternions", "Random", "StaticArrays", "Statistics"]
git-tree-sha1 = "dbf5f991130238f10abbf4f2d255fb2837943c43"
uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc"
version = "1.1.0"

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

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

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

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

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

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

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

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

[[deps.Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

[[deps.SymEngine]]
deps = ["Compat", "Libdl", "LinearAlgebra", "RecipesBase", "SpecialFunctions", "SymEngine_jll"]
git-tree-sha1 = "6cf88a0b98c758a36e6e978a41e8a12f6f5cdacc"
uuid = "123dc426-2d89-5057-bbad-38513e3affd8"
version = "0.8.7"

[[deps.SymEngine_jll]]
deps = ["Artifacts", "GMP_jll", "JLLWrappers", "Libdl", "MPC_jll", "MPFR_jll", "Pkg"]
git-tree-sha1 = "3cd0f249ae20a0093f839738a2f2c1476d5581fe"
uuid = "3428059b-622b-5399-b16f-d347a77089a4"
version = "0.8.1+0"

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

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

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

[[deps.TimerOutputs]]
deps = ["ExprTools", "Printf"]
git-tree-sha1 = "7cb456f358e8f9d102a8b25e8dfedf58fa5689bc"
uuid = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
version = "0.5.13"

[[deps.TupleTools]]
git-tree-sha1 = "3c712976c47707ff893cf6ba4354aa14db1d8938"
uuid = "9d95972d-f1c8-5527-a6e0-b4b365fa01f6"
version = "1.3.0"

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

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

[[deps.Viznet]]
deps = ["Compose", "Dierckx"]
git-tree-sha1 = "7a022ae6ac8b153d47617ed8c196ce60645689f1"
uuid = "52a3aca4-6234-47fd-b74a-806bdf78ede9"
version = "0.3.3"

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

[[deps.Zygote]]
deps = ["AbstractFFTs", "ChainRules", "ChainRulesCore", "DiffRules", "Distributed", "FillArrays", "ForwardDiff", "IRTools", "InteractiveUtils", "LinearAlgebra", "MacroTools", "NaNMath", "Random", "Requires", "SpecialFunctions", "Statistics", "ZygoteRules"]
git-tree-sha1 = "76475a5aa0be302c689fd319cd257cd1a512fb3c"
uuid = "e88e6eb3-aa80-5325-afca-941959d7151f"
version = "0.6.32"

[[deps.ZygoteRules]]
deps = ["MacroTools"]
git-tree-sha1 = "8c1a8e4dfacb1fd631745552c8db35d0deb09ea0"
uuid = "700de1a5-db45-46bc-99cf-38207098b444"
version = "0.2.2"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

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

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

# ╔═╡ Cell order:
# ╟─bb3d0c31-787c-44d6-a415-4f85a20eac5d
# ╟─b526ffe6-5c4a-450e-99ba-fc44fc4f6f72
# ╟─a858ec12-ae05-433c-8850-84630ae10896
# ╟─bac7b9bb-8d9a-418e-b8d5-ffb00796dd22
# ╠═9c3b4290-138c-406c-a980-966dd93b11d2
# ╠═ab5c94d0-a69e-476c-9548-b522c5c48fc3
# ╟─e08fdfd1-a521-4434-af61-bfed3e42c61a
# ╠═dddaf055-f769-4b80-8a97-ff1e76ad99a8
# ╟─b3cba07a-84cd-4796-aa01-01e4719e8ff0
# ╠═1aef6d34-41f0-4717-9b13-f81f19e8825f
# ╟─399afc64-039f-43da-9994-05f4cc44d633
# ╠═7a9e9466-03fd-4955-8e5d-5cba4544e7c7
# ╟─b4088ca7-fadd-4491-a15b-9648004b238c
# ╠═7209ba2c-94d9-4102-9772-68e11c9f9fa5
# ╟─39149c34-76c1-46eb-9c1c-a0e7e2e51d17
# ╠═ae4ef95d-0a65-4a80-8abd-1a684c60b30d
# ╠═b3240fff-de5d-475b-9875-9440e61a858a
# ╠═700874d1-1dbf-4ac8-91d7-523733a176e7
# ╟─a1b9e48d-7bb5-461c-b161-aae154fcb799
# ╠═1e9ca8b5-81bc-4946-a8c4-c08afe903d8b
# ╟─31e06d5b-2efa-41a8-987f-5e75c87a3eff
# ╠═7a7dd9a1-fed7-428c-899e-90cbe3b6d699
# ╟─ed6fa023-43b1-4f5c-be3f-f93f16dfe285
# ╠═778d16ea-67f1-4903-9767-28682b99527c
# ╟─5cf51764-3138-4fd9-8bcd-c399ba4306f7
# ╠═f23efdd0-47dd-49b8-88c5-3debd35cd55a
# ╠═005fddc2-1378-426c-b4b7-80a09baf8ec7
# ╟─dfcb8259-7f6f-40b1-ae75-15eaaabe7279
# ╠═db6ba19e-2425-4ddc-a67a-d68ebadfd0b2
# ╟─3dbb855a-4045-4433-93c8-43997161807b
# ╠═fbb23079-6b77-412e-8648-e0bf10fabab5
# ╠═7ae59add-d3d4-42bf-83fd-989c2848225a
# ╠═14ad6f1c-cabf-47c8-ad39-81bcd92bf840
# ╠═d14e10af-5179-476d-ab8a-63d1837e866d
# ╠═61545cc3-f486-4dea-83f8-448eb23646fb
# ╟─2790a9b1-f706-4167-8b7c-8d8432b2511a
# ╠═f8cc753b-8ba0-4212-80ef-1ced60866389
# ╠═051de93e-f189-4a75-bd82-ebd646eb7eda
# ╠═68e643ba-f852-4b9e-9799-13f193df3bfe
# ╟─d00afc01-ecb0-454e-8cac-342d5b75101e
# ╠═b7b777bc-3f38-4a4f-a438-454187602962
# ╠═e4ffef2b-9b19-40ed-8547-63673a3ea649
# ╠═6c2955de-be35-4fd9-a5c8-ef430ed0013a
# ╠═579222f1-da51-4afa-9087-2c0799bbe6ad
# ╠═b22796e6-d163-4bbb-b4d4-bbaf57e63f8f
# ╟─955d364d-5225-4833-95d5-18265e319523
# ╠═a5ca245f-d67b-48a5-a70e-afdc3ee840ec
# ╠═d7c25437-2bc9-473d-846e-d9f8a4ca7d9c
# ╠═1f57a827-c57b-4d1e-ac73-e41d6206052a
# ╠═5dbedaab-48d8-4abe-8046-eee0e4cd4093
# ╟─015b2b71-b6aa-4b6e-b6bf-75776c0f3c90
# ╟─7f8a6c77-75a6-4063-9fc6-409397bb6306
# ╠═62e98e87-800d-4780-9fd7-70b82f2a0c3e
# ╠═dcc74a07-e7e4-48a5-9942-4f7a9714db9b
# ╟─6f9df5fe-70de-4bab-8fd6-e0173f06d6e4
# ╟─f3b5656d-5608-4325-a884-65bceddb4963
# ╟─a9d59880-0cc1-441b-9329-f58d39c23f16
# ╟─3c50281e-ae20-40ea-85ea-dcccfbd2756e
# ╠═7f6c0836-7c9e-4726-9512-dc2e5ad86e1f
# ╠═0efbdbad-a1e1-4aab-a9e7-143a05c934eb
# ╠═e2275f15-a57b-4a74-9826-2c723aad096c
# ╠═ebfe52bb-76c1-4b39-8f8c-0dec56cc80f3
# ╠═744576a6-6d31-48a6-a71c-cb6246b76bef
# ╠═e92b78f1-8ac6-421f-930b-e092402845cd
# ╠═623551e9-48bb-4976-920e-12396a596975
# ╠═d4a3557a-9de2-4500-b09e-5b3c249906fc
# ╠═5ff2bdd9-a708-4ee3-92fa-b8fbae6bd3e3
# ╟─484b3561-6ed3-40bf-b94d-526f508c4755
# ╟─d4040b3f-5595-4fe9-9356-6438017820fa
# ╟─ca083761-51d9-4144-ad79-5281e3ca4522
# ╟─dec4a621-8d5c-4c20-a9ae-d7f765852950
# ╟─8482d4d2-b377-4d36-a395-5db79ccb136c
# ╟─7288ba08-f845-428e-a02c-cccfe4d160cb
# ╠═87613668-8585-49fe-b43a-8bb4be430287
# ╠═004ebf76-0bce-4e4d-99f6-ef60816ecc07
# ╠═f3ad225a-0b42-460e-a82c-b799d5da8f13
# ╠═21c55a1d-018f-480a-91e6-c46f3a516fe3
# ╠═e731f0ed-948d-49ea-81ad-5bb8d66802ae
# ╠═6746d834-9c40-4e32-b0f4-a6d716df277a
# ╠═7107b506-58e3-4d5a-8899-4dccff1c6591
# ╠═ce134b9f-d4be-4728-b883-01b1c2ec9158
# ╟─738e9ed9-c6b3-4b90-8475-1923a3485447
# ╠═70cd5e7d-9200-444f-b974-0238c74c6013
# ╠═41f07437-6f81-4da7-b4f2-037d9f36daff
# ╠═647b241c-b08c-4a0c-9b40-c0398ad6b6ca
# ╟─0739e08b-0873-415e-a77b-07b83eae9fb6
# ╟─739e3777-b8fc-4cd4-a92e-960f355a74ad
# ╟─78da91e7-1b76-46df-ae9a-6d611c2638e1
# ╟─2bff8d2e-838e-4581-8d2a-1dbde609eccc
# ╠═174b14a9-91fe-4c6e-861d-b669fe791138
# ╠═63001407-5b10-4b0d-b4ec-98138d24b927
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


function _JCircle(center, radius, color, linewidth, action)
    sethue(color)
    setline(linewidth)
    circle(center, radius, action)
    return center
end

"""
    1. JCircle(center::Point, radius::Real; kwargs...)
    2. JCircle(center_x::Real, center_y::Real, radius::Real; kwargs...)
    3. JCircle(p1::Point, p2::Point; kwargs...)
        - A circle that touches `p1` and `p2`
    4. JCircle(radius::Real)
        - A circle at the origin

Draw a circle at `center` with the given `radius`

# Keywords for all
- `color` = "black"
- `linewidth` = 1
- `action::Symbol` :stroke by default can be `:fill` or other actions explained in the Luxor documentation.

Returns the center of the circle
"""
JCircle(center::Point, radius::Real; color = "black", linewidth = 1, action = :stroke) =
    (
        args...;
        center = center,
        radius = radius,
        color = color,
        linewidth = linewidth,
        action = action,
    ) -> _JCircle(center, radius, color, linewidth, action)

JCircle(center_x::Real, center_y::Real, radius::Real; kwargs...) =
    JCircle(Point(center_x, center_y), radius; kwargs...)

JCircle(p1::Point, p2::Point; kwargs...) =
    JCircle(midpoint(p1, p2), distance(p1, p2) / 2; kwargs...)

JCircle(radius::Real; kwargs...) = JCircle(O, radius; kwargs...)


export Zeros

"""
`Zeros(domainType::Type, dim_in::Tuple, [codomainType::Type,] dim_out::Tuple)`

Create a `LinearOperator` which, when multiplied with an array `x` of size `dim_in`, returns an array `y` of size `dim_out` filled with zeros.

For convenience `Zeros` can be constructed from any `AbstractOperator`.

```julia
julia> Zeros(Eye(10,20))
0  ℝ^(10, 20) -> ℝ^(10, 20)

julia> Zeros([Eye(10,20) Eye(10,20)])
[0,0]  ℝ^(10, 20)  ℝ^(10, 20) -> ℝ^(10, 20)
```

"""
struct Zeros{C,N,D,M} <: LinearOperator
	dim_out::NTuple{N, Int}
	dim_in::NTuple{M, Int}
end

# Constructors
#default
Zeros(domainType::Type, dim_in::NTuple{M,Int},
      codomainType::Type, dim_out::NTuple{N,Int}) where {N,M} =
Zeros{codomainType,N,domainType,M}(dim_out,dim_in)

Zeros(domainType::Type, dim_in::NTuple{M,Int}, dim_out::NTuple{N,Int}) where {N,M} =
Zeros{domainType,N,domainType,M}(dim_out,dim_in)

function Zeros(domainType::NTuple{NN,Type}, dim_in::NTuple{NN,Tuple},
	       codomainType::Type, dim_out::Tuple) where {NN}
	HCAT([Zeros(domainType[i], dim_in[i], codomainType, dim_out) for i =1:NN]...)
end

function Zeros(domainType::Type, dim_in::Tuple,
	       codomainType::NTuple{NN,Type}, dim_out::NTuple{NN,Tuple}) where {NN}
	VCAT([Zeros(domainType, dim_in, codomainType[i], dim_out[i]) for i =1:NN]...)
end

Zeros(A::AbstractOperator) = Zeros(domainType(A),size(A,2),codomainType(A),size(A,1))

# Mappings

mul!(y::AbstractArray{C,N}, A::Zeros{C,N,D,M}, b::AbstractArray{D,M}) where {C,N,D,M}= fill!(y,zero(C))
mul!(y::AbstractArray{D,M}, A::AdjointOperator{Zeros{C,N,D,M}}, b::AbstractArray{C,N}) where {C,N,D,M}= fill!(y,zero(D))

# Properties

  domainType(L::Zeros{C,N,D,M}) where {C,N,D,M} = D
codomainType(L::Zeros{C,N,D,M}) where {C,N,D,M} = C

size(L::Zeros) = (L.dim_out, L.dim_in)

fun_name(A::Zeros)  = "0"

is_null(L::Zeros) = true


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.5.0
#   kernelspec:
#     display_name: Julia 1.4.2
#     language: julia
#     name: julia-1.4
# ---

# + active=""
# # Harris Corner Detector
#
# - [実践 コンピュータビジョン](https://www.oreilly.co.jp/books/9784873116075/)
# - https://en.wikipedia.org/wiki/Harris_Corner_Detector
# - https://www.researchgate.net/publication/299711769_Revisiting_Harris_Corner_Detector_Algorithm_A_Gradual_Thresholding_Approach
# - [解説コンピュータビジョンのための 画像の特徴点の抽出](http://www.iim.cs.tut.ac.jp/~kanatani/papers/harris.pdf)
# -

using Images
using TestImages
using ImageShow
using ImageFiltering

img = testimage("c")

typeof(img), eltype(img)

# +
# img = testimage("jetplane")
# img = Gray.(img) # GrayA to Gray
# -

imfilter(img, 8 .* Kernel.sobel())

∂x, ∂y = 8 .* Kernel.sobel()

Ix = imfilter(img, ∂x)

Iy = imfilter(img, ∂y)

Iy .^ 2.

# ## Calculate Harris response

# +
#=
W = [Wxx Wxy
     Wxy Wyy]y
  = G * I
where G * I is the convolution between Gauss Operator G and I
I = [Ix^2  IxIy
    IyIx  Iy^2]
  = [Ix * [Ix Iy]
    Iy]
=#

σ = 3.

Wxx = imfilter(Ix .^ 2., Kernel.gaussian([σ,σ], [3,3]))
Wyy = imfilter(Iy .^ 2., Kernel.gaussian([σ,σ], [3,3]))
Wyx = Wxy = imfilter(Ix .* Iy, Kernel.gaussian([σ,σ], [3,3]))

detW = @. Wxx * Wyy - Wxy * Wyx
trW = @. Wxx + Wyy
κ = detW ./ trW
# -

thresh = 0.08
corner_thresh = maximum(κ) * thresh
iscorner = 1. .* (κ .> corner_thresh)
colorview(Gray, iscorner)

# # Apply NMS (Non maximum suppression)

# +
mindist = 10
indices_ = sortperm(κ[:],rev=true) # reshape Mat to Vector
indices = Int[]
for i in indices_
    if κ[i] > corner_thresh
        push!(indices, i)
    end
end

indices;
# -

loc = ones(Bool, img |> size)
H, W = img |> size
valids = zeros(img|>size)
for i in indices
    ix = min(div(i, H)+1,W)
    iy = min(rem(i, H)+1,H)
    if loc[iy, ix] == true
        valids[iy, ix] = 1
        for s in -mindist:mindist
            for t in -mindist:mindist
                if 1<= iy+s <= H && 1<= ix+t <=W
                    loc[iy+s, ix+t] = false
                end
            end
        end
    end
end

colorview(Gray, hcat(channelview(img), valids))


# v0.6

function shellsort!(a::Array{Int})::Array{Int}
    incr = div(length(a), 2)
    while incr > 0
        for i in incr+1:length(a)
            j = i
            tmp = a[i]
            while j > incr && a[j - incr] > tmp
                a[j] = a[j-incr]
                j -= incr
            end
            a[j] = tmp
        end
        if incr == 2
            incr = 1
        else
            incr = floor(Int, incr * 5.0 / 11)
        end
    end
    return a
end

x = rand(1:10, 10)
@show x shellsort!(x)
@assert issorted(x)


function bubblesort(A::Array{Int})
    X=deepcopy(A)
    for i in 1:size(A, 1)
        for j in 1:size(A, 1)-i
            if(X[j]>X[j+1])
                X[j],X[j+1]=X[j+1],X[j]
            end
        end
    end
    return X
end

function bubblesort!(A::Array{Int})
    for i in 1:size(A, 1)
        for j in 1:size(A, 1)-i
            if(A[j]>A[j+1])
                A[j],A[j+1]=A[j+1],A[j]
            end
        end
    end
    return A
end

function bubblesortperm(A::Array{Int})
    indexes=collect(1:size(A, 1))
    for i in 1:size(A, 1)
        for j in 1:size(A, 1)-i
            if(A[j]>A[j+1])
                indexes[j],indexes[j+1]=indexes[j+1],indexes[j]
            end
        end
    end
    return indexes
end

function bubblesortperm!(A::Array{Int}, B::Array{Int})
    if(size(A, 1)==size(B, 1))
        B=collect(1:size(A, 1))
        for i in 1:size(A, 1)
            for j in 1:size(A, 1)-i
                if(A[j]>A[j+1])
                    B[j],B[j+1]=B[j+1],B[j]
                end
            end
        end
        return B
    end
end

print(bubblesortperm([2,6,4,1,7,3]))


using TrajectoryOptimization
using Random
using StaticArrays
using LinearAlgebra
using MeshCat
using TrajOptPlots
using Rotations
using BenchmarkTools
import TrajectoryOptimization: Rotation
const TO = TrajectoryOptimization

max_con_viol = 1.0e-3
T = Float64
verbose = true

opts_ilqr = iLQRSolverOptions{T}(verbose=verbose,
    iterations=300)

opts_al = AugmentedLagrangianSolverOptions{T}(verbose=verbose,
    opts_uncon=opts_ilqr,
    iterations=40,
    # cost_tolerance=1.0e-5,
    cost_tolerance_intermediate=1.0e-4,
    # constraint_tolerance=max_con_viol,
    # penalty_scaling=10.,
    penalty_initial=10.)

opts_pn = ProjectedNewtonSolverOptions{T}(verbose=verbose,
    feasibility_tolerance=max_con_viol,
    solve_type=:feasible)

opts_altro = ALTROSolverOptions{T}(verbose=verbose,
    opts_al=opts_al,
    # R_inf=1.0e-4,
    resolve_feasible_problem=false,
    opts_pn=opts_pn,
    projected_newton=false,
    projected_newton_tolerance=1.0e-4)

model = Dynamics.Quadrotor2{UnitQuaternion{Float64,IdentityMap}}(use_rot=false)
n,m = 13,4

if !isdefined(Main,:vis)
    vis = Visualizer(); open(vis);
    set_mesh!(vis, model)
end

function gen_quad_maze(Rot; kwargs...)
    prob = Problems.QuadrotorMaze(Rot; kwargs...)
    solver = ALTROSolver(prob, opts_altro, infeasible=true)
    solver.solver_al.opts.verbose = false
    solver
end

# Initialize visualization
solver = gen_quad_maze(UnitQuaternion{Float64,IdentityMap}, costfun=:Quadratic)
solver = gen_quad_maze(RPY{Float64}, use_rot=false)
solver = gen_quad_maze(UnitQuaternion{Float64,CayleyMap}, costfun=:QuatLQR)
# solver = gen_quad_maze(UnitQuaternion{Float64,IdentityMap}, use_rot=:slack, normcon=true)
Z0 = deepcopy(get_trajectory(solver))
opts_ilqr.verbose = false
opts_altro.projected_newton = false
solver.opts.verbose = true
add_cylinders!(vis, solver, height=4, robot_radius=1.0)
visualize!(vis, solver)

# Solve
# opts_altro.R_inf = 1e-8
# opts_al.penalty_initial = 10
# opts_al.penalty_scaling = 10
opts_al.cost_tolerance_intermediate = 1e-2
solver = gen_quad_maze(UnitQuaternion{Float64,IdentityMap}, costfun=:Quadratic, use_rot=false)
solver = gen_quad_maze(UnitQuaternion{Float64,IdentityMap}, costfun=:Quadratic, use_rot=:slack, normcon=true)
solver = gen_quad_maze(UnitQuaternion{Float64,IdentityMap}, costfun=:Quadratic, use_rot=false, normcon=true)
# solver = gen_quad_maze(RPY{Float64}, use_rot=false)
solver = gen_quad_maze(UnitQuaternion{Float64,CayleyMap}, costfun=:QuatLQR)
Z0 = deepcopy(get_trajectory(solver))
initial_trajectory!(solver, Z0)
@time solve!(solver)
iterations(solver)
visualize!(vis, solver)
benchmark_solve!(solver)
conSet = get_constraints(solver)

# Plot waypoints
waypoints!(vis, get_model(solver), get_trajectory(solver), length=41)

# Step through solve
prob = Problems.QuadrotorMaze(Rot,use_rot=true, costfun=:Quadratic)
prob.constraints.constraints
opts_ilqr.verbose = false
opts_altro.projected_newton = false
opts_altro.R_inf = 1e-8
opts_al.penalty_initial = 1
solver = ALTROSolver(prob, opts_altro, infeasible=true)

al = solver.solver_al
initialize!(al)
TO.set_tolerances!(al, al.solver_uncon, 1)
visualize!(vis, al)

TO.step!(al)
max_violation(al)
visualize!(vis, al)

function compare_quats(gen_prob; kwargs...)
    # Initial data structure
    data = Dict{Symbol,Vector}(:name=>Symbol[], :costfun=>Symbol[], :iterations=>Int[],
        :rotation=>Symbol[], :time=>Float64[], :cost=>Float64[])

    # Treat quaternion as normal vector
    println("Quat")
    log_solve!(gen_prob(UnitQuaternion{Float64, IdentityMap}, use_rot=false),
        data; name=:Quat, kwargs...)

    # Re-normalize after disretization
    println("ReNorm")
    log_solve!(gen_prob(UnitQuaternion{Float64, ReNorm}, use_rot=false),
        data; name=:ReNorm, kwargs...)

    # Use Unit Norm Constraint
    println("NormCon")
    log_solve!(gen_prob(UnitQuaternion{Float64, IdentityMap}, use_rot=false,
        normcon=true), data; name=:NormCon, kwargs...)

    # Use Unit Norm Constrained with slack
    println("QuatSlack")
    log_solve!(gen_prob(UnitQuaternion{Float64, IdentityMap}, use_rot=:slack,
        normcon=true), data; name=:QuatSlack, kwargs...)

    for rmap in [ExponentialMap, CayleyMap, MRPMap, VectorPart]
        println(rmap)
        for costfun in [:QuatLQR, :Quadratic] #[:Quadratic, :QuatLQR, :ErrorQuad]
            log_solve!(gen_prob(UnitQuaternion{Float64, rmap}, costfun=costfun,
                normcon=false), data; kwargs...)
        end
    end
    return data
end

qdata_maze = compare_quats(gen_quad_maze, samples=1, evals=1)

# Run benchmarks
function run_all(gen_prob;kwargs...)
    # Initial data structure
    data = Dict{Symbol,Vector}(:name=>Symbol[], :costfun=>Symbol[], :iterations=>Int[], :rotation=>Symbol[],
        :time=>Float64[], :cost=>Float64[])

    # Baseline methods
    println("Baseline methods")
    log_solve!(gen_prob(UnitQuaternion{Float64, IdentityMap}, use_rot=false), data; kwargs...)
    # log_solve!(gen_prob(RodriguesParam{Float64}, use_rot=false), data; kwargs...)
    # log_solve!(gen_prob(MRP{Float64}, use_rot=false), data; kwargs...)
    log_solve!(gen_prob(RPY{Float64}, use_rot=false), data; kwargs...)

    # Quaternion Methods
    println("Quaternion methods")
    for rmap in [CayleyMap,] #[ExponentialMap, CayleyMap, MRPMap, VectorPart]
        for costfun in [:QuatLQR,] #[:Quadratic, :QuatLQR, :ErrorQuad]
            log_solve!(gen_prob(UnitQuaternion{Float64, rmap}, costfun=costfun), data; kwargs...)
        end
    end
    return data
end
data_maze = run_all(gen_quad_maze, samples=10, evals=1)

df = DataFrame(data_maze)
df.rots = string.(short_names.(df.rotation))
df.time_per_iter = df.time ./ df.iterations
quats = in([:ExponentialMap, :CayleyMap, :MRPMap, :VectorPart])
bases = in([:IdentityMap, :MRP, :RP, :RPY])
comp = in([:IdentityMap, :RPY, :CayleyMap])

############################################################################################
# NEW SIMPLIFIED PLOTS
############################################################################################
label = ["Quat","RPY","iMLQR"]
p = @pgf Axis(
    {
        ybar,
        ylabel="solve time (ms)",
        # "enlarge limits" = 0.20,
        legend_style =
        {
            at = Coordinate(0.5, -0.07),
            anchor = "north",
            legend_columns = -1
        },
        symbolic_x_coords=label,
        xtick = "data",
        nodes_near_coords,
        nodes_near_coords_align={vertical},
        "every node near coord/.append style={/pgf/number format/.cd, fixed,precision=0}"
    },
    Plot(Coordinates(label, df.time)),
    # Legend(["Quadratic", "Error Quadratic", "Geodesic"])
)
pgfsave("figs/maze_time_best.tikz", p, include_preamble=false)

############################################################################################
# PLOT 1: Iterations
############################################################################################

base = df[bases.(df.rotation),:]
quat = df[quats.(df.rotation),:]
qlqr  = quat[quat.costfun .== :QuatLQR,:]
equad = quat[quat.costfun .== :ErrorQuadratic,:]
quad  = quat[quat.costfun .== :Quadratic,:]
coord_base = Coordinates(base.rots, base.iterations)
coord1 = Coordinates(qlqr.rots, qlqr.iterations)
coord2 = Coordinates(equad.rots, equad.iterations)
coord3 = Coordinates(quad.rots, quad.iterations)

p = @pgf Axis(
    {
        ybar,
        ylabel="iterations",
        "enlarge x limits" = 0.20,
        legend_style =
        {
            at = Coordinate(0.5, -0.15),
            anchor = "north",
            legend_columns = -1
        },
        symbolic_x_coords=quad.rots,
        xtick = "data",
        nodes_near_coords,
        nodes_near_coords_align={vertical},
    },
    Plot(coord3),
    Plot(coord2),
    Plot(coord1),
    Legend(["Quadratic", "Error Quadratic", "Geodesic"])
)
pgfsave("figs/maze_quat_iters.tikz", p, include_preamble=false)

############################################################################################
# PLOT 2: Time
############################################################################################
df[:,[:rotation,:costfun,:time]]
base = df[bases.(df.rotation),:]
quat = df[quats.(df.rotation),:]
qlqr  = quat[quat.costfun .== :QuatLQR,:]
equad = quat[quat.costfun .== :ErrorQuadratic,:]
quad  = quat[quat.costfun .== :Quadratic,:]
quad_all = df[df.costfun .== :Quadratic,:]
best = [base; qlqr]
p = @pgf Axis(
    {
        ybar,
        ylabel="solve time (ms)",
        # "enlarge limits" = 0.20,
        legend_style =
        {
            at = Coordinate(0.5, -0.07),
            anchor = "north",
            legend_columns = -1
        },
        symbolic_x_coords=best.rots,
        xtick = "data",
        nodes_near_coords,
        nodes_near_coords_align={vertical},
        "every node near coord/.append style={/pgf/number format/.cd, fixed,precision=0}"
    },
    Plot(Coordinates(best.rots, best.time)),
    # Legend(["Quadratic", "Error Quadratic", "Geodesic"])
)
pgfsave("figs/maze_time_best.tikz", p, include_preamble=false)

coord_base = Coordinates(base.rots, base.time)
coord_all = Coordinates(quad_all.rots, quad_all.time)
coord1 = Coordinates(qlqr.rots, qlqr.time)
coord2 = Coordinates(equad.rots, equad.time)
coord3 = Coordinates(quad.rots, quad.time)

p = @pgf Axis(
    {
        ybar,
        width="20cm",
        ylabel="solve time (ms)",
        # "enlarge limits" = 0.20,
        legend_style =
        {
            at = Coordinate(0.5, -0.07),
            anchor = "north",
            legend_columns = -1
        },
        symbolic_x_coords=quad_all.rots,
        xtick = "data",
        nodes_near_coords,
        nodes_near_coords_align={vertical},
        "every node near coord/.append style={/pgf/number format/.cd, fixed,precision=0}"
    },
    Plot(coord_all),
    Plot(coord2),
    Plot(coord1),
    Legend(["Quadratic", "Error Quadratic", "Geodesic"])
)
pgfsave("figs/maze_time_all.tikz", p, include_preamble=false)

############################################################################################
# PLOT 4: Quat method comparison - time
############################################################################################

df = DataFrame(qdata_maze)
df.rots = string.(short_names.(df.name))
sub = df[in([:Quat,:ReNorm,:NormCon,:QuatSlack,:CayleyMap,:RPY]).(df.name),:]
quats = by(df, :rots, :time=>minimum)

p = @pgf Axis(
    {
        ybar,
        ylabel="solve time (ms)",
        # "enlarge limits" = 0.20,
        legend_style =
        {
            at = Coordinate(0.5, -0.07),
            anchor = "north",
            legend_columns = -1
        },
        symbolic_x_coords=quats.rots,
        xtick = "data",
        nodes_near_coords,
        nodes_near_coords_align={vertical},
        "every node near coord/.append style={/pgf/number format/.cd, fixed,precision=0}"
    },
    # HLine({"ultra thick", "white"}, 35),
    Plot(Coordinates(quats.rots, quats.time_minimum)),
    # Legend(["Quadratic", "Error Quadratic", "Geodesic"])
)
pgfsave("figs/maze_qcomp.tikz", p, include_preamble=false)


import Pkg; Pkg.add(Pkg.PackageSpec(url="https://github.com/JuliaComputing/JuliaAcademyData.jl"))
using JuliaAcademyData; activate("Foundations of machine learning")

# # Representing data in a computer
#
# The core of data science and machine learning is **data**: we are interested in extracting knowledge from data.
#
# But how exactly do computers represent data? Let's find out exactly what an "artificial intelligence" has at its disposal to learn from.

#-

# ## Data is represented as arrays
#
# Let's take a look at some fruit. Using the `Images.jl` library, we can load in some images:

using Images

apple = load(datapath("data/10_100.jpg"))

#-

banana = load(datapath("data/104_100.jpg"))

# Here we have images of apples and bananas. We would eventually like to build a program that can automatically distinguish between the two. However, the computer doesn't "see" an apple or a banana; instead, it just sees numbers.
#
# An image is encoded in something called an **array**, which is like a container that has boxes or slots for individual pieces of data:
#
# An array is a bunch of numbers in connected boxes; the figure above shows a 1-dimensional array. Our images are instead 2-dimensional arrays, or matrices, of numbers, arranged something like this:

#-

# <img src="https://raw.githubusercontent.com/JuliaComputing/JuliaAcademyData.jl/master/courses/Foundations%20of%20machine%20learning/data/array2d.png" alt="attachment:array2d.png" width="500"/>

#-

# For example, `apple` is an image, consisting of a 100x100 array of numbers:

typeof(apple)

#-

size(apple)

#-

a = [ 1 2 3;4 5 6]

#-

typeof(a)

#-

size(a)

# We can grab the datum stored in the box at row `i` and column `j` by *indexing* using square brackets: `[i, j]`. For example, let's get the pixel (piece of the image) in box $(40, 60)$, i.e. in the 40th row and 60th column of the image:

apple

#-

dump(apple[40, 60])

#-

apple[18:20,29:31]

# We see that Julia displays a coloured box! Julia, via the `Colors.jl` package, is clever enough to display colours in a way that is useful to us humans!
#
# So, in fact, an image is a 2D array, in which each element of the array is an object (a collection of numbers) describing a coloured pixel.

#-

# ## Colors as numbers
#
# How, then, are these colors actually stored? Computers store colors in RGB format, that is they store a value between 0 and 1 for each of three "channels": red, green, and blue. Here, 0 means none of that color and 1 means the brightest form of that color. The overall color is a combination of those three colors.
#
# For example, we can pull out the `red` value using the function `red` applied to the color. Since internally the actual value is stored in a special format, we choose to convert it to a standard floating-point number using the `Float64` function:

Float64(red(apple[40, 60]))

#-

using Statistics

#-

[ mean(float.(c.(img))) for c = [red,green,blue], img = [apple,banana] ]

#-

using Plots
histogram(float.(green.(apple[:])),color="red",label="apple", normalize=true, nbins=25)
histogram!(float.(green.(banana[:])),color="yellow",label="banana",normalize=true, nbins=25)

#-

apple

#-

float(red(banana[50,20]))

#-

banana[50,20]

#-

pixel = apple[40, 60]

red_value   = Float64( red(pixel) )
green_value = Float64( green(pixel) )
blue_value  = Float64( blue(pixel) )

print("The RGB values are ($red_value, $green_value, $blue_value)")

# Since the red value is high while the others are low, this means that at pixel `(40, 60)`, the picture of the apple is very red. If we do the same at one of the corners of the picture, we get the following:

pixel = apple[1, 1]

red_value   = Float64( red(pixel) )
green_value = Float64( green(pixel) )
blue_value  = Float64( blue(pixel) )

print("The RGB values are ($red_value, $green_value, $blue_value)")

#-

apple

# We see that every color is bright, which corresponds to white.

#-

# ## Working on an image as a whole
#
# In Julia, to apply a function to the whole of an array, we place a `.` between the function name and the left parenthesis (`(`), so the following gives us the `red` value of every pixel in the image:

redpartofapple = Float64.(red.(apple))
mean(redpartofapple)

#-

using Plots

#-

gr()

#-

histogram(redpartofapple[:],color=:red,label="redness in the apple")

# Note that we get a 2D array (matrix) back.

#-

# Julia's [mathematical standard library](https://docs.julialang.org/en/stable/stdlib/math/#Mathematics-1) has many mathematical functions built in. One of them is the `mean` function, which computes the average value. If we apply this to our apple:

mean(Float64.(red.(apple)))

# we see that the value indicates that the average amount of red in the image is a value between the amount of red in the apple and the amount of red in the white background.
#
# *Somehow we need to teach a computer to use this information about a picture to recognize that there's an apple there!*

#-

# ## A quick riddle
#
# Here's a quick riddle. Let's check the average value of red in the image of the banana.

mean(Float64.(red.(banana)))

# Oh no, that's more red than our apple? This isn't a mistake and is actually true! Before you move onto the next exercise, examine the images of the apple and the banana very carefully and see if you can explain why this is expected.

#-

# #### Exercise 1
#
# What is the average value of blue in the banana?
#
# (To open a new box use <ESC>+b (b is for "below", what do you think a does?))

#-

# #### Solution
#
# We can calculate the average value of blue in the banana via

mean(Float64.(blue.(banana)))

# The result is approximately `0.8`.

#-

# #### Exercise 2
#
# Does the banana have more blue or more green?

#-

# #### Solution
#
# The average value of green in the banana is

mean(Float64.(green.(banana)))

# which gives approximately `0.88`. The banana has more green on average.

Courses/Foundations of machine learning/0100.Representing-data-in-a-computer.jl


function transform_density!(img::AbstractArray, edges::AbstractArray, cdf::AbstractArray, minval::Union{Real,AbstractGray}, maxval::Union{Real,AbstractGray})
    first_edge = first(edges)
    step_size = step(edges)
    T = eltype(img)
    map!(img,img) do val
        if val >= edges[end]
            newval = cdf[end]
        elseif val < first_edge
            newval = first(cdf)
        else
            index = Int(Base.div(val-first_edge,step_size)) + 1
            newval = cdf[index]
        end
        # Scale the new intensity value to so that it lies in the range [minval, maxval].
        if T <: Integer
            newval = ceil(minval + ((newval - first(cdf)) * (maxval - minval) / (cdf[end] - first(cdf))))
        else
            newval = minval + ((newval - first(cdf)) * (maxval - minval) / (cdf[end] - first(cdf)))
        end
    end
end

function construct_pdfs(img::AbstractArray, targetimg::AbstractArray, edges::AbstractRange)
    _, histogram = build_histogram(img, edges)
    _, target_histogram = build_histogram(targetimg, edges)
    return edges, histogram / sum(histogram), target_histogram / sum(target_histogram)
end

function construct_pdfs(img::AbstractArray, targetimg::AbstractArray, nbins::Integer = 256)
    if eltype(img) <: AbstractGray
        imin, imax = 0, 1
    else
        imin, imax = min(minfinite(img), minfinite(targetimg)), max(maxfinite(img), maxfinite(targetimg))
    end
    edges, histogram = build_histogram(img, nbins, minval = imin, maxval = imax)
    _, target_histogram = build_histogram(targetimg, edges)
    return edges, histogram / sum(histogram), target_histogram / sum(target_histogram)
end

function lookup_icdf(cdf::AbstractArray, targetcdf::AbstractArray)
    lookup_table = zeros(Int, length(cdf))
    i = 1
    for j = 1:length(cdf)
        p = cdf[j]
        while i < length(targetcdf) && targetcdf[i+1] <= p
            i += 1
        end
        lookup_table[j] = i
    end
    lookup_table
end

function cdf2pdf!(pdf::AbstractArray, cdf::AbstractArray)
    pdf[1] = cdf[1]
    for i = length(cdf)-1:-1:2
        pdf[i] = cdf[i] - cdf[i-1]
    end
end

function linear_stretch(x, A, B, a, b)
    return (x-A) * ((b-a)/(B-A)) + a
end


#=

prec=precision(midpt); # bits of significand (signficant radix-2 digits
prec, typeof(midpt)
# (57,ArbFloats.ArbFloat{57})

1 + round(Int,log2(ufp2(midpt))) -  round(Int,log2(ulp2(midpt)))
# 57

prec == (1 + round(Int,log2(ufp2(midpt))) -  round(Int,log2(ulp2(midpt))))
# true

=#

"""
   x.midpoint -> (significand, exponentOf2)
                  [0.5,1.0)     2^expo
   x.radius   -> (radial significand, radial exponentOf2)
"""
function frexp(x::ArfFloat{P}) where {P}
    exponentOf2 = x.exponentOf2
    significandOf = deepcopy(x)
    significandOf.exponentOf2 = 0
    return significandOf, exponentOf2
end

function ldexp(s::ArfFloat{P}, e::Int) where {P}
    z = deepcopy(s)
    z.exponentOf2 = e
    return z
end

function frexp(x::ArbFloat{P}) where {P}
    significandOf, exponentOf2 = frexp(ArfFloat{P}(x))
    return ArbFloat{P}(significandOf), exponentOf2
end

function ldexp(s::ArbFloat{P}, e::Int) where {P}
    z = deepcopy(s)
    z.exponentOf2 = e
    return z
end
ldexp(x::Tuple{ArbFloats.ArbFloat{P}, Int}) where {P} = ldexp(x[1],x[2])

#=

      gte_bits2digs(b_bits)    ~>  d_digits
      d_digits | d digits suffice to encode and recover b bits without error

      gte_digs2bits(d_digits)  ~>  b_bits
      b_bits |  b bits suffice to encode and recover d digits without error

      lte_bits2digs(b_bits)    ~>  c_digits
      c_digits | b bits are necessary encode and recover c digits without error

      lte_digs2bits(d_digits)  ~>  a_bits
      a_bits |  d digits are necessary encode and recover a bits without error

      lte_digs2bits( gte_bits2digs(b_bits  ) ) == b_bits
      lte_bits2digs( gte_digs2bits(d_digits) ) == d_digits

const log2_log10 = log(10,2)  # 0.3010299956639812 ~= log(2)/log(10)
const log10_log2 = log(2,10)  # 3.321928094887362  ~= log(10)/log(2)

lte_bits2digs(nbits::Int) = floor(Int, nbits * log2_log10)
lte_digs2bits(ndigs::Int) = floor(Int, ndigs * log10_log2)
=#

lte_bits2digs(nbits::Int) = floor( Int, nbits * 0.30102999566398125 ) # log(10,2) RoundUp
gte_bits2digs(nbits::Int)  = ceil( Int, nbits * 0.3010299956639812  ) # log(10,2) RoundDown
lte_digs2bits(ndigs::Int) = floor( Int, ndigs * 3.3219280948873626  ) # log(2,10) RoundUp
gte_digs2bits(ndigs::Int)  = ceil( Int, ndigs * 3.3219280948873617  ) # log(2,10) RoundDown

"""
logarithm_base(x)
"""
function log_base(x::Real, base::Int)
   z = if base == 2
           log2(x)
        elseif base == 10
           log10(x)
        else
           log(x) / log(base)
        end
   return z
end
log_base(x::ArbFloat{P}, base::Int) where {P} = ArbFloats.logbase(x,base)

#=
    position_first_place: the radix position of the most significant nonzero bit|digit

    pfp{T<:AbstractFloat}(x::T, base::Int=2)
    pfp{T<:AbstractFloat}(x::T)              == pfp(x,  2)  ==  pfp2{T<:AbstractFloat}(x::T)
    pfp{T<:AbstractFloat}(x::T, base=10)     == pfp(x, 10)  ==  pfp10{T<:AbstractFloat}(x::T)

    position_last_place: the radix position of the least significant nonzero bit|digit

    plp{T<:AbstractFloat}(x::T, base::Int=2)
    plp{T<:AbstractFloat}(x::T)              == p;p(x,  2)  ==  plp2{T<:AbstractFloat}(x::T)
    plp{T<:AbstractFloat}(x::T, base=10)     == plp(x, 10)  ==  p;p10{T<:AbstractFloat}(x::T)

    unit_first_place: the radix *value) of the most significant nonzero bit|digit

    pfp{T<:AbstractFloat}(x::T, base::Int=2)
    pfp{T<:AbstractFloat}(x::T)              == pfp(x,  2)  ==  pfp2{T<:AbstractFloat}(x::T)
    pfp{T<:AbstractFloat}(x::T, base=10)     == pfp(x, 10)  ==  pfp10{T<:AbstractFloat}(x::T)

    unit_last_place: the radix *value* of the least significant nonzero bit|digit

    plp{T<:AbstractFloat}(x::T, base::Int=2)
    plp{T<:AbstractFloat}(x::T)              == plp(x,  2)  ==  plp2{T<:AbstractFloat}(x::T)
    plp{T<:AbstractFloat}(x::T, base=10)     == plp(x, 10)  ==  plp10{T<:AbstractFloat}(x::T)

=#

"""
position_first_place
determine the position of the most significant nonzero bit|digit
"""
function pfp(x::T, base::Int=2) where {T <: Real}
   z = 0 # if x==0.0
   if notzero(x)
       z = floor( Int, log_base(abs(x), base) )
   end
   return z
end
pfp(x::ArbFloat{P}, base::Int=2) where {P} =
    return iszero(x) ? 0 : floor( Int, log_base(abs(smartvalue(x)), base) )
"""
binary position_first_place
determine the position of the most significant nonzero bit
"""
pfp2(x::T) where {T <: Real} = (x==zero(T) ? 0 : floor( Int, log2(abs(x)) ))
pfp2(x::ArbFloat{P}) where {P} = (iszero(x) ? 0 : floor( Int, log2(abs(smartvalue(x))) ))
"""
decimal position_first_place
determine the position of the most significant nonzero digit
"""
pfp10(x::T) where {T <: Real} = (x==zero(T) ? 0 : floor( Int, log10(abs(x)) ))
pfp10(x::ArbFloat{P}) where {P} = (iszero(x) ? 0 : floor( Int, log10(abs(smartvalue(x))) ))

"""
ufp is unit_first_place
the float value given by a 1 at the position of
  the most significant nonzero bit|digit in _x_
"""
function ufp(x::AbstractFloat, base::Int=2)
   z = pfp(x, base)
   b = convert(Float64, base)
   return b^z
end
function ufp(x::ArbFloat{P}, base::Int=2) where {P}
   z = pfp(x, base)
   return Float64(base)^z
end
ufp(x::Integer, base::Int=2) = ufp(Float64(x), base)
"ufp2 is unit_first_place in base 2"
ufp2(x::T) where {T <: Real} = 2.0^pfp2(x)
ufp2(x::ArbFloat{P}) where {P} = 2.0^pfp2(x)
ufp2(x::Integer) = ufp2(Float64(x))
"ufp10 is unit_first_place in base 10"
ufp10(x::T) where {T <: Real} = 10.0^pfp10(x)
ufp10(x::ArbFloat{P}) where {P} = 10.0^pfp10(x)
ufp10(x::Integer) = ufp10(Float64(x))
"""
ulp   is unit_last_place
the float value given by a 1 at the position of
  the least significant nonzero bit|digit in _x_
"""
function ulp(x::Real, precision::Int, base::Int)
   unitfp  = base==2 ? ufp2(x) : (base==10 ? ufp10(x) : throw(DomainError()))
   twice_u = 2.0^(1-precision)
   return twice_u * unitfp
end
ulp(x::T, base::Int=2) where {T <: AbstractFloat}  =
    ulp(x, 1+Base.significand_bits(T), base)
ulp(x::ArbFloat{P}, base::Int=2) where {P}  =
    ulp(x, P, base)
ulp(x::Integer, base::Int=2) = ulp(Float64(x), base)

"""ulp2  is unit_last_place base 2"""
function ulp2(x::Real, precision::Int)
   unitfp  = ufp2(x)
   twice_u = 2.0^(1-precision)
   return (*)(promote(twice_u, unitfp)...)
end
function ulp2(x::ArbFloat{P}) where {P}
   unitfp  = ufp2(x)
   twice_u = 2.0^(1-P)
   return (*)(promote(twice_u, unitfp)...)
end
ulp2(x::T) where {T <: AbstractFloat}  = ulp2(x, 1+Base.significand_bits(T))
ulp2(x::Integer) = ulp2(Float64(x))

"""ulp10 is unit_last_place base 10"""
function ulp10(x::Real, bitprecision::Int)
    unit10fp = ufp10(x)
    digitprecision = lte_bits2digs(bitprecision)
    twice_u10 = 10.0^(1-digitprecision)
    return twice_u10 * unit10fp
end
function ulp10(x::ArbFloat{P}) where {P}
    unit10fp = ufp10(x)
    digitprecision = lte_bits2digs(P)
    twice_u10 = 10.0^(1-digitprecision)
    return twice_u10 * unit10fp
end
ulp10(x::T) where {T <: AbstractFloat} = ulp10( x, (1+Base.significand_bits(T)) )
ulp10(x::Integer) = ulp10(Float64(x))

function eps(::Type{T}) where {T <: ArbFloat}
    P = precision(T)
    return ldexp(one(T), 1-P)
end
function eps(x::ArbFloat{P}) where {P}
    a = eps(ArbFloat{P})
    return midpoint(abs(x)*a)
end

# !!revisit!!
function nextfloat(x::ArbFloat{P}) where {P}
    m,r = midpoint_radius(x)
    e = eps(m)
    n = m
    i = 1
    while n == m
      n = midpoint(n + i*e)
      i += 1
    end
    return midpoint_radius(n, r)
end

# !!revisit!!
function prevfloat(x::ArbFloat{P}) where {P}
    m,r = midpoint_radius(x)
    e = eps(m)
    n = m
    i = 1
    while n == m
      n = midpoint(n - i*e)
      i += 1
    end
    return midpoint_radius(n, r)
end


using LinearAlgebra
using Distances, StaticArrays, StatsFuns

if true
x = rand(10000, 100000)
x = x'*x+UniformScaling(1.0)
end

#cholesky 10000x10000 takes 5-6 sec
# matmul two 100000 matrices takes 30 secr
# matvec takes 0.1 sec
#cholesky solve 0.15 sec

#forming kernel matrix takes 2.217 seconds when s is 1D, 3 seconds when s is 7D
#0.5 seconds to take dot product of 10,000^2 matrix with another matrix
#.4 seconds to square entries of 10,000^2 matrix

preallocated = zeros(10000,10000)

#15000x15000 case
#x*x 50 sec O(n^3)
#x*z 0.2 sec
# x hadamard product x: 1 second
#cholesky(x) solve: .3 seconds
#cholesky: 17 seconds O(n^3)

# ------------takeaway here is the math adds up-------------
#fastK(s, s):  7 seconds
#pairwise(s, s): 2 seconds
#exponentiate the result^: 2seconds
#divide by pi: 1.2 seconds

#0 pairwise: 2 seconds
#1.squaring operation ~1 second
#2. scale by - theta operation ~1 second
# 3. exponentiate operation ~2seconds
#divide by constant operation ~1 sec

#20000 x 20000 case
#fastK:  17 - 33 sec
#cholesky: 61 sec
#cholesky solve: 0.5 sec
#matvec with kernel matrix: 0.3 sec
#dot product k .* k: 1.3 sec


module JaynesCummings

global const h_bar = 1.0#1.05457173E-34 # in Joules*seconds

export a, a_dagger

include("gen_initialstate.jl")
include("rabi_hamiltonian.jl")
include("gen_timeevoarray.jl")
include("calc_fullevo.jl")
include("partialtrace.jl")
include("print_matrix.jl")
include("get_eig_hamiltonian.jl")
include("get_prob.jl")

function a(cutoffN) # Truncated matrix for the lowering operator
  out = zeros(Complex128,cutoffN,cutoffN);
  for i = 1:cutoffN-1
      out[i,i+1] = sqrt(i);
  end
  return out
end

function a_dagger(cutoffN) # Truncated matrix for the raising operator
  out = zeros(Complex128,cutoffN,cutoffN)
  for i = 1:cutoffN-1
    out[i+1,i] = sqrt(i);
  end
  return out
end

end


"""
Power nonlinearity, with energy C ∫ρ^α.
"""
struct PowerNonlinearity
    C::Real
    α::Real
end
(P::PowerNonlinearity)(basis) = TermPowerNonlinearity(basis, P.C, P.α)

struct TermPowerNonlinearity <: Term
    basis::PlaneWaveBasis
    C::Real
    α::Real
end

function ene_ops(term::TermPowerNonlinearity, ψ, occ; ρ, kwargs...)
    basis = term.basis
    dVol = basis.model.unit_cell_volume / prod(basis.fft_size)

    E = term.C * sum(ρ.real .^ term.α) * dVol
    potential = @. term.C * term.α * ρ.real^(term.α-1)

    ops = [RealSpaceMultiplication(basis, kpoint, potential) for kpoint in basis.kpoints]
    (E=E, ops=ops)
end
_kernel(C, α, ρ) = @. C * α * (α-1) * ρ^(α-2)
function compute_kernel(term::TermPowerNonlinearity; ρ::RealFourierArray, kwargs...)
    k = _kernel(term.C, term.α, ρ.real)
    Diagonal(vec(k))
end
function apply_kernel(term::TermPowerNonlinearity, dρ; ρ::RealFourierArray, kwargs...)
    k = _kernel(term.C, term.α, ρ.real)
    k .* dρ
end


using ForwardDiff
using FixedPointAcceleration
func(x, params) = [params[1]*sqrt(abs(x[1] + x[2])), 1.5*x[1] + params[2]*x[2]]

function ML_estimation(params)
    Inputs = [0.3,900.0]
    fp_simple   = fixed_point(inp -> func(inp, params), Inputs; Algorithm = :Simple)
    return fp_simple.FixedPoint_[1]
end

params = [1.5, 0.5]
ML_estimation(params)
grads = ForwardDiff.gradient(ML_estimation, params)
isa(grads, Array{Float64,1})


function ArgLexMin(A,b,C,d,XLB,XUB,VariableDef,ObjOrd; TimeLimit = 1e+20)

    const EPS = 1e-10
    m,n = size(A);
    ObjNum = size(C)[1];
    MyModel = Model();
    ObjVal = zeros(ObjNum)
    @variable(MyModel,XLB[i] <= X[i=1:n] <= XUB[i],category = VariableType(VariableDef,i))
    for i = 1:m
        @constraint(MyModel,sum(A[i,j]*X[j] for j = 1:n) >= b[i] )
    end

    for i in ObjOrd
        @objective(MyModel, Min, sum(C[i,j]*X[j] for j = 1:n )+d[i] )
        setsolver(MyModel, CplexSolver(CPXPARAM_ScreenOutput = 0,CPXPARAM_Threads = 1,CPXPARAM_TimeLimit = TimeLimit))
        status = solve(MyModel)
        if (i == ObjOrd[1]) && (status != :Optimal)
            return [],[]
        elseif (status != :Optimal)
            error("In ArgLexMin the first objective was optimal but we have some infeasible in the rest.")
        end
        ObjVal[i] = getobjectivevalue(MyModel)
        @constraint(MyModel,sum(C[i,j]*X[j] for j = 1:n )+d[i] <= ObjVal[i]+EPS )
    end
    XVal = getvalue(X)
    return ObjVal, XVal
end


"""
Makes a coordinate given latitude and longitude
#Example:

```julia
Coordinate(37.5, 67.91666)
```
"""
struct Coordinate
    latitude
    longitude
end
"""
The mapping between the coordinates of earth and indices of the image is needed to convert from one to another. In this package such mapping is referred to as a coordinate system. To define a coordinate system, you need to provide coordinates of the top-left and bottom-right pixels, and the height and the width of the image.

Suppose the image we plan to load had 8000 rows and 7100 columns. The top-left coordinate (37.5, 67.91666) is and bottom-right (4.166, 97.5). We can use these to create a the mapping.

```julia
top_left     = Coordinate(37.5, 67.91666)
bottom_right = Coordinate(4.166, 97.5)
height = 8000
width  = 7100
my_coordinate_system = CoordinateSystem(top_left, bottom_right, height, width)
```

Now functions can use this mapping to go from one to another.
"""
struct CoordinateSystem
    top_left::Coordinate
    bottom_right::Coordinate
    height::Int64
    width::Int64
end
"""
To convert from latitude to row number of an image, use the ```lat_to_row``` function.
# Example:
```julia
lat_to_row(my_coordinate_system, 19.6)
```
"""
function lat_to_row(geometry::CoordinateSystem, x)
    top_left        = geometry.top_left.latitude
    bottom_right    = geometry.bottom_right.latitude
    height          = geometry.height
    return Int(round(((x - top_left) * height / (bottom_right - top_left))))
end
"""
To convert from longitude to column number of an image use the ```long_to_column``` function.
# Example:
```julia
long_to_column(my_coordinate_system, 73.33)
```
"""
function long_to_column(geometry::CoordinateSystem, x)
    top_left        = geometry.top_left.longitude
    bottom_right    = geometry.bottom_right.longitude
    width           = geometry.width
    return Int(round(((x - top_left) * width/(bottom_right - top_left))))
end
"""
To convert from row number of an image to latitude use the ```row_to_lat``` function.
# Example:
```julia
row_to_lat(my_coordinate_system, 100)
```
"""
function row_to_lat(geometry::CoordinateSystem, x)
    top_left        = geometry.top_left.latitude
    bottom_right    = geometry.bottom_right.latitude
    height          = geometry.height
    return  ((bottom_right - top_left)/height * x + top_left)
end
"""
To convert from column number of an image to longitude use the ```column_to_long``` function.
# Example:
```julia
column_to_long(my_coordinate_system, 100)
```
"""
function column_to_long(geometry::CoordinateSystem, x)
    top_left        = geometry.top_left.longitude
    bottom_right    = geometry.bottom_right.longitude
    width           = geometry.width
    return((bottom_right - top_left)/width * x + top_left)
end

"""
To convert a coordinate from earth's coordinate system to image's row number and column number, use the ```coordinate_to_image``` function.
# Example:
```julia
coordinate_to_image(my_coordinate_system, Coordinate(19.49907, 72.721252))
```
"""
function coordinate_to_image(geometry::CoordinateSystem, x::Coordinate)
    return [lat_to_row(geometry, x.latitude), long_to_column(geometry, x.longitude)]
end
"""
To convert a coordinate from image's row number and column number to earth's coordinate system use the ```image_to_coordinate``` function.
# Example:
```julia
image_to_coordinate(my_coordinate_system, [4320, 1153])
```
"""
function image_to_coordinate(geometry::CoordinateSystem, x)
    return Coordinate(row_to_lat(geometry, x[1]), column_to_long(geometry, x[2]))
end

"""
Translates a coordinate system given the new top-left and bottom-right coordinates
# Example:
```julia
MUMBAI = translate_geometry(INDIA_COORDINATE_SYSTEM, Coordinate(19.49907, 72.721252), Coordinate(18.849475, 73.074187))
```
"""
function translate_geometry(geometry::CoordinateSystem, top_left::Coordinate, bottom_right::Coordinate)
    top_left_row = lat_to_row(geometry, top_left.latitude)
    bottom_right_row = lat_to_row(geometry, bottom_right.latitude)
    bottom_right_col = long_to_column(geometry, bottom_right.longitude)
    top_left_col = long_to_column(geometry, top_left.longitude)
    height = round(bottom_right_row - top_left_row)
    width = round(bottom_right_col - top_left_col)
    return CoordinateSystem(top_left, bottom_right, height, width)
end

global TILE1_COORDINATE_SYSTEM = CoordinateSystem(Coordinate(75, -180), Coordinate(0, -60), 18000, 28800)
global TILE2_COORDINATE_SYSTEM = CoordinateSystem(Coordinate(75, -60), Coordinate(0, 60), 18000, 28800)
global TILE3_COORDINATE_SYSTEM = CoordinateSystem(Coordinate(75, 60), Coordinate(0, 180), 18000, 28800)
global TILE4_COORDINATE_SYSTEM = CoordinateSystem(Coordinate(0, -180), Coordinate(-65, -60), 15600, 28800)
global TILE5_COORDINATE_SYSTEM = CoordinateSystem(Coordinate(0, -60), Coordinate(-65, 60), 15600, 28800)
global TILE6_COORDINATE_SYSTEM = CoordinateSystem(Coordinate(0, 60), Coordinate(-65, 180), 15600, 28800)
global FULL_COORDINATE_SYSTEM  = CoordinateSystem(Coordinate(75, -180), Coordinate(-65, 180), 33600, 86400)

global INDIA_COORDINATE_SYSTEM = CoordinateSystem(Coordinate(37.5, 67.91666), Coordinate(4.166, 97.5), 8000, 7100)
global MUMBAI_COORDINATE_SYSTEM = translate_geometry(INDIA_COORDINATE_SYSTEM, Coordinate(19.49907, 72.721252), Coordinate(18.849475, 73.074187))


"""
    CalculateSampling(in)

Calculate the sampling operator of an n-dimension input. The output has the
same size as the input.

"""
function CalculateSampling(in)

cutoff = 1e-10
	itrace = 1

	n=size(in)
	in=reshape(in,n[1],:)
        wd = zeros(Float32,size(in))
	n2=size(in,2)
	for itrace = 1 : n2
		a = sum(in[:,itrace].*in[:,itrace])
		if (a > cutoff)
			wd[:,itrace] .= 1.
		end
	end
	wd=reshape(wd,n)
	return wd;

end


using Dice
using Dice: num_flips, num_nodes, to_dice_ir

code = @dice begin
    n = 10
    x = Vector(undef, n)
    x[1] = flip(0.5)
    for i=2:n
        x[i] = if x[i-1]
            flip(0.9)
        else
            flip(0.4)
        end
    end
    x[end]
end

# BDD analysis
bdd = compile(code)
num_flips(bdd)
num_nodes(bdd)
infer(code, :bdd)

# IR analysis
to_dice_ir(code)
has_dice_binary() && rundice(code)
has_dice_binary() && infer(code, :ocaml)


# This file provides reexported functions.

using ArnoldiMethod

"""
    adjacency_matrix(g[, T=Int; dir=:out])

Return a sparse adjacency matrix for a graph, indexed by `[u, v]`
vertices. Non-zero values indicate an edge between `u` and `v`. Users may
override the default data type (`Int`) and specify an optional direction.

### Optional Arguments
`dir=:out`: `:in`, `:out`, or `:both` are currently supported.

### Implementation Notes
This function is optimized for speed and directly manipulates CSC sparse matrix fields.
"""
function adjacency_matrix(g::AbstractGraph, T::DataType=Int; dir::Symbol=:out)
    nzmult = 1
    # see below - we iterate over columns. That's why we take the
    # "opposite" neighbor function. It's faster than taking the transpose
    # at the end.
    if (dir == :out)
        _adjacency_matrix(g, T, inneighbors, 1)
    elseif (dir == :in)
        _adjacency_matrix(g, T, outneighbors, 1)
    elseif (dir == :both)
        if is_directed(g)
            _adjacency_matrix(g, T, all_neighbors, 2)
        else
            _adjacency_matrix(g, T, outneighbors, 1)
        end
    else
        error("Not implemented")
    end
end

function _adjacency_matrix(g::AbstractGraph{U}, T::DataType, neighborfn::Function, nzmult::Int=1) where U
    n_v = nv(g)
    nz = ne(g) * (is_directed(g) ? 1 : 2) * nzmult
    colpt = ones(U, n_v + 1)

    rowval = sizehint!(Vector{U}(), nz)
    selfloops = Vector{U}()
    for j in 1:n_v  # this is by column, not by row.
        if has_edge(g, j, j)
            push!(selfloops, j)
        end
        dsts = neighborfn(g, j)
        colpt[j + 1] = colpt[j] + length(dsts)
        append!(rowval, sort!(dsts))
    end
    spmx = SparseMatrixCSC(n_v, n_v, colpt, rowval, ones(T, nz))

    # this is inefficient. There should be a better way of doing this.
    # the issue is that adjacency matrix entries for self-loops are 2,
    # not one(T).
    if !is_directed(g)
        for i in selfloops
            if !(T <: Bool)
                spmx[i, i] += one(T)
            end
        end
    end
    return spmx
end

"""
    laplacian_matrix(g[, T=Int; dir=:unspec])

Return a sparse [Laplacian matrix](https://en.wikipedia.org/wiki/Laplacian_matrix)
for a graph `g`, indexed by `[u, v]` vertices. `T` defaults to `Int` for both graph types.

### Optional Arguments
`dir=:unspec`: `:unspec`, `:both`, :in`, and `:out` are currently supported.
For undirected graphs, `dir` defaults to `:out`; for directed graphs,
`dir` defaults to `:both`.
"""
function laplacian_matrix(g::AbstractGraph{U}, T::DataType=Int; dir::Symbol=:unspec) where U
    if dir == :unspec
        dir = is_directed(g) ? :both : :out
    end
    A = adjacency_matrix(g, T; dir=dir)
    D = convert(SparseMatrixCSC{T,U}, Diagonal(sparse(sum(A, dims=2)[:])))
    return D - A
end

"""
    laplacian_spectrum(g[, T=Int; dir=:unspec])

Return the eigenvalues of the Laplacian matrix for a graph `g`, indexed
by vertex. Default values for `T` are the same as those in
[`laplacian_matrix`](@ref).

### Optional Arguments
`dir=:unspec`: Options for `dir` are the same as those in [`laplacian_matrix`](@ref).

### Performance
Converts the matrix to dense with ``nv^2`` memory usage.

### Implementation Notes
Use `eigs(laplacian_matrix(g);  kwargs...)` to compute some of the
eigenvalues/eigenvectors.
"""
laplacian_spectrum(g::AbstractGraph, T::DataType=Int; dir::Symbol=:unspec) = eigvals(Matrix(laplacian_matrix(g, T; dir=dir)))

"""
    adjacency_spectrum(g[, T=Int; dir=:unspec])

Return the eigenvalues of the adjacency matrix for a graph `g`, indexed
by vertex. Default values for `T` are the same as those in
[`adjacency_matrix`](@ref).

### Optional Arguments
`dir=:unspec`: Options for `dir` are the same as those in [`laplacian_matrix`](@ref).

### Performance
Converts the matrix to dense with ``nv^2`` memory usage.

### Implementation Notes
Use `eigs(adjacency_matrix(g);  kwargs...)` to compute some of the
eigenvalues/eigenvectors.
"""
function adjacency_spectrum(g::AbstractGraph, T::DataType=Int; dir::Symbol=:unspec)
    if dir == :unspec
        dir = is_directed(g) ?  :both : :out
    end
    return eigvals(Matrix(adjacency_matrix(g, T; dir=dir)))
end

"""
    incidence_matrix(g[, T=Int; oriented=false])

Return a sparse node-arc incidence matrix for a graph, indexed by
`[v, i]`, where `i` is in `1:ne(g)`, indexing an edge `e`. For
directed graphs, a value of `-1` indicates that `src(e) == v`, while a
value of `1` indicates that `dst(e) == v`. Otherwise, the value is
`0`. For undirected graphs, both entries are `1` by default (this behavior
can be overridden by the `oriented` optional argument).

If `oriented` (default false) is true, for an undirected graph `g`, the
matrix will contain arbitrary non-zero values representing connectivity
between `v` and `i`.
"""
function incidence_matrix(g::AbstractGraph, T::DataType=Int; oriented=false)
    isdir = is_directed(g)
    n_v = nv(g)
    n_e = ne(g)
    nz = 2 * n_e

    # every col has the same 2 entries
    colpt = collect(1:2:(nz + 1))
    nzval = repeat([(isdir || oriented) ? -one(T) : one(T), one(T)], n_e)

    # iterate over edges for row indices
    rowval = zeros(Int, nz)
    i = 1
    for u in vertices(g)
        for v in outneighbors(g, u)
            if isdir || u < v # add every edge only once
                rowval[2 * i - 1] = u
                rowval[2 * i] = v
                i += 1
            end
        end
    end

    spmx = SparseMatrixCSC(n_v, n_e, colpt, rowval, nzval)
    return spmx
end

"""
    spectral_distance(G₁, G₂ [, k])

Compute the spectral distance between undirected n-vertex
graphs `G₁` and `G₂` using the top `k` greatest eigenvalues.
If `k` is ommitted, uses full spectrum.

### References
- JOVANOVIC, I.; STANIC, Z., 2014. Spectral Distances of Graphs Based on their Different Matrix Representations
"""
function spectral_distance end

# can't use Traitor syntax here (https://github.com/mauro3/SimpleTraits.jl/issues/36)
@traitfn function spectral_distance(G₁::G, G₂::G, k::Integer) where {G <: AbstractGraph; !IsDirected{G}}
    A₁ = adjacency_matrix(G₁)
    A₂ = adjacency_matrix(G₂)

    λ₁ = k < nv(G₁) - 1 ? eigs(A₁, nev=k, which=LR())[1] : eigvals(Matrix(A₁))[end:-1:(end - (k - 1))]
    λ₂ = k < nv(G₂) - 1 ? eigs(A₂, nev=k, which=LR())[1] : eigvals(Matrix(A₂))[end:-1:(end - (k - 1))]

    return sum(abs, (λ₁ - λ₂))
end

# can't use Traitor syntax here (https://github.com/mauro3/SimpleTraits.jl/issues/36)
@traitfn function spectral_distance(G₁::G, G₂::G) where {G <: AbstractGraph; !IsDirected{G}}
    nv(G₁) == nv(G₂) || throw(ArgumentError("Spectral distance not defined for |G₁| != |G₂|"))
    return spectral_distance(G₁, G₂, nv(G₁))
end


module ModelTests

using Test
using Random
using LinearAlgebra
using Parametron
using OSQP
using OSQP.MathOptInterfaceOSQP: OSQPSettings
using GLPK
using StaticArrays: SVector

import MathOptInterface

const MOI = MathOptInterface

function defaultoptimizer()
    optimizer = OSQP.Optimizer()
    MOI.set(optimizer, OSQPSettings.Verbose(), false)
    MOI.set(optimizer, OSQPSettings.EpsAbs(), 1e-8)
    MOI.set(optimizer, OSQPSettings.EpsRel(), 1e-16)
    MOI.set(optimizer, OSQPSettings.MaxIter(), 10000)
    MOI.set(optimizer, OSQPSettings.AdaptiveRhoInterval(), 25) # required for deterministic behavior
    optimizer
end

function test_unconstrained(model, x, Q, r, s; atol=1e-8)
    solve!(model)
    @test terminationstatus(model) == MOI.OPTIMAL
    @test primalstatus(model) == MOI.FEASIBLE_POINT
    @test dualstatus(model) == MOI.FEASIBLE_POINT
    xval = value.(model, x)
    expected = -2 * Q() \ r()
    @test xval ≈ expected atol = atol
    @test objectivevalue(model) ≈ dot(xval, Q() * xval) + dot(r(), xval) + s() atol = atol
end

@testset "Model: unconstrained" begin
    optimizer = defaultoptimizer()
    model = Model(optimizer)
    n = 2
    x = [Variable(model) for _ = 1 : n]
    x1, x2 = x
    @test x1.index == 1
    @test x2.index == 2

    rng = MersenneTwister(1)

    sval = Ref(1.0)
    Q = let rng = rng # https://github.com/JuliaLang/julia/issues/15276
        Parameter(Matrix(1.0I, n, n), model) do Q
            Q[1, 1] = rand(rng)
            Q[2, 2] = rand(rng)
        end
    end
    r = let rng = rng # https://github.com/JuliaLang/julia/issues/15276
        Parameter(zeros(n), model) do r
            rand!(rng, r)
        end
    end
    s = let sval = sval # https://github.com/JuliaLang/julia/issues/15276
        Parameter{Float64}(model) do
            sval[]
        end
    end

    @objective(model, Minimize, transpose(x) * Q * x + r ⋅ x + s)

    Parametron.initialize!(model)
    @test model.initialized

    test_unconstrained(model, x, Q, r, s)
    solve!(model)
    allocs = @allocated solve!(model)
    @test allocs == 0

    # constant modification
    sval[] = 2.0
    test_unconstrained(model, x, Q, r, s)
    allocs = @allocated solve!(model)
    @test allocs == 0
end

@testset "Model: equality constrained" begin
    # Minimize ||A x - b||^2 = x' A' A x - (2 * A' * b)' x + b' * b
    # subject to C x = d

    test_equality_constrained = function(A, b, C, d, x; rtol = 1e-4)
        C⁺ = pinv(C)
        Q = I - C⁺ * C
        expected = Q * (pinv(A * Q) * (b - A * C⁺ * d)) + C⁺ * d # note: can be quite badly conditioned
        @test x ≈ expected rtol = rtol
    end

    n = 8
    m = 2

    optimizer = defaultoptimizer()
    model = Model(optimizer)
    x = [Variable(model) for _ = 1 : n]

    rng = MersenneTwister(1234)
    randrng! = let rng = rng
        x -> rand!(rng, x)
    end
    A = Parameter(randrng!, zeros(n, n), model)
    b = Parameter(randrng!, zeros(n), model)
    C = Parameter(randrng!, zeros(m, n), model)
    d = Parameter(randrng!, zeros(m), model)

    residual = @expression A * x - b

    @objective(model, Minimize, residual ⋅ residual)
    @constraint(model, C * x == d)
    @test_throws ArgumentError @constraint(model, C * x ≈ d)

    for i = 1 : 100
        allocs = @allocated solve!(model)
        @test terminationstatus(model) == MOI.OPTIMAL
        @test primalstatus(model) == MOI.FEASIBLE_POINT
        if i > 1
            @test allocs == 0
        end
        test_equality_constrained(A(), b(), C(), d(), value.(model, x))
    end
end

@testset "Model: box constrained" begin
    # Minimize || x - p ||^2 = x' I x - 2 * p' * x + p ' * p
    # subject to l <= x <= p
    # where p is outside {x : l <= x <= p}

    n = 10

    optimizer = defaultoptimizer()
    model = Model(optimizer)
    x = [Variable(model) for _ = 1 : n]

    rng = MersenneTwister(1234)
    l = let rng = rng # https://github.com/JuliaLang/julia/issues/15276
        Parameter(zeros(n), model) do l
            l .= .-rand.(Ref(rng))
            l
        end
    end
    u = let rng = rng # https://github.com/JuliaLang/julia/issues/15276
        Parameter(u -> rand!(rng, u), zeros(n), model)
    end
    p = let l = l, u = u, rng = rng # https://github.com/JuliaLang/julia/issues/15276
        Parameter(zeros(n), model) do p
            if rand(rng, Bool)
                p .= 2 .* l()
            else
                p .= 2 .* u()
            end
            p
        end
    end

    residual = @expression x - p
    # TODO: currently need to subtract p ⋅ p to make it so that constant is zero (due to limitation in MOI 0.3)
    @objective(model, Minimize, residual ⋅ residual - p ⋅ p)
    @constraint(model, x >= l)
    @constraint(model, x <= u)

    for testnum = 1 : 100
        allocs = @allocated solve!(model)
        expected = p() ./ 2
        @test value.(model, x) ≈ expected rtol = 1e-4
        testnum > 1 && @test allocs == 0
    end
end

@testset "Issue 30 #1" begin
    optimizer = defaultoptimizer()
    model = Model(optimizer)
    x = Variable(model)
    @constraint(model, [x] <= [-3.])
    @objective model Minimize x^2
    solve!(model)
    @test value(model, x) ≈ -3. atol = 1e-8
    allocs = @allocated solve!(model)
    @test allocs == 0
end

@testset "Issue 30 #2" begin
    optimizer = defaultoptimizer()
    model = Model(optimizer)
    constraintexpr(x, upperbound) = @expression [x] - [upperbound]
    x = Variable(model)
    @constraint model constraintexpr(x, -3.0) <= [0.0]
    @objective model Minimize x^2
    solve!(model)
    @test value(model, x) ≈ -3. atol = 1e-8
    allocs = @allocated solve!(model)
    @test allocs == 0
end

@testset "Issue 29" begin
    model = Model(defaultoptimizer())
    x = Variable(model)
    @constraint model [x] >= [0]
    @objective model Minimize x^2 + 1
    solve!(model)
    @test value(model, x) ≈ 0 atol=1e-8
    @test objectivevalue(model) ≈ 1 atol=1e-8
end

@testset "MOI Issue 426" begin
    model = Model(defaultoptimizer())
    x = Variable(model)
    for vector_set_type in (MOI.Nonnegatives, MOI.Nonpositives, MOI.Zeros)
        @test(length(@inferred(MOI.get(model.backend, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, vector_set_type}()))) == 0)
    end
    @constraint model [x] >= [0]
    @constraint model [x] <= [1]
    @constraint model [x] == [0.5]
    for vector_set_type in (MOI.Nonnegatives, MOI.Nonpositives, MOI.Zeros)
        @test(length(@inferred(MOI.get(model.backend, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, vector_set_type}()))) == 1)
    end
end

@testset "boolean basics" begin
    optimizer = GLPK.Optimizer()
    model = Model(optimizer)
    x = Variable(model)
    @constraint model x ∈ {0, 1}
    @objective model Maximize x

    solve!(model)

    @test terminationstatus(model) == MOI.OPTIMAL
    @test primalstatus(model) == MOI.FEASIBLE_POINT
    @test value(model, x) ≈ 1.0 atol=1e-8
end

@testset "integer basics" begin
    # https://github.com/JuliaOpt/GLPK.jl/issues/58
    @testset "scalar constraint" begin
        optimizer = GLPK.Optimizer()
        model = Model(optimizer)
        x = Variable(model)
        @constraint model x ∈ ℤ
        @constraint model x >= 0.5
        @objective model Minimize x

        solve!(model)

        @test terminationstatus(model) == MOI.OPTIMAL
        @test primalstatus(model) == MOI.FEASIBLE_POINT
        @test value(model, x) ≈ 1.0 atol=1e-8
    end

    @testset "vector constraint" begin
        optimizer = GLPK.Optimizer()
        model = Model(optimizer)
        x = Variable(model)
        @constraint model x ∈ ℤ
        @constraint model [x] >= [0.5]
        @objective model Minimize x

        solve!(model)

        @test terminationstatus(model) == MOI.OPTIMAL
        @test primalstatus(model) == MOI.FEASIBLE_POINT
        @test value(model, x) ≈ 1.0 atol=1e-8
    end
end

@testset "scalar constraints 1" begin
    optimizer = defaultoptimizer()
    model = Model(optimizer)
    x = Variable(model)
    @constraint(model, x <= -3)
    @objective model Minimize x^2
    solve!(model)
    @test value(model, x) ≈ -3. atol = 1e-8
    allocs = @allocated solve!(model)
    @test allocs == 0
end

@testset "scalar constraints 2" begin
    # from https://github.com/JuliaOpt/MathOptInterface.jl/blob/575647cfa344ed7656e0eee16537017184a011a8/src/Test/contquadratic.jl#L3
    optimizer = defaultoptimizer()
    model = Model(optimizer)
    x = Variable(model)
    y = Variable(model)
    z = Variable(model)
    @objective model Minimize x^2 + x * y + y^2 + y * z + z^2
    @constraint model x + 2y + 3z >= 4
    @constraint model x +  y      >= 1
    solve!(model)

    @test terminationstatus(model) == MOI.OPTIMAL
    @test primalstatus(model) == MOI.FEASIBLE_POINT
    @test objectivevalue(model) ≈ 13/7 atol=1e-6
    @test value.(model, [x, y, z]) ≈ [4/7, 3/7, 6/7] atol=1e-6

    allocs = @allocated solve!(model)
    @test allocs == 0
end

@testset "scalar constraints 3" begin
    # from https://github.com/JuliaOpt/MathOptInterface.jl/blob/575647cfa344ed7656e0eee16537017184a011a8/src/Test/contquadratic.jl#L160
    optimizer = defaultoptimizer()
    model = Model(optimizer)
    x = Variable(model)
    y = Variable(model)
    @objective model Minimize 2 * x^2 + y^2 + x * y + x + y + 1
    @constraint model x >= 0
    @constraint model -y <= 0
    @constraint model x + y == 1
    solve!(model)

    @test terminationstatus(model) == MOI.OPTIMAL
    @test primalstatus(model) == MOI.FEASIBLE_POINT
    @test objectivevalue(model) ≈ 2.875 atol=1e-6
    @test value.(model, [x, y]) ≈ [0.25, 0.75] atol=1e-6

    allocs = @allocated solve!(model)
    @test allocs == 0
end

@testset "AbstractVector constraints" begin
    model = Model(defaultoptimizer())
    x = SVector(Variable(model), Variable(model))
    @constraint model x == SVector(1.0, 2.0)
end

@testset "default objective (issue #62)" begin
    model = Model(defaultoptimizer())
    x = Variable(model)
    @constraint model x == 1
    solve!(model)
    @test terminationstatus(model) == MOI.OPTIMAL
    @test primalstatus(model) == MOI.FEASIBLE_POINT
    @test objectivevalue(model) == 0.0
    @test value(model, x) ≈ 1.0 atol=1e-6
end

@testset "Parameter(A, model)" begin
    # From the README
    rng = MersenneTwister(1)
    n, m = 5, 15
    Xdata = randn(n, m)
    pdata = Vector{Float64}(undef, m);
    model = Model(defaultoptimizer())
    X = Parameter(model, val=Xdata)
    p = Parameter(model, val=pdata)
    g = [Variable(model) for _ = 1:n]
    resid = @expression X'*g - p
    @objective(model, Minimize, resid'*resid)
    ggt = randn(n)
    pdata .= Xdata'*ggt
    solve!(model)
    gv = value.(model, g)
    @test gv ≈ ggt rtol=0.01
    ggt = randn(n)
    pdata .= Xdata'*ggt
    solve!(model)
    gv = value.(model, g)
    @test gv ≈ ggt rtol=0.01
end

@testset "Quadratic constraints" begin
    if !haskey(ENV, "CI")
        using Gurobi
        rng = MersenneTwister(1)
        optimizer = Gurobi.Optimizer(OutputFlag=0)
        model = Model(optimizer)

        direction = Parameter(x -> normalize!(randn!(rng, x)), zeros(2), model)
        zmax = Parameter{Float64}(() -> rand(rng), model)

        x = Variable(model)
        y = Variable(model)
        z = Variable(model)
        μ = 0.7

        @constraint model x^2 + y^2 <= μ^2 * z^2
        @constraint model z >= 0
        @constraint model z <= zmax
        @objective model Maximize direction ⋅ [x, y]

        for i = 1 : 5
            solve!(model)
            @test terminationstatus(model) == MOI.OPTIMAL
            @test primalstatus(model) == MOI.FEASIBLE_POINT

            xval, yval, zval = value.(model, (x, y, z))
            @test zval ≈ zmax() atol=1e-6
            @test [xval, yval] ⋅ direction() ≈ zval * μ atol=1e-6
        end
    end
end

end # module