### A Pluto.jl notebook ###
# v0.16.4

using Markdown
using InteractiveUtils

# ╔═╡ 51a61cd4-185c-11ec-0a43-4517c063bc43
begin
	using LinearAlgebra
	using Random; Random.seed!()
	using DelimitedFiles
	using Plots
	"packages"
end

# ╔═╡ 0520406d-2b17-427d-a830-c563a191a816
md"""
Conformal transform h and h1
"""

# ╔═╡ c27ab801-37a4-4174-9c7d-623c08f63203
begin
	arg = [i for i in range(0,2π, length=100)]
	circle_x = [cos(i) for i in arg]
	circle_y = [sin(i) for i in arg]
	"circle r = 1"
end

# ╔═╡ d2f19b71-1947-4197-a00c-4d90df01fd96
md"""
###  Check θ(z)
"""

# ╔═╡ b71809a2-fc70-438c-8205-60dce946131f
md"""
#### delta function
"""

# ╔═╡ 0d7c7bef-170e-46e1-907f-9b2a8e60bd91
begin
	function eye(n::Int64)
		res = zeros(n,n)
		for i = 1:n res[i,i] = 1.0 end
		return res
	end

	function eye(dtype::DataType, n::Int64)
		res = zeros(dtype, n, n)
		I = 1.0 |> dtype
		for i = 1:n res[i,i] = I end
		return res
	end

	function pauli(symbol::Symbol)
		if symbol==:x return [0. 1.; 1. 0.]
		elseif symbol==:y return [0. -1im; 1im 0.]
		elseif symbol==:z return [1. 0.; 0. -1.]
		elseif symbol==:+ return [0. 1.; 0. 0.]
		elseif symbol==:- return [0. 0.; 1. 0.]
		else
			error("The input should be :x,:y,:z,:+,:-.")
		end
	end

	function delta(x::Real; c::Real = 0., η::Real = 0.05)
		num = η
		den = ((x-c)^2 + η^2) * π
		return num/den
	end

	function gaussian(x::Real, fac::AbstractVector)
		# f(x) = 1/(σ√(2π)) exp(-((x-μ)/σ)^2 /2 )
		μ = fac[1]; σ2 = fac[2]
		σ2 <= 0. ? (@error "σ2 should be positive.") :
		norm = √(2π * σ2)
		m = (x-μ)^2 / σ2
		return 1/norm*exp(-m/2)
	end

	function multi_gaussian(N::Int64; ms::Vector{Float64}=[-3.,3.], smax::Real=3.)
		fac = rand(N,2)
		fac[:,1] = rand(N) .* (ms[2] - ms[1]) .+ ms[1]
		fac[:,2] = rand(N) .* smax
		function f(x::Real)
			res = 0.
			for i = 1: N
				res += gaussian(x, fac[i,:])
			end
			res/N
		end
		return f, fac
	end

	function Masubara_freq(n::Int64, β::Real; type::Symbol= :f)
		if type == :b  N = 2n
		elseif type == :f  N = 2n + 1
		else @error "type should be :b for bosons and :f for fermions"
		end
		return N*π/β
	end

	function Masubara_GF(n::Int64, A::Function, β::Real;
		type::Symbol = :f, Λ::Float64=100., L::Int64=1000000)
		# G(iωn) = 1/2π ∫dΩ A(Ω)/(iωn - Ω)
		ωn = Masubara_freq(n, β, type=type)
		dL = 2*Λ/L
		Gn = 0.
		for n = 0:L
			ω = -Λ + dL*n
			Gn += A(ω)/(1.0im*ωn-ω) * dL
		end
		return Gn/(2π)
	end

	function h(z::Number)
		#Conformal mapping(Mobius transform)
		#\bar{C+} -> \bar{D}
		return (z - 1.0im)/(z + 1.0im)
	end

	function invh(z::Number)
		return 1.0im*(1 + z)/(1 - z)
	end

	function h1(z::Number, Y::Number)
		#Conformal mapping(Mobius transform)
		#C+ -> D
		return (z - Y)/(z - Y')
	end

	function invh1(z::Complex, Y::Number)
		return (z*Y'-Y)/(z-1)
	end

	function ispossemidef(A::Matrix)
		evals = eigvals(A)
		return all(evals .>= 0)
	end
	"utilities"
end

# ╔═╡ ac782b5a-26b2-4b8f-a9d8-3baf7d1c22c5
begin
	fnum = 50; β=100
	plot(circle_x, circle_y, line=(2, :black),label=false)
	ωn = [Masubara_freq(i,β,type=:f) for i=1:fnum]
	scatter!(zeros(fnum), ωn, label="iωn")
	hω = [h(1.0im*i) for i in ωn]
	scatter!(real.(hω), imag.(hω), label="h(iωn)")
	plot!(ylim = (-1.5,1.5), xlim =(-1.2,1.2))
end

# ╔═╡ 22756d1d-983c-47eb-b6a5-0caeaa8d6fa0
begin
	struct Giωn
		ωn::Real
		val::Complex
	end

	struct MasubaraGF
		number::Int64
		gtype::Symbol
		GF::Vector{Giωn}
	end

	function λ(G::Giωn)
    	return h(-G.val)
	end

	function pick_matrix(G::MasubaraGF)
		# input :freq: Masubara frenquencies; val: G
		N = G.num; g = G.GF
		pick = zeros(ComplexF64, N, N)
		for i = 1:N, j = 1:N
			up = 1 - λ(g[i])*λ(g[j])'
			down = 1 - h(1.0im*g[i].ωn)*h(1.0im*g[j].ωn)'
			pick[i,j] = up/down
		end
		return pick
	end

	function isNevanlinnasolvable(G::MasubaraGF; err::Float64 = 1.e-6)
		# check if the pick_matrix is semi positive semidefinite
		pick = pick_matrix(G)
		evals = eigvals(pick)
		return all((evals .+ err) .>= 0), minimum(evals)
	end
	"struct and pick_matrix"
end

# ╔═╡ e7027211-d85d-43c1-8647-8c48ce0c54f8
begin
	function θk(abcd::Matrix, val::Number)
	    up = -abcd[2,2] * val + abcd[1,2]
	    down = abcd[2,1] * val - abcd[1,1]
	    return up/down
	end

	function core(G::MasubaraGF)
		T = ComplexF64
		M=G.number; g = G.GF
		phis = zeros(T, M) ; phis[1] = λ(g[1]) #store λk(iω_(k+1))
		abcds = Vector{Matrix{T}}(undef,M)
		for i = 1:M
			abcds[i] = eye(T,2)
		end
		for j = 1:(M-1)
			for k=j:(M-1)
				prod = zeros(T,2,2)
				prod[1,1] = h1(1.0im*g[k].ωn,1.0im*g[j].ωn)
				prod[1,2] = phis[j]
				prod[2,1] = phis[j]' * h1(1.0im*g[k].ωn,1.0im*g[j].ωn)
				prod[2,2] = 1. |> T
				abcds[k] *= prod
			end
			phis[j+1] = θk(abcds[j+1], λ(g[j+1]))
		end
		return phis
	end

	function evaluation(z::Number, G::MasubaraGF;
		optim::Symbol = :none)
		dtype = ComplexF64
		M = G.number
		g = G.GF
		phis = core(G)
		abcd = eye(dtype,2)
		for j = 1:M
			prod = zeros(dtype,2,2)
			prod[1,1] = h1(z,1.0im*g[j].ωn)
			prod[1,2]= phis[j]
			prod[2,1] = phis[j]' * h1(z,1.0im*g[j].ωn)
			prod[2,2] = 1. |> dtype
			abcd *= prod
		end

		if optim == :none
			θm(z::Number) = 0.0 + 0.0im
		end

		up = abcd[1,1] * θm(z) + abcd[1,2]
		down = abcd[2,1] * θm(z) + abcd[2,2]
		θ = up/down
		return invh(θ)
	end

	function spectrum_density(ω::Real, η::Real, G::MasubaraGF)
		z = ω + 1.0im * η
		return evaluation(z, G)/π |> imag
	end
end

# ╔═╡ 02175367-7ea1-45c8-8188-10b01993efde
function make_input(path::String, num::Int64; type::Symbol = :f)
    data = readdlm(path)
    fnum = min(num, length(data[:,1])) # number of frenquencies used
    res = [Giωn(data[i,1],data[i,2]+1.0im*data[i,3]) for i = 1:fnum] |> reverse
    return MasubaraGF(fnum, type, res)
end

# ╔═╡ 4033c412-34be-439f-8dcf-42f4ed570611
begin
	N = 36
	p1 = "./data/idelta.txt"; G1 = make_input(p1,N)
	p2 = "./data/ig1.txt"; G2 = make_input(p2,N)
	p3 = "./data/ig3.txt"; G3 = make_input(p3,N)

	gf1 = G1.GF
	gf2 = G2.GF
	gf3 = G3.GF
	"load data"
end

# ╔═╡ 290742b3-a4fb-4763-b0b2-eaad702e7c94
N

# ╔═╡ 08f205d9-b396-4b95-9068-3097c36cf1bd
begin
	w = [gf1[i].ωn for i=1:N]
	gt = [-gf1[i].val for i=1:N]
	g = [evaluation(1.0im*i, G1) for i in w]
	scatter(real.(gt), imag.(gt),marker=(:hex,5),label="data:-g(iωn)")
	scatter!(real.(g), imag.(g), label="interploate")
	plot!(xlabel="1", ylabel="i")
end

# ╔═╡ 1a3e64ec-4491-478d-9885-d7c8f828618c
begin
	omega = [i for i in range(-5,5,length=1000)] ; η = 0.001
	A = [spectrum_density(i,η,G1) for i in omega]
	plot(omega,A,label=false)
end

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[compat]
Plots = "~1.22.1"
"""

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

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

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

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

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

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

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

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

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

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

[[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 = "4866e381721b30fac8dda4c8cb1d9db45c8d2994"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.37.0"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# ╔═╡ Cell order:
# ╟─290742b3-a4fb-4763-b0b2-eaad702e7c94
# ╟─0520406d-2b17-427d-a830-c563a191a816
# ╟─c27ab801-37a4-4174-9c7d-623c08f63203
# ╟─ac782b5a-26b2-4b8f-a9d8-3baf7d1c22c5
# ╟─d2f19b71-1947-4197-a00c-4d90df01fd96
# ╟─b71809a2-fc70-438c-8205-60dce946131f
# ╟─08f205d9-b396-4b95-9068-3097c36cf1bd
# ╟─1a3e64ec-4491-478d-9885-d7c8f828618c
# ╟─e7027211-d85d-43c1-8647-8c48ce0c54f8
# ╟─4033c412-34be-439f-8dcf-42f4ed570611
# ╟─02175367-7ea1-45c8-8188-10b01993efde
# ╟─22756d1d-983c-47eb-b6a5-0caeaa8d6fa0
# ╠═0d7c7bef-170e-46e1-907f-9b2a8e60bd91
# ╟─51a61cd4-185c-11ec-0a43-4517c063bc43
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


"""
    ParametricEntity <: AbstractEntity

A geometric entity given by an explicit `parametrization` field. The valid
arguments should lie in its `domain`. This differs from an `ElementaryEntity` in
that the underlying representation of the geometric entity is available.
"""
struct ParametricEntity <: AbstractEntity
    dim::UInt8
    tag::Int
    parametrization
    domain
    boundary::Vector{<:AbstractEntity}
    function ParametricEntity(dim, tag, f, d)
        ent = new(dim, tag, f, d,AbstractEntity[])
        # every entity gets added to a global variable ENTITIES so that we can
        # ensure the (d,t) pair is a UUID for an entity, and to easily retrieve
        # different entities.
        global_add_entity!(ent)
        return ent
    end
end

domain(p::ParametricEntity)              = p.domain
parametrization(p::ParametricEntity)     = p.parametrization
geometric_dimension(p::ParametricEntity) = p.dim
tag(p::ParametricEntity) = p.tag
boundary(p::ParametricEntity) = p.boundary

function flip_normal(ent::ParametricEntity)
    @assert ambient_dimension(ent) == geometric_dimension(ent) + 1
    ParametricEntity(geometric_dimension(ent),-tag(ent),parametrization(ent),domain(ent))
end

function ParametricEntity(f,dom)
    d = geometric_dimension(dom)
    t = new_tag(d) # automatically generate a new (valid) tag
    ParametricEntity(d, t, f, dom)
end

function return_type(p::ParametricEntity)
    # NOTE: this relies on the brittle promote_op
    d = domain(p)
    x = center(d)
    f = parametrization(p)
    T = Base.promote_op(f, typeof(x))
    return T
end

ambient_dimension(p::ParametricEntity) = length(return_type(p))

function (par::ParametricEntity)(x)
    @assert x in domain(par)
    par.parametrization(x)
end

jacobian(psurf::ParametricEntity,s::SVector)  = ForwardDiff.jacobian(psurf.parametrization, s::SVector)
jacobian(psurf::ParametricEntity,s)           = jacobian(psurf, SVector(s))

"""
    line(a,b)

Create a straight line connecting points `a` and `b`. Returns an instance
of [`ParametricEntity`](@ref).
"""
function line(a::SVector,b::SVector)
    f = (u) -> a + u[1]*(b-a)
    d = HyperRectangle(0.,1.)
    ParametricEntity(f,d)
end
line(a,b) = line(SVector(a),SVector(b))


"""
    morsefreq(c::CWT{W,T,Morse,N}) where {W,T,N}

Measures of frequency for generalized Morse wavelet. [with F. Rekibi]
The output returns the modal or peak.
For `be=0`, the wavelet becomes an analytic lowpass filter.
Reference: Lilly and Olhede (2009).  Higher-order properties of analytic wavelets.
IEEE Trans. Sig. Proc., 57 (1), 146--160.
"""
function morsefreq(c::CWT{W,T,Morse,N}) where {W,T,N}
    ga = c.waveType.ga
    be = c.waveType.be

    fm = @. exp((log(be) - log(ga)) / ga)

    if sum(be .== 0) != 0 && isempty(fm)
        fm = (log(2))^(1 / ga)
    elseif sum(be .== 0) != 0 && !isempty(fm)
        fm[be.==0] = (log(2))^(1 / ga[be.==0])
    end

    return fm
end

"""
    getNWavelets(n1,c) -> nOctaves, totalWavelets, sRanges, sWidth

Utility for understanding the spacing of the wavelets. `sRanges` is a list of
the s values used in each octave. `sWidth`` is a list of the corresponding
variance adjustments.
"""
function getNWavelets(n1, c::CWT)
    nOctaves = getNOctaves(n1, c)
    n, nSpace = setn(n1, c)
    isAve = !(typeof(c.averagingType) <: NoAve)
    if nOctaves ≤ c.averagingLength + getMinScaling(c) # there isn't enough space for anything but averaging.
        totalWavelets = isAve
        sRanges = [1.0]
        sWidth = [1.0]
        return nOctaves, totalWavelets, sRanges, sWidth
    end
    sRange = 2 .^ (polySpacing(nOctaves, c))
    totalWavelets = round(Int, length(sRange) + isAve)
    sWidth = varianceAdjust(c, totalWavelets, nOctaves)
    return nOctaves, totalWavelets, sRange, sWidth
end

"""
Different wavelet familes need to end at a different number of octaves because they have different tail behavior.
"""
getNOctaves(n1, c::CWT{W,T,M,N}) where {W,T,N,M} = log2(n1 >> 1 + 1) + c.extraOctaves
# choose the number of octaves so the last mean, which is at s*σ[1]
# is 3 standard devations away from the end
getNOctaves(n1, c::CWT{W,T,Morlet,N}) where {W,T,N} = log2((n1 >> 1 + 1) / (c.σ[1] + 3)) + c.extraOctaves
getNOctaves(n1, c::CWT{W,T,<:Paul,N}) where {W,T,N} = log2((n1 >> 1 + 1) / (2c.α + 5)) + c.extraOctaves
# choose the number of octaves so the last mean is 5 standard deviations from the end
function getNOctaves(n1, c::CWT{W,T,<:Dog,N}) where {W,T,N}
    μ = getMean(c)
    σ = getStd(c)
    log2(n1 >> 1 / (μ + 5σ)) + c.extraOctaves
end
# choose the number of octaves so the smallest support is twice the qmf
getNOctaves(n1, c::CWT{W,T,<:ContOrtho,N}) where {W,T,N} = log2(n1) - 2 - log2(length(qmf(c.waveType))) + c.extraOctaves
getNOctaves(n1, c::CWT{W,T,Morse,N}) where {W,T,N} = log2((n1 >> 1 + 1) / (morsefreq(c) + 1)) + c.extraOctaves
# getNOctaves(n1,c::CWT{W,T, Morse, N}) where {W, T, N} = 4 + c.extraOctaves

"""
As with the last octave, different wavelet families have different space decay rates, and in the case of symmetric or zero padding we don't want wavelets that bleed across the boundary from the center.
"""
getMinScaling(c::CWT{W,T,M,N}) where {W,T,N,M} = 0 # by default all scales are allowed (all of the orthogonal transforms)
getMinScaling(c::CWT{W,T,<:Morlet,N}) where {W,T,N} = 1 / (c.β)^0.8 # morlet is slightly too large at the boundaries by default
getMinScaling(c::CWT{W,T,<:Paul,N}) where {W,T,N} = 2 / (2c.α + 1) # Paul presents some difficulties, as the decay changes quickly (like 1/t^(α+1))
getMinScaling(c::CWT{W,T,<:Dog,N}) where {W,T,N} = 2 # like morlet, the decay for Dog is exponential and consistent across derivatives
# getMinScaling(c::CWT{W,T,<:Morse,N}) where {W,T,N,M} = log2(2 * morsefreq(c))
getMinScaling(c::CWT{W,T,<:Morse,N}) where {W,T,N,M} = 5 * morsefreq(c)

function varianceAdjust(this::CWT{W,T,M,N}, totalWavelets, nOct) where {W,T,N,M}
    # increases the width of the wavelets by σ[i] = (1+a(total-i)ᴾ)σₛ
    # which is a polynomial of order p going from 1 at the highest frequency to
    # sqrt(p) at the lowest
    β = this.β
    x = exp(nOct) / (100 + exp(nOct)) # the fewer octaves there are, the smaller the width adjustment we need
    a = (β^x - 1) / (totalWavelets - 1)^β
    sWidth = 1 .+ a .* (totalWavelets .- (1:totalWavelets)) .^ β
    if any(isnan.(sWidth))
        return [1]
    end
    return sWidth
end

function polySpacing(nOct, c)
    a = max(getMinScaling(c) + c.averagingLength, 0)
    O = nOct
    β = c.β
    Q = c.Q
    if O ≤ a
        # averagingLength and the min scaling are too large for anything to be done
        return [1.0]
    elseif 0 < O - a ≤ 1
        # there's only one octave, just return the linear rate of Q
        return range(a, O, length = 1 + round(Int, Q))
    end
    # x is the index, y is the scale
    # y= b*x^(1/β), solve for a and b with
    # (x₀,y₀)=(1, aveLength + minScale)
    # dy/dx = 1/s, so the Quality factor gives the slope at the last frequency
    b = (β / Q)^(1 / β) * (O)^((β - 1) / β)
    # the point x so that the second condition holds
    lastWavelet = Q * (O) / β
    # the point so that the first wavelet is at a
    firstWavelet = (a / b)^β
    # step size so that there are actually Q wavelets in the last octave
    startOfLastOctave = ((nOct - 1) / b)^β
    stepSize = (lastWavelet - startOfLastOctave) / Q
    samplePoints = range(firstWavelet, stop = lastWavelet,
        step = stepSize)#, length = round(Int, O * Q^(1/p)))
    return b .* (samplePoints) .^ (1 / β)
end

# a utility to just get the start, stop, and step size used in polySpacing. Only used for explanatory purposes
function genSamplePoints(nOct, c)
    a = getMinScaling(c) + c.averagingLength
    O = nOct
    β = c.β
    Q = c.Q
    if O ≤ a
        # averagingLength and the min scaling are too large for anything to be done
        return [1.0]
    elseif 0 < O - a ≤ 1
        # there's only one octave, just return the linear rate of Q
        return range(a, O, length = 1 + round(Int, Q))
    end
    # x is the index, y is the scale
    # y= b*x^(1/β), solve for a and b with
    # (x₀,y₀)=(1, aveLength + minScale)
    # dy/dx = 1/s, so the Quality factor gives the slope at the last frequency
    b = (β / Q)^(1 / β) * (O)^((β - 1) / β)
    # the point x so that the second condition holds
    lastWavelet = Q * (O) / β
    # the point so that the first wavelet is at a
    firstWavelet = (a / b)^β
    # step size so that there are actually Q wavelets in the last octave
    startOfLastOctave = ((nOct - 1) / b)^β
    stepSize = (lastWavelet - startOfLastOctave) / Q
    firstWavelet, lastWavelet, stepSize
end

"Adjust the length of the storage based on the boundary conditions."
function setn(n1, c)
    if boundaryType(c) <: ZPBoundary
        base2 = ceil(Int, log2(n1))   # power of 2 nearest to n1
        nSpace = 2^(base2)
        n = nSpace >> 1 + 1
    elseif boundaryType(c) <: SymBoundary
        # n1+1 rather than just n1 because this is going to be used in an rfft
        # for real data
        n = n1 + 1
        nSpace = 2 * n1
    elseif boundaryType(c) <: PerBoundary
        n = n1 >> 1 + 1
        nSpace = n1
    else
        error("No such boundary as $(boundaryType(c))")
    end
    return n, nSpace
end

# computing averaging function utils

# this makes sure the averaging function is real and positive
adjust(c::CWT) = 1
adjust(c::CWT{W,T,<:Dog}) where {W,T} = 1 / (im)^(c.α)

function locationShift(c::CWT{W,T,<:Morlet,N}, s, ω, sWidth) where {W,T,N}
    s0 = 3 * c.σ[1] * s * sWidth
    ω_shift = ω .+ c.σ[1] * s0
    return (s0, ω_shift)
end

function locationShift(c::CWT{W,T,<:Dog,N}, s, ω, sWidth) where {W,T,N}
    μNoS = getMean(c)
    μLast = μNoS * (2s)
    s0 = μLast / 2 / getStd(c)
    μ = μNoS * s0
    ω_shift = ω .+ μ
    return (s0, ω_shift)
end

function locationShift(c::CWT{W,T,<:Morse,N}, s, ω, sWidth) where {W,T,N}
    # s0 = c.waveType.cf * s * sWidth
    s0 = morsefreq(c) * s * sWidth
    # ω_shift = ω .+ c.waveType.cf * s0
    ω_shift = ω .+ s0
    return (s0, ω_shift)
end

"Get the mean of the mother wavelet where s=1."
function getMean(c::CWT{W,T,<:Dog}, s = 1) where {W,T}
    Gm1 = gamma((c.α + 1) / 2)
    Gm2 = gamma((c.α + 2) / 2)
    Gm2 / Gm1 * sqrt(2) * s^2
end
getMean(c::CWT{W,T,<:Paul}, s = 1) where {W,T} = (c.α + 1) * s
function getMean(c::CWT{W,T,<:Morlet}, s = 1) where {W,T}
    return s * c.σ[1]
end
function getMean(c::CWT{W,T,<:Morse}, s = 1) where {W,T}
    #return s*c.waveType.cf
    return s * morsefreq(c)
end

"""
    getStd(c::CWT{W, T, <:Dog}, s=1) where {W,T}

Get the standard deviation of the mother wavelet.
"""
getStd(c::CWT{W,T,<:Dog}, s = 1) where {W,T} = sqrt(c.α + 1 - getMean(c)^2) * s^(3 / 2)
getStd(c::CWT{W,T,<:Paul}, s = 1) where {W,T} = sqrt((c.α + 2) * (c.α + 1)) * s
getStd(c::CWT{W,T,<:Morlet}, s = 1) where {W,T} = (s * c.β^0.8) / sqrt(2)
getStd(c::CWT{W,T,<:Morse}, s = 1) where {W,T} = (s * c.β^0.8) / sqrt(2)

function locationShift(c::CWT{W,T,<:Paul,N}, s, ω, sWidth) where {W,T,N}
    s0 = s * sqrt(c.α + 1)
    ω_shift = ω .+ (c.α .+ 1) * s0
    return (s0, ω_shift)
end

function getUpperBound(c::CWT{W,T,<:Morlet,N}, s) where {W,T,N}
    return c.σ[1] * s
end

function getUpperBound(c::CWT{W,T,<:Dog,N}, s) where {W,T,N}
    return sqrt(2) * s * gamma((c.α + 2) / 2) / gamma((c.α + 1) / 2)
end

function getUpperBound(c::CWT{W,T,<:Paul,N}, s) where {W,T,N}
    return (c.α + 1) * s
end

"""
    getMeanFreq(Ŵ, fsample=2000) -> arrayOfFreqs

Calculate each of the mean frequencies of a collection of analytic or real wavelets `Ŵ`.
The default sampling rate `fsample=2kHz`, so the maximum frequency is 1kHz.
"""
function getMeanFreq(Ŵ, fsample = 2000)
    eachNorm = [norm(w, 1) for w in eachslice(Ŵ, dims = ndims(Ŵ))]'
    freqs = range(0, fsample / 2, length = size(Ŵ, 1))
    dropdims(sum(freqs .* abs.(Ŵ), dims = 1) ./ eachNorm, dims = 1)
end

function getMeanFreq(n1, cw::CWT, fsample = 2000)
    Ŵ, ω = computeWavelets(n1, cw)
    eachNorm = [norm(w, 1) for w in eachslice(Ŵ, dims = ndims(Ŵ))]'
    freqs = range(0, fsample / 2, length = size(Ŵ, 1))
    dropdims(sum(freqs .* abs.(Ŵ), dims = 1) ./ eachNorm, dims = 1)
end

# orthogonal wavelet utils
function getContWaveFromOrtho(c, N)
    depth, ψLen = calcDepth(c, N)
    φ, ψ, t = makewavelet(c.waveType.o, depth)
    return φ, -ψ, ψLen
end

"""
    calcDepth(w,N) -> nIters, sigLength

Given a `CWT` with an orthogonal wavelet type, calculate the depth `nIters`
necessary to get a resulting wavelet longer than `N`. `sigLength` is the
resulting length.
"""
function calcDepth(w, N)
    origLen = length(qmf(w.waveType))
    netLen = origLen
    nIters = 0
    if N < netLen
        return nIters
    end
    while N > netLen - 2^nIters + 1
        netLen = nextLength(netLen, origLen)
        nIters += 1
    end
    return (nIters, netLen - 2^nIters + 1)
end
# the actual length calculator, as this is recursive
nextLength(curr, orig) = curr * 2 + orig - 1 # upsample then convolve
# pad a vector `v` to have length `N`. Or chop off from both sides if its too long
function padTo(v, N)
    nDiff = N - length(v)
    if nDiff > 0 # there should be more entries than there are
        cat(v, zeros(eltype(v), max(N - length(v), 0)), dims = 1)
    elseif nDiff == 0
        return v
    else # there are too many, so chop off half the difference from each side
        inds = ceil(Int, 1 - nDiff / 2):length(v)+ceil(Int, nDiff / 2)
        return v[inds]
    end
end

# create interpolater for the orthogonal cases
genInterp(ψ) = interpolate(ψ, BSpline(Quadratic(Reflect(OnGrid()))))

"""
    computeDualWeights(Ŵ) -> β

Compute the weight given to each wavelet so that in the Fourier domain, the sum across
wavelets is as close to 1 at every frequency below the peak of the last wavelet
(that is `β' .*abs.(Ŵ[1:lastFreq,:]) ≈ ones(lastFreq)` in an ℓ^2 sense).
"""
function computeDualWeights(Ŵ, wav)
    @views lastReasonableFreq = computeLastFreq(Ŵ[:, end], wav)
    Wdag = pinv(Ŵ[1:lastReasonableFreq, :])
    β = conj.(Wdag * ones(size(Wdag, 2)))'
    return β
end
# function computeDualWeights(Ŵ, wav::CWT{W,T,<:ContOrtho,N}, n,naive=true) where {W,T,N}
#     β = computeNaiveDualWeights(Ŵ, wav, n)
#     return β
# end
computeLastFreq(ŵ, wav) = length(ŵ) - 3 # we have constructed the wavelets so the last frequency is <1%, so trying to get it to 1 will fail
computeLastFreq(ŵ, wav::CWT{W,T,<:Morlet}) where {W,T} = findlast(abs.(ŵ) .> 1 / 4 * maximum(abs.(ŵ))) # the gaussian decay makes this way too large by the end

"""
    computeNaiveDualWeights(Ŵ, wav, n1)

Compute the dual weights using the scaling amounts and the normalization power p. Primarily
used when the least squares version is poorly constructed. When the least squares version
doesn't perform well, this is also likely to have poor reconstruction, but it won't give
extremely negative or oscillatory weights like the least squares version.
"""
function computeNaiveDualWeights(Ŵ, wav, n1)
    _, _, sRange, _ = ContinuousWavelets.getNWavelets(n1, wav)
    isAve = !(wav.averagingType isa NoAve)
    p = wav.p
    # every wavelet is multiplied by 1/s^(1/p), so we need to multiply by that to normalize in frequency. The extra +1 is to convert from uniform in scale to uniform in frequency
    sToTheP = sRange' .^ (1 / p + 1)
    if isAve && size(Ŵ, 2) > 1
        # averaging needs to have maximum value of 1
        aveMultiplier = 1 / norm(Ŵ[:, 1], Inf)
        dual = sum(Ŵ[:, 2:end] .* sToTheP, dims = 2)
        renormalize = norm(dual, Inf)
        dual ./= renormalize
        #dual += Ŵ[:,1] .* sToTheP[1]
        β = cat(aveMultiplier, 2 .* sToTheP ./ renormalize..., dims = 2)
    elseif size(Ŵ, 2) > 1
        dual = sum(Ŵ .* sToTheP, dims = 2)
        β = sToTheP ./ norm(dual, Inf)
        β[2:end] .* 2
    else
        # there's only the averaging
        β = [1 / norm(Ŵ[:, 1], Inf)]
    end
    return β
end

"""
    getDualCoverage(n,cWav, invType) -> dualCover, dualNorm

Get the sum of the weights and its deviation from 1.
"""
function getDualCoverage(n, cWav, invType)
    Ŵ = computeWavelets(n, cWav)[1]
    if invType isa DualFrames
        dualCover = sum(conj.(Ŵ) .* explicitConstruction(Ŵ), dims = 2)
        return dualCover, norm(dualCover .- 1)
    elseif invType isa PenroseDelta
        β = computeDualWeights(Ŵ, cWav)
    elseif invType isa NaiveDelta
        β = computeNaiveDualWeights(Ŵ, cWav, n)
    end
    dualCover = sum(conj.(β) .* Ŵ, dims = 2)
    dualCover, norm(dualCover .- 1)
end

function dualDeviance(n, cWav, naive)
    getDualCoverage(n, cWav, naive)
end

"""
    explicitConstruction(Ŵ)

Explicitly construct the canonical dual frame elements for a translation invariant frame.
This is likely to end poorly due to the low representation at high frequencies.
"""
function explicitConstruction(Ŵ)
    Ŵdual = conj.(Ŵ ./ [norm(ŵ)^2 for ŵ in eachslice(Ŵ, dims = 1)])
end

"""
    caveats(n1, c::CWT{B,CT,W}; coiTolerance = exp(-2), fsample = 2000) -> sRange, meanFreqs, coi

Given a length `n1` and a CWT struct `c`, returns the scales `sRange` used, the mean frequencies of the wavelets `meanFreqs`, and the cone of influence `coi` for each wavelet.
Returns the period, the scales, and the cone of influence for the given wavelet transform.
If you have sampling information, you will need to scale the vector scale appropriately by
1/δt, and the actual transform by δt^(1/p).
"""
function caveats(n1, c::CWT; coiTolerance = exp(-2), fsample = 2000)
    nOctaves, totalWavelets, sRange, sWidth = getNWavelets(n1, c)
    # padding determines the actual number of elements
    n, nSpace = setn(n1, c)
    # indicates whether we should keep a spot for the father wavelet
    isAve = !(typeof(c.averagingType) <: NoAve)

    # Fourier equivalent frequencies
    Ŵ, ω = computeWavelets(n1, c)
    freqs = getMeanFreq(Ŵ, fsample)
    coi = directCoiComputation(n1, c; coiTolerance = coiTolerance)
    return sRange, freqs, coi
end

caveats(Y::AbstractArray{T}, w::ContWaveClass) where {T<:Number,S<:Real} = caveats(size(Y, 1), CWT(w), J1 = J1)

"""
    directCoiComputation(n1, c::CWT; coiTolerance = exp(-2)) -> coi

A straightforward computation of the cone of influence directly from the
constructed wavelets. `coi` is a binary matrix of dimensions `(signalLength)×(nscales+1)`
that indicates when the autocorrelation of the wavlet is greater than
`coiTolerance` of the maximum value.
"""
function directCoiComputation(n1, c::CWT; coiTolerance = exp(-2))
    ψ, ω = ContinuousWavelets.computeWavelets(n1, c, space = true)
    autoCorr = cat([sum(ψ .* conj.(circshift(ψ, (ii, 0))), dims = 1) for ii = 1:size(ψ, 1)]..., dims = 1) # auto correlation in time domain
    normedAutoCorr = autoCorr ./ maximum(abs.(autoCorr), dims = 1) # normalize by the max norm
    return abs.(normedAutoCorr) .>= coiTolerance # the cone is where the autocorrelation is larger than the tolerance
end

"""
    coi(n, s, wave::CWT)

for a signal of length `n` and wavelet transform of type wave, return a binary vector indicating the cone of influence from the edges so that the auto-correlation of the wavelet has decreased by a factor of ``\\textrm{e}^2``.
"""
function coi(n, s, ::Morlet)
    sqrt(2) * s
end

@doc raw"""
    crossSpectrum(X, Y, c::CWT)

Compute the cross spectrum of signals `X` and `Y`, which is defined as ``S(\\conj{C(X)})``
Note that unlike `cwt`, `crossSpectrum` only works for collections `X` and `Y` of vectors of shape (signalLength)×(nSignals), and outputs a collection of cross spectrums that has shape (signalLength)×(nscales+1)×(nSignalsX)×(nSignalsY).

# Examples
```jldoctest
julia> crossSpectrum(X, Y)
insert result of crossSpectrum(X, Y)
```
"""
function crossSpectrum(X, Y, c::CWT{B,S,W,N,isAn}) where {B,S,W,N,isAn}
    aveCXY, _ = sharedCrossSpectrum(X, Y, c)
    return aveCXY
end

@doc raw"""
    waveletCoherence(X, Y, c)

Compute the wavelet coherence between `X` and `Y`. This is given by the power of the cross spectrum, normalized by smoothed powers of both `X` and `Y`. Explicitly, that is ``\frac{|S(C^*(X)C(Y))|^2}{S(|C(X)|^2)S(|C(Y)|^2)}``

# Examples
```jldoctest
julia> waveletCoherence(X,Y,c)
insert result of waveletCoherence(X,Y,c)
```
"""
function waveletCoherence(X, Y, c)
    (aveCXY, aveWavelet, CX, CY) = sharedCrossSpectrum(X, Y, c)
    aveCX = abs.(dropdims(ContinuousWavelets.cwt(abs.(CX) .^ 2, c, aveWavelet), dims = 2))
    aveCY = abs.(dropdims(ContinuousWavelets.cwt(abs.(CY) .^ 2, c, aveWavelet), dims = 2))
    return abs.(aveCXY) .^ 2 ./ (aveCX .* aveCY)
end

"""
`waveletCoherence` depends on results precomputed by `crossSpectrum` in addition to the actual output of `crossSpectrum`.
"""
function sharedCrossSpectrum(X, Y, c)
    # ensure that the wavelet transform uses some sort of averaging
    isAve = !(typeof(c.averagingType) <: NoAve)
    if !isAve
        cAve = CWT(c.waveType, c.Q, ContinuousWavelets.boundaryType(c)(), ContinuousWavelets.Father(), c.averagingLength, c.frameBound, c.p, c.β)
    else
        cAve = c
    end

    # make sure both X and Y have the right shape
    if ndims(X) < 2
        X = reshape(X, (size(X)..., 1))
    elseif ndims(X) > 2
        X = reshape(X, (size(X, 1), :))
    end
    if ndims(Y) < 2
        Y = reshape(Y, (size(Y)..., 1))
    elseif ndims(Y) > 2
        Y = reshape(Y, (size(Y, 1), :))
    end
    daughters, _ = ContinuousWavelets.computeWavelets(size(Y, 1), cAve)
    aveWavelet = daughters[:, 1:1] ./ norm(daughters[:, 1:1], c.p) # we need an averaging function that returns the ave value, rather than the one that preserves the frame bound
    CX = ContinuousWavelets.cwt(X, cAve, daughters)
    CY = ContinuousWavelets.cwt(Y, cAve, daughters)
    # reshape so we can do an outer product
    CX = reshape(CX, (size(CX)[1:3]..., 1))
    CY = reshape(CY, (size(CY)[1:2]..., 1, size(CY, 3)))
    CXY = conj.(CX) .* CY # conjugate of the first times the second
    aveCXY = dropdims(ContinuousWavelets.cwt(CXY, cAve, aveWavelet), dims = 2) # get the average of the multiplication
    return (aveCXY, aveWavelet, CX, CY)
end


# MovingAverageFunction.jl - Tipo para computar medias móviles de medidas de inflación

"""
    InflationMovingAverage{F <: InflationFunction} <: InflationFunction
Función de inflación que computa el promedio móvil de `k` períodos de la
trayectoria interanual de la medida de inflación `inflfn` almacenada.

## Ejemplo

Para computar la media móvil de 6 meses de la variación interanual del IPC:
```julia-repl
julia> inflfn = InflationMovingAverage(InflationTotalCPI(), 6)
(::InflationMovingAverage{InflationTotalCPI}) (generic function with 5 methods)
```
"""
struct InflationMovingAverage{F <: InflationFunction} <: InflationFunction
    inflfn::F
    periods::Int
end

# 2. Extender el método de nombre
measure_name(mafn::InflationMovingAverage) = "Promedios Móvil de $(mafn.periods) períodos de " * measure_name(mafn.inflfn)

# Método que opera sobre CountryStructure: computa la trayectoria de inflación
# con la función inflfn y luego computa el promedio móvil de k períodos
function (mafn::InflationMovingAverage)(cs::CountryStructure)

    # Cómputo usual de inflación
    tray_infl = mafn.inflfn(cs)

    # Algoritmo de promedio móvil
    k = mafn.periods
    k == 1 && return tray_infl

    # Computar el promedio móvil (in-place)
    ma_tray_infl = moving_average(tray_infl, k)

    # Devolver la trayectoria de inflación
    ma_tray_infl
end

# Actualmente, se define el resumen intermensual por el obtenido con la función
# de inflación interna
# function (mafn::InflationMovingAverage)(base::VarCPIBase)
#     # Obtener resumen intermensual y aplicarle media móvil
#     vm = mafn.inflfn(base)
#     moving_average!(vm, mafn.periods)
#     vm
# end

# Función para computar el promedio móvil de la serie `v` con `k` períodos. Esta
# función modifica los elementos de `v` con el promedio móvil.
function moving_average(v, k)
    ma = similar(v)
    ma[1] = v[1]
    for j = 2:k-1
        ma[j] = mean(@view v[1:j])
    end
    for t = k:length(v)
        ma[t] = mean(@view v[t-k+1:t])
    end

    ma
end


"""
    AbstractNearestNeighbors

Abstract type for nearest neighbor plug-in implementations.
"""
abstract type AbstractNearestNeighbors end

"""
    knn(NN::AbstractNearestNeighbors, X::AbstractVecOrMat{T}; kwargs...) -> (D,E)

Perform construction of the distance matrix `D` and neares neighbor weighted graph `E` from the `NN` object
"""
function knn(NN::AbstractNearestNeighbors, X::AbstractVecOrMat{T}; kwargs...) where T<:Real end

"""
    BruteForce

Calculate NN using pairwise distance matrix.
"""
struct BruteForce{T<:Real} <: AbstractNearestNeighbors
    k::Integer
    fitted::AbstractMatrix{T}
end
fit(::Type{BruteForce}, X::AbstractMatrix{T}, k::Integer) where {T<:Real} = BruteForce(k, X)
show(io::IO, NN::BruteForce) = print(io, "BruteForce(k=$(NN.k))")

function knn(NN::BruteForce{T}, X::AbstractVecOrMat{T}; self=false) where T<:Real
    m, n = size(X)
    k = NN.k
    @assert n > k "Number of observations must be more then $(k)"

    D = pairwise((x,y)->norm(x-y), eachcol(NN.fitted), eachcol(X))

    d = Array{T}(undef, k, n)
    e = Array{Int}(undef, k, n)
    idxs = self ? (1:k) : (2:k+1)

    @inbounds for j = 1 : n
        e[:, j] = sortperm(D[:,j])[idxs]
        d[:, j] = D[e[:, j],j]
    end

    return (d, e)
end


# compute L2 norm
function sqnorm(x)
  return sum(abs2, x)
end

# compute binary crossentropy loss
function crossentropy(p, l)

  # add epsilon perturbation for floating-point stability
  return -1*(l*log(p+eps(float(eltype(p))))+(1-l)*log(1-p+eps(float(eltype(p)))))
end

# get a batch of indicies for NN training
function get_batches(indices, batchsize, batch)
  if batch*batchsize > length(indices)
    if (batch-1)*batchsize+1 > length(indices)
      error("batchsize too large")
    else
      return indices[1+batchsize*(batch-1):end]
    end
  else
    return indices[1+batchsize*(batch-1):batch*batchsize]
  end
end

# construct data set for NN training based on prototypical probability distributions (cases 1-3)
function get_dataset_proto(p_range, distribution, samples)
  dataset = zeros(eltype(p_range[1]), (3, length(p_range)*length(samples)))
  count = 1
  for i in 1:length(p_range)
    for j in 1:length(samples)
      dataset[:, count] = [j, distribution(samples[j], p_range[i]), i]
      count += 1
    end
  end
  return dataset
end

# construct data set for NN training based on a given probability distributions
function get_dataset(p_range, distribution, samples)
  dataset_ul, n_unique = get_dataset_unlabelled(p_range, distribution, samples)

  dataset = zeros(eltype(p_range[1]), (3, n_unique))
  count = 1
  len_p_train = 0
  for i in 1:length(dataset_ul)
    data = dataset_ul[i]
    p = p_range[i]

    for j in 1:length(data)
      freq = data[j][2]
      if freq != zero(eltype(freq))
        dataset[:, count] = [data[j][1], freq, i*one(eltype(freq))]
        count += 1
      end
    end
  end
  return dataset
end

# construct data set for NN training based on a given probability distributions, where labels are not yet included
function get_dataset_unlabelled(p_range, distribution, samples)
  data, counter = prepare_dataset(p_range[1], distribution, samples)
  datasets = [data]
  for indxp in 2:length(p_range)
    data,count = prepare_dataset(p_range[indxp], distribution, samples)
    push!(datasets, data)
    counter += count
  end
  return datasets, Int(counter)
end

# prepare list of samples and count number of unique samples
function prepare_dataset(p, distribution, samples)
  counter = zero(eltype(length(samples)))
  dist = distribution(samples[1][1],p)
  if dist != zero(eltype(dist))
    counter += one(eltype(counter))
  end

  data = [[samples[1][1], dist]]
  for indx in 2:length(samples)
    dist = distribution(samples[indx][1], p)
    if dist != zero(eltype(dist))
      counter += one(eltype(counter))
    end
    push!(data, [samples[indx][1], dist])
  end
  return data, counter
end

# construct the training data set for applying SL with NNs, where we modify the underlying probability distributions to combat deviations due to finite sample statistics (applicable for data generated from Boltzmann distributions)
function get_modified_dataset_train_SL(distr, samples, p_range, n_samples; gs_index=1)
  new_dataset_train_SL = zeros(eltype(p_range[1]), (3, gs_index+length(samples)))

  for gs_indx in 1:gs_index
    new_dataset_train_SL[:, gs_indx]=[samples[gs_indx], distr(samples[gs_indx], p_range[1]), 1]
  end
  num_samples = n_samples

  for indx in collect(1:gs_index)
    sample = samples[indx]
    p = length(p_range)

    new_dataset_train_SL[:, indx+gs_index]=[sample, distr(sample,p_range[end]), p]
  end

  for indx in collect(gs_index+1:length(samples))
    sample = samples[indx]
    prob = Int(round(distr(sample,p_range[end])*n_samples))

    # add an instance of each sample which has not appeared in the training set to the set of samples associated with the largest sampled temperature value
    if prob == zero(eltype(p_range[1]))
      prob = one(eltype(p_range[1]))
      num_samples += one(eltype(n_samples))
    end
    p = length(p_range)

    new_dataset_train_SL[:, indx+gs_index]=[sample, prob, p]
  end

  new_dataset_train_SL[2, gs_index+1:end] = new_dataset_train_SL[2, gs_index+1:end]./num_samples

  return new_dataset_train_SL
end

# construct the training data set for applying SL with NNs, where we do not modify the underlying probability distributions (applicable for data generated from Boltzmann distributions)
function get_unmodified_dataset_train_SL(distr, samples, p_range, n_samples)
  new_dataset_train_SL = zeros(eltype(p_range[1]), (3, 1+length(samples)))
  new_dataset_train_SL[:, 1] = [samples[1], distr(samples[1], p_range[1]), 1]

  for indx in collect(1:length(samples))
    sample = samples[indx]
    prob = distr(sample, p_range[end])
    p = length(p_range)
    new_dataset_train_SL[:, indx+1] = [sample, prob, p]
  end

  new_dataset_train_SL[2, 2:end] = new_dataset_train_SL[2, 2:end]

  return new_dataset_train_SL
end

# construct the training data set for applying SL with NNs (general probability distributions)
function get_training_data_SL(dataset_SL, p_range, p_max, p_min)
	train = [dataset_SL[:, 1]]
	for i in 2:size(dataset_SL)[2]
		ele = dataset_SL[:, i]
		if p_range[Int(ele[3])] <= p_min || p_range[Int(ele[3])] >= p_max
			push!(train, ele)
		end
	end
	return hcat(train...)
end

# compute mean and standard deviation of input features given a data set (used for standardization)
function get_dataset_stats(dataset, inputs, n_points)
  mu = zeros(eltype(dataset[2, 1]), size(inputs)[1])
  musq = zeros(eltype(dataset[2, 1]), size(inputs)[1])
  for indx in collect(1:size(dataset)[2])
    mu .+= dataset[2, indx]*inputs[Int(dataset[1, indx])]
    musq .+= dataset[2, indx]*inputs[Int(dataset[1, indx])]^2
  end
  mu = mu./n_points
  musq = musq./n_points
  return mu, sqrt.(musq.-mu.^2)
end


"""
    equispaced_rectangular_mesh(
    x1min::FT,
    x2min::FT,
    x1max::FT,
    x2max::FT,
    n1,
    n2,
    per = (false, false),
) where {FT<:AbstractFloat}

This function generates an equispaced rectangular mesh with user provided coordinates of left bottom and
right top vertices, number of mesh elements in each direction and periodicity information.
"""
function equispaced_rectangular_mesh(
    x1min::FT,
    x2min::FT,
    x1max::FT,
    x2max::FT,
    n1,
    n2,
    per = (false, false),
) where {FT<:AbstractFloat}
    x1c = range(x1min, x1max; length = n1 + 1)
    x2c = range(x2min, x2max; length = n2 + 1)
    return Mesh2D(x1c, x2c, per)
end

"""
    Mesh2D(x1c, x2c, per = (false, false))

This function builds a 2D rectangular mesh with points located at x1c and x2c in x1 and x2 directions respectively.
"""
function Mesh2D(x1c, x2c, per = (false, false))
    FT = eltype(x1c)
    I = Int

    nx1, nx2 = length(x1c), length(x2c)
    nel1, nel2 = nx1 - 1, nx2 - 1

    nverts = nx1 * nx2
    nfaces = nx1 * (nx2 - 1) + (nx1 - 1) * nx2
    nelems = (nx1 - 1) * (nx2 - 1)
    nbndry = 4

    nfc1 = nx1 * (nx2 - 1) # faces with normals along x1 direction

    ndmat = reshape(1:nverts, nx1, nx2) # node numbers

    fcmat1 = reshape(1:nfc1, nx1, nx2 - 1)        # faces with normals along x1 direction
    fcmat2 = reshape((nfc1+1):nfaces, nx1 - 1, nx2) # faces with normals along x2 direction

    emat = reshape(1:nelems, nx1 - 1, nx2 - 1) # element numbers

    coordinates = [repeat(x1c, 1, nx2)[:] repeat(x2c', nx1, 1)[:]] # node coordinates

    face_verts = hcat(
        vcat(ndmat[:, 1:(nx2-1)][:], ndmat[1:(nx1-1), :][:]), # face nodes
        vcat(ndmat[:, 2:nx2][:], ndmat[2:nx1, :][:]),
    )

    face_boundary = Vector{I}(zeros(nfaces))
    # accounting for periodic boundaries, wherever applicable
    periodic = per[1] || per[2]
    periodic_faces = Dict{Int,Int}()
    global_vert = Vector{I}(1:nverts)
    if per[1]
        bdy1 = emat[end:end, :]
        bdy2 = emat[1:1, :]
        nbndry -= 2
        for pfc = 1:(nx2-1)
            pfc1, pfc2 = fcmat1[1, pfc], fcmat1[nx1, pfc]
            periodic_faces[pfc1] = fcmat1[pfc2]
            periodic_faces[pfc2] = fcmat1[pfc1]
        end
        for i = 1:nx2
            global_vert[ndmat[nx1, i]] = ndmat[1, i]
        end
    else
        bdy1 = bdy2 = zeros(I, 1, nx2 - 1)
        face_boundary[fcmat1[1, :]] .= 1  # left boundary
        face_boundary[fcmat1[end, :]] .= 2  # right boundary
    end
    if per[2]
        bdy3 = emat[:, end:end]
        bdy4 = emat[:, 1:1]
        nbndry -= 2
        for pfc = 1:(nx1-1)
            periodic_faces[fcmat2[pfc, 1]] = fcmat2[pfc, nx2]
            periodic_faces[fcmat2[pfc, nx2]] = fcmat2[pfc, 1]
        end
        for i = 1:nx1
            global_vert[ndmat[i, nx2]] = global_vert[ndmat[i, 1]]
        end
    else
        bdy3 = bdy4 = zeros(I, nx1 - 1, 1)
        face_boundary[fcmat2[:, 1]] .= 3  # bottom boundary
        face_boundary[fcmat2[:, end]] .= 4  # top boundary
    end

    face_neighbors = hcat(
        vcat(vcat(bdy1, emat)[:], hcat(bdy3, emat)[:]),
        zeros(I, nfaces),
        vcat(vcat(emat, bdy2)[:], hcat(emat, bdy4)[:]),
        zeros(I, nfaces),
        -ones(I, nfaces), # relative orientation is always reversed
    )

    elem_verts = hcat(
        ndmat[1:(nx1-1), 1:(nx2-1)][:], # node numbers (local vertex # 1)
        ndmat[2:nx1, 1:(nx2-1)][:], # for each element (local vertex # 2)
        ndmat[2:nx1, 2:nx2][:], #(local vertex # 3)
        ndmat[1:(nx1-1), 2:nx2][:], # (local vertex # 4)
    )
    elem_faces = hcat( # face numbers for each element
        fcmat2[:, 1:(nx2-1)][:], # local face 1
        fcmat1[2:nx1, :][:], # local face 2
        fcmat2[:, 2:nx2][:], # local face face 3
        fcmat1[1:(nx1-1), :][:], # local face 4
    )
    ref_fc_verts = [
        1 2 3 4
        2 3 4 1
    ]
    for fc = 1:nfaces
        elems = (face_neighbors[fc, 1], face_neighbors[fc, 3])

        if periodic && haskey(periodic_faces, fc)
            shadow = periodic_faces[fc]
            localface1 = findfirst(elem_faces[elems[1], :] .== fc)
            if !isnothing(localface1)
                localface2 = findfirst(elem_faces[elems[2], :] .== shadow)
                if !isnothing(localface2)
                    face_neighbors[fc, 2] = localface1
                    face_neighbors[fc, 4] = localface2
                else
                    error(
                        "rectangular_mesh: Fatal error, face could not be located in neighboring element",
                    )
                end
            else
                localface1 = findfirst(elem_faces[elems[1], :] .== shadow)
                localface2 = findfirst(elem_faces[elems[2], :] .== fc)
                if !isnothing(localface1) && !isnothing(localface2)
                    face_neighbors[fc, 2] = localface1
                    face_neighbors[fc, 4] = localface2
                else
                    error(
                        "rectangular_mesh: Fatal error, face could not be located in neighboring element",
                    )
                end
            end
        else # no periodic boundaries
            for e = 1:2
                el = elems[e]
                if el ≠ 0
                    localface = findfirst(elem_faces[el, :] .== fc)
                    face_neighbors[fc, 2+(e-1)*2] = localface
                end
            end
        end
    end

    if periodic # marking shadow faces with -1
        for (per, shd) in periodic_faces
            face_boundary[max(per, shd)] = -1
        end
    end
    boundary_tags = sort(unique(face_boundary))
    face_boundary_offset = ones(I, length(boundary_tags) + 1)
    face_boundary_tags = sort(face_boundary)
    face_boundary = sortperm(face_boundary)

    if periodic # remove redundant shadow faces
        loc = findlast(face_boundary_tags .== -1) + 1
        boundary_tags = boundary_tags[2:end]
        face_boundary_offset = face_boundary_offset[2:end]
        face_boundary_tags = face_boundary_tags[loc:end]
        face_boundary = face_boundary[loc:end]
    end

    for i = 1:(length(boundary_tags)-1)
        tag = boundary_tags[i+1]
        face_boundary_offset[i+1] = findfirst(face_boundary_tags .== tag)
    end
    face_boundary_offset[end] = length(face_boundary) + 1
    tag_names = (:interior, :west, :east, :south, :north)

    boundary_tag_names = tuple(tag_names[boundary_tags.+1]...)
    NB = length(boundary_tag_names)

    # add unique vertex iterator information
    vtconn = map(i -> zeros(I, i), zeros(I, nverts))

    for el = 1:nelems
        for lv = 1:4
            vt = global_vert[elem_verts[el, lv]]
            push!(vtconn[vt], el, lv)
        end
    end
    unique_verts = I[]
    uverts_conn = I[]
    uverts_offset = I.([1])
    for vt = 1:nverts
        lconn = length(vtconn[vt])
        if lconn > 0
            push!(unique_verts, vt)
            push!(uverts_conn, vtconn[vt]...)
            push!(uverts_offset, uverts_offset[end] + lconn)
        end
    end

    return Mesh2D(
        nverts,
        nfaces,
        nelems,
        nbndry,
        coordinates,
        unique_verts,
        uverts_conn,
        uverts_offset,
        face_verts,
        face_neighbors,
        face_boundary,
        boundary_tags,
        boundary_tag_names,
        face_boundary_offset,
        elem_verts,
        elem_faces,
    )
end


struct Fuselage{T <: Real} <: AbstractAircraft
    length   :: T
    weights  :: Vector{T}
    radii    :: Vector{T}
    position :: SVector{3,T}
end

function Fuselage(L :: T, weights :: AbstractVector{T}, radii :: AbstractVector{T}, position = zeros(3)) where T <: Real
    @assert all(x -> 0 <= x <= 1., weights) "The distribution of weights must be lie in [0,1] ⊂ ℝ."
    perms = sortperm(weights); sort!(weights)
    Fuselage{T}(L, weights, radii[perms], position)
end

projected_area(fuse :: Fuselage) = forward_sum(fuse.radii) / 2 .* fuse.weights
Base.length(fuse :: Fuselage) = fuse.length

function coordinates(fuse :: Fuselage, n)
    ws_rads = cosine_spacing(fuse, n)

    [ ws_rads[:,1] .* fuse.length ws_rads[:,2] ]
end

function cosine_spacing(fuse :: Fuselage, n)
    xs = fuse.weights
    ys = fuse.radii
    cosine_interp([ xs ys ], n)
end


module Chirps

using HDF5, ..Fct

thresholds = [
  5   1.5
  6   4.0
  7   5.9
  8   7.3
  9   8.6
 10   9.7
 11  10.8
 12  11.8
 13  12.8
 14  13.8
 15  14.8
 16  15.8
 17  16.9
]

L=size(thresholds)[1]

offsets = accumulate(+,vcat([1],[1+2^(Int64(thresholds[i,1])-1) for i in 1:L]))

#even-degree Chebyshev coefficients of linear chirps
chirps = Vector{Complex{Float64}}(undef,offsets[end])

degrees = [2 ^ Int64(p) for p in thresholds[:,1]]
rates = 2.0 .^ thresholds[:,2]

function getchirp(i)# 1 <= i <= L
    view(chirps,offsets[i]:offsets[i+1]-1)
end

function store(file)
    h=h5open(file,"w") #overwrite
    for i in 1:L
        N = degrees[i]
        a = BigFloat(rates[i])
        s = Fct.chebsample(x->exp(a*im*x^2),2N,T=Complex{BigFloat})
        g = getchirp(i)
        h[string(N)] = g[:] = Complex{Float64}.(Fct.chebcoeffs(s)[1:2:N+1])
    end
    close(h)
end

function load(file)
    h=h5open(file,"r")
    for i in 1:L
        g = getchirp(i)
        g[:] = read(h, string(degrees[i]))
    end
    close(h)
end

end #module


@testset "Phase and BoundedFluidQueue" begin
    @testset "PhaseSet struct" begin
        @test S[1]==Phase(0.0,1)
        @test_throws DomainError PhaseSet([-1.0],[1])
        @test_throws DomainError PhaseSet([-1.0],[2])
        @test S == [Phase(C[i]) for i in 1:length(C)]
        @test n_phases(S)==3
        @test [rates(S,i) for i in 1:length(C)]==C
        @test rates(S)==C
        @test [membership(S,i) for i in 1:length(m)]==m
        @test membership(S)==m
        @test phases(S)==1:length(C)
        @test N₊(S)==sum(C.>=0.0)
        @test N₋(S)==sum(C.<=0.0)
        @test DiscretisedFluidQueues.checksquare(T)===nothing
        @test_throws DomainError DiscretisedFluidQueues.checksquare(T[1:2,:])
    end

    @testset "BoundedFluidQueue struct" begin
        @test model.T==T
        @test n_phases(model)==length(C)
        @test n_phases(model.S)==length(C)
        @test model.S==S
        @test rates(model)==C
        @test rates(model,1)==C[1]
        @test n_phases(model)==length(C)
        @test phases(model)==1:length(C)

        @test_logs (:warn,"row sums of T should be 0") BoundedFluidQueue(T_warn,S,nodes[end])

        @test_throws DomainError BoundedFluidQueue(T_nz[1:2,:],S,nodes[end])
        @test_throws DomainError BoundedFluidQueue(T,S[1:end-1],nodes[end])
    end

    for f in fieldnames(BoundedFluidQueue)
        @eval @test am.$f==augment_model(model).$f
    end
end


import NLPModels: grad, cons, jac

"""
unconstrained: return the infinite norm of the gradient of the objective function

required: state.gx (filled if nothing)
"""
function unconstrained_check(pb    :: AbstractNLPModel,
                             state :: NLPAtX;
                             pnorm :: Float64 = Inf,
                             kwargs...)

    if state.gx == nothing # should be filled if empty
        update!(state, gx = grad(pb, state.x))
    end

    res = norm(state.gx, pnorm)

    return res
end

"""
unconstrained 2nd: check the norm of the gradient and the smallest
                   eigenvalue of the hessian.

required: state.gx, state.Hx (filled if nothing)
"""
function unconstrained2nd_check(pb    :: AbstractNLPModel,
                                state :: NLPAtX;
                                pnorm :: Float64 = Inf,
                                kwargs...)

    if state.gx == nothing # should be filled if empty
        update!(state, gx = grad(pb, state.x))
    end
    if state.Hx == nothing
        update!(state, Hx = hess(pb, state.x))
    end

    res = max(norm(state.gx, pnorm),
              max(- eigmin(state.Hx + state.Hx' - diagm(0 => diag(state.Hx))), 0.0))

    return res
end

"""
optim_check_bounded: gradient of the objective function projected

required: state.gx (filled if void)
"""
function optim_check_bounded(pb    :: AbstractNLPModel,
                             state :: NLPAtX;
                             pnorm :: Float64 = Inf,
                             kwargs...)

    if state.gx == nothing # should be filled if void
     update!(state, gx = grad(pb, state.x))
    end

    proj = max.(min.(state.x - state.gx, pb.meta.uvar), pb.meta.lvar)
    gradproj = state.x - proj
    res = norm(gradproj, pnorm)

    return res
end

"""
constrained: return the violation of the KKT conditions
length(lambda) > 0
"""
function _grad_lagrangian(pb    :: AbstractNLPModel,
                          state :: NLPAtX)

 if (pb.meta.ncon == 0) & !has_bounds(pb)
  return state.gx
 elseif pb.meta.ncon == 0
  return state.gx + state.mu
 else
  return state.gx + state.mu + state.Jx' * state.lambda
 end
end

function _sign_multipliers_bounds(pb    :: AbstractNLPModel,
                                  state :: NLPAtX)
 if has_bounds(pb)
  return vcat(min.(max.( state.mu,0.0), - state.x + pb.meta.uvar),
              min.(max.(-state.mu,0.0),   state.x - pb.meta.lvar))
 else
  return zeros(0)
 end
end

function _sign_multipliers_nonlin(pb    :: AbstractNLPModel,
                                  state :: NLPAtX)
 if pb.meta.ncon == 0
  return zeros(0)
 else
  return vcat(min.(max.( state.lambda,0.0), - state.cx + pb.meta.ucon),
              min.(max.(-state.lambda,0.0),   state.cx - pb.meta.lcon))
 end
end

function _feasibility(pb    :: AbstractNLPModel,
                      state :: NLPAtX)
 if pb.meta.ncon == 0
  return vcat(max.(  state.x  - pb.meta.uvar,0.0),
              max.(- state.x  + pb.meta.lvar,0.0))
 else
  return vcat(max.(  state.cx - pb.meta.ucon,0.0),
              max.(- state.cx + pb.meta.lcon,0.0),
              max.(  state.x  - pb.meta.uvar,0.0),
              max.(- state.x  + pb.meta.lvar,0.0))
 end
end

"""
KKT: verifies the KKT conditions

required: state.gx
+ if bounds: state.mu
+ if constraints: state.cx, state.Jx, state.lambda
"""
function KKT(pb    :: AbstractNLPModel,
             state :: NLPAtX;
             pnorm :: Float64 = Inf,
             kwargs...)

    #Check the gradient of the Lagrangian
    gLagx      = _grad_lagrangian(pb, state)
    #Check the complementarity condition for the bounds
    res_bounds = _sign_multipliers_bounds(pb, state)
    #Check the complementarity condition for the constraints
    res_nonlin = _sign_multipliers_nonlin(pb, state)
    #Check the feasibility
    feas       = _feasibility(pb, state)

    res = vcat(gLagx, feas, res_bounds, res_nonlin)

    return norm(res, pnorm)
end


function UW(M,ndim,nbins)
    results = zeros(size(M))

    for i in 1:ndim

        if length(unique(M[i,:]))<3
            res_temp = zeros(length(M[i,:]))

            for u in 1:length(unique(M[i,:]))
                res_temp[M[i,:] .== unique(M[i,:])[u]] .= u
            end

            results[i,:] = res_temp
        else

            disc = LinearDiscretizer(binedges(DiscretizeUniformWidth(nbins), M[i,:]))
            results[i,:] = [encode(disc,one) for one in M[i,:]]

        end

    end

    return(DataFrame(results))

    return(results)

end


cd(@__DIR__)
using DiscreteEvents, DataStructures, Distributions, Random, BenchmarkTools
using Plots, Printf
const _bench = [false]

Random.seed!(8710) # set random number seed for reproducibility
num_customers = 10 # total number of customers generated
num_servers = 2 # number of servers
mu = 1.0 / 2 # service rate
lam = 0.9 # arrival rate
arrival_dist = Exponential(1 / lam) # interarrival time distriubtion
service_dist = Exponential(1 / mu); # service time distribution

# Define Customer Behavior
function customer(clk::Clock, server::Channel, id::Int, ta::Float64, ds::Distribution)
    delay!(clk, ta)       # customer arrives
    _bench[end] || now!(clk, ()->@printf("%5.3f: customer %d arrived\n", tau(clk), id))
    s = take!(server)     # customer starts service
    _bench[end] || now!(clk, ()->@printf("%5.3f: customer %d entered service\n", tau(clk), id))
    delay!(clk, rand(ds)) # server is busy
    put!(server, s)       # customer exits service
    _bench[end] || now!(clk, ()->@printf("%5.3f: customer %d exited service\n", tau(clk), id))
end

function initialize!(clock, arrival_dist, service_dist, num_customers, server)
    arrival_time = 0.0
    for i = 1:num_customers # initialize customers
        arrival_time += rand(arrival_dist)
        process!(clock, Prc(i, customer, server, i, arrival_time, service_dist), 1)
    end
end

function run_model(arrival_dist, service_dist, num_customers, num_servers, t)
    clock = Clock() # initialize simulation environment
    server = Channel{Int}(num_servers)  # initialize servers
    for i in 1:num_servers
        put!(server, i)
    end
    initialize!(clock, arrival_dist, service_dist, num_customers, server)
    run!(clock, t)
end

function run_model(num_customers)
    result = @benchmark run_model($arrival_dist, $service_dist, $num_customers, $num_servers, 2000000)
    return mean(result).time*1e-9
end

_bench[end] = true
N = [10,1000,2000,4000]

times = run_model.(N)
plot(N, times, xlabel="Customers", ylabel="Time (seconds)", leg=false, grid=false)
savefig("DiscreteEvents_Queue_Channel.png")


using Test
using QuantumOpticsBase
using LinearAlgebra, Random

@testset "states" begin

Random.seed!(0)

D(x1::Number, x2::Number) = abs(x2-x1)
D(x1::StateVector, x2::StateVector) = norm(x2-x1)

b1 = GenericBasis(3)
b2 = GenericBasis(5)
b3 = GenericBasis(7)
b = b1 ⊗ b2

bra = Bra(b)
ket = Ket(b)

# Test creation
@test_throws DimensionMismatch Bra(b, [1, 2])
@test_throws DimensionMismatch Ket(b, [1, 2])
@test 0 ≈ norm(bra)
@test 0 ≈ norm(ket)
@test_throws QuantumOpticsBase.IncompatibleBases bra*Ket(b1)
@test bra == bra
@test length(bra) == length(bra.data) == 15
@test length(ket) == length(ket.data) == 15
@test QuantumOpticsBase.basis(ket) == b
@test QuantumOpticsBase.basis(bra) == b
@test bra != basisstate(b, 1)

# Test copy
psi1 = randstate(b1)
psi2 = copy(psi1)
@test psi1.data == psi2.data
@test !(psi1.data === psi2.data)
psi2.data[1] = complex(10.)
@test psi1.data[1] != psi2.data[1]

# Arithmetic operations
# =====================
bra_b1 = dagger(randstate(b1))
bra_b2 = dagger(randstate(b2))
bra_b3 = randstate(b3)'

ket_b1 = randstate(b1)
ket_b2 = randstate(b2)
ket_b3 = randstate(b3)

# Addition
@test_throws DimensionMismatch bra_b1 + bra_b2
@test_throws DimensionMismatch ket_b1 + ket_b2
@test 1e-14 > D(bra_b1 + Bra(b1), bra_b1)
@test 1e-14 > D(ket_b1 + Ket(b1), ket_b1)
@test 1e-14 > D(bra_b1 + dagger(ket_b1), dagger(ket_b1) + bra_b1)

# Subtraction
@test_throws DimensionMismatch bra_b1 - bra_b2
@test_throws DimensionMismatch ket_b1 - ket_b2
@test 1e-14 > D(bra_b1 - Bra(b1), bra_b1)
@test 1e-14 > D(ket_b1 - Ket(b1), ket_b1)
@test 1e-14 > D(bra_b1 - dagger(ket_b1), -dagger(ket_b1) + bra_b1)

# Multiplication
@test 1e-14 > D(-3*ket_b1, 3*(-ket_b1))
@test 1e-14 > D(0.3*(bra_b1 - dagger(ket_b1)), 0.3*bra_b1 - dagger(0.3*ket_b1))
@test 1e-14 > D(0.3*(bra_b1 - dagger(ket_b1)), bra_b1*0.3 - dagger(ket_b1*0.3))
@test 0 ≈ bra*ket
@test 1e-14 > D((bra_b1 ⊗ bra_b2)*(ket_b1 ⊗ ket_b2), (bra_b1*ket_b1)*(bra_b2*ket_b2))

# Tensor product
@test tensor(ket_b1) == ket_b1
@test 1e-14 > D((ket_b1 ⊗ ket_b2) ⊗ ket_b3, ket_b1 ⊗ (ket_b2 ⊗ ket_b3))
@test 1e-14 > D((bra_b1 ⊗ bra_b2) ⊗ bra_b3, bra_b1 ⊗ (bra_b2 ⊗ bra_b3))

ket_b1b2 = ket_b1 ⊗ ket_b2
shape = (ket_b1b2.basis.shape...,)
idx = LinearIndices(shape)[2, 3]
@test ket_b1b2.data[idx] == ket_b1.data[2]*ket_b2.data[3]
ket_b1b2b3 = ket_b1 ⊗ ket_b2 ⊗ ket_b3
@test ket_b1b2b3 == tensor(ket_b1, ket_b2, ket_b3)
shape = (ket_b1b2b3.basis.shape...,)
idx = LinearIndices(shape)[1, 4, 3]
@test ket_b1b2b3.data[idx] == ket_b1.data[1]*ket_b2.data[4]*ket_b3.data[3]

# Norm
bf = FockBasis(1)
bra = Bra(bf, [3.0im, -4])
ket = Ket(bf, [-4.0im, 3])
@test 5 ≈ norm(bra)
@test 5 ≈ norm(ket)

bra_normalized = normalize(bra)
ket_normalized = normalize(ket)
@test 5 ≈ norm(bra)
@test 5 ≈ norm(ket)
@test 1 ≈ norm(bra_normalized)
@test 1 ≈ norm(ket_normalized)

bra_copy = deepcopy(bra)
ket_copy = deepcopy(ket)
normalize!(bra_copy)
normalize!(ket_copy)
@test 5 ≈ norm(bra)
@test 5 ≈ norm(ket)
@test 1 ≈ norm(bra_copy)
@test 1 ≈ norm(ket_copy)

# Test basis state
b1 = GenericBasis(2)
b2 = GenericBasis(3)
b = b1 ⊗ b2
x1 = basisstate(b1, 2)
x2 = basisstate(b2, 1)

@test norm(x1) == 1
@test x1.data[2] == 1
@test basisstate(b, [2, 1]) == x1 ⊗ x2

# Test permutating systems
b1 = GenericBasis(2)
b2 = GenericBasis(5)
b3 = FockBasis(3)

psi1 = randstate(b1)
psi2 = randstate(b2)
psi3 = randstate(b3)

psi123 = psi1 ⊗ psi2 ⊗ psi3
psi132 = psi1 ⊗ psi3 ⊗ psi2
psi213 = psi2 ⊗ psi1 ⊗ psi3
psi231 = psi2 ⊗ psi3 ⊗ psi1
psi312 = psi3 ⊗ psi1 ⊗ psi2
psi321 = psi3 ⊗ psi2 ⊗ psi1

@test 1e-14 > D(psi132, permutesystems(psi123, [1, 3, 2]))
@test 1e-14 > D(psi213, permutesystems(psi123, [2, 1, 3]))
@test 1e-14 > D(psi231, permutesystems(psi123, [2, 3, 1]))
@test 1e-14 > D(psi312, permutesystems(psi123, [3, 1, 2]))
@test 1e-14 > D(psi321, permutesystems(psi123, [3, 2, 1]))

@test 1e-14 > D(dagger(psi132), permutesystems(dagger(psi123), [1, 3, 2]))
@test 1e-14 > D(dagger(psi213), permutesystems(dagger(psi123), [2, 1, 3]))
@test 1e-14 > D(dagger(psi231), permutesystems(dagger(psi123), [2, 3, 1]))
@test 1e-14 > D(dagger(psi312), permutesystems(dagger(psi123), [3, 1, 2]))
@test 1e-14 > D(dagger(psi321), permutesystems(dagger(psi123), [3, 2, 1]))

# Test Broadcasting
@test_throws QuantumOpticsBase.IncompatibleBases psi123 .= psi132
@test_throws QuantumOpticsBase.IncompatibleBases psi123 .+ psi132
bra123 = dagger(psi123)
bra132 = dagger(psi132)
@test_throws ArgumentError psi123 .+ bra123
@test_throws QuantumOpticsBase.IncompatibleBases bra123 .= bra132
@test_throws QuantumOpticsBase.IncompatibleBases bra123 .+ bra132
psi_ = copy(psi123)
psi_ .+= psi123
@test psi_ == 2*psi123
bra_ = copy(bra123)
bra_ .= 3*bra123
@test bra_ == 3*dagger(psi123)
@test_throws ErrorException cos.(psi_)
@test_throws ErrorException cos.(bra_)

end # testset


using Rubin
using Tests
using Elliptic
using HypergeometricFunctions
using Polylogarithms
using SpecialFunctions

@test integrate((im+cot(x))^-1*cos(x)^4, x) == :(1//32*(im+-1*cot(x))^-2+5//32*(im+cot(x))^-2+im*(-8*cot(x)+8im)^-1+3*im*(16im+16*cot(x))^-1+-1//16*im*x+-1//24*im*(im+cot(x))^-3)
@test integrate((im+cot(x))^-1*cos(x)^3, x) == :(-1//5*cos(x)^5+-1//3*im*sin(x)^3+1//5*im*sin(x)^5)
@test integrate((im+cot(x))^-1*cos(x)^2, x) == :(1//8*(im+cot(x))^-2+im*(-8*cot(x)+8im)^-1+im*(4im+4*cot(x))^-1+-1//8*im*x)
@test integrate((im+cot(x))^-1*cos(x), x) == :(-1//3*cos(x)^3+-1//3*im*sin(x)^3)
@test integrate((im+cot(x))^-1*sec(x), x) == :(-1*cos(x)+im*sin(x)+-1*im*arctanh(sin(x)))
@test integrate((im+cot(x))^-1*sec(x)^2, x) == :(-1*log(sin(x))+im*x+-1*im*tan(x)+log(tan(x)))
@test integrate((im+cot(x))^-1*sec(x)^3, x) == :((1/2)*im*arctanh(sin(x))+-1//2*im*sec(x)*tan(x)+sec(x))
@test integrate((im+cot(x))^-1*sec(x)^4, x) == :((1/2)*tan(x)^2+-1//3*im*tan(x)^3)
@test integrate((im+cot(x))^-1*sec(x)^5, x) == :(1//3*sec(x)^3+1//8*im*arctanh(sin(x))+-1//4*im*sec(x)^3*tan(x)+1//8*im*sec(x)*tan(x))
@test integrate((im+cot(x))^-1*sec(x)^6, x) == :((1/2)*tan(x)^2+1//4*tan(x)^4+-1//3*im*tan(x)^3+-1//5*im*tan(x)^5)
@test integrate((a+b*cot(x))^-1*cos(x)^4, x) == :(-1*(4*a^2+4*b^2)^-1*sin(x)^4*(b+a*cot(x))+1//8*(a^2+b^2)^-2*sin(x)^2*(4*b*(b^2+2*a^2)+a*(b^2+5*a^2)*cot(x))+-1*b*a^4*(a^2+b^2)^-3*log(a*sin(x)+b*cos(x))+1//8*a*x*(a^2+b^2)^-3*(-1*b^4+3*a^4+-6*a^2*b^2))
@test integrate((a+b*cot(x))^-1*cos(x)^3, x) == :(a*(a^2+b^2)^-1*sin(x)+-1*a*(3*a^2+3*b^2)^-1*sin(x)^3+-1*b*(3*a^2+3*b^2)^-1*cos(x)^3+b*a^3*(a^2+b^2)^-5//2*arctanh((a^2+b^2)^-1//2*(a*cos(x)+-1*b*sin(x)))+-1*a*b^2*(a^2+b^2)^-2*sin(x)+-1*b*a^2*(a^2+b^2)^-2*cos(x))
@test integrate((a+b*cot(x))^-1*cos(x)^2, x) == :((2*a^2+2*b^2)^-1*sin(x)^2*(b+a*cot(x))+(1/2)*a*x*(a^2+b^2)^-2*(a^2+-1*b^2)+-1*b*a^2*(a^2+b^2)^-2*log(a*sin(x)+b*cos(x)))
@test integrate((a+b*cot(x))^-1*cos(x), x) == :(a*(a^2+b^2)^-1*sin(x)+-1*b*(a^2+b^2)^-1*cos(x)+a*b*(a^2+b^2)^-3//2*arctanh((a^2+b^2)^-1//2*(a*cos(x)+-1*b*sin(x))))
@test integrate((a+b*cot(x))^-1*sec(x), x) == :(a^-1*arctanh(sin(x))+b*a^-1*(a^2+b^2)^-1//2*arctanh((a^2+b^2)^-1//2*(a*cos(x)+-1*b*sin(x))))
@test integrate((a+b*cot(x))^-1*sec(x)^2, x) == :(a^-1*tan(x)+-1*b*a^-2*log(a+b*cot(x))+-1*b*a^-2*log(tan(x)))
@test integrate((a+b*cot(x))^-1*sec(x)^3, x) == :((1/2)*a^-1*arctanh(sin(x))+a^-3*b^2*arctanh(sin(x))+(1/2)*a^-1*sec(x)*tan(x)+-1*b*a^-2*sec(x)+b*a^-3*(a^2+b^2)^(1/2)*arctanh((a^2+b^2)^-1//2*(a*cos(x)+-1*b*sin(x))))
@test integrate((a+b*cot(x))^-1*sec(x)^4, x) == :(1//3*a^-1*tan(x)^3+a^-3*(a^2+b^2)*tan(x)+-1//2*b*a^-2*tan(x)^2+-1*b*a^-4*(a^2+b^2)*log(a+b*cot(x))+-1*b*a^-4*(a^2+b^2)*log(tan(x)))
@test integrate((1+2*cot(x))^-1*sec(x), x) == :(2//5*5^(1/2)*arctanh(1//5*5^(1/2)*(-2*sin(x)+cos(x)))+arctanh(sin(x)))

test/4 Trig functions/4.4 Cotangent/4.4.1.3 (d cos)^m (a+b cot)^n.jl


typealias BigFloats Union{BigFloat,Complex{BigFloat}}

function Base.fft{F<:Fun}(x::Vector{F})
    n,T = length(x),mapreduce(eltype,promote_type,x)
    if ispow2(n) return fft_pow2(x) end
    ks = linspace(zero(real(T)),n-one(real(T)),n)
    Wks = exp(-im*convert(T,π)*ks.^2/n)
    xq,wq = x.*Wks,conj([exp(-im*convert(T,π)*n);reverse(Wks);Wks[2:end]])
    return Wks.*conv(xq,wq)[n+1:2n]
end

Base.ifft{F<:Fun}(x::Vector{F}) = conj(fft(conj(x)))/length(x)
function Base.ifft!{F<:Fun}(x::Vector{F})
    y = conj(fft(conj(x)))/length(x)
    x[:] = y
    return x
end

function Base.conv{F<:Fun}(u::StridedVector{F}, v::StridedVector)
    nu,nv = length(u),length(v)
    n = nu + nv - 1
    np2 = nextpow2(n)
    pad!(u,np2),pad!(v,np2)
    y = ifft_pow2(fft_pow2(u).*fft_pow2(v))
    #TODO This would not handle Dual/ComplexDual numbers correctly
    T = promote_type(mapreduce(eltype,promote_type,u),mapreduce(eltype,promote_type,v))
    y = T<:Real ? real(y[1:n]) : y[1:n]
end

######################################################################
# TO BE DEPRECATED FOR V0.4 UPGRADE
######################################################################

# plan_fft for BigFloats (covers Laurent svfft)

Base.plan_fft{F<:Fun}(x::Vector{F}) = fft
Base.plan_ifft{F<:Fun}(x::Vector{F}) = ifft
Base.plan_ifft!{F<:Fun}(x::Vector{F}) = ifft

# Chebyshev transforms and plans for BigFloats

plan_chebyshevtransform{T<:BigFloats}(x::Vector{T};kwds...) = identity
plan_ichebyshevtransform{T<:BigFloats}(x::Vector{T};kwds...) = identity

#following Chebfun's @Chebtech1/vals2coeffs.m and @Chebtech2/vals2coeffs.m
function chebyshevtransform{T<:BigFloats}(x::Vector{T},plan;kind::Integer=1)
    if kind == 1
        n = length(x)
        if n == 1
            x
        else
            w = [2exp(im*convert(T,π)*k/2n) for k=0:n-1]
            ret = w.*ifft([reverse(x);x])[1:n]
            ret = T<:Real ? real(ret) : ret
            ret[1] /= 2
            ret
        end
    elseif kind == 2
        n = length(x)
        if n == 1
            x
        else
            ret = ifft([reverse(x);x[2:end-1]])[1:n]
            ret = T<:Real ? real(ret) : ret
            ret[2:n-1] *= 2
            ret
        end
    end
end

#following Chebfun's @Chebtech1/vals2coeffs.m and @Chebtech2/vals2coeffs.m
function ichebyshevtransform{T<:BigFloats}(x::Vector{T},plan;kind::Integer=1)
    if kind == 1
        n = length(x)
        if n == 1
            x
        else
            w = [exp(-im*convert(T,π)*k/2n)/2 for k=0:2n-1]
            w[1] *= 2;w[n+1] *= 0;w[n+2:end] *= -1
            ret = fft(w.*[x;one(T);x[end:-1:2]])[n:-1:1]
            ret = T<:Real ? real(ret) : ret
        end
    elseif kind == 2
        n = length(x)
        if n == 1
            x
        else
            ##TODO: make thread safe
            x[1] *= 2;x[end] *= 2
            ret = chebyshevtransform(x;kind=kind)
            x[1] /=2;x[end] /=2
            ret[1] *= 2;ret[end] *= 2
            negateeven!(ret)
            ret *= .5*(n-1)
            reverse!(ret)
        end
    end
end

# Fourier space plans for BigFloat

function plan_transform{T<:BigFloat,D}(::Fourier{D},x::Vector{T})
    function plan(x)
        v = fft(x)
        n = div(length(x),2)+1
        [real(v[1:n]);imag(v[n-1:-1:2])]
    end
    plan
end

function plan_itransform{T<:BigFloat,D}(::Fourier{D},x::Vector{T})
    function plan(x)
        n = div(length(x),2)+1
        v = complex([x[1:n];x[n-1:-1:2]],[0;-x[2n-2:-1:n+1];0;x[n+1:2n-2]])
        real(fft(v))
    end
    plan
end

# SinSpace plans for BigFloat

function plan_transform{T<:BigFloat}(::SinSpace,x::Vector{T})
    function plan(x)
        imag(fft([0;-x;0;reverse(x)]))[2:length(x)+1]
    end
    plan
end

function plan_itransform{T<:BigFloat}(::SinSpace,x::Vector{T})
    function plan(x)
        imag(fft([0;-x;0;reverse(x)]))[2:length(x)+1]
    end
    plan
end

# Fourier space & SinSpace plans for Complex{BigFloat}

for SP in (:Fourier,:SinSpace), pl in (:plan_transform,:plan_itransform)
    @eval begin
        function $pl{T<:Complex{BigFloat},D}(::$SP{D},x::Vector{T})
            function plan(x)
                complex($pl($SP(),real(x))(real(x)),$pl($SP(),imag(x))(imag(x)))
            end
            plan
        end
    end
end