### A Pluto.jl notebook ###
# v0.18.4

using Markdown
using InteractiveUtils

# ╔═╡ 7d0e3452-aff5-11ec-0675-bb8d03a2aff3
using CSV, DataFrames, Glob, Measures

# ╔═╡ 79a18654-7a71-4392-b2b0-dbc2ac4f1527
begin
	using Plots
	theme(:ggplot2)
	gr()
end

# ╔═╡ 6e04bd62-cc5c-4f1d-834c-a5944a4e601c
using LinearAlgebra, Distributions

# ╔═╡ 3a24c99f-0a6f-440c-af77-a2c364286296
md"""
# Category Distributions
"""

# ╔═╡ 77e650f3-fa34-4dd7-ba66-2183d6b09d19
struct Category
	name::Symbol
	dist::Distribution
end

# ╔═╡ c5a14200-d383-4247-a7a9-b653a83a71e1
begin
	"""
		TactileObject(label::Symbol, sides::Real, dots::Real)
	Tactile Object called label with two features, sides and dots

		Tactileobject(label::Symbol, feat_dist::Distribution, feat_ranges)
	Tactile Object called label constructed from bounded feature distributions

		TactileObject(category::Category, feat_ranges)
	Tactile Object in category with bounded features
	"""
	struct TactileObject
		category::Symbol
		sides::Real
		dots::Real
		# Alternative methods to construct a TactileObject
		TactileObject(label::Symbol, sides::Real, dots::Real) = new(label, sides, dots)
		TactileObject(label::Symbol, feat_dist::Distribution) = new(label, rand(feat_dist)...)
		function TactileObject(label::Symbol, feat_dist::Distribution, feat_ranges)
			sides, dots = rand(feat_dist)
			sides = clamp(sides, extrema(feat_ranges.sides)...)
			dots = clamp(dots, extrema(feat_ranges.dots)...)
			return new(label, sides, dots)
		end
		TactileObject(category::Category, feat_ranges) = TactileObject(category.name, category.dist, feat_ranges)
	end

	# Pretty print TactileObject
	Base.show(io::IO, tactobj::TactileObject) = print(io, "$(tactobj.category) with Sides: $(tactobj.sides), Dots: $(tactobj.dots)")

	"""
		get_feats(tactobj::TactileObject)

	Returns features (sides & dots) of Tactile Object as a tuple
	"""
	function get_feats(tactobj::TactileObject)
		return tactobj.sides, tactobj.dots
	end
end

# ╔═╡ 0befab8a-8bc9-46f9-93ad-aef57c016f06
begin
	feat_ranges = (sides=6:10, dots=4:8)
	feat_ranges
end

# ╔═╡ d71cf3fd-9e64-4001-8963-e5d2539387d1
begin
	# Mean Sides, Dots
	elyk_μ = [7.0, 5.0]
	# Stdev Sides, Dots
	elyk_Σ = (1.5^2)I
	# Elyk Object Distribution
	elyk_dist = MvNormal(elyk_μ, elyk_Σ)

	elyk = Category(:elyk, elyk_dist)
end

# ╔═╡ fd37e561-6ef8-427f-b7ef-66168673c617
begin
	res = 1
	sides_ax = range(extrema(feat_ranges.sides)..., step=res)
	dots_ax = range(extrema(feat_ranges.dots)..., step=res)

	elyk_pdf = [pdf(elyk_dist, [side, dot]) for side in sides_ax, dot in dots_ax]

	Plots.heatmap(sides_ax, dots_ax, elyk_pdf,
    	xlabel="Sides",
		ylabel="Dots (mm)",
    	title="Elyk Distribution",
	)
end

# ╔═╡ a2d57bed-0cf3-4895-a1ce-a7113bddd7de
begin
	# Sides, Dots mean
	noek_μ = [9.0, 7.0]
	# Sides, Dots stdev
	noek_Σ = (1.5^2)I
	# Noek Object Distribution
	noek_dist = MvNormal(noek_μ, noek_Σ)

	noek = Category(:noek, noek_dist)
end

# ╔═╡ 37af3db1-2e23-4637-ae27-5f2c73d520e5
begin
	noek_pdf = [pdf(noek_dist, [side, dot]) for side in sides_ax, dot in dots_ax]

	Plots.heatmap(sides_ax, dots_ax, noek_pdf,
    	xlabel="Sides",
		ylabel="Dots (mm)",
    	title="Noek Distribution",
	)
end

# ╔═╡ 58e30adf-d2b7-45ca-9ea8-578dc48f7d71
md"""
# Data Analysis
"""

# ╔═╡ e062bc9c-f51e-42d2-919d-246adbb55373
function get_csv(dir_with_csvs::String)
    ispath(dir_with_csvs) || error("Could not find path: $dir_with_csvs")
    csv_files = glob("*csv", dir_with_csvs)
    return csv_files
end

# ╔═╡ 81fafb11-a4d4-493b-a9bb-acbfdd38515a
function get_categorization_data(exp_dir)
    ispath(exp_dir) || error("Could not find path: $exp_dir")
	# p = participant
	p_dirs = readdir(exp_dir, join=true)
	p_data = [CSV.read(get_csv(p_dir), DataFrame) for p_dir in p_dirs]
	return p_data
end

# ╔═╡ daf71960-3d51-4989-b977-0644eb2a1875
function filter_p_data(all_p_data, cols_to_keep)
	return [p_data[:, cols_to_keep] for p_data in all_p_data]
end

# ╔═╡ 3afb7a95-6dc2-43d8-b121-a8eed4ae1b04
readdir("./categorizationdata")

# ╔═╡ f88c2324-935e-4def-b79c-cb4144e39921
all_p_data = get_categorization_data("./categorizationdata")

# ╔═╡ 1c4e653f-4df8-4804-9d75-b53383eaa756
p_dfs = filter_p_data(all_p_data, [:Sides, :Spacing, :Response, Symbol("Correct.")])

# ╔═╡ d5cc4ade-f6ee-4820-8d91-e5ad47269c3e
function df_to_dict(p_df)
	coordinates = [Dict([]), Dict([])]
	for row in eachrow(p_df)
		coord = (sides=row.Sides+6, dots=row.Spacing+4)
		response = row.Response
		if haskey(coordinates[response], coord)
			coordinates[response][coord] += 1
		else
			coordinates[response][coord] = 1
		end
	end

	# Calculate response probability

	# Iterate the longer dictionary
	longer_coordinates = length(coordinates[1]) > length(coordinates[2]) ? coordinates[1] : coordinates[2]

	shorter_coordinates = length(coordinates[1]) < length(coordinates[2]) ? coordinates[1] : coordinates[2]

	for k in keys(longer_coordinates)
		if !haskey(shorter_coordinates, k)
			# If this coordinate was never given other response 1, then 100% of time it was given response 2
			longer_coordinates[k] = 1.0
		else
			# Find total responses
			total_responses = longer_coordinates[k] + shorter_coordinates[k]
			longer_coordinates[k] /= total_responses
			shorter_coordinates[k] /= total_responses
		end
	end

	# Normalize all remaining keys in shorter_coordinates
	for k in keys(shorter_coordinates)
		if !haskey(longer_coordinates, k)
			shorter_coordinates[k] = 1.0
		end
	end
	return (res1=longer_coordinates, res2=shorter_coordinates)
end

# ╔═╡ 2588019b-7709-4e3e-9304-20bbc752ed31
begin
	test = Dict([(sides=2, dots=3)=>1])
	test[(sides=2, dots=3)] += 4
end

# ╔═╡ 06119f97-7405-4591-a080-7f2f4bbf59e5
all_p_probabilities = [df_to_dict(p_df) for p_df in p_dfs]

# ╔═╡ c88c8b35-99ba-4873-8a1d-db02bac8dbde
begin
	function dict_to_heatmap(p_prob_res, label)
		xs = collect(6:10)
		ys = collect(4:8)
		zs = zeros(length(xs), length(ys))
		for x in xs, y in ys
			if haskey(p_prob_res, (sides=x, dots=y))
				zs[x-5, y-3] = p_prob_res[(sides=x, dots=y)]
			end
		end
		resmap = Plots.heatmap(xs, ys, zs,
		    xlabel="Sides",
			ylabel="Dots (mm)",
	    	title="$label",
			margin=5mm
		)
		return resmap
	end

	function both_maps(p_prob)
		resmap1 = dict_to_heatmap(p_prob.res1, "Elyk")
		resmap2 = dict_to_heatmap(p_prob.res2, "Noek")

		p = Plots.plot(resmap1, resmap2, layout=2, plot_title="Participant Category Distribution", plot_titlefontsize=12, titlefontsize=10, size=(800, 300))
		return resmap1
	end
end

# ╔═╡ 80f06c64-fbd4-4a41-9eb6-816a7974a7ab
both_maps(all_p_probabilities[1])

# ╔═╡ 560ef6fc-06bc-4f26-a305-155a661ae29d
function p_dict_to_heatmap(p_probs)
	# Just ELyk
	p_zs = []
	xs = collect(6:10)
	ys = collect(4:8)
	for p_prob in p_probs
		zs = zeros(length(xs), length(ys))
		for x in xs, y in ys
			if haskey(p_prob.res1, (sides=x, dots=y))
				zs[x-5, y-3] = p_prob.res1[(sides=x, dots=y)]
			end
		end
		push!(p_zs, zs)
	end

	mean_zs = mean(p_zs)

	avgmap = Plots.heatmap(xs, ys, mean_zs,
		xlabel="Sides",
		ylabel="Dots (mm)",
		title="Mean Participant Elyk",
		margin=5mm
	)
	return avgmap
end

# ╔═╡ 29a226d0-bcb8-4bd8-9bfa-712044bd7dbd
p_dict_to_heatmap(all_p_probabilities)

# ╔═╡ ef69b242-4a96-4480-8c87-89677c3c2a1c
md"""
# Encoders & Decoders
"""

# ╔═╡ 4f21f20f-b677-4d7a-bc98-49e41ced32e3
function encode(stimulus::TactileObject, Σ_encoder)
	sensation_dist = MvNormal([stimulus.sides, stimulus.dots], Σ_encoder)
	sides, dots = rand(sensation_dist)
	return TactileObject(:object, sides, dots)
end

# ╔═╡ 981676e0-d852-4781-b7fd-3817a77cb872
"""
	decode_sense(sensations, Σ_encoder, feat_ranges)

Given set of stochastic sensations (i.e. encodings) of a stimulus, infer the most likely true features of the stimulus
"""
function decode_sense(sensations::Vector{T}, Σ_encoder, feat_ranges) where {T<:TactileObject}
	# Hypothesize several possible true stimulus values
	step = 0.1
	sides_hyp = range(extrema(feat_ranges.sides)..., step=step)
	dots_hyp = range(extrema(feat_ranges.dots)..., step=step)
	μ_hyps = [[side, dot] for side in sides_hyp, dot in dots_hyp]

	# Assume uniform prior for μ_hyps
	# prior = 1 / length(μ_hyps)

	# Calculate likelihood of sensed stimulus given hypotheses
	# 	Assume decoder knows the variability of the encoder
	data_loglikelihood(μ, stimuli) = sum(logpdf(Distributions.MvNormal(μ, Σ_encoder), collect(get_feats(sensation))) for sensation in sensations)

	loglikelihoods = [data_loglikelihood(μ, sensations) for μ in μ_hyps]
	likelihoods = exp.(loglikelihoods .- maximum(loglikelihoods))

	# Calculate most probable true stimulus values
	posteriors = likelihoods ./ sum(likelihoods)
	estimated_features = μ_hyps[argmax(posteriors)]
	return TactileObject(:object, estimated_features...)
end

# ╔═╡ bbf5078d-030c-46e8-bc30-eeddf43a0d2e
"""
	decode_category(stimulus, Σ_category)

Given a stimulus from a stochastic category distribution, infer the most likely true category of the stimulus
"""
function decode_category(stimulus::TactileObject, Σ_category)
	stimulus_feats = collect(get_feats(stimulus))
	# Calculate likelihoods
	loglikelihoods = [
		logpdf(Distributions.MvNormal(mean(elyk_dist), Σ_category), stimulus_feats),
		logpdf(Distributions.MvNormal(mean(noek_dist), Σ_category), stimulus_feats)
	]
	category_likelihoods = exp.(loglikelihoods .- maximum(loglikelihoods))
	# Calculate posteriors
	category_posteriors = category_likelihoods ./ sum(category_likelihoods)
	p_elyk, p_noek = category_posteriors
	return (elyk = round(p_elyk; digits=3), noek = round(p_noek; digits=3))
end

# ╔═╡ 91000689-45ac-474d-899a-2b691e3465f5
md"""
Bayesian Models
"""

# ╔═╡ a2caabf5-cec7-4a02-8014-6f247d37f700
struct ModelParams
	feat_ranges::NamedTuple
	num_encodings::Integer
	Σ_encoder
	Σ_category
end

# ╔═╡ 845655a2-fd26-45c6-b696-ad563a50c86b
begin
	function model_debug(stimulus::TactileObject, decoded_stim, decoded_cat)
		@show stimulus
		@show decoded_stim
		@show decoded_cat
		return
	end
end

# ╔═╡ 5334c8d1-6cd6-434a-9b0f-7664bee0e94b
"""
	model1(stimulus::TactileObject, params::ModelParams)

Decoder model that only takes into account sensory variability.

When making inferences, the model assumes Σ_category is sufficiently small to ignore.
"""
function model1(stimulus::TactileObject, params::ModelParams; debug=false)
	# Sense stimulus
	sensations = [encode(stimulus, params.Σ_encoder) for _=1:params.num_encodings]
	# Incorporate sensory variability
	estimated_stimulus = decode_sense(sensations, params.Σ_encoder, params.feat_ranges)
	# Assume sufficiently small category varibility
	assumed_Σ_category = (0.01^2)I
	estimated_category = decode_category(estimated_stimulus, assumed_Σ_category)
	debug && model_debug(stimulus, estimated_stimulus, estimated_category)
	return estimated_category
end

# ╔═╡ 0052708e-2d81-4b08-af51-3179addb0c00
"""
	model2(stimulus::TactileObject, params::ModelParams)

Decoder model that only takes into account category variability.

When making inferences, the model assumes Σ_encoder is sufficiently small to ignore.
"""
function model2(stimulus::TactileObject, params::ModelParams; debug=false)
	# Sense stimulus
	sensations = [encode(stimulus, params.Σ_encoder) for _=1:params.num_encodings]
	# Assume sufficiently small sensory varibility
	assumed_Σ_encoder = 0.001I
	estimated_stimulus = decode_sense(sensations, assumed_Σ_encoder, params.feat_ranges)
	# Incorporate category variability
	estimated_category = decode_category(estimated_stimulus, params.Σ_category)
	debug && model_debug(stimulus, estimated_stimulus, estimated_category)
	return estimated_category
end

# ╔═╡ 5b516aa4-5466-4b92-95c0-17d284797044
"""
	model3(stimulus::TactileObject, params::ModelParams)

Decoder model that takes into account category and sensory variability; an optimal Bayesian decoder
"""
function model3(stimulus::TactileObject, params::ModelParams; debug=false)
	# Sense stimulus
	sensations = [encode(stimulus, params.Σ_encoder) for _=1:params.num_encodings]
	# Incorporate sensory variability
	estimated_stimulus = decode_sense(sensations, params.Σ_encoder, params.feat_ranges)
	# Incorporate category variability
	estimated_category = decode_category(estimated_stimulus, params.Σ_category)
	debug && model_debug(stimulus, estimated_stimulus, estimated_category)
	return estimated_category
end

# ╔═╡ a5453d7c-8348-4d3a-8df2-9168c029bc80
md"""
# Categorization Experiment With Decoders
"""

# ╔═╡ e1ea0852-4473-4368-b249-ead09195b22c
function category_experiment(model, model_params, runs)
	# Define sides and dots ranges (x & y axes)
	xs = collect(feat_ranges.sides)
	ys = collect(feat_ranges.dots)
	# Allocate z axis arrays for elyk and noek
	zs_elyk = zeros(length(xs), length(ys))
	zs_noek = zeros(length(xs), length(ys))
	for x in xs, y in ys
		# For a particular combination of sides and dots:
		elyk_responses = 0
		noek_responses = 0
		stimulus = TactileObject(:object, x, y)
		# Run many trials of the experiment
		for i in 1:runs
			# Just in case the model spits out 50/50, force it to make a decision
			response = nothing
			while true
				# model return tuple of category probabilities
				# 	(elyk=0.79, noek=0.21), so argmax returns the category symbol
				response = argmax(model(stimulus, model_params))
				response == 0.5 || break
			end
			if response == :elyk
				elyk_responses += 1
			elseif response == :noek
				noek_responses += 1
			end
		end
		zs_elyk[x-5, y-3] = elyk_responses / runs
		zs_noek[x-5, y-3] = noek_responses / runs
	end
	# Plotting
	elyk_map = Plots.heatmap(xs, ys, zs_elyk,
		xlabel="Sides",
		ylabel="Dots (mm)",
		title="$(Symbol(model)) Elyk",
		margin=5mm
	)

	noek_map = Plots.heatmap(xs, ys, zs_noek,
		xlabel="Sides",
		ylabel="Dots (mm)",
		title="Noek",
		margin=5mm
	)

	# Plot both together

	p = Plots.plot(elyk_map, noek_map, layout=2, plot_title="$(Symbol(model)) Category Distribution", plot_titlefontsize=12, titlefontsize=10, size=(800, 300))
	return elyk_map
end

# ╔═╡ c26d946f-9df5-4d14-a3d5-18fd44b8fa49
begin
	model_params = ModelParams(feat_ranges, 3, Diagonal([1.2^2, 1.4^2]), cov(elyk_dist))
end

# ╔═╡ 5790dfae-bb09-4a08-a768-72aa1cbc7e19
category_experiment(model1, model_params, 20)

# ╔═╡ 762aa7a5-fcba-450d-836a-9a3dddedeab3
category_experiment(model2, model_params, 20)

# ╔═╡ c6c647fc-840d-4ebc-aaeb-403fbdd982e6
category_experiment(model3, model_params, 20)

# ╔═╡ 7b55a52f-90ad-4218-a8b8-4fd70035baf5
σ

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Measures = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

[compat]
CSV = "~0.10.3"
DataFrames = "~1.3.2"
Distributions = "~0.25.53"
Glob = "~1.3.0"
Measures = "~0.3.1"
Plots = "~1.27.3"
"""

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

julia_version = "1.7.2"
manifest_format = "2.0"

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

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

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

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

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

[[deps.CSV]]
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"]
git-tree-sha1 = "9310d9495c1eb2e4fa1955dd478660e2ecab1fbb"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.10.3"

[[deps.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 = "4b859a208b2397a7a623a03449e4636bdb17bcf2"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+1"

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

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

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

[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"

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

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

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

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

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

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

[[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1"

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

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

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

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

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

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

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

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

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

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

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

[[deps.DualNumbers]]
deps = ["Calculus", "NaNMath", "SpecialFunctions"]
git-tree-sha1 = "5837a837389fccf076445fce071c8ddaea35a566"
uuid = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
version = "0.6.8"

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

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

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

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

[[deps.FilePathsBase]]
deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"]
git-tree-sha1 = "129b104185df66e408edd6625d480b7f9e9823a0"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.18"

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

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

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

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

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

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

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

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

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

[[deps.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 = "83578392343a7885147726712523c39edc714956"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.64.1+0"

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

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

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

[[deps.Glob]]
git-tree-sha1 = "4df9f7e06108728ebf00a0a11edee4b29a482bb2"
uuid = "c27321d9-0574-5035-807b-f59d2c89b15c"
version = "1.3.0"

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

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

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

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

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

[[deps.IniFile]]
git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.1"

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

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

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

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

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

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

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

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

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

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

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

[[deps.LERC_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434"
uuid = "88015f11-f218-50d7-93a8-a6af411a945d"
version = "3.0.0+1"

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

[[deps.LaTeXStrings]]
git-tree-sha1 = "f2355693d6778a178ade15952b7ac47a4ff97996"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.3.0"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[deps.PDMats]]
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
git-tree-sha1 = "e8185b83b9fc56eb6456200e873ce598ebc7f262"
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
version = "0.11.7"

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

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

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

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

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

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

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

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

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

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

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

[[deps.QuadGK]]
deps = ["DataStructures", "LinearAlgebra"]
git-tree-sha1 = "78aadffb3efd2155af139781b8a8df1ef279ea39"
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
version = "2.4.2"

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

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

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

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

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

[[deps.RelocatableFolders]]
deps = ["SHA", "Scratch"]
git-tree-sha1 = "cdbd3b1338c72ce29d9584fdbe9e9b70eeb5adca"
uuid = "05181044-ff0b-4ac5-8273-598c1e38db00"
version = "0.1.3"

[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"

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

[[deps.Rmath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "68db32dff12bb6127bac73c209881191bf0efbb7"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.3.0+0"

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

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

[[deps.SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "6a2f7d70512d205ca8c7ee31bfa9f142fe74310c"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.3.12"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[deps.TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

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

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

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

[[deps.UnicodeFun]]
deps = ["REPL"]
git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf"
uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1"
version = "0.4.1"

[[deps.Unzip]]
git-tree-sha1 = "34db80951901073501137bdbc3d5a8e7bbd06670"
uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d"
version = "0.1.2"

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

[[deps.Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4528479aa01ee1b3b4cd0e6faef0e04cf16466da"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.25.0+0"

[[deps.WeakRefStrings]]
deps = ["DataAPI", "InlineStrings", "Parsers"]
git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23"
uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5"
version = "1.4.2"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[deps.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:
# ╠═7d0e3452-aff5-11ec-0675-bb8d03a2aff3
# ╠═79a18654-7a71-4392-b2b0-dbc2ac4f1527
# ╠═6e04bd62-cc5c-4f1d-834c-a5944a4e601c
# ╠═3a24c99f-0a6f-440c-af77-a2c364286296
# ╠═77e650f3-fa34-4dd7-ba66-2183d6b09d19
# ╠═c5a14200-d383-4247-a7a9-b653a83a71e1
# ╠═0befab8a-8bc9-46f9-93ad-aef57c016f06
# ╠═d71cf3fd-9e64-4001-8963-e5d2539387d1
# ╠═fd37e561-6ef8-427f-b7ef-66168673c617
# ╠═a2d57bed-0cf3-4895-a1ce-a7113bddd7de
# ╠═37af3db1-2e23-4637-ae27-5f2c73d520e5
# ╠═58e30adf-d2b7-45ca-9ea8-578dc48f7d71
# ╠═e062bc9c-f51e-42d2-919d-246adbb55373
# ╠═81fafb11-a4d4-493b-a9bb-acbfdd38515a
# ╠═daf71960-3d51-4989-b977-0644eb2a1875
# ╠═3afb7a95-6dc2-43d8-b121-a8eed4ae1b04
# ╠═f88c2324-935e-4def-b79c-cb4144e39921
# ╠═1c4e653f-4df8-4804-9d75-b53383eaa756
# ╠═d5cc4ade-f6ee-4820-8d91-e5ad47269c3e
# ╠═2588019b-7709-4e3e-9304-20bbc752ed31
# ╠═06119f97-7405-4591-a080-7f2f4bbf59e5
# ╠═c88c8b35-99ba-4873-8a1d-db02bac8dbde
# ╠═80f06c64-fbd4-4a41-9eb6-816a7974a7ab
# ╠═560ef6fc-06bc-4f26-a305-155a661ae29d
# ╠═29a226d0-bcb8-4bd8-9bfa-712044bd7dbd
# ╠═ef69b242-4a96-4480-8c87-89677c3c2a1c
# ╠═4f21f20f-b677-4d7a-bc98-49e41ced32e3
# ╠═981676e0-d852-4781-b7fd-3817a77cb872
# ╠═bbf5078d-030c-46e8-bc30-eeddf43a0d2e
# ╠═91000689-45ac-474d-899a-2b691e3465f5
# ╠═a2caabf5-cec7-4a02-8014-6f247d37f700
# ╠═845655a2-fd26-45c6-b696-ad563a50c86b
# ╠═5334c8d1-6cd6-434a-9b0f-7664bee0e94b
# ╠═0052708e-2d81-4b08-af51-3179addb0c00
# ╠═5b516aa4-5466-4b92-95c0-17d284797044
# ╠═a5453d7c-8348-4d3a-8df2-9168c029bc80
# ╠═e1ea0852-4473-4368-b249-ead09195b22c
# ╠═c26d946f-9df5-4d14-a3d5-18fd44b8fa49
# ╠═5790dfae-bb09-4a08-a768-72aa1cbc7e19
# ╠═762aa7a5-fcba-450d-836a-9a3dddedeab3
# ╠═c6c647fc-840d-4ebc-aaeb-403fbdd982e6
# ╠═7b55a52f-90ad-4218-a8b8-4fd70035baf5
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


"""
Baseclass for any kind of cost. Notable examples for
costs are `Loss` and `Penalty`.
"""
abstract type Cost end

"""
Baseclass for all losses. A loss is some (possibly simplified)
function `L(x, y, ŷ)`, of features `x`, targets `y` and outputs
`ŷ = f(x)` for some function `f`.
"""
abstract type Loss <: Cost end

"""
A loss is considered **supervised**, if all the information needed
to compute `L(x, y, ŷ)` are contained in `y` and `ŷ`, and thus allows
for the simplification `L(y, ŷ)`.
"""
abstract type SupervisedLoss <: Loss end

"""
A supervised loss that can be simplified to `L(y, ŷ) = L(ŷ - y)`
is considered **distance-based**.
"""
abstract type DistanceLoss <: SupervisedLoss end

"""
A supervised loss with targets `y ∈ {-1, 1}`, and which
can be simplified to `L(y, ŷ) = L(y⋅ŷ)` is considered
**margin-based**.
"""
abstract type MarginLoss <: SupervisedLoss end

"""
A loss is considered **unsupervised**, if all the information needed
to compute `L(x, y, ŷ)` are contained in `x` and `ŷ`, and thus allows
for the simplification `L(x, ŷ)`.
"""
abstract type UnsupervisedLoss <: Loss end

"""
Baseclass for all penalties.
"""
abstract type Penalty <: Cost end

"""
    value(loss, target, output) -> Number

Compute the (non-negative) numeric result for the `loss` function.
Note that `target` and `output` can be of different numeric type,
in which case promotion is performed in the manner appropriate for
the given loss.

    value(loss, targets, outputs) -> AbstractVector

Compute the result for each pair of values in `targets` and `outputs`.

    value(loss, targets, outputs, aggmode) -> Number

Compute the weighted or unweighted sum or mean (depending on
aggregation mode `aggmode`) of the individual values of the `loss`
function for each pair in `targets` and `outputs`. This method
will not allocate a temporary array.

In the case that the two parameters are arrays with a different
number of dimensions, broadcast will be performed. Note that the
given parameters are expected to have the same size in the
dimensions they share.

    value(loss, targets, outputs, aggmode, obsdim) -> AbstractVector

Compute the aggregated result along dimension with observations `obsdim`.
This method will not allocate a temporary array, but it will allocate
the resulting vector.

Both arrays have to be of the same shape and size. Furthermore they have
to have at least two dimensions (i.e. they must not be vectors).

## Notes

- New loss functions only need to implement the first method with single
  `target` and `output`. Fallback implementations are available for other
  methods in `LossFunctions.jl`.
"""
function value end

"""
    deriv(loss, target, output) -> Number

Compute the analytical derivative with respect to the `output` for the
`loss` function. Note that `target` and `output` can be of different
numeric type, in which case promotion is performed in the manner
appropriate for the given loss.

    deriv(loss, targets, outputs) -> AbstractVector

Compute the result for each pair of values in `targets` and `outputs`.

    deriv(loss, targets, outputs, aggmode) -> Number

Compute the weighted or unweighted sum or mean (depending on
aggregation mode `aggmode`) of the individual values of the `loss`
function for each pair in `targets` and `outputs`. This method
will not allocate a temporary array.

In the case that the two parameters are arrays with a different
number of dimensions, broadcast will be performed. Note that the
given parameters are expected to have the same size in the
dimensions they share.

    deriv(loss, targets, outputs, aggmode, obsdim) -> AbstractVector

Compute the aggregated result along dimension with observations `obsdim`.
This method will not allocate a temporary array, but it will allocate
the resulting vector.

Both arrays have to be of the same shape and size. Furthermore they have
to have at least two dimensions (i.e. they must not be vectors).

## Notes

- New loss functions only need to implement the first method with single
  `target` and `output`. Fallback implementations are available for other
  methods in `LossFunctions.jl`.
"""
function deriv end

"""
    deriv2(loss, target, output) -> Number

Compute the second derivative with respect to the `output` for the
`loss` function. Note that `target` and `output` can be of different
numeric type, in which case promotion is performed in the manner
appropriate for the given loss.

    deriv2(loss, targets, outputs) -> AbstractVector

Compute the result for each pair of values in `targets` and `outputs`.

    deriv2(loss, targets, outputs, aggmode) -> Number

Compute the weighted or unweighted sum or mean (depending on
aggregation mode `aggmode`) of the individual values of the `loss`
function for each pair in `targets` and `outputs`. This method
will not allocate a temporary array.

In the case that the two parameters are arrays with a different
number of dimensions, broadcast will be performed. Note that the
given parameters are expected to have the same size in the
dimensions they share.

    deriv2(loss, targets, outputs, aggmode, obsdim) -> AbstractVector

Compute the aggregated result along dimension with observations `obsdim`.
This method will not allocate a temporary array, but it will allocate
the resulting vector.

Both arrays have to be of the same shape and size. Furthermore they have
to have at least two dimensions (i.e. they must not be vectors).

## Notes

- New loss functions only need to implement the first method with single
  `target` and `output`. Fallback implementations are available for other
  methods in `LossFunctions.jl`.
"""
function deriv2 end

"""
    isconvex(loss) -> Bool

Return `true` if the given `loss` denotes a convex function.
A function `f: ℝⁿ → ℝ` is convex if its domain is a convex set
and if for all `x, y` in that domain, with `θ` such that for
`0 ≦ θ ≦ 1`, we have `f(θ x + (1 - θ) y) ≦ θ f(x) + (1 - θ) f(y)`.
"""
isconvex(loss::SupervisedLoss) = isstrictlyconvex(loss)

"""
    isstrictlyconvex(loss) -> Bool

Return `true` if the given `loss` denotes a strictly convex function.
A function `f : ℝⁿ → ℝ` is strictly convex if its domain is a convex
set and if for all `x, y` in that domain where `x ≠ y`, with `θ` such
that for `0 < θ < 1`, we have `f(θ x + (1 - θ) y) < θ f(x) + (1 - θ) f(y)`.
"""
isstrictlyconvex(loss::SupervisedLoss) = isstronglyconvex(loss)

"""
    isstronglyconvex(loss) -> Bool

Return `true` if the given `loss` denotes a strongly convex function.
A function `f : ℝⁿ → ℝ` is `m`-strongly convex if its domain is a convex
set, and if for all `x, y` in that domain where `x ≠ y`, and `θ` such that
for `0 ≤ θ ≤ 1`, we have
`f(θ x + (1 - θ)y) < θ f(x) + (1 - θ) f(y) - 0.5 m ⋅ θ (1 - θ) | x - y |₂²`

In a more familiar setting, if the loss function is differentiable we have
`(∇f(x) - ∇f(y))ᵀ (x - y) ≥ m | x - y |₂²`
"""
isstronglyconvex(loss::SupervisedLoss) = false

"""
    isdifferentiable(loss, [x]) -> Bool

Return `true` if the given `loss` is differentiable
(optionally limited to the given point `x` if specified).

A function `f : ℝⁿ → ℝᵐ` is differentiable at a point `x` in the interior
domain of `f` if there exists a matrix `Df(x) ∈ ℝ^(m × n)` such that
it satisfies:

`lim_{z ≠ x, z → x} (|f(z) - f(x) - Df(x)(z-x)|₂) / |z - x|₂ = 0`

A function is differentiable if its domain is open and it is
differentiable at every point `x`.
"""
isdifferentiable(loss::SupervisedLoss) = istwicedifferentiable(loss)
isdifferentiable(loss::SupervisedLoss, at) = isdifferentiable(loss)

"""
    istwicedifferentiable(loss, [x]) -> Bool

Return `true` if the given `loss` is differentiable
(optionally limited to the given point `x` if specified).

A function `f : ℝⁿ → ℝ` is said to be twice differentiable
at a point `x` in the interior domain of `f`, if the function
derivative for `∇f` exists at `x`: `∇²f(x) = D∇f(x)`.

A function is twice differentiable if its domain is open and it
is twice differentiable at every point `x`.
"""
istwicedifferentiable(loss::SupervisedLoss) = false
istwicedifferentiable(loss::SupervisedLoss, at) = istwicedifferentiable(loss)

"""
    islocallylipschitzcont(loss) -> Bool

Return `true` if the given `loss` function is locally-Lipschitz
continous.

A supervised loss `L : Y × ℝ → [0, ∞)` is called locally Lipschitz
continuous if for all `a ≥ 0` there exists a constant `cₐ ≥ 0`,
such that

`sup_{y ∈ Y} | L(y,t) − L(y,t′) | ≤ cₐ |t − t′|, t, t′ ∈ [−a,a]`

Every convex function is locally lipschitz continuous.
"""
islocallylipschitzcont(loss::SupervisedLoss) =
    isconvex(loss) || islipschitzcont(loss)

"""
    islipschitzcont(loss) -> Bool

Return `true` if the given `loss` function is Lipschitz continuous.

A supervised loss function `L : Y × ℝ → [0, ∞)` is Lipschitz continous,
if there exists a finite constant `M < ∞` such that
`|L(y, t) - L(y, t′)| ≤ M |t - t′|, ∀ (y, t) ∈ Y × ℝ`
"""
islipschitzcont(loss::SupervisedLoss) = false

"""
    isnemitski(loss) -> Bool

Return `true` if the given `loss` denotes a Nemitski loss function.

We call a supervised loss function `L : Y × ℝ → [0,∞)` a Nemitski
loss if there exist a measurable function `b : Y → [0, ∞)` and an
increasing function `h : [0, ∞) → [0, ∞)` such that
`L(y,ŷ) ≤ b(y) + h(|ŷ|), (y, ŷ) ∈ Y × ℝ`

If a loss if locally lipsschitz continuous then it is a Nemitski loss.
"""
isnemitski(loss::SupervisedLoss) = islocallylipschitzcont(loss)
isnemitski(loss::MarginLoss) = true

"""
    isunivfishercons(loss) -> Bool
"""
isunivfishercons(loss::Loss) = false

"""
    isfishercons(loss) -> Bool

Return `true` if the givel `loss` is Fisher consistent.

We call a supervised loss function `L : Y × ℝ → [0,∞)` a Fisher
consistent loss if the population minimizer of the risk `E[L(y,f(x))]`
for all measurable functions leads to the Bayes optimal decision rule.
"""
isfishercons(loss::Loss) = isunivfishercons(loss)

"""
    isclipable(loss) -> Bool

Return `true` if the given `loss` function is clipable. A
supervised loss `L : Y × ℝ → [0,∞)` can be clipped at `M > 0`
if, for all `(y,t) ∈ Y × ℝ`, `L(y, t̂) ≤ L(y, t)` where
`t̂` denotes the clipped value of `t` at `± M`.
That is
`t̂ = -M` if `t < -M`, `t̂ = t` if `t ∈ [-M, M]`, and `t = M` if `t > M`.
"""
isclipable(loss::SupervisedLoss) = false
isclipable(loss::DistanceLoss) = true # can someone please double check?

"""
    isdistancebased(loss) -> Bool

Return `true` if the given `loss` is a distance-based loss.

A supervised loss function `L : Y × ℝ → [0,∞)` is said to be
distance-based, if there exists a representing function `ψ : ℝ → [0,∞)`
satisfying `ψ(0) = 0` and `L(y, ŷ) = ψ (ŷ - y), (y, ŷ) ∈ Y × ℝ`.
"""
isdistancebased(loss::Loss) = false
isdistancebased(loss::DistanceLoss) = true

"""
    ismarginbased(loss) -> Bool

Return `true` if the given `loss` is a margin-based loss.

A supervised loss function `L : Y × ℝ → [0,∞)` is said to be
margin-based, if there exists a representing function `ψ : ℝ → [0,∞)`
satisfying `L(y, ŷ) = ψ(y⋅ŷ), (y, ŷ) ∈ Y × ℝ`.
"""
ismarginbased(loss::Loss) = false
ismarginbased(loss::MarginLoss) = true

"""
    isclasscalibrated(loss) -> Bool
"""
isclasscalibrated(loss::SupervisedLoss) = false
isclasscalibrated(loss::MarginLoss) =
    isconvex(loss) && isdifferentiable(loss, 0) && deriv(loss, 0) < 0

"""
    issymmetric(loss) -> Bool

Return `true` if the given loss is a symmetric loss.

A function `f : ℝ → [0,∞)` is said to be symmetric
about origin if we have `f(x) = f(-x), ∀ x ∈ ℝ`.

A distance-based loss is said to be symmetric if its
representing function is symmetric.
"""
issymmetric(loss::SupervisedLoss) = false

"""
    isminimizable(loss) -> Bool

Return `true` if the given `loss` is a minimizable loss.
"""
isminimizable(loss::SupervisedLoss) = isconvex(loss)


# from grid/grid.jl
__precompile__()

module GridArrays

include("ModCartesianIndices.jl")
using ..ModCartesianIndicesBase

using DomainSets, StaticArrays, RecipesBase, Test, FastGaussQuadrature, GaussQuadrature, FillArrays

using DomainSets: endpoints

import Base: *, size, length, @propagate_inbounds, step, ndims, unsafe_getindex,
    checkbounds, IndexStyle, ==, ≈, getindex, eachindex, convert, in, ^, string, axes, convert
import Base.Broadcast: broadcast

import DomainSets: cartesianproduct, element, elements, numelements, ×, cross, minimum, maximum, dimension

export AbstractGrid, AbstractGrid1d, AbstractGrid3d,
        AbstractEquispacedGrid, EquispacedGrid, PeriodicEquispacedGrid,
        FourierGrid, MidpointEquispacedGrid, RandomEquispacedGrid,
        AbstractIntervalGrid, eachelement, ScatteredGrid, ×, cartesianproduct,
        TensorSubGrid, instantiate, support, float_type, isperiodic,
        boundary, subgrid, mask, randomgrid, boundingbox, TensorSubGrid,
        iscomposite, dimension, prectype, ChebyshevTNodes, ChebyshevUNodes,
        LaguerreNodes, HermiteNodes, LegendreNodes, JacobiNodes
export ChebyshevNodes, ChebyshevGrid, ChebyshevPoints, ChebyshevExtremae, ×

# from grid/productgrid.jl
export ProductGrid

# from grid/subgrid.jl
export subindices, supergrid, issubindex, similar_subgrid

# from grid/mappedgrid.jl
export MappedGrid, mapped_grid, apply_map

import Base: isapprox

# TODO move to domainsets
isapprox(d1::DomainSets.ProductDomain,d2::DomainSets.ProductDomain) =
    reduce(&, map(isapprox,DomainSets.elements(d1), DomainSets.elements(d2)))

"Assign a floating point type to a domain element type T."
float_type(::Type{T}) where {T <: Real} = T
float_type(::Type{Complex{T}}) where {T <: Real} = Complex{T}
float_type(::Type{SVector{N,T}}) where {N,T} = T
float_type(::Type{NTuple{N,T}}) where {N,T} = T

# Fallback: we return Float64
float_type(::Type{T}) where {T} = Float64

include("grid.jl")
include("productgrid.jl")
include("intervalgrids.jl")
include("mappedgrid.jl")
include("scattered_grid.jl")

include("domains/boundingbox.jl")
include("domains/broadcast.jl")

include("randomgrid.jl")

include("subgrid/AbstractSubGrids.jl")

include("recipes.jl")

export test_generic_grid, test_interval_grid
export grid_iterator1, grid_iterator2
include("test/test_grids.jl")

≈(d1::DomainSets.Interval, d2::DomainSets.Interval) =
    1≈1+abs(DomainSets.infimum(d1)-DomainSets.infimum(d2))+abs(DomainSets.supremum(d1)-DomainSets.supremum(d2))

end # module


# ================
# Homogeneous case
# ================

function reach_homog!(R::Vector{ReachSet{<:Zonotope{Float64}}},
                      Ω0::Zonotope,
                      Φ::AbstractMatrix,
                      N::Int,
                      δ::Float64)
    # initial reach set
    t0, t1 = zero(δ), δ
    R[1] = ReachSet(Ω0, t0, t1)

    k = 2
    while k <= N
        Rₖ = linear_map(Φ, set(R[k-1]))
        t0 = t1
        t1 += δ
        R[k] = ReachSet(Rₖ, t0, t1)

        k += 1
    end
    return R
end

# ==================
# Inhomogeneous case
# ==================

function reach_inhomog!(R::Vector{ReachSet{<:Zonotope{Float64}}},
                        Ω0::Zonotope,
                        U::ConstantInput,
                        Φ::AbstractMatrix,
                        N::Int,
                        δ::Float64,
                        max_order::Int)
    # initial reach set
    t0, t1 = zero(δ), δ
    R[1] = ReachSet(Ω0, t0, t1)

    V = next_set(U)
    Wk₊ = V
    Φ_power_k = copy(Φ)
    Φ_power_k_cache = similar(Φ)

    k = 2
    while k <= N
        Rₖ = minkowski_sum(linear_map(Φ_power_k, Ω0), Wk₊)
        Rₖ = reduce_order(Rₖ, max_order)
        t0 = t1
        t1 += δ
        R[k] = ReachSet(Rₖ, t0, t1)

        Wk₊ = minkowski_sum(Wk₊, linear_map(Φ_power_k, V))
        Wk₊ = reduce_order(Wk₊, max_order)

        mul!(Φ_power_k_cache, Φ_power_k, Φ)
        copyto!(Φ_power_k, Φ_power_k_cache)
        k += 1
    end
    return R
end


#
# function test_point_bound(p_test, eps_1, clamp_val)
#     n = soft_clamp(p_test - eps_1, clamp_val)
#     z = soft_clamp(p_test,         clamp_val)
#     p = soft_clamp(p_test + eps_1, clamp_val)
#     floating_point_error_allowance = 1.0 + 1.0e-7  # TODO: work on getting this down
#     @test abs(n - z) <= (eps_1 * floating_point_error_allowance)
#     @test abs(p - z) <= (eps_1 * floating_point_error_allowance)
# end
#
# @testset "soft_clamp" begin
#     eps_1 = 1.0e-10
#
#     for scale = (1.0, 2.0)
#         for p_test = [-1.5, -0.5, 0.0, 0.5, 1.5]
#             test_point_bound(p_test * scale, eps_1, 1.0 * scale)
#             test_point_bound(p_test * scale, eps_1, 1.0 * scale)
#         end
#
#         @test -1.0 * scale== soft_clamp(-1.5* scale, 1.0* scale)
#         @test -0.5* scale == soft_clamp(-0.5* scale, 1.0* scale)
#         @test  0.0 * scale== soft_clamp( 0.0* scale, 1.0* scale)
#         @test  0.5 * scale == soft_clamp( 0.5 * scale, 1.0 * scale)
#         @test  1.0 * scale == soft_clamp( 1.5 * scale, 1.0 * scale)
#     end
# end
#
# @testset "fastSigmoid" begin
#     for TYPE_k = (Float64, Dual{Nothing,Float64,12})
#         x_cut⁻¹ = 1.0
#         out = fastSigmoid(TYPE_k( 0.0), x_cut⁻¹)
#         @test zero(TYPE_k) == out
#         @test typeof(out) == TYPE_k
#
#         out = fastSigmoid(TYPE_k( 1.0), x_cut⁻¹)
#         @test  one(TYPE_k) == out
#         @test typeof(out) == TYPE_k
#
#         out = fastSigmoid(TYPE_k(-1.0), x_cut⁻¹)
#         @test -one(TYPE_k) == out
#         @test typeof(out) == TYPE_k
#
#         out = fastSigmoid(TYPE_k( 0.5), x_cut⁻¹)
#         @test zero(TYPE_k) < out < one(TYPE_k)
#         @test typeof(out) == TYPE_k
#
#         out = fastSigmoid(TYPE_k(-0.5), x_cut⁻¹)
#         @test -one(TYPE_k) < out < zero(TYPE_k)
#         @test typeof(out) == TYPE_k
#     end
# end
#
# @testset "poly_approx" begin
#     # fastSoftPlus
#     for center_value = [0.1, 0.25, 0.5, 1.0, 2.0]
#         p = 1.0e-14
#         x = center_value * LinRange(-1.0 - p, -1.0 + p, 1001)
#         y = fastSoftPlus.(x, center_value)
#         @test 0.0 == minimum(y)
#     end
# end


# ---
# title: 561. Array Partition I
# id: problem561
# author: Indigo
# date: 2021-02-16
# difficulty: Easy
# categories: Array
# link: <https://leetcode.com/problems/array-partition-i/description/>
# hidden: true
# ---
#
# Given an integer array `nums` of `2n` integers, group these integers into `n`
# pairs `(a1, b1), (a2, b2), ..., (an, bn)` such that the sum of `min(ai, bi)`
# for all `i` is **maximized**. Return _the maximized sum_.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [1,4,3,2]
#     Output: 4
#     Explanation: All possible pairings (ignoring the ordering of elements) are:
#     1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
#     2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
#     3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
#     So the maximum possible sum is 4.
#
# **Example 2:**
#
#
#
#     Input: nums = [6,2,6,5,1,2]
#     Output: 9
#     Explanation: The optimal pairing is (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9.
#
#
#
#
# **Constraints:**
#
#   * `1 <= n <= 104`
#   * `nums.length == 2 * n`
#   * `-104 <= nums[i] <= 104`
#
#
## @lc code=start
using LeetCode

function array_pair_sum(nums::Vector{Int})
    sort!(nums)
    sum(nums[1:2:end])
end
## @lc code=end


fnames = [
          "area",
          "ftc",

          "substitution",
          "integration_by_parts",
          "partial_fractions", # XX add in trig integrals (cos()sin() stuff? mx or ^m... XXX
          "improper_integrals", ##

          "mean_value_theorem",
          "area_between_curves",
          "center_of_mass",
          "volumes_slice",
          #"volumes_shell", ## XXX add this in if needed, but not really that excited to now XXX
          "arc_length",
          "surface_area"
]

function process_file(nm, twice=false)
    include("$nm.jl")
    mmd_to_md("$nm.mmd")
    markdownToHTML("$nm.md")
    twice && markdownToHTML("$nm.md")
end

process_files(twice=false) = [process_file(nm, twice) for nm in fnames]

"""
## TODO integrals

* add in volumes shell???
* mean value theorem is light?
* could add surface area problems

"""


const SymbolCollection = Union{Symbol, Tuple{Symbol, Vararg{Symbol}}, AbstractVector{Symbol}}

function _group_var(vc::VarCorr, group::Symbol)
    return sum(abs2, getproperty(getproperty(vc.σρ, group), :σ))
end

function _group_var(vc::VarCorr, groups::SymbolCollection)
    return sum(_group_var.(Ref(vc), groups))
end

function _group_var(vc::VarCorr)
    groups = collect(propertynames(vc.σρ))
    return _group_var(vc, groups)
end

"""
    icc(model::MixedModel, [groups])

Compute the intra-class correlation coefficient (ICC) for a mixed model.

The ICC is defined as the variance attributable to the `groups` divided by
the total variance from all groups and the observation-level (residual) variance.
In other words, the ICC can be interpreted as the proportion of the variance explainable
by the grouping/nesting structure.

A single `group` can be specified as a Symbol, e.g. `:subj` or a number of groups
can be specified as an array: `[:subj, :item]`. If no `groups` are specified, then
all grouping variables are used.

!!! note
    The value returned here is sometimes called the "adjusted ICC" and does not take
    the variance of the fixed effects into account (the "conditional ICC").

!!! note
    The result returned aggregates across groups. If you require the ICC for
    each group separately, then you must call `icc` separately for each group.
"""
function icc(model::GeneralizedLinearMixedModel,
             groups::Union{Symbol, SymbolCollection})
    dispersion_parameter(model) && throw(ArgumentError("GLMMs with dispersion parameters are not currently supported."))

    if model.resp.d isa Union{Binomial, Bernoulli}
        σ²res = π^2 / 3
    elseif model.resp.d isa Poisson
        σ²res = 1.0
    else
        throw(ArgumentError("Family $(typeof(model.resp.d)) currently unsupported, please file an issue."))
    end

    return _icc(VarCorr(model), groups, σ²res)
end

icc(model::LinearMixedModel,
    groups::Union{Symbol, SymbolCollection}) = _icc(VarCorr(model), groups, varest(model))

icc(model::MixedModel) = icc(model, fnames(model))

function _icc(vc::VarCorr, groups::Union{Symbol, SymbolCollection}, σ²res)
    σ²_α  = _group_var(vc, groups) # random effect(s) in numerator
    σ² = σ²res + _group_var(vc)
    return σ²_α / σ²
end


module FirstPkg

using Distributions

export pdf_normal

"""
   pdf_normal(x, mu, sigma)

Return pdf of a normal distribution with mean mu and standard deviation sigma at point x.
"""
function pdf_normal(x::Number, mu::Number, sigma::Number)
  dist = Normal(mu,sigma)
  pdf(dist,x)
end

end # module


## Functions to make running and analyzing persistent homology a little easier.

function createSimplexLists(incidenceMatrix,simplex_named_list)

    # Will make lists of edges, 2-simplices, and 3-simplices
    edge_list = Any[]
    simp2_list = Any[]
    simp3_list = Any[]

    for (index,simplex_name) in enumerate(simplex_named_list)

        indices_of_simplex_nodes = findall(incidenceMatrix[index,:].>0)

        edges_from_simplex = collect(combinations(indices_of_simplex_nodes,2))
        simp2_from_simplex = collect(combinations(indices_of_simplex_nodes,3))
        simp3_from_simplex = collect(combinations(indices_of_simplex_nodes,4))

        for (index2,edge) in enumerate(edges_from_simplex)
            push!(edge_list,edge)
        end
        for (index2,simp2) in enumerate(simp2_from_simplex)
            push!(simp2_list,simp2)
        end
        for (index2,simp3) in enumerate(simp3_from_simplex)
            push!(simp3_list,simp3)
        end
    end
    edge_list = unique(edge_list)
    simp2_list = unique(simp2_list)
    simp3_list = unique(simp3_list)

    return edge_list, simp2_list, simp3_list
end

function createCellList(nNodes,edgeList,keepTriangles)
    n2Simps = length(keepTriangles)
    nEdges = length(edgeList)
    #E = Array{Array{Float64}}((Int(nNodes+nEdges+n2Simps)),1)
    E = Array{Array{Float64,1},1}(undef,Int(nNodes+nEdges+n2Simps))
    nCells = length(E)
    for a0 in collect(1:nCells)

        if a0 <= nNodes
            E[a0] = [a0]
        elseif a0 > (nNodes) && a0<=(nNodes+nEdges)
            E[a0] = edgeList[a0-nNodes]
        else
            E[a0] = keepTriangles[a0-(nNodes+nEdges)]

        end

    end
    E
end

function createDMatrix(E,nNodes,nEdges)
    D_01 = Int.(issubset.(E[1:nNodes],E[(nNodes+1):(nNodes+nEdges)]'))
    D_12 = Int.(issubset.(E[(nNodes+1):(nNodes+nEdges)],E[(nNodes+nEdges+1):length(E)]'))
    D = zeros(length(E),length(E))
    D[1:nNodes,(nNodes+1):(nNodes+nEdges)] = D_01
    D[(nNodes+1):(nNodes+nEdges),(nNodes+nEdges+1):length(E)] = D_12
    D = sparse(D)
end

function determineCellWeight(cell)
    cellWeight = maximum(node_weights[Int.(cell)])
end

function bettiCurveFromBarcode(barcode,nNodes,node_weights)

    nNodes = Int(nNodes)
    bettiCurve = zeros(nNodes+1,1)
    ordered_weights = sort(unique(node_weights))

    for bar in collect(1:size(barcode,1))

        birth_index = Int(findall(ordered_weights.==barcode[bar,1])[1])

        if barcode[bar,2]>nNodes

            bettiCurve[birth_index:Int(nNodes+1),1] = bettiCurve[birth_index:Int(nNodes+1),1] .+1
        else

            death_index = Int(findall(ordered_weights.==barcode[bar,2])[1])
            bettiCurve[birth_index:death_index,1] = bettiCurve[birth_index:death_index,1].+1

        end
    end

    return bettiCurve
end

function plotBarcode(barcode,nNodes)

    nNodes = Int(nNodes)
    pbar = plot(1:6,zeros(6,1),c=:black)

    barcode_sorted = barcode[sortperm(barcode[:,1]),:]
    nbars = size(barcode)[1]

    for bar_index in collect(1:nbars)
        birth = barcode[bar_index,1]
        death = barcode[bar_index,2]
        if death>nNodes
            death = nNodes+1
        end

        plot!([birth, death],[bar_index, bar_index], legend = false,
                            xlim = (0,nNodes))
    end

    return pbar
end


@testset "Vector Constraints - Nonnegatives - Parameter in affine part" begin
    """
        min x + y
            x - t + 1 >= 0
            y - t + 2 >= 0

        opt
            x* = t-1
            y* = t-2
            obj = 2*t-3
    """
    cached = MOIU.CachingOptimizer(
        MOIU.UniversalFallback(MOIU.Model{Float64}()),
        ECOS.Optimizer(),
    )
    model = POI.Optimizer(cached)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)
    t, ct = MOI.add_constrained_variable(model, POI.Parameter(5))

    A = [1.0 0 -1; 0 1 -1]
    b = [1.0; 2]
    terms =
        MOI.VectorAffineTerm.(
            1:2,
            MOI.ScalarAffineTerm.(A, reshape([x, y, t], 1, 3)),
        )
    f = MOI.VectorAffineFunction(vec(terms), b)
    set = MOI.Nonnegatives(2)

    cnn = MOI.add_constraint(model, f, MOI.Nonnegatives(2))

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
        MOI.ScalarAffineFunction(
            MOI.ScalarAffineTerm.([1.0, 1.0], [y, x]),
            0.0,
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
    MOI.optimize!(model)

    @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
    @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 4 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 3 atol = ATOL
    @test MOI.get(model, MOI.ConstraintPrimal(), cnn) ≈ [0.0, 0.0] atol = ATOL
    @test MOI.get(model, MOI.ObjectiveValue()) ≈ 7 atol = ATOL
    @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 7 atol = ATOL
    @test MOI.get(model, MOI.ConstraintDual(), cnn) ≈ [1.0, 1.0] atol = ATOL

    MOI.set(model, POI.ParameterValue(), t, 6)
    MOI.optimize!(model)

    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 5 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 4 atol = ATOL
    @test MOI.get(model, MOI.ObjectiveValue()) ≈ 9 atol = ATOL
end

@testset "JuMP direct model - Vector Constraints - Nonnegatives - Parameter in affine part" begin
    """
        min x + y
            x - t + 1 >= 0
            y - t + 2 >= 0

        opt
            x* = t-1
            y* = t-2
            obj = 2*t-3
    """

    cached = MOIU.CachingOptimizer(
        MOIU.UniversalFallback(MOIU.Model{Float64}()),
        ECOS.Optimizer(),
    )
    optimizer = POI.Optimizer(cached)

    model = direct_model(optimizer)
    @variable(model, x)
    @variable(model, y)
    @variable(model, t in POI.Parameter(5))
    @constraint(model, [(x - t + 1), (y - t + 2)...] in MOI.Nonnegatives(2))
    @objective(model, Min, x + y)
    optimize!(model)

    @test isapprox.(value(x), 4.0, atol = ATOL)
    @test isapprox.(value(y), 3.0, atol = ATOL)

    MOI.set(model, POI.ParameterValue(), t, 6)
    optimize!(model)

    @test isapprox.(value(x), 5.0, atol = ATOL)
    @test isapprox.(value(y), 4.0, atol = ATOL)
end

@testset "Vector Constraints - Nonpositives - Parameter in affine part" begin
    """
        min x + y
            - x + t - 1 ≤ 0
            - y + t - 2 ≤ 0

        opt
            x* = t-1
            y* = t-2
            obj = 2*t-3
    """
    cached = MOI.Bridges.full_bridge_optimizer(
        MOIU.CachingOptimizer(
            MOIU.UniversalFallback(MOIU.Model{Float64}()),
            ECOS.Optimizer(),
        ),
        Float64,
    )
    model = POI.Optimizer(cached)

    x = MOI.add_variable(model)
    y = MOI.add_variable(model)
    t, ct = MOI.add_constrained_variable(model, POI.Parameter(5))

    A = [-1.0 0 1; 0 -1 1]
    b = [-1.0; -2]
    terms =
        MOI.VectorAffineTerm.(
            1:2,
            MOI.ScalarAffineTerm.(A, reshape([x, y, t], 1, 3)),
        )
    f = MOI.VectorAffineFunction(vec(terms), b)
    set = MOI.Nonnegatives(2)

    cnn = MOI.add_constraint(model, f, MOI.Nonpositives(2))

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
        MOI.ScalarAffineFunction(
            MOI.ScalarAffineTerm.([1.0, 1.0], [y, x]),
            0.0,
        ),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
    MOI.optimize!(model)

    @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
    @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 4 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 3 atol = ATOL
    @test MOI.get(model, MOI.ConstraintPrimal(), cnn) ≈ [0.0, 0.0] atol = ATOL
    @test MOI.get(model, MOI.ObjectiveValue()) ≈ 7 atol = ATOL
    @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 7 atol = ATOL
    @test MOI.get(model, MOI.ConstraintDual(), cnn) ≈ [-1.0, -1.0] atol = ATOL

    MOI.set(model, POI.ParameterValue(), t, 6)
    MOI.optimize!(model)

    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 5 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 4 atol = ATOL
    @test MOI.get(model, MOI.ObjectiveValue()) ≈ 9 atol = ATOL
end

@testset "JuMP direct model - Vector Constraints - Nonnegatives - Parameter in affine part" begin
    """
        min x + y
            - x + t - 1 ≤ 0
            - y + t - 2 ≤ 0

        opt
            x* = t-1
            y* = t-2
            obj = 2*t-3
    """

    cached = MOI.Bridges.full_bridge_optimizer(
        MOIU.CachingOptimizer(
            MOIU.UniversalFallback(MOIU.Model{Float64}()),
            ECOS.Optimizer(),
        ),
        Float64,
    )
    optimizer = POI.Optimizer(cached)

    model = direct_model(optimizer)
    @variable(model, x)
    @variable(model, y)
    @variable(model, t in POI.Parameter(5))
    @constraint(model, [(-x + t - 1), (-y + t - 2)...] in MOI.Nonpositives(2))
    @objective(model, Min, x + y)
    optimize!(model)

    @test isapprox.(value(x), 4.0, atol = ATOL)
    @test isapprox.(value(y), 3.0, atol = ATOL)

    MOI.set(model, POI.ParameterValue(), t, 6)
    optimize!(model)

    @test isapprox.(value(x), 5.0, atol = ATOL)
    @test isapprox.(value(y), 4.0, atol = ATOL)
end

@testset "Vector Constraints - SOC - Parameter in affine part" begin
    """
        Problem SOC2 from MOI

        min  x
        s.t. y ≥ 1/√2
            (x-p)² + y² ≤ 1

        in conic form:

        min  x
        s.t.  -1/√2 + y ∈ R₊
            1 - t ∈ {0}
            (t, x-p ,y) ∈ SOC₃

        opt
            x* = p - 1/√2
            y* = 1/√2
    """
    cached = MOIU.CachingOptimizer(
        MOIU.UniversalFallback(MOIU.Model{Float64}()),
        ECOS.Optimizer(),
    )
    model = POI.Optimizer(cached)

    x, y, t = MOI.add_variables(model, 3)
    p, cp = MOI.add_constrained_variable(model, POI.Parameter(0))

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
        MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    cnon = MOI.add_constraint(
        model,
        MOI.VectorAffineFunction(
            [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y))],
            [-1 / √2],
        ),
        MOI.Nonnegatives(1),
    )

    ceq = MOI.add_constraint(
        model,
        MOI.VectorAffineFunction(
            [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(-1.0, t))],
            [1.0],
        ),
        MOI.Zeros(1),
    )

    A = [
        1.0 0.0 0.0 0.0
        0.0 1.0 0.0 -1
        0.0 0.0 1.0 0.0
    ]
    f = MOI.VectorAffineFunction(
        vec(
            MOI.VectorAffineTerm.(
                1:3,
                MOI.ScalarAffineTerm.(A, reshape([t, x, y, p], 1, 4)),
            ),
        ),
        zeros(3),
    )
    csoc = MOI.add_constraint(model, f, MOI.SecondOrderCone(3))

    f_error = MOI.VectorOfVariables([t, p, y])
    @test_throws ErrorException MOI.add_constraint(
        model,
        f_error,
        MOI.SecondOrderCone(3),
    )

    MOI.optimize!(model)

    @test MOI.get(model, MOI.ObjectiveValue()) ≈ -1 / √2 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ -1 / √2 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1 / √2 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), t) ≈ 1 atol = ATOL

    MOI.set(model, POI.ParameterValue(), p, 1)
    MOI.optimize!(model)

    @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1 - 1 / √2 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 - 1 / √2 atol = ATOL
end

@testset "Vector Constraints - SOC - No parameters" begin
    """
        Problem SOC2 from MOI

        min  x
        s.t. y ≥ 1/√2
            x² + y² ≤ 1

        in conic form:

        min  x
        s.t.  -1/√2 + y ∈ R₊
            1 - t ∈ {0}
            (t, x ,y) ∈ SOC₃

        opt
            x* = 1/√2
            y* = 1/√2
    """
    cached = MOI.Bridges.full_bridge_optimizer(
        MOIU.CachingOptimizer(
            MOIU.UniversalFallback(MOIU.Model{Float64}()),
            ECOS.Optimizer(),
        ),
        Float64,
    )
    model = POI.Optimizer(cached)

    x, y, t = MOI.add_variables(model, 3)

    MOI.set(
        model,
        MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
        MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0),
    )
    MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

    cnon = MOI.add_constraint(
        model,
        MOI.VectorAffineFunction(
            [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y))],
            [-1 / √2],
        ),
        MOI.Nonnegatives(1),
    )

    ceq = MOI.add_constraint(
        model,
        MOI.VectorAffineFunction(
            [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(-1.0, t))],
            [1.0],
        ),
        MOI.Zeros(1),
    )

    f = MOI.VectorOfVariables([t, x, y])
    csoc = MOI.add_constraint(model, f, MOI.SecondOrderCone(3))

    MOI.optimize!(model)

    @test MOI.get(model, MOI.ObjectiveValue()) ≈ -1 / √2 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), x) ≈ -1 / √2 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1 / √2 atol = ATOL
    @test MOI.get(model, MOI.VariablePrimal(), t) ≈ 1 atol = ATOL
end

@testset "JuMP direct model - Vector Constraints - SOC - Parameter in affine part" begin
    """
        Problem SOC2 from MOI

        min  x
        s.t. y ≥ 1/√2
            (x-p)² + y² ≤ 1

        in conic form:

        min  x
        s.t.  -1/√2 + y ∈ R₊
            1 - t ∈ {0}
            (t, x-p ,y) ∈ SOC₃

        opt
            x* = p - 1/√2
            y* = 1/√2
    """

    cached = MOIU.CachingOptimizer(
        MOIU.UniversalFallback(MOIU.Model{Float64}()),
        ECOS.Optimizer(),
    )
    optimizer = POI.Optimizer(cached)

    model = direct_model(optimizer)
    @variable(model, x)
    @variable(model, y)
    @variable(model, t)
    @variable(model, p in POI.Parameter(0))

    @constraint(model, [y - 1 / √2] in MOI.Nonnegatives(1))
    @constraint(model, [t - 1] in MOI.Zeros(1))
    @constraint(model, [t, (x - p), y...] in SecondOrderCone())
    @objective(model, Min, 1.0 * x)
    optimize!(model)

    @test objective_value(model) ≈ -1 / √2 atol = ATOL
    @test value(x) ≈ -1 / √2 atol = ATOL
    @test value(y) ≈ 1 / √2 atol = ATOL
    @test value(t) ≈ 1 atol = ATOL

    MOI.set(model, POI.ParameterValue(), p, 1)
    optimize!(model)

    @test objective_value(model) ≈ 1 - 1 / √2 atol = ATOL
    @test value(x) ≈ 1 - 1 / √2 atol = ATOL
end