### A Pluto.jl notebook ###
# v0.12.20

using Markdown
using InteractiveUtils

# ╔═╡ 2b3d2532-1bac-491a-aeef-d9c29786341c
begin
	using Pkg
	Pkg.activate(mktempdir())
	Pkg.Registry.update()
	Pkg.add("Yao")
	Pkg.add("YaoPlots")
	Pkg.add("Plots")
	Pkg.add("BitBasis")
	Pkg.add("StatsBase")
end

# ╔═╡ e3cae266-2140-11eb-12f9-31b294a31586
using Yao, YaoPlots

# ╔═╡ 16d09514-2a5d-11eb-23da-b968d2ae5d16
begin
	using StatsBase: Histogram, fit
	using Plots: bar, scatter!, gr; gr()
	using BitBasis
	function plotmeasure(x::Array{BitStr{n,Int},1}, st="#") where n
		hist = fit(Histogram, Int.(x), 0:2^n)
		x = 0
		if(n<=3)
			s=8
		elseif(n>3 && n<=6)
			s=5
		elseif(n>6 && n<=10)
			s=3.2
		elseif(n>10 && n<=15)
			s=2
		elseif(n>15)
			s=1
		end
		bar(hist.edges[1] .- 0.5, hist.weights, title = "Histogram", label="Found in "*string(st)*" tries", size=(600*(2^n)/s,400), ylims=(0:maximum(hist.weights)), xlims=(0:2^n), grid=:false, ticks=false, border=:none, color=:lightblue, lc=:lightblue, foreground_color_legend = nothing, background_color_legend = nothing)
		scatter!(0:2^n-1, ones(2^n,1), markersize=0, label=:none,
         series_annotations="|" .* string.(hist.edges[1]; base=2, pad=n) .* "⟩")
		scatter!(0:2^n-1, zeros(2^n,1) .+ maximum(hist.weights), markersize=0, label=:none, series_annotations=string.(hist.weights))
	end
end

# ╔═╡ ecb98520-212d-11eb-02e4-f3c89c254998
md"# Grover's Algorithm"

# ╔═╡ 0b9fb54a-212e-11eb-1f1d-8912b444cc0c
md"Suppose you've got 10 boxes, each with a paper with a random number on it, and you're searching for a number which may or may not be in one of the boxes. You'll have to search the first box, then the second, then the third... and so on, until you've found what you were looking for. Best case scenario - it just took one search (the number could be in the first box)! Worst case - it took 10 searches(it could've been in the last box, or in no box at all! ).

Even if you get a bit clever, sort all the boxes according to the numbers in them, in ascending or descending order, and apply something like [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm), it'll almost take log₂(n) searches.

Grover's Algorithm is a search algorithm, which solves the problem in O(`` \sqrt{n} ``) [time complexity](https://en.wikipedia.org/wiki/Time_complexity)(it takes `` 𝜋\sqrt{N}/4 `` searches, for one possible match)."

# ╔═╡ 88c22c5e-2130-11eb-2b73-5fbbbd662df9
md"## Sign flipping"

# ╔═╡ 326c3b46-2140-11eb-333e-27744c831983
md"Consider you've 3 qubits, with the state vector :-

`` \frac{1}{\sqrt{8}} |000〉 + \frac{1}{\sqrt{8}} |001〉 + \frac{1}{\sqrt{8}} |010〉 + \frac{1}{\sqrt{8}} |011〉 + \frac{1}{\sqrt{8}} |100〉 + \frac{1}{\sqrt{8}} |101〉 + \frac{1}{\sqrt{8}} |110〉 + \frac{1}{\sqrt{8}} |111〉 ``"

# ╔═╡ c72bdf32-2140-11eb-1aec-55864296f0d3
begin
	qubits = uniform_state(3)
	state(qubits)
end

# ╔═╡ 746f121a-2141-11eb-2fab-5333ec478316
md"Suppose there was a circuit U, and if you passed the three qubits to U, the resultant state vector would look somewhat like this :-

`` \frac{1}{\sqrt{8}} |000〉 + \frac{1}{\sqrt{8}} |001〉 - \frac{1}{\sqrt{8}} |010〉 + \frac{1}{\sqrt{8}} |011〉 + \frac{1}{\sqrt{8}} |100〉 + \frac{1}{\sqrt{8}} |101〉 + \frac{1}{\sqrt{8}} |110〉 + \frac{1}{\sqrt{8}} |111〉 ``"

# ╔═╡ b8c52404-2141-11eb-0759-6db99f9ffa3a
md"One thing we know about this *magical* circuit is that its matrix representation looks like this,

`` \begin{bmatrix}1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & -1 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 1 & 0 & 0\\1 & 0 & 0 & 0 & 0 & 0 & 1 & 0\\1 & 0 & 0 & 0 & 0 & 0 & 0 & 1\end{bmatrix} ``

Try multiplying the above state vector to the new matrix."

# ╔═╡ b47f24e2-2143-11eb-2e85-59a9f7762ddd
let
	U = rand(8,8) |> U->round.(round.(U * inv(U)))
	U[3,3] = -1
	U * state(qubits)
end

# ╔═╡ 433f40da-2145-11eb-35c0-6faaad23f32b
md"### Creating the magic circuit"

# ╔═╡ 9971e84e-28df-11eb-25e2-d9c71ae9dc42
md"You'll have to think about each circuit individually, according to the the element you want to flip. We're flipping `` \; |010〉 `` in this case. The circuit will be denoted by `` U_𝑓 ``."

# ╔═╡ 656cf846-444f-11eb-369e-f7919462e228
plot(chain(3, put(1:3 => label(chain(3), "U𝑓"))))

# ╔═╡ 4d81dbde-444f-11eb-1aae-7b666f5af174
md"Below is my implementation of this magic circuit"

# ╔═╡ 1f33e8c2-28e2-11eb-2f62-1fc0b46a05d3
begin
	U𝑓 = chain(3, repeat(X, [1 3]), control(1:2, 3=>Z), repeat(X, [1 3]))
	plot(U𝑓)
end

# ╔═╡ 9f8330b4-28e2-11eb-1a9d-1dce3076770d
md"The circuit for `` |011〉 `` will be"

# ╔═╡ bbc3dac6-28e2-11eb-0907-b72813ebe035
begin
	U𝑓_1 = chain(3, repeat(X, [3]), control(1:2, 3=>Z), repeat(X, [3]))
	plot(U𝑓_1)
end

# ╔═╡ 73b8c0aa-28e4-11eb-2c7c-b3177793ff92
state(uniform_state(3))

# ╔═╡ 72eb8480-28f5-11eb-3315-779a888c1d55
md"After passing the uniform qubits through U𝑓."

# ╔═╡ 089832b4-28f4-11eb-0c43-3de7e3518c9b
state(uniform_state(3) |> U𝑓)

# ╔═╡ d53e1414-28ea-11eb-35ab-81897180dd8b
md"## Amplitude Amplification"

# ╔═╡ cef6748e-28ea-11eb-1956-b9c7e4dd4d2b
md"Lets consider you want to increase one particular probability amplitude and decrease the rest."

# ╔═╡ f10966fe-28f5-11eb-0630-753ba509b76e
md"If your qubits were initially in the state :-

`` \frac{1}{\sqrt{8}} |000〉 + \frac{1}{\sqrt{8}} |001〉 + \frac{1}{\sqrt{8}} |010〉 + \frac{1}{\sqrt{8}} |011〉 + \frac{1}{\sqrt{8}} |100〉 + \frac{1}{\sqrt{8}} |101〉 + \frac{1}{\sqrt{8}} |110〉 + \frac{1}{\sqrt{8}} |111〉 ``"

# ╔═╡ fc46d108-28f5-11eb-0a1c-19b2ba4aa3df
md"Then you want them in the state :-"

# ╔═╡ 2a73a70e-28f6-11eb-1af0-0b5f034c2cf4
md"`` 1 × |010〉 ``"

# ╔═╡ 664e40c2-28f6-11eb-1957-5702b452c054
md"That means, ideally, the probability amplitude of `` |010〉 `` being close to `` 1 ``, while of others being close to `` 0 ``."

# ╔═╡ b5687650-28f6-11eb-12d3-eb2ddd6a7a2b
md"Inversion about the mean is a neat trick which helps you achieve that."

# ╔═╡ c966ad98-28f6-11eb-2f80-5fa5c652e0ea
md"### Inversion about the mean"

# ╔═╡ d0ee0d5e-28f6-11eb-38c5-8b634905b9ff
md"Its simple. I'll give an example."

# ╔═╡ e4be8aca-28f6-11eb-0f6b-35d51dcbce35
testmat = rand(10)

# ╔═╡ 210ef6b8-28f7-11eb-3277-a9fe7833f046
md"Lets plot it as a histogram"

# ╔═╡ 2af2a47c-28f7-11eb-1a3c-77de4ea4f692
bar(testmat)

# ╔═╡ f1fbacd6-28f6-11eb-27bc-e31d6628e6db
md"Say, I want to amplify the 4th element. Here's the procedure"

# ╔═╡ 95570f44-4470-11eb-17d4-83ffdf99888e
mean(x) = sum(x)/length(x)

# ╔═╡ 0fcf3598-28f7-11eb-2716-d7974741c9a8
begin
	matamplified = copy(testmat)
	matamplified[4] = -matamplified[4]
	matamplified = (2 .* mean(matamplified)) .- matamplified
end

# ╔═╡ cecd6894-446f-11eb-3ba2-876a7ff2acef
bar(matamplified)

# ╔═╡ e17009b0-28f9-11eb-0000-5db9eb38b481
md"What just happened:-
- In an array, choose the element you want to amplify.
- Flip the sign of that element.
- Then the new array with the element amplified, will have the elements :-
`` Amplified\; array = (2 × mean) - (the\; original\; array\; with\; the\; flipped\; element). ``
"

# ╔═╡ 98810d58-2a4b-11eb-25c2-9991e8613634
md"What if we do it again? Will it get amplified again?"

# ╔═╡ aa9a9d1a-2a4b-11eb-2433-69c46633f83c
begin
	newmatamplified = copy(matamplified)
	newmatamplified[4] = -newmatamplified[4]
	newmatamplified = (2 .* mean(newmatamplified)) .- newmatamplified
end

# ╔═╡ d6ff51ac-2a4b-11eb-10c8-b715525467d3
bar(newmatamplified)

# ╔═╡ bc7e15c4-2a56-11eb-1645-95457df3f2b6
md"So yes, it does amplified again"

# ╔═╡ dfe846d8-2a56-11eb-31cd-d3d400d0c0b2
md"### The Circuit Implementation"

# ╔═╡ f60823d4-2a56-11eb-2552-e540e863a617
md"The circuit for amplification looks like this, if the sign of the desired state is flipped"

# ╔═╡ 350bf890-4450-11eb-21cc-6db807eeb0e1
YaoPlots.plot(put(3, 1:3 => label(chain(3), "U𝜑")))

# ╔═╡ b2964bf8-4450-11eb-298e-9bf5088ef7ac
md"Again, below is my implementation of it."

# ╔═╡ c5c81956-2a59-11eb-3656-d35c46e52f2a
begin
	U𝜑 = chain(3, repeat(H, 1:3), repeat(X, 1:3), put(3=>H), control(1:2, 3=>X), put(3=>H), repeat(X, 1:3), repeat(H, 1:3))
	plot(U𝜑)
end

# ╔═╡ 923205f4-2a5c-11eb-2075-4bda871c8155
md"Combining this with the circuit for flipping, we get the Grovers Search Circuit, to which we feed qubits with _**uniform state**_."

# ╔═╡ d44b8aba-4450-11eb-39bd-e1e784c7c489
YaoPlots.plot(chain(3, put(1:3 => label(chain(3), "U𝑓")), put(1:3 => label(chain(3), "U𝜑")), Measure(3, locs=1:3)))

# ╔═╡ 4fd7213a-4451-11eb-2c68-81536a97de0a
md"Below is the complete circuit implementation. Remember, the input to the circuit is  quits with uniform state."

# ╔═╡ d8fef778-2a5a-11eb-069c-351c0ff94b59
begin
	GroversSearchCircuit = chain(3, put(1:3=>U𝑓), put(1:3=>U𝜑), Measure(3, locs=1:3))
	plot(GroversSearchCircuit)
end

# ╔═╡ 19662fd2-2a5d-11eb-22fd-771b25ebce68
md"The below function plots the measurement function, just pass the measured values to it. You don't need to know its inner mechanisms to use it."

# ╔═╡ d4234cc0-2a89-11eb-0801-4bdffce7f359
md"Below is the implementation of the **_Grover's Search Algorithm_** to find `` |010〉 ``."

# ╔═╡ d2766870-2a5d-11eb-0484-7dc4c494b160
begin
	n = 3
	output = 0
	j = 0
	for i in 1:(2^3)
		input = uniform_state(n)
		global output = input |> GroversSearchCircuit |> r->measure(r, nshots=1000)
		if(output[1] == bit"010") #Checking for |010〉
			break
		end
		global j = i
	end
	plotmeasure(output, j+1)
end

# ╔═╡ 4f8749de-2a8f-11eb-0687-4b44e155f53c
md"You can keep running the above block of code, and you'll find that it takes a maximum of 4 tries to find the state `` |010〉 ``, which would be the worst case. Most of the times, you can find it in the first try!

Which sounds about right."

# ╔═╡ Cell order:
# ╠═2b3d2532-1bac-491a-aeef-d9c29786341c
# ╟─ecb98520-212d-11eb-02e4-f3c89c254998
# ╟─0b9fb54a-212e-11eb-1f1d-8912b444cc0c
# ╟─88c22c5e-2130-11eb-2b73-5fbbbd662df9
# ╟─326c3b46-2140-11eb-333e-27744c831983
# ╠═e3cae266-2140-11eb-12f9-31b294a31586
# ╠═c72bdf32-2140-11eb-1aec-55864296f0d3
# ╟─746f121a-2141-11eb-2fab-5333ec478316
# ╟─b8c52404-2141-11eb-0759-6db99f9ffa3a
# ╠═b47f24e2-2143-11eb-2e85-59a9f7762ddd
# ╟─433f40da-2145-11eb-35c0-6faaad23f32b
# ╟─9971e84e-28df-11eb-25e2-d9c71ae9dc42
# ╟─656cf846-444f-11eb-369e-f7919462e228
# ╟─4d81dbde-444f-11eb-1aae-7b666f5af174
# ╠═1f33e8c2-28e2-11eb-2f62-1fc0b46a05d3
# ╟─9f8330b4-28e2-11eb-1a9d-1dce3076770d
# ╠═bbc3dac6-28e2-11eb-0907-b72813ebe035
# ╠═73b8c0aa-28e4-11eb-2c7c-b3177793ff92
# ╟─72eb8480-28f5-11eb-3315-779a888c1d55
# ╠═089832b4-28f4-11eb-0c43-3de7e3518c9b
# ╟─d53e1414-28ea-11eb-35ab-81897180dd8b
# ╟─cef6748e-28ea-11eb-1956-b9c7e4dd4d2b
# ╟─f10966fe-28f5-11eb-0630-753ba509b76e
# ╟─fc46d108-28f5-11eb-0a1c-19b2ba4aa3df
# ╟─2a73a70e-28f6-11eb-1af0-0b5f034c2cf4
# ╟─664e40c2-28f6-11eb-1957-5702b452c054
# ╟─b5687650-28f6-11eb-12d3-eb2ddd6a7a2b
# ╟─c966ad98-28f6-11eb-2f80-5fa5c652e0ea
# ╟─d0ee0d5e-28f6-11eb-38c5-8b634905b9ff
# ╠═e4be8aca-28f6-11eb-0f6b-35d51dcbce35
# ╟─210ef6b8-28f7-11eb-3277-a9fe7833f046
# ╠═2af2a47c-28f7-11eb-1a3c-77de4ea4f692
# ╟─f1fbacd6-28f6-11eb-27bc-e31d6628e6db
# ╠═95570f44-4470-11eb-17d4-83ffdf99888e
# ╠═0fcf3598-28f7-11eb-2716-d7974741c9a8
# ╠═cecd6894-446f-11eb-3ba2-876a7ff2acef
# ╟─e17009b0-28f9-11eb-0000-5db9eb38b481
# ╟─98810d58-2a4b-11eb-25c2-9991e8613634
# ╠═aa9a9d1a-2a4b-11eb-2433-69c46633f83c
# ╠═d6ff51ac-2a4b-11eb-10c8-b715525467d3
# ╟─bc7e15c4-2a56-11eb-1645-95457df3f2b6
# ╟─dfe846d8-2a56-11eb-31cd-d3d400d0c0b2
# ╟─f60823d4-2a56-11eb-2552-e540e863a617
# ╟─350bf890-4450-11eb-21cc-6db807eeb0e1
# ╟─b2964bf8-4450-11eb-298e-9bf5088ef7ac
# ╠═c5c81956-2a59-11eb-3656-d35c46e52f2a
# ╟─923205f4-2a5c-11eb-2075-4bda871c8155
# ╟─d44b8aba-4450-11eb-39bd-e1e784c7c489
# ╟─4fd7213a-4451-11eb-2c68-81536a97de0a
# ╠═d8fef778-2a5a-11eb-069c-351c0ff94b59
# ╟─19662fd2-2a5d-11eb-22fd-771b25ebce68
# ╠═16d09514-2a5d-11eb-23da-b968d2ae5d16
# ╟─d4234cc0-2a89-11eb-0801-4bdffce7f359
# ╠═d2766870-2a5d-11eb-0484-7dc4c494b160
# ╟─4f8749de-2a8f-11eb-0687-4b44e155f53c


function parse_line(line)
  low = 0
  high = 0
  char = ""
  passw = ""
  start = 1
  idx = 1
  len = length(line)
  while idx <= len
    if line[idx + 1] == '-'
      break
    end

    idx += 1
  end

  low = parse(Int32, line[start:idx])
  idx += 2
  start = idx
  while idx <= len
    if line[idx + 1] == ' '
      break
    end

    idx += 1
  end

  high = parse(Int32, line[start:idx])
  char = line[idx + 2]
  passw = line[idx + 5:end]

  low, high, char, passw
end

function load(input_path)
  map(parse_line, readlines(input_path))
end

function valid_pw_1(spec)
  low, high, char, passw = spec
  count = 0
  idx = 1
  while (count <= high + 1) && (idx <= length(passw))
    if passw[idx] == char
      count += 1
    end

    idx += 1
  end

  low <= count <= high
end

function solve_1(data)
  length(filter(valid_pw_1, data))
end

function valid_pw_2(spec)
  low, high, char, passw = spec
  xor(passw[low] == char, passw[high] == char)
end

function solve_2(data)
  length(filter(valid_pw_2, data))
end

function solve(data)
  println(@time solve_1(data))
  println(@time solve_2(data))
end

data = load("input.txt")
solve(data)


module UnitConversions

export change_energy_unit,
       energy_from,
       energy_to

"""
    energy_to(e, unit)

Changes energy from hartrees to given unit

Suported units include:
  * cm^-1/cm⁻¹/cm-1
  * eV
  * hartree
  * kcal/mol
  * kJ/mol
  * K

If unit is not recognized defaults to hartree.

Source is [Wikipedia](https://en.wikipedia.org/wiki/Hartree)
"""
function energy_to(e, unit)
    if unit in ["cm-1", "cm^-1", "cm⁻¹"]
        return e*219474.6313702
    elseif unit in ["ev", "eV"]
        return e*27.21138602
    elseif unit in ["kcal/mol"]
        return e*627.509474
    elseif unit in ["kJ/mol", "kj/mol"]
        return e*2625.499639
    elseif unit in ["K"]
        return e*315775.13
    else
        return e
    end
end

"""
    energy_from(e, unit)

Changes given energy unit to hartree

Suported units include:
  * cm^-1/cm⁻¹/cm-1
  * eV
  * hartree
  * kcal/mol
  * kJ/mol
  * K

If unit is not recognized defaults to hartree.

Source is [Wikipedia](https://en.wikipedia.org/wiki/Hartree)
"""
function energy_from(e, unit)
    if unit in ["cm-1", "cm^-1", "cm⁻¹"]
        return e/219474.6313702
    elseif unit in ["ev", "eV"]
        return e/27.21138602
    elseif unit in ["kcal/mol"]
        return e/627.509474
    elseif unit in ["kJ/mol", "kj/mol"]
        return e/2625.499639
    elseif unit in ["K"]
        return e/315775.13
    else
        return e
    end
end

"""
    change_energy(e, from, to)

Changes energy unit

Suported units include:
  * cm^-1/cm⁻¹/cm-1
  * eV
  * hartree
  * kcal/mol
  * kJ/mol
  * K

If unit is not recognized defaults to hartree.

Source is [Wikipedia](https://en.wikipedia.org/wiki/Hartree)
"""
function change_energy_unit(e, from, to)
    tmp = energy_from(e, from)
    return energy_to(tmp,to)
end

end #module


module LatentHeat

# If you want to add a new method here, feel free to do so.
# Remember to also export the function name in GeoParams.jl (in addition to here)

using Parameters, LaTeXStrings, Unitful
using ..Units
using GeoParams: AbstractMaterialParam
using ..MaterialParameters: MaterialParamsInfo
import Base.show, GeoParams.param_info

abstract type AbstractLatentHeat{T} <: AbstractMaterialParam end

export  compute_latent_heat,                  # calculation routines
        param_info,
        ConstantLatentHeat                  # constant

# Constant  -------------------------------------------------------
"""
    ConstantLatentHeat(Q_L=400kJ/kg)

Set a constant latent heat:
```math
    Q_L  = cst
```
where ``Q_L`` is the latent heat [``kJ/kg``].
"""
@with_kw_noshow struct ConstantLatentHeat{T,U} <: AbstractLatentHeat{T}
    Q_L::GeoUnit{T,U}         =   400kJ/kg                # Latent heat
end
ConstantLatentHeat(a...) = ConstantLatentHeat(convert.(GeoUnit,a)...)

function param_info(s::ConstantLatentHeat) # info about the struct
    return MaterialParamsInfo(Equation = L"Q_L = cst")
end

# Calculation routine
function compute_latent_heat(s::ConstantLatentHeat)
    @unpack_val Q_L   = s

    return Q_L
end

# Print info
function show(io::IO, g::ConstantLatentHeat)
    print(io, "Constant latent heat: Q_L=$(g.Q_L.val)")
end
#-------------------------------------------------------------------------

# Help info for the calculation routines
"""
    Ql = compute_latent_heat(s:<AbstractLatentHeat)

Returns the latent heat `Q_L`

"""
compute_latent_heat()

end


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

using Markdown
using InteractiveUtils

# ╔═╡ 8d2a2140-4d6b-11eb-1ed0-5dfeba2603af
begin
	using Pkg
    Pkg.activate(pwd())
end

# ╔═╡ b9452d10-4d6b-11eb-1074-83294cc14eec
begin
	using CSV
	using GraphIO
	using PlutoUI
	using GraphPlot
	using DataFrames
	using LightGraphs
	using SimpleWeightedGraphs
end

# ╔═╡ a4778db2-4d6b-11eb-0e97-c368743a47ea
md"""
# Data Mining
## Semester Project - Clustering by Density
### Aadam - CS1945
"""

# ╔═╡ b046df70-4e5a-11eb-350f-d1abb711d409
md"""
## Utility Functions
"""

# ╔═╡ 822e6af0-4d73-11eb-32e2-216f0814a3c0
# Convert string keys into integer mappings
function str_int(array)
	d = Dict{String, Int}()
	for (i, s) in enumerate(array)
		push!(d, s => i)
	end
	d
end

# ╔═╡ f0bd31ee-4d77-11eb-2834-85e4981d8680
# Return key of dictionary containing value 'i'
function get_key(i, d)
	for (k,v) in d
		if v==i
			return k
		end
	end
end

# ╔═╡ f60d9610-4d75-11eb-002e-6f23a07f66ad
# Return a cleaned DataFrame from a CSV file
function get_df(path)
	df = CSV.read(path, DataFrame, datarow=1)
	d = str_int(union(df.Column1, df.Column2))
	df.sources = [d[i] for i in df.Column1]
	df.destinations = [d[i] for i in df.Column2]
	rename!(df, :Column3 => :weights)
	return df, d
end

# ╔═╡ 097a9b52-4e37-11eb-2afc-0102d9e2c69d
# Neighbors of a cluster are the nodes connected to any node of cluster but
# not part of the cluster.
function get_neighbors(g, c, excluded)
	neighbors_set = Set()

	for n in c
		union!(neighbors_set, neighbors(g, n))
	end

	return collect(setdiff(setdiff(neighbors_set, c), excluded))
end

# ╔═╡ 30c41780-4e5b-11eb-0508-3b2e824a0607
md"""
The priority is determined based on two measures
1. the sum of the weights of the edges between a neighbor and the cluster,
2. the number of edges between a neighbor and the cluster.
"""

# ╔═╡ d4c26d30-4e3f-11eb-07d2-2d722ea4c878
function count_edges(g, cluster, n)
	count = 0
	for c in cluster
		count += has_edge(g, c, n)
	end
	return count
end

# ╔═╡ 95d86970-4e45-11eb-0929-692e7fab4639
# Remove nodes from the graph that are already in the cluster
function rem_nodes(g, cluster)
	for c in cluster
		rem_vertex!(g, c)
	end
	g
end

# ╔═╡ 7b666270-4e5b-11eb-1f65-b5eafbc71680
md"""
**Density** is given as:

$d = \frac{2 \times \left|E\right|}{\left|N\right| \times (\left|N\right| - 1)}$
"""

# ╔═╡ 3df86fd0-4e3f-11eb-3a9c-1b73eabf5568
function density(N, E)
	return (2 * E)/(N * (N - 1))
end

# ╔═╡ c063ab80-4e5b-11eb-283f-6749471fae2d
md"""
To determine whether node is part of periphery we use **cluster property** `cp` of node. It is given as:

$cp = \frac{\left|E_c\right|}{d \times \left|N_c\right|}$
"""

# ╔═╡ ab04f4e0-4e49-11eb-19be-49390f4285e3
function periphery(N, E, d)
	return E/(d * N)
end

# ╔═╡ eba1e8b0-4e57-11eb-1cee-252cd18c350c
md"""
## Algorithm
- Start with a single node as a cluster
- Grow cluster by adding nodes from neighbors one by one
- Continue expanding cluster as long as following 2 conditions are satisfied
  - Density of cluster > = d’
  - Node is in periphery of cluster
- Remove Cluster from graph
- Apply same procedure to rest of nodes to find other clusters in graph.

---

We will follow the algorithm as shown below:
$(LocalResource("algo.png"))
"""

# ╔═╡ ba80451e-4d6b-11eb-0949-6de777cd3780
filename = joinpath("data", "PPI-I.txt")

# ╔═╡ 5655df00-4d76-11eb-01d6-9d21ce3aa823
df, d = get_df(filename)

# ╔═╡ baf68690-4d6b-11eb-3151-b5e91d478309
g = SimpleWeightedGraph(
	Vector(df.sources),
	Vector(df.destinations),
	Vector(df.weights)
)

# ╔═╡ ebca8580-4e3a-11eb-2a8b-711f8bc91692
function get_priorities(graph, cluster, neighbors, edge_weights)
	m1 = Vector()
	m2 = Vector()

	for n in neighbors
		weight_sum = 0
		edge_count = 0

		for c in cluster
			weight_sum += edge_weights[n, c]
			edge_count += has_edge(g, n, c)
		end
		push!(m1, weight_sum)
		push!(m2, edge_count)
	end
	return m1, m2
end

# ╔═╡ 843b4660-4d7d-11eb-387c-6ddb73c11e73
function density_clustering(G, dʼ, cpʼ)
	g = deepcopy(G)
	clusters = Vector()

	# Δ(g) returns highest node degree (LightGraphs)
	while (Δ(g) != 0)

		edge_weights = adjacency_matrix(g) * adjacency_matrix(g)
		for i in 1:size(edge_weights)[1]
			edge_weights[i, i] = 0
		end
		node_weights = degree_matrix(g)

		highest_nw = findall(node_weights .== maximum(node_weights))[1]
		starting_node = highest_nw != 0 ? highest_nw[1] : Δ(g)

		cluster = Vector()
		push!(cluster, starting_node)
		cluster_edges = 0
		excluded = Set()

		while true
			cluster_neighbors = get_neighbors(g, cluster, excluded)
			m1, m2 = get_priorities(g, cluster, cluster_neighbors, edge_weights)
			sorted_priorities = sortperm(m1)

			if (isempty(sorted_priorities))
				break
			end

			@label addNode
			highest_priority = pop!(sorted_priorities)
			highest_node = cluster_neighbors[highest_priority]

			push!(cluster, highest_node)
			cluster_edges += count_edges(g, cluster, highest_node)

			d = density(length(cluster), cluster_edges)
			cp = periphery(length(cluster), count_edges(g, cluster, highest_node), d)

			println("Density: $d and CP: $cp")

			if (d < dʼ && cp < cpʼ)
				push!(excluded, pop!(cluster))
				if (!isempty(sorted_priorities))
					println("Remaining Neighbors: $(length(sorted_priorities))")
					@goto addNode
				else
					println("No neighbors left")
					break
				end
			end
		end

		push!(clusters, cluster)
		println("Added $(length(clusters)) clusters.")
		g = rem_nodes(g, cluster)
	end
	[c for c in clusters if length(c) > 2]
end

# ╔═╡ 845006e0-4d7d-11eb-1fe7-1d59f31b1ca6
clusters = density_clustering(g, 0.6, 0.6)

# ╔═╡ d9f94720-4e43-11eb-063f-49c9038aa982
# gplot(g)

# ╔═╡ 822fb7b0-4e69-11eb-3cd4-07f9188ca131
md"""
### Clusters
"""

# ╔═╡ 2710db30-4e6d-11eb-2441-b7d19fe74697
with_terminal() do
    for (i, c) in enumerate(clusters)
		println("Cluster $i: $(convert.(Int, c))")
		println()
	end
end

# ╔═╡ e1bb91a0-4e6d-11eb-1214-09f5dcf2e4a2
md"""
#### Save to File
"""

# ╔═╡ 7f986070-4e68-11eb-16a3-93a29ab63135
output_file = joinpath("output", "clusters.txt")

# ╔═╡ da256032-4e43-11eb-3690-c186bee53f59
open(output_file, "w") do io
	for c in clusters
		println(io, convert.(Int, c))
		println(io)
	end
end

# ╔═╡ 409381b0-4e6e-11eb-1654-21a675b7eb7e
md"""
Save clusters with their original node labels.
"""

# ╔═╡ f1324f20-4e6d-11eb-21fc-e3461700c517
output_file_names = joinpath("output", "named_clusters.txt")

# ╔═╡ 06fcec20-4e6e-11eb-3a44-f754f81f3290
open(output_file_names, "w") do io
	for c in clusters
		println(io, [get_key(i, d) for i in c])
		println(io)
	end
end

# ╔═╡ Cell order:
# ╟─8d2a2140-4d6b-11eb-1ed0-5dfeba2603af
# ╟─a4778db2-4d6b-11eb-0e97-c368743a47ea
# ╠═b9452d10-4d6b-11eb-1074-83294cc14eec
# ╟─b046df70-4e5a-11eb-350f-d1abb711d409
# ╠═822e6af0-4d73-11eb-32e2-216f0814a3c0
# ╠═f0bd31ee-4d77-11eb-2834-85e4981d8680
# ╠═f60d9610-4d75-11eb-002e-6f23a07f66ad
# ╠═097a9b52-4e37-11eb-2afc-0102d9e2c69d
# ╟─30c41780-4e5b-11eb-0508-3b2e824a0607
# ╠═ebca8580-4e3a-11eb-2a8b-711f8bc91692
# ╠═d4c26d30-4e3f-11eb-07d2-2d722ea4c878
# ╠═95d86970-4e45-11eb-0929-692e7fab4639
# ╟─7b666270-4e5b-11eb-1f65-b5eafbc71680
# ╠═3df86fd0-4e3f-11eb-3a9c-1b73eabf5568
# ╟─c063ab80-4e5b-11eb-283f-6749471fae2d
# ╠═ab04f4e0-4e49-11eb-19be-49390f4285e3
# ╟─eba1e8b0-4e57-11eb-1cee-252cd18c350c
# ╠═843b4660-4d7d-11eb-387c-6ddb73c11e73
# ╠═ba80451e-4d6b-11eb-0949-6de777cd3780
# ╠═5655df00-4d76-11eb-01d6-9d21ce3aa823
# ╠═baf68690-4d6b-11eb-3151-b5e91d478309
# ╠═845006e0-4d7d-11eb-1fe7-1d59f31b1ca6
# ╠═d9f94720-4e43-11eb-063f-49c9038aa982
# ╟─822fb7b0-4e69-11eb-3cd4-07f9188ca131
# ╟─2710db30-4e6d-11eb-2441-b7d19fe74697
# ╟─e1bb91a0-4e6d-11eb-1214-09f5dcf2e4a2
# ╠═7f986070-4e68-11eb-16a3-93a29ab63135
# ╠═da256032-4e43-11eb-3690-c186bee53f59
# ╟─409381b0-4e6e-11eb-1654-21a675b7eb7e
# ╠═f1324f20-4e6d-11eb-21fc-e3461700c517
# ╠═06fcec20-4e6e-11eb-3a44-f754f81f3290


"""
    IPR = Inverse Participation Ratio
"""
function get_IPRs(problem)
    n_modes = get_number_modes(problem)
    IPRs = zeros(n_modes)
    Threads.@threads for n = 1:n_modes
        Ψ² = get_ψ²(problem, n)
        Ψ⁴ = Ψ² .^ 2
        IPRs[n] = sum(Ψ⁴) / sum(Ψ²)
    end
    return IPRs
end
function get_PRs(problem)
    IPRs = get_IPRs(problem)
    return 1.0 ./ IPRs
end

function get_number_modes(problem::LinearOptics{Scalar})
    return problem.atoms.N
end
"""
    get_spectrum(problem; forceComputation=false)
return ωₙ, Γₙ
"""
function get_spectrum(problem; forceComputation = false)
    @debug "start: get spectrum"
    ωₙ, Γₙ = zeros(problem.atoms.N), zeros(problem.atoms.N)

    if is_spectrum_NOT_available(problem) || forceComputation
        H = get_interaction_matrix(problem)
        spectrum = eigen(H)

        problem.spectrum[:λ] = spectrum.values
        problem.spectrum[:ψ] = spectrum.vectors
        make_spectrum_available(problem)

        ωₙ, Γₙ = imag.(spectrum.values), -real.(spectrum.values)
        if any(Γₙ .< 0)
            @warn "some Γₙ were negatives and ignored without further investigation"
            Γₙ = abs.(Γₙ)
        end
    else
        ωₙ, Γₙ = imag.(problem.spectrum[:λ]), -real.(problem.spectrum[:λ])
    end

    @debug "end  : get spectrum"
    return ωₙ, Γₙ
end

function is_spectrum_NOT_available(problem)
    if problem.spectrum[:isSpectrumAvailable]
        return false
    else
        return true
    end
end
function make_spectrum_available(problem)
    problem.spectrum[:isSpectrumAvailable] = true
end

function get_ψ(problem::LinearOptics, n::Integer)
    ψ = view(problem.spectrum[:ψ], :, n)
    return ψ
end
function get_ψ²(problem::LinearOptics, n::Integer)
    ψ = problem.spectrum[:ψ][:, n]
    return abs2.(ψ)
end

"""
    get_localization_length(LinearOptics; forceComputation=false)

Main function to obtain the `Localization Length`, ξ, and its `Coefficient of Determination`, R1 (R¹).
"""
function get_localization_length(problem::LinearOptics; forceComputation = false)
    if is_localization_NOT_available(problem) && forceComputation == false
        return problem.data[:ξ], problem.data[:R1]
    end
    N = problem.atoms.N
    ξₙ = zeros(N)
    R¹ₙ = zeros(N)

    # create spectrum if neeeded
    get_spectrum(problem)

    for n = 1:N # faster without Threads.@threads
        DCM, ψ² = get_spatial_profile_single_mode(problem, n)
        ξₙ[n], R¹ₙ[n] = get_single_ξ_and_R1(DCM, ψ²)
    end
    problem.data[:ξ], problem.data[:R1] = ξₙ, R¹ₙ
    return ξₙ, R¹ₙ
end

function is_localization_NOT_available(problem)
    if haskey(problem.data, :ξ) && haskey(problem.data, :R1)
        return true
    else
        return false
    end

end

function get_spatial_profile_single_mode(problem, mode_index::Integer)
    r = problem.atoms.r
    try
        ψ²ₙ = get_ψ²(problem, mode_index)
        r_cm = get_coordinates_of_center_of_mass(r, ψ²ₙ)

        # Specific for the problem (ones needs to define this function)
        DCM = get_Distances_from_r_to_CM(r, r_cm)

        sort_spatial_profile!(DCM, ψ²ₙ)
        return DCM, ψ²ₙ
    catch
        @error(
            "Probably you tried to access a spectrum not created (run `get_spectrum(problem)`). Or `mode_index` is not valid"
        )
    end
end
"""
    get_coordinates_of_center_of_mass(r, Ψ²_mode)
"""
function get_coordinates_of_center_of_mass(r, Ψ²_mode)
    N = length(Ψ²_mode)
    dimensions = size(r, 1)
    r_cm = zeros(dimensions)
    j = 1
    for c in eachcol(r)
        r_cm .+= c .* Ψ²_mode[j]
        j += 1
    end
    return r_cm ./ sum(Ψ²_mode)
end

"""
    get_Distances_from_r_to_CM(r, r_CM)

YOU need to specify for how to get the distance from points.
By default, I compute the Canonical Euclidian Distance
"""
get_Distances_from_r_to_CM(r, r_CM) = get_Distance_A_to_b(r, r_CM)

function sort_spatial_profile!(DCM, ψ²ₙ)
    idx = sortperm(DCM)
    DCM[:] = DCM[idx]
    ψ²ₙ[:] = ψ²ₙ[idx]
    return nothing
end

### --------------- Classification r---------------
"""
    classify_modes(problem)
returns a tuple `(loc, sub, super)` with indices
"""
function classify_modes(problem; forceComputation = false)
    ωₙ, Γₙ = get_spectrum(problem; forceComputation = forceComputation)
    ξₙ, R¹ₙ = get_localization_length(problem; forceComputation = forceComputation)

    localized_modes = findall((Γₙ .< Γ) .* (R¹ₙ .≥ R1_threshold))
    sub_radiant_modes = findall((Γₙ .< Γ) .* (R¹ₙ .< R1_threshold))
    super_radiant_modes = findall(Γₙ .> Γ)
    return (loc = localized_modes, sub = sub_radiant_modes, super = super_radiant_modes)
end


"""
$(SIGNATURES)

Implements a sparse regression, given an `AbstractOptimizer` or `AbstractSubspaceOptimizer`.
`X` denotes the coefficient matrix, `A` the design matrix and `Y` the matrix of observed or target values.
`X` can be derived via `init(opt, A, Y)`.
`maxiter` indicates the maximum iterations for each call of the optimizer, `abstol` the absolute tolerance of
the difference between iterations in the 2 norm. If the optimizer is called with a `Vector` of thresholds, each `maxiter` indicates
the maximum iterations for each threshold.

If `progress` is set to `true`, a progressbar will be available. `progress_outer` and `progress_offset` are used to compute the initial offset of the
progressbar.

If used with a `Vector` of thresholds, the functions `f` with signature `f(X, A, Y)` and `g` with signature `g(x, threshold) = G(f(X, A, Y))` with the arguments given as stated above can be passed in. These are
used for finding the pareto-optimal solution to the sparse regression.
"""
function sparse_regression!(X, A, Y, opt::AbstractOptimizer{T};
    maxiter::Int = maximum(size(A)),
    abstol = eps(eltype(T)), progress::Bool = false,
    f::Function = F(opt),
    g::Function = G(opt),
    progress_outer::Int = 1, progress_offset::Int = 0, kwargs...) where T

    # Closure for the pareto function
    fg(x, A, y, lambda) = (g∘f)(x, A, y, lambda)

    # TODO Tmp Result
    X_tmp = similar(X)
    X_tmp .= X

    λ = get_threshold(opt)
    λ = issorted(λ) ? λ : sort(λ)
    λs = zeros(eltype(λ), size(Y,2))

    if progress
        progress =  init_progress(opt, X, A, Y, length(λ)*progress_outer, progress_offset)
    else
        progress = nothing
    end

    obj = zero(eltype(X))
    objtmp = zero(eltype(X))
    sparsity = zero(eltype(X))
    sparsitytmp = zero(eltype(X))

    @views for (i,λi) in enumerate(λ)
        init!(X_tmp, opt, A, Y)

        opt(X_tmp, A, Y, λi, maxiter = maxiter, abstol = abstol, f = f, g = g)

        # Increasing the threshold makes no sense
        all(iszero(X_tmp)) && break

        for j in 1:size(Y, 2)
            if fg(X_tmp[:, j], A, Y[:, j], λi) < fg(X[:, j], A, Y[:, j], λi)
                λs[j] =λi
                X[:, j] .= X_tmp[:, j]
            end
        end

        if !isnothing(progress)
            sparsity, obj = f(X, A, Y, λi)
            sparsitytmp, objtmp = f(X_tmp, A, Y, λi)

            ProgressMeter.next!(
            progress;
            showvalues = [
                (:Threshold, λi), (Symbol("Best Objective"), obj), (Symbol("Best Sparsity"), sparsity),
                (Symbol("Current Objective"), objtmp), (Symbol("Current Sparsity"), sparsitytmp)
            ]
            )
        end
    end

    for i in 1:size(Y, 2)
        @views clip_by_threshold!(X[:, i], λs[i])
    end

    return λs
end


using LinearAlgebra
using FileIO
using Makie

const TOLERANCE = 1e-7
N = 51
ϵ = 1e-7

"""
get_rotation_axis(i, n)

Calculates a unit quaternion corresponding to the axis of a 3D rotation with
the given initial and end unit vectors.
"""
function get_rotation_axis(i, n)
    u = normalize(cross(i, n))
    # The angle of rotation
    θ = acos(dot(i, n))
    u, θ/2
end

"""
build_surface(scene, points, color; transparency, shading)

Builds a surface with the given scene, points, color, transparency
and shading.
"""
function build_surface(
    scene,
    points,
    color;
    transparency = true,
    shading = false,
)
    surface!(
        scene,
        @lift($points[:, :, 1]),
        @lift($points[:, :, 2]),
        @lift($points[:, :, 3]),
        color = color,
        transparency = transparency,
        shading = shading,
    )
end

f(x, y) = (-x .* exp.(-x .^ 2 .- (y') .^ 2)) .* 4
f(A) = begin
    x, y = eachcol(A)
    (-x .* exp.(-x .^ 2 .- y .^ 2)) .* 4
end
d(Φ, A) = begin
    rows, cols = size(A)
    D = Matrix{Float64}(undef, rows, cols)
    E = Matrix{Float64}(I, cols, cols) .* ϵ
    for (i, v) in enumerate(eachrow(A))
        P = repeat(v, 1, cols)
        P′ = P + E
        D[i, :] .= (f(transpose(P′)) - f(transpose(P))) ./ ϵ
    end
    D
end
ξΦ(Φ, ξ, A) = begin
    derivative = d(Φ, A)
    D = similar(derivative)
    for (i, v) in enumerate(eachrow(derivative))
        D[i, :] .= v .* ξ
    end
    D
end

lspace = range(-2, stop = 2, length = N)
points = f(lspace, lspace)

universe = surface(lspace,
                   lspace,
                   f(lspace, lspace),
                   colormap = :rainbow,
                   show_axis = false,
                   resolution = (360, 360),
                   backgroundcolor = :royalblue)
xm, ym, zm = minimum(scene_limits(universe))

slu, olu = textslider(-2.0:0.01:2.0, "u", start = -0.5)
slv, olv = textslider(-2.0:0.01:2.0, "v", start = -0.09)
slξ, olξ = textslider(0.0:0.01:2pi, "ξ", start = 0)

tangent_tail(x) =
    [Point3f0(x[i, 1], x[i, 2], f(x[i, 1], x[i, 2])) for i = 1:size(x, 1)]
tangent_head(x, f, ξ) = [
    Point3f0(
        ξ[1] * ϵ,
        ξ[2] * ϵ,
        f(x[i, 1] + ξ[1] * ϵ, x[i, 2] + ξ[2] * ϵ) - f(x[i, 1], x[i, 2]),
    ) ./ ϵ for i = 1:size(x, 1)
]
stiff_tail(x) = [Point3f0(x[i, 1], x[i, 2], zm) for i = 1:size(x, 1)]
stiff_head(x, f, ξ) = begin
    derivative = ξΦ(f, ξ, x)
    [
        Point3f0(derivative[i, 1], derivative[i, 2], 0)
        for i = 1:size(x, 1)
    ]
end
normal(x, f) = begin
    tx = tangent_head(x, f, [1; 0])
    ty = tangent_head(x, f, [0; 1])
    [normalize(cross(tx[i], ty[i])) for i = 1:size(x, 1)]
end
plane(x, f, ξ) = begin
    t = tangent_tail(x)
    tx = tangent_head(x, f, [ξ[1]*0.1+1; 0])
    ty = tangent_head(x, f, [0; ξ[2]*0.1+1])
    surf(i) = begin
        p = Array{Float64}(undef, 2, 2, 3)
        p[1, 1, :] = normalize(tx[i] + ty[i] .+ 1e-5 * rand()) + t[i]
        p[1, 2, :] = normalize(-tx[i] + ty[i] .+ 1e-5 * rand()) + t[i]
        p[2, 1, :] = normalize(tx[i] - ty[i] .+ 1e-5 * rand()) + t[i]
        p[2, 2, :] = normalize(-tx[i] - ty[i] .+ 1e-5 * rand()) + t[i]
        p
    end
    [surf(i) for i = 1:size(x, 1)]
end
number = 1
P = @lift([$olu $olv])
tangent_arrowtail = @lift(tangent_tail($P))
tangent_arrowhead = @lift(tangent_head($P, f, [cos($olξ); sin($olξ)]))
yconst_tangent_arrowhead = @lift(tangent_head($P, f, [cos($olξ); 0]))
xconst_tangent_arrowhead = @lift(tangent_head($P, f, [0; sin($olξ)]))
normal_arrowhead = @lift(normal($P, f))
stiff_arrowtail = @lift(stiff_tail($P))
ξarrowhead = @lift(stiff_head($P, f, [cos($olξ); sin($olξ)]))
yconst_stiff_arrowhead = @lift(stiff_head($P, f, [cos($olξ); 0]))
xconst_stiff_arrowhead = @lift(stiff_head($P, f, [0; sin($olξ)]))

arrowmesh = load("test/data/boquarrow.obj")
arrow = mesh!(universe, arrowmesh, color = :gold)[end]
scale!(arrow, 0.02, 0.02, 0.02)
on(olu) do val
    i = [0; 1; 0]
    n = normalize(to_value(normal_arrowhead)[1])
    u, θ = get_rotation_axis(i, n)
    rotate!(arrow, Quaternionf0((sin(θ).*u)..., cos(θ)))
    x = to_value(olu)
    y = to_value(olv)
    z = f(x, y)
    translate!(arrow, Vec3f0(x, y, z))
end

on(olv) do val
    i = [0; 1; 0]
    n = normalize(to_value(normal_arrowhead)[1])
    u, θ = get_rotation_axis(i, n)
    #rotate!(arrow, Vec3f0(u...), θ)
    rotate!(arrow, Quaternionf0((sin(θ).*u)..., cos(θ)))
    x = to_value(olu)
    y = to_value(olv)
    z = f(x, y)
    translate!(arrow, Vec3f0(x, y, z))
end

# Instantiate a horizontal box for holding the visuals and the controls
#box = vbox(slu, slv, slξ)
#scene = hbox(universe, box, parent = Scene(resolution = (1920, 1080)))
contour!(
    universe,
    lspace,
    lspace,
    f(lspace, lspace),
    levels = 15,
    linewidth = 2,
    transformation = (:xy, zm),
    colormap = :cinferno,
)
wireframe!(
    universe,
    lspace,
    lspace,
    f(lspace, lspace),
    overdraw = true,
    transparency = true,
    color = (:black, 0.1),
)
image = load("gallery/porta.jpg")
for i in 1:number
    build_surface(universe,
                  @lift(plane($P, f, [cos($olξ); sin($olξ)])[i]),
                  image)
end
arrows!(
    universe,
    tangent_arrowtail,
    tangent_arrowhead,
    arrowsize = 0.1,
    linecolor = (:white, 2.0),
    linewidth = 3,
)
arrows!(
    universe,
    tangent_arrowtail,
    xconst_tangent_arrowhead,
    arrowsize = 0.1,
    linecolor = (:red, 2.0),
    linewidth = 3,
)
arrows!(
    universe,
    tangent_arrowtail,
    yconst_tangent_arrowhead,
    arrowsize = 0.1,
    linecolor = (:green, 2.0),
    linewidth = 3,
)

arrows!(
    universe,
    stiff_arrowtail,
    ξarrowhead,
    arrowsize = 0.1,
    linecolor = (:white, 2.0),
    linewidth = 3,
)
arrows!(
    universe,
    stiff_arrowtail,
    xconst_stiff_arrowhead,
    arrowsize = 0.1,
    linecolor = (:red, 2.0),
    linewidth = 3
)
arrows!(
    universe,
    stiff_arrowtail,
    yconst_stiff_arrowhead,
    arrowsize = 0.1,
    linecolor = (:green, 2.0),
    linewidth = 3,
)
center!(universe) # center the Scene on the display
# update eye position
eye_position, lookat = Vec3f0(3, 3, 3), Vec3f0(0)
update_cam!(universe, eye_position, lookat)
universe.center = false # prevent scene from recentering on display
#Makie.save("gallery/surfaces.jpg", scene)
display(universe)

record(universe, "gallery/surfaces.gif") do io
    frames = 90
    for i = 1:frames
        progress = i / frames
        olu[] = cos(progress * 2pi + pi/2)
        olv[] = sin(progress * 2pi + pi/2)
        olξ[] = progress * 2pi # animate scene
        eye_position = Vec3f0(1 - sin(progress * 2pi),
                              1 - sin(progress * 2pi),
                              1 + sin(progress * 2pi)) .* 3 .+ 0.00001
        update_cam!(universe, eye_position, to_value(tangent_arrowtail)[1])
        recordframe!(io) # record a new frame
    end
end


function orig_eval(exp::Expr)
    xs = 0.0:0.1:5
    ys = 0.0:0.1:5
    ls = []
    fun = @eval (xg, yg) -> $exp
    # fun = eval(exp)
    for x in xs
        for y in ys
            if Base.invokelatest(fun, x, y)
                push!(ls, (x, y))
            end
        end
    end
    return ls
end

function get_pose_from_fxn!(exp::Expr, ls::Vector{Any})
    xs = 0.0:0.1:5
    ys = 0.0:0.1:5
    fun = @eval (xg, yg) -> $exp
    for x in xs
        for y in ys
            if Base.invokelatest(fun, x, y)
                push!(ls, (x, y))
            end
        end
    end
end

function get_poses_from_fxn(exps::Vector{Expr})
    ls = []
    for exp in exps
        get_pose_from_fxn!(exp, ls)
    end
    return collect(Set(ls))
end

function and_get_pose_from_fxn(exps::Vector{Expr})
    xs = 0.0:0.1:5
    ys = 0.0:0.1:5
    ls = []
    for xg in xs
        for yg in ys
            sat = true
            for exp in exps
                @eval f(xg, yg)=$exp
                sat = sat && f(xg, yg)
            end
            if sat push!(ls, (xg, yg)) end
        end
    end
    return ls
end

function or_get_pose_from_fxn(exps::Vector{Expr})
    xs = 0.0:0.1:5
    ys = 0.0:0.1:5
    ls = []
    for exp in exps
        for xg in xs
            for yg in ys
                @eval f(xg, yg)=$exp
                if f(xg, yg)
                    push!(ls, (xg, yg))
                end
            end
        end
        println("length ls ",length(ls))
    end
    return ls
end
# xg=0.; yg=0.
get_poses_from_fxn([:(0.0<=xg<=0.05&&0.0<=yg<=1.5) , :(2xg+3yg>=0.7&&2xg+3yg<=1.7)] )

# orig_eval(:(0.0<=xg<=0.05&&0.0<=yg<=1.5))


export estimateCoilSensitivities, mergeChannels, espirit, estimateCoilSensitivitiesFixedPoint

"""
    `s = estimateCoilSensitivities(I::AbstractArray{T,5})`

Estimates the coil sensitivity based on a reconstruction where the data
from each coil has been reconstructed individually.
Returns a 5D array.
"""
function estimateCoilSensitivities(I::AbstractArray{T,5}, thresh=1.e-2) where T
  nx,ny,nz,ne,numChan = size(I)

  I_sum = sqrt.( sum(abs.(I).^2, dims=5) ).+eps()
  I_max = maximum(abs.(I_sum))
  msk = zeros(size(I_sum))
  msk[findall(x->x>thresh*I_max,I_sum)] .= 1

  s = zeros(eltype(I),size(I))
  for i=1:numChan
      s[:,:,:,:,i] = msk .* I[:,:,:,:,i] ./ I_sum
  end

  return s
end

"""
    `I4 = mergeChannels(I::AbstractArray{T,5})`

Merge the channels of a multi-coil reconstruction.
Returns a 4D array.
"""
mergeChannels(I::AbstractArray{T,5}) where T = sqrt.(sum(abs.(I).^2,dims=5))

"""
    `maps = espirit(acqData::AcquisitionData, ksize::NTuple{2,Int64}, ncalib::Int64
               ; eigThresh_1::Float64=0.02, eigThresh_2::Float64=0.95)`

obtains coil sensitivities from a calibration area using ESPIRiT
adapted from the MATLAB code by Uecker et al. for the paper'
M. Uecker, P. Lai, MJ Murphy, P. Virtue, M Elad, JM Pauly, SS Vasanawala and M Lustig, "ESPIRiT- an
eigenvalue approach to autocalibrating parallel MRI: Where SENSE meets GRAPPA", Magn Reson Med, 2013

the matlab code can be found at: [http://people.eecs.berkeley.edu/~mlustig/Software.html]

# Arguments
* `acqData::AcquisitionData`  - AcquisitionData
* `ksize::NTuple{2,Int64}`    - size of the k-space kernel
* `ncalib::Int64`             - number of calibration points in each dimension
* `eigThresh_1::Float64=0.02` - threshold for the singular values of the calibration matrix (relative to the largest value)
* `eigThresh_2::Float64=0.95` - threshold of the image space kernels (if no singular value > `eigThresh_2` exists)
                                , the corresponding pixel has a sensitivity of 0.

Returns a 4D array.
"""
function espirit(acqData::AcquisitionData, ksize::NTuple{2,Int64}, ncalib::Int64
                ; eigThresh_1::Float64=0.02, eigThresh_2::Float64=0.95)

  if !isCartesian(trajectory(acqData,1))
    @error "espirit does not yet support non-cartesian sampling"
  end

  nx,ny = acqData.encodingSize[1:2]
  numChan, numSl = numChannels(acqData), numSlices(acqData)
  maps = zeros(ComplexF64,acqData.encodingSize[1],acqData.encodingSize[2],numSl,numChan)

  for slice = 1:numSl
    # form zeropadded array with kspace data
    kdata = zeros(ComplexF64,nx*ny,numChan)
    for coil = 1:numChan
      kdata[acqData.subsampleIndices[1],coil] .= kData(acqData,1,coil,slice)
    end
    kdata = reshape(kdata,nx,ny,numChan)

    calib = crop(kdata,(ncalib,ncalib,numChan))

    maps[:,:,slice,:] .= espirit(calib,(nx,ny),ksize,eigThresh_1=eigThresh_1,eigThresh_2=eigThresh_2)
  end
  return maps
end

"""
`maps = espirit(calibData::Array{ComplexF64,3}, imsize::NTuple{2,Int64}, ksize::NTuple{2,Int64}
                ; eigThresh_1::Float64=0.02, eigThresh_2::Float64=0.95)`
Returns a 3D array.
"""
function espirit(calibData::Array{ComplexF64,3}, imsize::NTuple{2,Int64}, ksize::NTuple{2,Int64}
                ; eigThresh_1::Float64=0.02, eigThresh_2::Float64=0.95)
  sx,sy = imsize
  nc = size(calibData,3)

  # compute calibration matrix, perform SVD and convert right singular vectors
  # into k-space kernels
  k, S = dat2Kernel(calibData,ksize)
  idx = findlast(x->x>=S[1]*eigThresh_1,S) # last singular vector to keep

  # crop kernels and compute eigenvalue decomposition in images space
  # to get sensitivity maps
  M, W = kernelEig(k[:,:,:,1:idx],(sx,sy))

  # sensitivity maps correspond to eigen vectors with a singular value of 1
  msk = zeros(size(W,1),size(W,2),1)
  msk[findall(x->x>eigThresh_2, abs.(W[:,:,end]))] .= 1
  maps = M[:,:,:,end] .* repeat(msk,1,1,nc)

  return maps
end

#
# computes ESPIRiT step I:
#   form calibration matrix from data, perform SVD and convert right singular vectors
#   into k-space kernels
#
function dat2Kernel(data::Array{T,3}, ksize::NTuple{2,Int64}) where T
  sx,sy,nc = size(data)
  imSize=(sx,sy)

  tmp = im2row(data,ksize)
  tsx,tsy,tsz = size(tmp)
  A = reshape(tmp, tsx, tsy*tsz)
  usv = svd(A)
  kernel = reshape(usv.V,ksize[1],ksize[2],nc,size(usv.V,2))
  return kernel, usv.S
end

# computes ESPIRiT step II:
#   eigenvalue decomposition of a k-space kernel in image space.
#
# inputs:
#         kernel - kspace kernels (4D)
#         imSize - size of the image to compute maps
#
# outputs :
#         eigenvecs: images representing the eigenvectors (sx,sy,numChan,numChan)
#         eigenvals: images representing th eigenvalues (sx,sy,numChan)
function kernelEig(kernel::Array{T,4},imsize::NTuple{2,Int64}) where T
  nx,ny,nc,nv = size(kernel)
  ksize = (nx,ny)

  # rotate kernel to order by maximum variance
  k = permutedims(kernel,[1,2,4,3])
  k = reshape(k,nx*ny*nv,nc)

  if size(k,1) < size(k,2)
    usv = svd(k,full=true)
  else
    usv = svd(k)
  end

  k = k*usv.V
  kernel = reshape(k, nx,ny,nv,nc)
  kernel = permutedims(kernel,[1,2,4,3])
  kern2 = zeros(ComplexF64, imsize[1],imsize[2],size(kernel,3),size(kernel,4))
  for n = 1:size(kernel,4)
    kern2[:,:,:,n] .= fft2c( zeropad( conj.(kernel[end:-1:1,end:-1:1,:,n])*sqrt(imsize[1]*imsize[2]), (imsize[1],imsize[2],size(kernel,3)) ) )
  end
  kern2 = kern2/sqrt(prod(ksize))

  eigenVecs = zeros(ComplexF64,imsize[1],imsize[2],nc,min(nc,nv))
  eigenVals = zeros(ComplexF64,imsize[1],imsize[2],min(nc,nv))

  for n = 1:prod(imsize)
    x,y = Tuple( CartesianIndices((imsize[1],imsize[2]))[n] )
    mtx = kern2[x,y,:,:]

    cdv = svd(mtx)
    ph = transpose( repeat( exp.(-1im*angle.(cdv.U[1,:])), 1,size(cdv.U,1) ) )
    C = usv.V*(cdv.U .* ph)
    D = real.(cdv.S)
    eigenVals[x,y,:] .= D[end:-1:1]
    eigenVecs[x,y,:,:] .= C[:,end:-1:1]
  end

  return eigenVecs, eigenVals
end

#
# rearrange data from the image data to form kernels for dat2Kernel
#
function im2row(img::Array{T,3}, winSize::NTuple{2,Int64}) where T
  sx,sy,sz = size(img)
  res = zeros(T,(sx-winSize[1]+1)*(sy-winSize[2]+1),prod(winSize),sz)
  cnt = 0
  for y = 1:winSize[2]
    for x = 1:winSize[1]
      cnt += 1
      res[:,cnt,:] .= reshape(img[x:sx-winSize[1]+x,y:sy-winSize[2]+y,:]
                              ,(sx-winSize[1]+1)*(sy-winSize[2]+1),sz)
    end
  end
  return res
end

#
# crop the central area (of size s) of an array A
#
function crop(A::Array{T,3},s::NTuple{3,Int64}) where T
  nx,ny,nz = size(A)
  idx_x = div(nx,2)-div(s[1],2)+1:div(nx,2)-div(s[1],2)+s[1]
  idx_y = div(ny,2)-div(s[2],2)+1:div(ny,2)-div(s[2],2)+s[2]
  idx_z = div(nz,2)-div(s[3],2)+1:div(nz,2)-div(s[3],2)+s[3]
  return A[idx_x,idx_y,idx_z]
end

#
# zeropoad an array A to a target size s
#
function zeropad(A::Array{T,3},s::NTuple{3,Int64}) where T
  nx,ny,nz = size(A)
  idx_x = div(s[1],2)-div(nx,2)+1:div(s[1],2)-div(nx,2)+nx
  idx_y = div(s[2],2)-div(ny,2)+1:div(s[2],2)-div(ny,2)+ny
  idx_z = div(s[3],2)-div(nz,2)+1:div(s[3],2)-div(nz,2)+nz

  res = zeros(T,s[1],s[2],s[3])
  res[idx_x,idx_y,idx_z] .= A

  return res
end

#
#  apply 2d fft to all slices of a higher dimensional array
#
function fft2c(x::Array{T}) where T
  s = size(x)
  x = reshape(x,s[1],s[2],:)
  res = zeros(ComplexF64,size(x))
  for n=1:size(x,3)
    res[:,:,n] = 1/sqrt(s[1]*s[2])*fftshift(fft(ifftshift(x[:,:,n])))
  end
  return reshape(res,s)
end

#
#  apply 2d ifft to all slices of a higher dimensional array
#
function ifft2c(x::Array{T}) where T
  s = size(x)
  x = reshape(x,s[1],s[2],:)
  res = zeros(ComplexF64,size(x))
  for n=1:size(x,3)
    res[:,:,n] = sqrt(s[1]*s[2])*fftshift(fft(ifftshift(x[:,:,n])))
  end
  return reshape(res,s)
end

function estimateCoilSensitivitiesFixedPoint(acqData::AcquisitionData;
                                             iterations=1, outerIterations=3, cmap = nothing)

  N = acqData.encodingSize

  params = Dict{Symbol, Any}()
  params[:reco] = "standard" # "multiCoil"
  #params[:solver] = "cgnr" #solver
  #params[:iterations] = iterations
  params[:reconSize] = (N,N)
  params[:alpha] = 1.25
  #params[:iterationsInner] = 5
  params[:normalizeReg] = true

  if cmap != nothing
    params[:correctionMap] = cmap
    params[:K] = 15
    params[:m] = 3.0
  end

  Ireco = reconstruction(acqData, params)
  s = estimateCoilSensitivities(IrecoCorr).data

  #if !isempty(coilsens)
  #  params[:senseMaps] = coilsens
  #end

end


### A Pluto.jl notebook ###
# v0.14.0

using Markdown
using InteractiveUtils

# ╔═╡ d5a6bdfa-fbcd-436e-99cd-eadb8f5c63c8
using GMT

# ╔═╡ 89b3b5b0-d071-11eb-3994-7932442f1611
md"
# Ternary plots

To plot points on a ternary diagram at the positions listed in the file ternary.txt (that GMT knows where to find it), with default annotations and gridline spacings, using the specified labeling, do
"

# ╔═╡ 9ffa3892-5e30-4e46-a00a-a1682eebd888
ternary("@ternary.txt", labels=("Clay","Silt","Sand"), marker=:p, show=true)

# ╔═╡ be06e94b-ec5c-442b-9273-d55b6a6fede8
md"
-- Ok, good but a bit dull. What about coloring the points? And if I want to have the axes runing in clock-wise order? And what about adding a percentage symbol to the annotations?

Simple. First we create a colormap and to rotte the axes we use the option `clockwise=true`. Regarding the `%` sign, it requires using the `frame` option and that obliges to be explicit on the axes labels because we are no longer using handy defaults.
"

# ╔═╡ d9a81565-1dc7-482f-ac74-5102b5c79969
# Make use of the knowledge that z ranges berween 0 and 71 (gmtinfo module is a friend)
C = makecpt(T=(0,71));

# ╔═╡ 20979dbf-3d32-4764-975b-286469458b92
ternary("@ternary.txt", frame=(annot=:auto, grid=:a, ticks=:a, alabel="Clay", blabel="Silt", clabel="Sand", suffix=" %"), marker=:p, cmap=C, clockwise=true, show=true)

# ╔═╡ 6c7960c0-d0a4-11eb-3147-f92e4a7436e6
md"
-- Ah, much better, but now I would like to display the above data as an image.

Solution: use the `image=true` option. Note that we may skip the `cmap` option and an automatic `cmap` is compute for us (but one can use whatever cmap we want, just create a colormap with the wished colors)
"

# ╔═╡ ceff64b0-d0a4-11eb-1881-7d9a7b2be78e
ternary("@ternary.txt", frame=(annot=:auto, grid=:a, ticks=:a, alabel="Clay", blabel="Silt", clabel="Sand", suffix=" %"), marker=:p, image=true, clockwise=true, show=true)

# ╔═╡ 4700fb42-d0a5-11eb-3914-53b959143ec5
md"
-- And to overlay some contours?

Add the `contour` option. This option works either with automatically picked parameters or under the user full control (which contours to draw and which to annotate, etc). For simplicity we could use the automatic mode (just set `contour=true`) but the ternary plots may have several short contour lines that would not be annotated because they are too short for the default setting. So, and for demonstration sake, we will use the explicit `contour` form where we set also the distance between the labels.
"

# ╔═╡ c636e63e-d0a5-11eb-2d6a-ab44a74366a1
ternary("@ternary.txt", frame=(annot=:auto, grid=:a, ticks=:a, alabel="Clay", blabel="Silt", clabel="Sand", suffix=" %"), clockwise=true, image=true, contour=(annot=10,cont=5,labels=(distance=3,)), colorbar=true, show=true)

# ╔═╡ f9c30160-d0a5-11eb-18ea-81a47cb85b76
md"
And we can do a `contourf` style plot too, but in this case only the area inside the data cloud is imaged since the method used involves a Delaunay triangulation.
"

# ╔═╡ 161efc10-d0a6-11eb-3d7f-af1d658a3cf0
ternary("@ternary.txt", frame=(annot=:auto, grid=:a, ticks=:a, alabel="Clay", blabel="Silt", clabel="Sand", suffix=" %"), marker=:p, clockwise=true, contourf=(annot=10, cont=5), show=true)

# ╔═╡ Cell order:
# ╟─89b3b5b0-d071-11eb-3994-7932442f1611
# ╠═d5a6bdfa-fbcd-436e-99cd-eadb8f5c63c8
# ╠═9ffa3892-5e30-4e46-a00a-a1682eebd888
# ╟─be06e94b-ec5c-442b-9273-d55b6a6fede8
# ╠═d9a81565-1dc7-482f-ac74-5102b5c79969
# ╠═20979dbf-3d32-4764-975b-286469458b92
# ╟─6c7960c0-d0a4-11eb-3147-f92e4a7436e6
# ╠═ceff64b0-d0a4-11eb-1881-7d9a7b2be78e
# ╟─4700fb42-d0a5-11eb-3914-53b959143ec5
# ╠═c636e63e-d0a5-11eb-2d6a-ab44a74366a1
# ╟─f9c30160-d0a5-11eb-18ea-81a47cb85b76
# ╠═161efc10-d0a6-11eb-3d7f-af1d658a3cf0


### A Pluto.jl notebook ###
# v0.18.1

using Markdown
using InteractiveUtils

# ╔═╡ 7306f20f-9da1-4419-9222-1704fd296ee8
begin
	using ClimateBase, NCDatasets, Downloads, Dates, PlutoUI
	"Done with loading packages"
end

# ╔═╡ 15f947af-0485-433c-a424-ba0c0a5f376a
md"""# Alternative NetCDF Methods

The examples below focus on reading CF-compliant NetCDF datasets.

- [NCDataSets.jl](https://alexander-barth.github.io/NCDatasets.jl/latest/) : loading/writing NetCDF data, as well as doing basic numerics with them.
- [ClimateBase.jl](https://juliaclimate.github.io/ClimateBase.jl/dev/) provides tools to analyze and manipulate climate (spatiotemporal) data.
- [ClimateTools.jl](https://github.com/JuliaClimate/ClimateTools.jl) is a collection of commonly-used tools in Climate science.

_More packages to add : NetCDF.jl, DiskArrays.jl, NCTiles.jl, ..._
"""

# ╔═╡ e112e6e8-50b8-496e-b4ea-8c37a288e4a0
TableOfContents()

# ╔═╡ d9874aec-7c7f-4818-81a8-a5fd520159d5
md"""## Standard Test Case : NCDataSets.jl"""

# ╔═╡ e39f0208-c7a1-44fc-b4b9-07f83a86e8d8
md"""## Standard Test Case : ClimateBase.jl"""

# ╔═╡ 0a92c84f-bc1c-4624-ada3-d4202e9f37fa
md"""## Standard Test Case : ClimateTools.jl

!!! warning
    The `ClimateTools.jl` test are commented out due to the package's use of deprecated package dependency versions -- using these seems to conflict with using of e.g. current version of ClimateBase.jl.
"""

# ╔═╡ d2ae000a-4c5a-4289-aaee-431e064a7aef
md"""
```
let
	using ClimateTools
	fil=joinpath(tempdir(),"sresa1b_ncar_ccsm3-example.nc")

	C=load(fil,"tas")
	Csub =  temporalsubset(C, (2000,05,16,12), (2000,05,16,12))
	poly=[NaN -90.0 0.0 0.0 -90.0 -90.0 ; NaN -90.0 -90.0 0.0 0.0 -90.0]
	spatialsubset(C,poly)
end
```
"""

# ╔═╡ e5ef340e-e087-4b2b-be6e-da6255e3677a
md"""## Extended Testing : ClimateTools.jl"""

# ╔═╡ bd831162-b479-4148-9031-de6529f4bf89
md"""
```
let
	using ClimateTools
	fil=joinpath(tempdir(),"tas_day_MIROC5_piControl_r1i1p1_20000101-20091231.nc")

	if isfile(fil)
		C=load(fil,"tas")
		Csub = resample(C, "JJA") # hardcoded ClimateTools's season
		Csub =  temporalsubset(C, (2000,06,01,12), (2000,06,02,12))
		poly=[NaN -90.0 0.0 0.0 -90.0 -90.0 ; NaN -90.0 -90.0 0.0 0.0 -90.0]
		spatialsubset(C,poly)
	end
end
```
"""

# ╔═╡ af6450d9-d92a-4331-853f-cd19ff72abd2
begin
		url_large_file="http://esgf-data1.diasjp.net/thredds/fileServer/esg_dataroot/cmip5/output1/MIROC/MIROC5/piControl/day/atmos/day/r1i1p1/v20161012/tas/tas_day_MIROC5_piControl_r1i1p1_20000101-20091231.nc"
	md"""## Download Test File

	For extended and performance testing, download the large file ([this url](url_large_file)).
	"""
end

# ╔═╡ 9f0a3ab4-9d79-11ec-33e2-eb9616058d43
begin
	#url="http://esgf-data1.diasjp.net/thredds/fileServer/esg_dataroot/cmip5/output1/MIROC/MIROC5/piControl/day/atmos/day/r1i1p1/v20161012/tas/tas_day_MIROC5_piControl_r1i1p1_20000101-20091231.nc"
	#fil=joinpath(tempdir(),"tas_day_MIROC5_piControl_r1i1p1_20000101-20091231.nc")

	url="https://github.com/JuliaClimate/ClimateTools.jl/raw/master/test/data/sresa1b_ncar_ccsm3-example.nc"
	fil=joinpath(tempdir(),"sresa1b_ncar_ccsm3-example.nc")

	!isfile(fil) ? Downloads.download(url,fil) : nothing
	"File currently used : $(fil)"
end

# ╔═╡ 6c52f938-86db-496c-a1c2-ffdd7836f533
begin
	ds=NCDataset(fil)
	tas_NCDatasets = ds["tas"]
end

# ╔═╡ 88714902-cf5f-4cd7-adb3-443dd6d48c35
begin
	#Tutorial example:
	lats = -90:5:90
	lons = 0:10:359
	t = Date(2000, 3, 15):Month(1):Date(2020, 3, 15)
	dimensions = (Lon(lons), Lat(lats), Ti(t))
	data = rand(36, 37, 241) # same size as `dimensions`
	A = ClimArray(data, dimensions)

	#Reading from file:
	tas_ClimateBase = ncread(fil,"tas")
end

# ╔═╡ d164f293-b571-4218-8411-bf01f52e7a20
md"""## Packages"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
ClimateBase = "35604d93-0fb8-4872-9436-495b01d137e2"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
NCDatasets = "85f8d34a-cbdd-5861-8df4-14fed0d494ab"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
ClimateBase = "~0.15.1"
NCDatasets = "~0.11.9"
PlutoUI = "~0.7.36"
"""

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

julia_version = "1.7.2"
manifest_format = "2.0"

[[deps.AbstractFFTs]]
deps = ["ChainRulesCore", "LinearAlgebra"]
git-tree-sha1 = "6f1d9bc1c08f9f4a8fa92e3ea3cb50153a1b40d4"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.1.0"

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

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

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

[[deps.ArrayInterface]]
deps = ["Compat", "IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "d49f55ff9c7ee06930b0f65b1df2bfa811418475"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "4.0.4"

[[deps.ArrayLayouts]]
deps = ["Compat", "FillArrays", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "a577e27915fdcb3f6b96118b56655b38e3b466f2"
uuid = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
version = "0.4.12"

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

[[deps.AxisAlgorithms]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
git-tree-sha1 = "66771c8d21c8ff5e3a93379480a2307ac36863f7"
uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950"
version = "1.0.1"

[[deps.BandedMatrices]]
deps = ["ArrayLayouts", "Compat", "FillArrays", "LinearAlgebra", "Random", "SparseArrays"]
git-tree-sha1 = "8c83ee44dc9835263760ad4e77ed4eed4b3490c1"
uuid = "aae01518-5342-5314-be14-df237901396f"
version = "0.15.25"

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

[[deps.CFTime]]
deps = ["Dates", "Printf"]
git-tree-sha1 = "bca6cb6ee746e6485ca4535f6cc29cf3579a0f20"
uuid = "179af706-886a-5703-950a-314cd64e0468"
version = "0.1.1"

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

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

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

[[deps.ClimateBase]]
deps = ["Dates", "DimensionalData", "Interpolations", "NCDatasets", "Requires", "SignalDecomposition", "StaticArrays", "Statistics", "StatsBase"]
git-tree-sha1 = "71ff28714da3f02404f8ff4c522301aec74c305a"
uuid = "35604d93-0fb8-4872-9436-495b01d137e2"
version = "0.15.1"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.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 = "44c37b4636bc54afac5c574d2d02b625349d6582"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.41.0"

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

[[deps.ConstructionBase]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f74e9d5388b8620b4cee35d4c5a618dd4dc547f4"
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
version = "1.3.0"

[[deps.DSP]]
deps = ["FFTW", "IterTools", "LinearAlgebra", "Polynomials", "Random", "Reexport", "SpecialFunctions", "Statistics"]
git-tree-sha1 = "2a63cb5fc0e8c1f0f139475ef94228c7441dc7d0"
uuid = "717857b8-e6f2-59f4-9121-6e50c889abd2"
version = "0.6.10"

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

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

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

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

[[deps.DelayEmbeddings]]
deps = ["Distances", "Distributions", "LinearAlgebra", "Neighborhood", "Random", "StaticArrays", "Statistics", "StatsBase"]
git-tree-sha1 = "78fe228cf9f8b4f7badc393cd40b6af8de936479"
uuid = "5732040d-69e3-5649-938a-b6b4f237613f"
version = "2.1.0"

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

[[deps.DensityInterface]]
deps = ["InverseFunctions", "Test"]
git-tree-sha1 = "80c3e8639e3353e5d2912fb3a1916b8455e2494b"
uuid = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
version = "0.4.0"

[[deps.DimensionalData]]
deps = ["Adapt", "ArrayInterface", "ConstructionBase", "Dates", "IntervalSets", "LinearAlgebra", "Random", "RecipesBase", "SparseArrays", "Statistics", "Tables"]
git-tree-sha1 = "40bf62b8912fbe675c2f11ec6ba491e67ae4d5d9"
uuid = "0703355e-b756-11e9-17c0-8b28908087d0"
version = "0.20.4"

[[deps.Distances]]
deps = ["LinearAlgebra", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "3258d0659f812acde79e8a74b11f17ac06d0ca04"
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
version = "0.10.7"

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

[[deps.Distributions]]
deps = ["ChainRulesCore", "DensityInterface", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SparseArrays", "SpecialFunctions", "Statistics", "StatsBase", "StatsFuns", "Test"]
git-tree-sha1 = "9d3c0c762d4666db9187f363a76b47f7346e673b"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.49"

[[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 = "90b158083179a6ccbce2c7eb1446d5bf9d7ae571"
uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
version = "0.6.7"

[[deps.EllipsisNotation]]
deps = ["ArrayInterface"]
git-tree-sha1 = "d7ab55febfd0907b285fbf8dc0c73c0825d9d6aa"
uuid = "da5c29d0-fa7d-589e-88eb-ea29b0a81949"
version = "1.3.0"

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

[[deps.FFTW]]
deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"]
git-tree-sha1 = "505876577b5481e50d089c1c68899dfb6faebc62"
uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
version = "1.4.6"

[[deps.FFTW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c6033cc3892d0ef5bb9cd29b7f2f0331ea5184ea"
uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
version = "3.3.10+0"

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

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

[[deps.HDF5_jll]]
deps = ["Artifacts", "JLLWrappers", "LibCURL_jll", "Libdl", "OpenSSL_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "bab67c0d1c4662d2c4be8c6007751b0b6111de5c"
uuid = "0234f1f7-429e-5d53-9886-15a909be8d59"
version = "1.12.1+0"

[[deps.HypergeometricFunctions]]
deps = ["DualNumbers", "LinearAlgebra", "SpecialFunctions", "Test"]
git-tree-sha1 = "65e4589030ef3c44d3b90bdc5aac462b4bb05567"
uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
version = "0.3.8"

[[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.IfElse]]
git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1"
uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
version = "0.1.1"

[[deps.InlineStrings]]
deps = ["Parsers"]
git-tree-sha1 = "61feba885fac3a407465726d0c330b3055df897f"
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
version = "1.1.2"

[[deps.IntelOpenMP_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d979e54b71da82f3a65b62553da4fc3d18c9004c"
uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0"
version = "2018.0.3+2"

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

[[deps.Interpolations]]
deps = ["AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"]
git-tree-sha1 = "b15fc0a95c564ca2e0a7ae12c1f095ca848ceb31"
uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
version = "0.13.5"

[[deps.IntervalSets]]
deps = ["Dates", "EllipsisNotation", "Statistics"]
git-tree-sha1 = "3cc368af3f110a767ac786560045dceddfc16758"
uuid = "8197267c-284f-5f27-9208-e0e47529a953"
version = "0.5.3"

[[deps.Intervals]]
deps = ["Dates", "Printf", "RecipesBase", "Serialization", "TimeZones"]
git-tree-sha1 = "323a38ed1952d30586d0fe03412cde9399d3618b"
uuid = "d8418881-c3e1-53bb-8760-2df7ec849ed5"
version = "1.5.0"

[[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.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

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

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

[[deps.LPVSpectral]]
deps = ["DSP", "FFTW", "LinearAlgebra", "Printf", "Random", "RecipesBase", "Requires", "Statistics", "StatsBase"]
git-tree-sha1 = "959cd3dde8f82e3f6929ee3c73a2b9ba6fc3d06d"
uuid = "26dcc766-85df-5edc-b560-6076d5dbac63"
version = "0.3.1"

[[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 = "e5718a00af0ab9756305a0392832c8952c7426c1"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.6"

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

[[deps.MKL_jll]]
deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "Pkg"]
git-tree-sha1 = "e595b205efd49508358f7dc670a940c790204629"
uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
version = "2022.0.0+0"

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

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

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

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

[[deps.Mocking]]
deps = ["Compat", "ExprTools"]
git-tree-sha1 = "29714d0a7a8083bba8427a4fbfb00a540c681ce7"
uuid = "78c3b35d-d492-501b-9361-3d52fe80e533"
version = "0.7.3"

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

[[deps.NCDatasets]]
deps = ["CFTime", "DataStructures", "Dates", "NetCDF_jll", "Printf"]
git-tree-sha1 = "17e39eb5bbe564f48bdbefbd103bd3f49fcfcb9b"
uuid = "85f8d34a-cbdd-5861-8df4-14fed0d494ab"
version = "0.11.9"

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

[[deps.NearestNeighbors]]
deps = ["Distances", "StaticArrays"]
git-tree-sha1 = "16baacfdc8758bc374882566c9187e785e85c2f0"
uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
version = "0.4.9"

[[deps.Neighborhood]]
deps = ["Distances", "NearestNeighbors", "Random", "Test"]
git-tree-sha1 = "1159fcaf3b72923cf623b2748f238a5115ed2623"
uuid = "645ca80c-8b79-4109-87ea-e1f58159d116"
version = "0.2.3"

[[deps.NetCDF_jll]]
deps = ["Artifacts", "HDF5_jll", "JLLWrappers", "LibCURL_jll", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Pkg", "Zlib_jll", "nghttp2_jll"]
git-tree-sha1 = "598f1a5e9829b3e57f233f98b34a22b376dff373"
uuid = "7243133f-43d8-5620-bbf4-c2c921802cf3"
version = "400.702.402+0"

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

[[deps.OffsetArrays]]
deps = ["Adapt"]
git-tree-sha1 = "043017e0bdeff61cfbb7afeb558ab29536bbb5ed"
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
version = "1.10.8"

[[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.OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "648107615c15d4e09f7eca16307bc821c1f718d8"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.13+0"

[[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.PDMats]]
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
git-tree-sha1 = "7e2166042d1698b6072352c74cfd1fca2a968253"
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
version = "0.11.6"

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

[[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", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "2c87c85e397b7ffed5ffec054f532d4edd05d901"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.36"

[[deps.Polynomials]]
deps = ["Intervals", "LinearAlgebra", "OffsetArrays", "RecipesBase"]
git-tree-sha1 = "0b15f3597b01eb76764dd03c3c23d6679a3c32c8"
uuid = "f27b6e38-b328-58d1-80ce-0feddd5e7a45"
version = "1.2.1"

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

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

[[deps.QuadGK]]
deps = ["DataStructures", "LinearAlgebra"]
git-tree-sha1 = "78aadffb3efd2155af139781b8a8df1ef279ea39"
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
version = "2.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.Ratios]]
deps = ["Requires"]
git-tree-sha1 = "dc84268fe0e3335a62e315a3a7cf2afa7178a734"
uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
version = "0.4.3"

[[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 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"

[[deps.Rmath]]
deps = ["Random", "Rmath_jll"]
git-tree-sha1 = "bf3188feca147ce108c76ad82c2792c57abe7b1f"
uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
version = "0.7.0"

[[deps.Rmath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "68db32dff12bb6127bac73c209881191bf0efbb7"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.3.0+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.SignalDecomposition]]
deps = ["BandedMatrices", "Dates", "DelayEmbeddings", "FFTW", "LPVSpectral", "LinearAlgebra", "Neighborhood", "Random", "StaticArrays", "Statistics"]
git-tree-sha1 = "1a341589c721154f5cac57aaae4a344fdc5bfd6a"
uuid = "11a47235-7b84-4c7c-b885-fc3e2a9cf955"
version = "1.0.3"

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

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

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

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

[[deps.Static]]
deps = ["IfElse"]
git-tree-sha1 = "65068e4b4d10f3c31aaae2e6cb92b6c6cedca610"
uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
version = "0.5.6"

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

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

[[deps.StatsAPI]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "c3d8ba7f3fa0625b062b82853a7d5229cb728b6b"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.2.1"

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

[[deps.StatsFuns]]
deps = ["ChainRulesCore", "HypergeometricFunctions", "InverseFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "25405d7016a47cf2bd6cd91e66f4de437fd54a07"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "0.9.16"

[[deps.SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"

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

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

[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"]
git-tree-sha1 = "5ce79ce186cc678bbb5c5681ca3379d1ddae11a1"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.7.0"

[[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.TimeZones]]
deps = ["Dates", "Downloads", "InlineStrings", "LazyArtifacts", "Mocking", "Printf", "RecipesBase", "Serialization", "Unicode"]
git-tree-sha1 = "2d4b6de8676b34525ac518de36006dc2e89c7e2e"
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
version = "1.7.2"

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

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

[[deps.WoodburyMatrices]]
deps = ["LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "de67fa59e33ad156a590055375a30b23c40299d3"
uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
version = "0.5.5"

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

[[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:
# ╟─15f947af-0485-433c-a424-ba0c0a5f376a
# ╟─e112e6e8-50b8-496e-b4ea-8c37a288e4a0
# ╟─d9874aec-7c7f-4818-81a8-a5fd520159d5
# ╟─6c52f938-86db-496c-a1c2-ffdd7836f533
# ╟─e39f0208-c7a1-44fc-b4b9-07f83a86e8d8
# ╟─88714902-cf5f-4cd7-adb3-443dd6d48c35
# ╟─0a92c84f-bc1c-4624-ada3-d4202e9f37fa
# ╟─d2ae000a-4c5a-4289-aaee-431e064a7aef
# ╟─e5ef340e-e087-4b2b-be6e-da6255e3677a
# ╟─bd831162-b479-4148-9031-de6529f4bf89
# ╟─af6450d9-d92a-4331-853f-cd19ff72abd2
# ╟─9f0a3ab4-9d79-11ec-33e2-eb9616058d43
# ╟─d164f293-b571-4218-8411-bf01f52e7a20
# ╟─7306f20f-9da1-4419-9222-1704fd296ee8
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


"""
    StdDPP(kernel::Kernel)

Standard DPP (Determinantal Point Process) sampling given `kernel`.
The size of the returned `Z` is not fixed (but cannot be empty unlike in a classical DPP)
"""
struct StdDPP{K<:Kernel} <: OffIPSA
    kernel::K
    function StdDPP(kernel::K) where {K<:Kernel}
        return new{K}(kernel)
    end
end

function inducingpoints(rng::AbstractRNG, alg::StdDPP, X::AbstractVector; kwargs...)
    dpp = DPP(kernel, X)
    samp = rand(rng, dpp)
    while isempty(samp) # Sample from the DPP until there is a non-empty set
        samp = rand(rng, dpp)
    end
    return X[samp]
end


@testset "ch05" begin
    @testset "ch05_1" begin
        Random.seed!(1234)
        rng = MersenneTwister(1234)

        function create_toy_data(func, sample_size, domain = [0.0, 1.0], noise = 0.1)
            x = collect(range(domain[1], stop = domain[2], length = sample_size))
            shuffle!(rng, x)
            noise = rand(Uniform(-noise, noise), sample_size)
            return x, func.(x) + noise
        end

        function func(x)
            2.0 * x + 0.7 * sin(2 * pi * x)
        end

        function square(x)
            return x^2
        end

        function sinusoidal(x)
            return sin(pi * x)
        end

        function absolute(x)
            if x >= 0.0
                return x
            else
                return -x
            end
        end

        function heaviside(x)
            sign_x = x / absolute(x)
            return 0.5 * (sign_x + 1.0)
        end

        layers = [TanhLayer(1, 4), TanhLayer(4, 3), LinearLayer(3, 1)]
        cost_function = SumSquareError()
        nn = NeuralNetwork(layers, cost_function)

        toy_functions = [square, sinusoidal, absolute, heaviside]
        # "RMSProp" seems to be contains sqrt(-) error
        policies = ["Adam", "SGD", "MomentumSGD", "MomentumSGD"]
        domain = [-1.0, 1.0]

        for (toy_func, policy) in zip(toy_functions, policies)
            x_train, y_train = create_toy_data(toy_func, 100, domain, 0.01)
            X = collect(reshape(x_train, 1, 100))
            t = collect(reshape(y_train, 1, 100))

            for i = 1:10
                X = collect(reshape(x_train, 1, 100))
                t = collect(reshape(y_train, 1, 100))

                fitting(nn, X, t, 0.001, policy)
            end

            x_test = collect(reshape(range(domain[1], domain[2], length = 100), 1, 100))
            X_test = copy(x_test)
            X_test = predict(nn, X_test)

            x_test = collect(reshape(x_test, 100))
            X_test = collect(reshape(X_test, 100))
        end
    end

    @testset "ch5_2" begin
        n_samples = 100
        points = rand(2, n_samples)
        points = points .* 2 .- 1.0

        function boundary(x)
            return 1.2 .* x .* x
        end

        targets = [boundary(points[1, i]) < points[2, i] for i = 1:n_samples] * 1.0

        x_test = collect(range(-1.0, stop = 1.0, length = 100))

        ## Classification
        layers = [TanhLayer(2, 2), SigmoidLayer(2, 1)]
        cost_function = SigmoidCrossEntropy()
        targets = collect(reshape(targets, 1, n_samples))
        nn = NeuralNetwork(layers, cost_function)

        ### Training
        n_training = 5000
        for i = 1:n_training
            fitting(nn, points, targets, 0.001)
        end

        ### Contour of nn prediction
        x = collect(range(-1.0, stop = 1.0, length = 10))
        y = collect(range(-1.0, stop = 1.0, length = 10))
        val = [predict(nn, reshape([i, j], 2, 1))[1] for j in y, i in x]
    end
end


using BoundingSphere
using NearestNeighbors
using Plots
using DynamicsPlots
using Random

struct AbstractSimplicialComplex
    Simplex::Set{Int64}
end
const ASC = AbstractSimplicialComplex

include("../utilities/tDATAsets.jl")
Random.seed!(0)
M2n = rand(Sphere(2), 20)
d, n = size(M2n)
flatΔ = 1:n
ε = 0.3
maxdim = 6

# Initialization
kdtree = KDTree(M2n)
knn0 = knn(kdtree, M2n, 2)
# bit_Δ0 = first.(knn0[2]) .≥ ε
# Δ0 = [[j] for j in 1:n][bit_Δ0]
begin
cache_r = Float64[]
Δ = []
preΔ = []
# push!(preΔ, knn0[1][.!bit_Δ0] |> sort .|> sort |> unique)
push!(preΔ, [[j] for j in 1:n])
for k in 1:maxdim
    if preΔ[k] |> isempty break end
    push!(Δ, [])
    push!(preΔ, [])
    # flatΔ = reduce(∪, preΔ[k]) |> sort
    for (idx_taboo, β) in enumerate(preΔ[k])
        flag_Δ = false
        for α in preΔ[k+1]
            if β ⊆ α
                flag_Δ = true
                break
            end
        end
        if flag_Δ continue end
        for v in flatΔ[idx_taboo:end]
            if v in β
                continue
            end
            # println("candy - β: ", β, " ∪  {", v, "}")
            candy = [β; v] |> sort # `candy` is a alias of 'candidate'.
            c, r = boundingsphere([M2n[:,idx] for idx in candy])
            checked = inrange(kdtree, c, r + eps(), true)
            if r ≤ ε && (length(candy) < length(checked) || candy == checked)
                flag_Δ = true
                push!(preΔ[k+1], candy)
                push!(cache_r, r)
                continue
            end
        end
        if flag_Δ continue end
        push!(Δ[k], β)
        println(Δ[k])
    end
end
end
# Δ0
Δ
preΔ[end]
cache_r |> unique |> sort
union(reduce(∪, Δ0), reduce(∪, reduce(∪, Δ))) |> sort

candy = [5,8,11,12]
c, r = boundingsphere([M2n[:,idx] for idx in candy])
inrange(kdtree, c, r + eps(), true)

color = [:yellow, :gray, :orange, :red, :black, :purple]
x,y = eachrow(M2n); p1 = scatter(x,y, text = 1:n, size = (600,600), color = :white)
for i in 1:length(Δ)
    for j in Δ[i]
        c, r = boundingsphere([M2n[:,k] for k in j])
        circle!(c,r, color = color[i], label = :none)
    end
end
circle!((0,0), ε, color = :blue, legend= :none)

c, r = boundingsphere([M2n[:,k] for k in [4,15,20]])
circle!(c, r, color = :green, legend= :none)
circle!(c, ε, color = :blue, legend= :none)


using DataFrames
using CSV
using Chain
using Antenna
using Interpolations

print("\ec")
extract = (filename) -> @chain CSV.File(filename)  DataFrame _[181:end, 2:end] Matrix circshift(_, (0, -180))
extract_theta_phi = (filename) -> @chain CSV.File(filename)  DataFrame _[181:end, 2:end] Matrix circshift(_, (0, -180))
# theta = @chain CSV.File("test/elem/mag_theta.csv")  DataFrame _[181:end,1] deg2rad.(_)
theta = collect(0.:180.) .|> deg2rad
phi = collect(-180.:180.) .|> deg2rad

mag_theta = extract("test/elem/mag_theta.csv")
mag_phi = extract("test/elem/mag_phi.csv")
ang_theta = extract("test/elem/ang_theta.csv")
ang_phi = extract("test/elem/ang_phi.csv")

rE_theta = [mag*exp(-1im*deg2rad(ang)) for (mag,ang) in zip(mag_theta, ang_theta)]
rE_phi = [mag*exp(-1im*deg2rad(ang)) for (mag,ang) in zip(mag_phi, ang_phi)]

anten_pattern(
    θ=LinearInterpolation((theta, phi), rE_theta),
    ϕ=LinearInterpolation((theta, phi), rE_phi)
)


function flow(t,t0,lat,long,x,vphi,d)

# computes the velocity and acceleration field on a lat,long
# grid at time t using a mode from macWave()
#
# input
#
# t    - time (years)
# t0   - origin time (years)
# colat - latitude (degree)
# long - longitude (degree)
# x    - meridional coordinate (x = cos(theta))
# vphi - solution for vphi at CMB (vs. x)
# d    - eigenvalue ( i x frequency )

#
# output
#
# vx   - phi component of velocity at CMB
# ax   - phi components of acceleration

# some constants
sectoyear = 365.25 * 24 * 60 * 60;
tsec = (t-t0) * sectoyear;
omega = 0.7292e-4;

# add solution at poles x = -1, 1
n = length(x)
vphi_ends = Array{ComplexF64}(undef,n+2);
x_ends = Array{Float64}(undef,n+2);
x_ends[1] = -1.0;
x_ends[2:n+1] = x;
x_ends[n+2] = 1.0;
vphi_ends[1] = 0.0;
vphi_ends[2:n+1] = vphi;
vphi_ends[n+2] = 0.0;

# allocate memory for velocity and acceleration on colat grid
vr = Array{Float64,1}(undef,length(lat));
vi = Array{Float64,1}(undef,length(lat));
v = Array{Float64,2}(undef,length(lat),length(long));
a = Array{Float64,2}(undef,length(lat),length(long));

# dimensional eigenvalue
dd = d * omega;

# evaluate latitude of wave solution
theta = -acos.(x_ends).+pi/2;

#  interpolate wave on to lat,long grid
nodes = (theta,);
itp = Interpolations.interpolate(nodes,real(vphi_ends),Gridded(Linear()));
vr = itp(lat);
itp = Interpolations.interpolate(nodes,imag(vphi_ends),Gridded(Linear()));
vi = itp(lat);

# copy real part onto grid
for j = 1 : length(lat)
    for k = 1 : length(long)

       phase = exp(dd * tsec);
       v[j,k] = real( (vr[j] + vi[j]*1im)*phase);
       a[j,k] = real( (vr[j] + vi[j]*1im)*dd*phase)*sectoyear;

    end
end

# normalize
vmax = maximum(v);
v = v / vmax;
a = a / vmax;

return v,a

end


@polly function kernel_symm(alpha, beta, C, A, B)
    m,n = size(C)
    for i = 1:m, j = 1:n
        temp2 = zero(eltype(C))
        for k = 1:(i-1)
            C[k,j] += alpha * B[i,j] * A[i,k]
            temp2 += B[k,j] * A[i,k]
        end
        C[i,j] = beta * C[i,j] + alpha * B[i,j] * A[i,i] + alpha * temp2
    end
end

let
    m = 1000
    n = 1200

    C = zeros(Float32,m,n)
    A = zeros(Float32,m,m)
    B = zeros(Float32,m,n)

    alpha = Float32(1.5)
    beta = Float32(1.2)
    for i = 1:m, j = 1:n
        C[i,j] = ((i+j) % 100) / m
        B[i,j] = ((n+i-j) % 100) / m
    end
    for i = 1:m
        for j = 1:i
            A[i,j] = ((i+j) % 100) / m
        end
        for j = i+1:m
            A[i,j] = -999 # regions of arrays that should not be used
        end
    end

    SUITE["symm"] = @benchmarkable kernel_symm($alpha, $beta, C, A, B) setup = (C = copy($C); A = copy($A); B = copy($B))
end


"""
    get_d_hsml( dx::Real, dy::Real,
                   hsml_inv::Real )

Computes the distance in 2D to the pixel center in units of the kernel support.
"""
@inline function get_d_hsml(dx::T, dy::T, hsml_inv::T) where T
    √( dx*dx + dy*dy ) * hsml_inv
end

"""
    function calculate_index(i::Integer, j::Integer, x_pixels::Integer)

Calculates the index of a flattened 2D image array.
"""
function calculate_index(i::T, j::T, x_pixels::T) where T
    return floor(T, i * x_pixels + j ) + 1
end

"""
    function calculate_weights_2D(  wk::Array{<:Real,1},
                                    iMin::Integer, iMax::Integer,
                                    jMin::Integer, jMax::Integer,
                                    x::Real, y::Real, hsml::Real, hsml_inv::Real,
                                    kernel::SPHKernel,
                                    x_pixels::Integer )

Calculates the kernel- and geometric weights of the pixels a particle contributes to.
"""
@fastmath function calculate_weights( wk::Vector{Float64},
                                      iMin::Integer, iMax::Integer,
                                      jMin::Integer, jMax::Integer,
                                      x::Real, y::Real, hsml::Real, hsml_inv::Real,
                                      kernel::SPHKernel,
                                      x_pixels::Integer )

    is_undersampled = false

    if hsml <= 1.0
        is_undersampled = true
    end

    distr_weight = 0.0
    n_distr_pix  = 0

    @inbounds for i = iMin:iMax
        x_dist, dx = get_x_dx(x, hsml, i)

        for j = jMin:jMax
            y_dist, dy = get_x_dx(y, hsml, j)

            # index in flattened 2D array
            idx = calculate_index(i, j, x_pixels)

            dxdy = dx * dy

            if is_undersampled

                wk[idx]       = dxdy
                distr_weight += dxdy
                n_distr_pix  += 1

            else # is_undersampled

                u = get_d_hsml(x_dist, y_dist, hsml_inv)

                # check if inside the kernel
                if u <= 1.0
                    # evaluate kernel
                    wk[idx]       = 𝒲₂(kernel, u, hsml_inv)
                    wk[idx]      *= dxdy
                    distr_weight += wk[idx]
                    n_distr_pix  += 1
                else
                    wk[idx] = 0.0
                end # u < 1.0

            end # is_undersampled

        end # j
    end # i

    return wk, n_distr_pix, distr_weight
end

"""
    get_quantities_2D( pos, weight, hsml,
                       rho, m, len2pix::T) where T

Helper function to convert quantities to pixel units and the correct data type.
"""
function get_quantities_2D( pos, weight, hsml,
                            rho, m, len2pix::T) where T

    hsml    *= T(len2pix)
    hsml_inv = T(1.0)/hsml
    area     = π * hsml^2

    rho *= T(1.0/(len2pix*len2pix*len2pix))
    dz   = m / rho / area

    return T.(pos), T(weight), hsml, hsml_inv, area, dz
end

"""
   sphMapping_2D( Pos, HSML,
                  M, Rho,
                  Bin_Q, Weights;
                  param::mappingParameters, kernel::SPHKernel,
                  show_progress::Bool=false )

Underlying function to map SPH data to a 2D grid.
"""
function sphMapping_2D( Pos, HSML,
                        M, Rho,
                        Bin_Q, Weights;
                        param::mappingParameters, kernel::SPHKernel,
                        show_progress::Bool=false )

    N = size(M,1)  # number of particles

    # max number of pixels over which the particle can be distributed
    N_distr = param.Npixels[1] * param.Npixels[2]

    image = zeros(Float64, N_distr, 2)

    if param.periodic
        k_start = 0
    else
        k_start = 7
    end

    # allocate arrays for weights
    wk = zeros(Float64, N_distr)

    if show_progress
        P = Progress(N)
    end

    # loop over all particles
    @inbounds for p = 1:N

        bin_q = Float64(Bin_Q[p])

        if bin_q == 0.0
            continue
        end

        _pos, weight, hsml, hsml_inv, area, dz = get_quantities_2D(Pos[:,p], Weights[p], HSML[p], Rho[p], M[p], param.len2pix)

        for k = k_start:7

            # simplify position quantities for performance
            x, y, z, skip_k = get_xyz( _pos, HSML[p], k, param)

            if skip_k
                continue
            end

            # calculate relevant pixel range
            iMin, iMax = pix_index_min_max( x, hsml, param.Npixels[1] )
            jMin, jMax = pix_index_min_max( y, hsml, param.Npixels[2] )

            wk, n_distr_pix, distr_weight = calculate_weights(wk, iMin, iMax, jMin, jMax,
                                                              x, y, hsml, hsml_inv,
                                                              kernel,
                                                              param.Npixels[1])

            # skip if the particle is not contained in the image
            # ( should only happen with periodic boundary conditions )
            if n_distr_pix == 0
                continue
            end

            weight_per_pix  = n_distr_pix / distr_weight
            kernel_norm     = area / n_distr_pix
            area_norm       = kernel_norm * weight_per_pix * weight * dz

            @inbounds for i = iMin:iMax, j = jMin:jMax
                idx = calculate_index(i, j, param.Npixels[1])

                image[idx,1], image[idx,2] = update_image( image[idx,1], image[idx,2],
                                                           wk[idx], bin_q, area_norm)

            end # i, j
        end # k

         # update for ProgressMeter
        if show_progress
            next!(P)
        end
    end # p

    return image

end # function


import Flux

export NormalHyperprior

"""
    Hyperprior

An abstract hyperprior.
"""
abstract type Hyperprior end

"""
    NormalHyperprior

An isotropic multivariate normal hyperprior, parameterized by mean and
element-wise standard deviation.
"""
struct NormalHyperprior{V<:AbstractVector} <: Hyperprior
  mean   :: V
  stddev :: V
end

Flux.trainable(k::NormalHyperprior) = ()
Flux.@functor NormalHyperprior

"""
    (hp::NormalHyperprior)(x::AbstractVector)

Evaluates the normal hyperprior at ``x``.
"""
function (hp::NormalHyperprior)(x::AbstractVector)
  sum(((x .- hp.mean) ./ hp.stddev).^2; dims=1) ./ 2
end


export exactpruning!

# Exact pruning
"""
    exactpruning!(man::AbstractCutPruner, solver::MathProgBase.AbstractMathProgSolver;
                  ub=Inf, lb=-Inf, epsilon=1e-5)

Remove dominated cuts in CutPruner `man`.

We use a LP solver to determine whether a cut is dominated or not.

# Arguments
* `man::AbstractCutPruner`
    Cut pruner where to remove cuts
* `solver`
    Solver used to solve LP
* `ub::Union{Float64, Vector{Float64}}`
    State x upper bound
* `lb::Union{Float64, Vector{Float64}}`
    State x lower bound
* `epsilon::Float64`
    Pruning's tolerance
"""
function exactpruning!(man::AbstractCutPruner, solver::MathProgBase.AbstractMathProgSolver; ub=Inf, lb=-Inf, epsilon=1e-5)
    K = getdominated(man.A, man.b, man.islb, man.isfun, solver, lb, ub, epsilon)
    removecuts!(man, K)
end

"""Return dominated cuts."""
function getdominated(A, b, islb, isfun, solver, lb, ub, epsilon)
    red = Int[]
    if size(A, 1) == 1
        return red
    end
    for i in 1:size(A, 1)
        if isdominated(A, b, islb, isfun, i, solver, lb, ub, epsilon)
            push!(red, i)
        end
    end
    red
end

"""State whether a cut is dominated with a tolerance epsilon."""
function isdominated(A, b, islb, isfun, k, solver, lb, ub, epsilon)
    # we use MathProgBase to solve the test
    # For instance, if islb & isfun, the LP becomes:
    # min - y
    #     x ∈ R^n, y ∈ R
    #     y <= (A[k, :] - A[i, :])*x + b[k] - b[i]     ∀ i != k

    # we formulate an equivalent problem as
    # min c'*z
    # s.t H*z <= h

    # get problem's dimension
    ncuts, nx = size(A)
    # allocate arrays
    h = zeros(ncuts - 1)
    H = zeros(ncuts - 1, nx + 1)
    c = zeros(nx + 1)
    c[1] = (islb) ? -1 : 1

    λk = @view A[k, :]

    ic = 0
    for ix in 1:ncuts
        if ix != k
            ic += 1
            δb = b[k] - b[ix]
            @inbounds h[ic] = ((isfun&islb) || (~isfun&~islb)) ? δb : -δb
            @inbounds H[ic, 1] = (islb) ? 1 : -1

            for jx in 1:nx
                dl = A[ix, jx] - λk[jx]
                @inbounds H[ic, jx+1] = (islb) ? dl : -dl
            end
        end
    end

    # update lower and upper bound if Vector
    lbx = isa(lb, Vector) ? vcat(-Inf, lb) : lb
    ubx = isa(ub, Vector) ? vcat( Inf, ub) : ub

    # solve the LP with MathProgBase
    res = linprog(c, H, -Inf, h, lbx, ubx, solver)
    if res.status == :Optimal
        res = res.objval
        return (islb) ? -res < epsilon : res > -epsilon
    else
        return false
    end
end