### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# ╔═╡ 8ae7bb38-5803-41d0-98e3-6d9dc4936406
using StatsPlots

# ╔═╡ 03eb200d-c3b4-43d6-b86f-017f5454bc86
using Random

# ╔═╡ 50cd5676-cdba-425d-84d3-7ad6fe21a2d5
md"""
The code in this notebook accompanies the essay "Riding the Covid Coaster," by Brian Hayes, published 17 August 2021 at [http://bit-player.org/2021/riding-the-covid-coaster](http://bit-player.org/2021/riding-the-covid-coaster). It explores various approaches to understanding the trajectory of the Covid-19 epidemic in the United States during its first 18 months.

This is one of three notebooks with code for the project:

* covidcurve.jl covers the reconstruction of the Covid-19 incidence curve and attempts to apply Fourier methods, looking for periodic signals in the data. Also a look at time derivatives. Includes code for figures 1, 3, 4, 5, 6, 7, 8, 9, 12.

* SIR_model.jl deals with exponential growth in epidemic processes, and models based on that idea. Figures 10, 11, 13, 14, 16, 17, 18, 19, 20, 21, 23, 24.

* us_states_data.jl breaks down the national Covid data set into records for inividual states and regions. Code for Figure 22.

The underlying data for most of this work was collected and curated by _The New York Times_. The archive is available at [https://github.com/nytimes/covid-19-data](https://github.com/nytimes/covid-19-data). The version I used in the code below, including data from 21 January 2020 through 20 July 2021, is commit c3ab8c1beba1f4728d284c7b1e58d7074254aff8. You can access the identical set of files at [https://github.com/nytimes/covid-19-data/tree/c3ab8c1beba1f4728d284c7b1e58d7074254aff8](https://github.com/nytimes/covid-19-data/tree/c3ab8c1beba1f4728d284c7b1e58d7074254aff8). But you should probably get the most up-to-date version, with the latest data.

Within the _Times_ archive I have used the files within a folder named "rolling-averages," which has daily new cases rather than cumulative cases, and has precomputed the seven-day trailing average of the data sequences.

The code is written in the [Julia programming language](https://julialang.org/) and organized in [Pluto.jl notebooks](https://github.com/fonsp/Pluto.jl).

Instructions:

1. You'll need a working installation of Julia with the Pluto.jl package installed. Version 0.15.1 or later is needed to automatically find, load, and install packages.

2. In a terminal window, cd to the directory where you have stored this file. Start Julia, then at the "julia>" prompt, type "using Pluto; Pluto.run('SIRmodel.jl')".

3. A browser window will open with this notebook displayed. Pluto will then download all the necessary packages and their dependencies, and will evaluate the cells in the proper order. This can take several minutes, and it can rev up your CPU fan. But then you're done.

"""

# ╔═╡ 035ad037-d231-4a7b-a451-213085be2eb4
# Figure 10

# Plot exponential growth of R^t for various values of R

exp_log_plot =
begin
	xs = 0:0.05:20
	plot(xs,
		[[0.8^x for x in xs],
		 [1.0^x for x in xs],
		 [1.25^x for x in xs],
		 [2.0^x for x in xs],
		 [3.0^x for x in xs]],
		ylims = [0.01, 1e10],
		yscale = :log10,
		yticks = [0.01, 1.0, 1e2, 1e4, 1e6, 1e8, 1e10],
		linewidth = 2.0,
		xlabel = "serial passage times",
		ylabel = "number of new cases",
		fontfamily = "helvetica",
		legend = false)
end

# ╔═╡ 243d8dbd-0f8f-4604-bdb9-9c9078e906be
md"""
#### SIR Models

This is a home-brewed discrete-time implementation of a SIR (susceptible-infective-recovered) model of epidemic dynamics. There are lots and lots of other implementations out there, mostly based on continuous-time differential equations.

Just within the Julia world, Simon Frost has written or collected more than two dozen versions at [https://github.com/epirecipes/sir-julia](https://github.com/epirecipes/sir-julia). And Roberto Alain Berwa has an implementation tuned specifically for Covid-19 at [https://nextjournal.com/berwa/sir-model-for-covid-19-estimating-R_o](https://nextjournal.com/berwa/sir-model-for-covid-19-estimating-R_o).
"""

# ╔═╡ 7fa5fed0-63a9-4332-a808-95e2b7bcf09d
# The basic model

# The population variables ss, ii, and rr are all floats in the interval
# [0, 1], with the condition that ss + ii + rr == 1.0 at all times. The
# initial infective density i0 is also a float in this range. β and γ are
# the infection and recovery constants. tmax is the total time span of the
# simulation measured in multiples of the serial passage time--the average
# time between acquiring an infection and infecting someone else. dt is the
# simulation time step; we start at tt = 0 and increment by dt until we
# reach tmax.

function sir(i0, β, γ, dt, tmax)
	steps = floor(Int, tmax / dt)

	times = zeros(steps)
	susceptibles = zeros(steps)
	infectives = zeros(steps)
	recovereds = zeros(steps)

	tt = 0.0        # time variable, in steps of dt
	ss = 1.0 - i0   # susceptible population as a function of tt
	ii = i0         # infective population as a function of tt
	rr = 0.0        # recovered population as a function of tt

	for count in 1:steps  # loop tmax/dt times

		times[count] = tt          # record current values in four vectors
		susceptibles[count] = ss
		infectives[count] = ii
		recovereds[count] = rr

		new_infections = dt * β * ss * ii  # calc infections and recoveries
		recoveries = dt * γ * ii

		ss = ss - new_infections              # update population numbers
		ii = ii + new_infections - recoveries
		rr = rr + recoveries
		tt = tt + dt                          # increment time
	end
	return (times, [susceptibles, infectives, recovereds])
end

# ╔═╡ b1f928d8-7175-4bd3-9adc-9cb93b0dbb40
# Figure 11 -- output of the basic SIR model

sir_graph =
plot(sir(0.000001, 0.6, 0.2, 0.1, 100),
	linecolor = [:green "#4100eb" "#dd4411"],
	linewidth = [2.0 3.5 2.0],
	label = false,
	annotationfontsize = 10,
	annotationhalign = :left,
	annotations = [(9, 0.90, "susceptibles"),
				   (44, 0.65, "recovereds"),
				   (45, 0.20, "infectives")],
	xticks = 0:20:100,
	yticks = 0.0:0.20:1.0,
	xlabel = "serial passage times",
	ylabel = "proportion of population"
	)

# ╔═╡ ad7091a0-399f-49ee-b273-4976ecb1b75e
# savefig(sir_graph, "SIR_model.svg")

# ╔═╡ 13abedaa-7df4-48c6-8d61-d81c8d585be8
# "SIR with fear" -- exploring feedback as a source of oscillatory
#  behavior in epidemic dynamics.

# We add two features to the basic SIR model. Negative feedback suppresses
# all new infections whenever the infection rate rises above a threshold. But
# the feedback may take effect only after a delay.

function sirf(i0, β, γ, threshold, delay, dt, tmax)
	steps = floor(Int, tmax / dt)

	times = zeros(steps)
	susceptibles = zeros(steps)
	infectives = zeros(steps)
	recovereds = zeros(steps)
	thresholds = fill(threshold, steps)

	tt = 0.0
	ss = 1.0 - i0
	ii = i0
	rr = 0.0
	ϕ  = 1.0

	for count in 1:steps

		times[count] = tt
		susceptibles[count] = ss
		infectives[count] = ii
		recovereds[count] = rr

		if count <= delay
			ϕ = 1.0
		else
			ϕ = infectives[count - delay] < threshold ? 1.0 : 0.0
		end

		new_infections = dt * β * ϕ * ss * ii
		recoveries = dt * γ * ii
		ss = ss - new_infections
		ii = ii + new_infections - recoveries
		rr = rr + recoveries
		tt = tt + dt
	end
	return (times, [susceptibles, infectives, recovereds, thresholds])
end

# ╔═╡ ef8592b6-7474-410c-9171-187893239151
sirf_result_no_delay = sirf(0.000001, 0.6, 0.2, 0.05, 0, 0.1, 100)

# ╔═╡ e3a0e0c3-1a96-4a9d-bbe3-a25061b1ee12
# Figure 13 -- SIR with feedback but no delay. Oscillations are too
# small in period and amplitude to be perceived.

sirf_graph_no_delay =
plot(sirf_result_no_delay,
	linecolor = [:green "#4100eb" "#dd4411" :black],
	linewidth = [2.0 2.5 2.0 0.75],
	linestyle = [:solid :solid :solid :dash],
	label = false,
	annotationfontsize = 10,
	annotationhalign = :left,
	annotations = [(33, 0.90, "susceptibles"),
				   (75, 0.69, "recovereds"),
				   (53, 0.09, "infectives"),
				   (3,  0.08, "threshold")],
	xticks = 0:20:100,
	yticks = 0.0:0.20:1.0,
	xlabel = "serial passage times",
	ylabel = "proportion of population"
	)

# ╔═╡ 228a6067-bddf-4e6e-b3d8-2a6ac47d3904
savefig(sirf_graph_no_delay, "SIR_model_with_feedback.svg")

# ╔═╡ 08be5eef-4bdc-4ee6-8e5e-507c8055c07e
sirf_result = sirf(0.000001, 0.6, 0.2, 0.05, 30, 0.1, 100)

# ╔═╡ 890fe7ce-d6cd-49c2-b888-421971118d8d
# Figure 14 -- SIR model with both feedback and delay.

sirf_graph =
plot(sirf_result,
	linecolor = [:green "#4100eb" "#dd4411" :black],
	linewidth = [2.0 2.5 2.0 0.75],
	linestyle = [:solid :solid :solid :dash],
	label = false,
	annotationfontsize = 10,
	annotationhalign = :left,
	annotations = [(30, 0.90, "susceptibles"),
				   (75, 0.69, "recovereds"),
				   (45, 0.12, "infectives"),
				   (3,  0.08, "threshold")],
	xticks = 0:20:100,
	yticks = 0.0:0.20:1.0,
	xlabel = "serial passage times",
	ylabel = "proportion of population"
	)

# ╔═╡ 7ba339f1-b03e-49d7-9821-bd731ffe664d
# savefig(sirf_graph, "SIR_model_with_oscillations.svg")

# ╔═╡ e8657ff4-4c7f-401c-a3e7-5098a90d99e5
# The basic SIR model uses exponential decay as well as exponential growth.
# Calculate the exponential falloff in infective numbers after new infections
# cease.

function decline(n, γ, reps)
	vec = [n]
	for i in 1:reps
		n -= n * γ
		push!(vec, n)
	end
	vec
end

# ╔═╡ 798a62d7-700b-400e-9bc9-efbe0b7758bc
# Figure 16

exponential_decline_bargraph =
plot(decline(1.0, 0.2, 10),
	seriestype = :bar,
	xticks = (1.0:1.0:11.0, 0:1:10),
	yticks = (0:0.2:1.0),
	size = [350, 200],
	fontfamily = "helvetica",
	guidefontsize = 10,
	fillcolor = "#4100eb",
	xlabel = "serial passage times",
	ylabel = "fraction remaining",
	legend = false
)

# ╔═╡ cb4c6cbb-5e44-49da-b82f-3551a1b3abbf
# savefig(exponential_decline_bargraph, "exponential_decline_bargraph.svg")

# ╔═╡ 235956e8-e4d6-49bc-b175-d7c2aed732a8
# Instead of exponential decay in infective numbers, we assign
# the infectious period a fixed duration. At each time step the
# newly recruited infectives enter a queue of length q, and
# progress through it at one place per time step. After q steps
# they reach the head of the q and are transformed into recovereds.

function sirq(i0, β, q, dt, tmax)
	steps = floor(Int, tmax / dt)

	times = zeros(steps)
	susceptibles = zeros(steps)
	infectives = zeros(steps)
	recovereds = zeros(steps)
	Q = zeros(Int(ceil(q / dt)))

	tt = 0
	ss = 1 - i0
	ii = i0
	rr = 0

	for count in 1:steps

		times[count] = tt
		susceptibles[count] = ss
		infectives[count] = ii
		recovereds[count] = rr

		new_infections = dt * β * ss * ii
		recoveries = pop!(Q)
		ss = ss - new_infections
		ii = ii + new_infections - recoveries
		rr = rr + recoveries
		tt = tt + dt
		pushfirst!(Q, new_infections)
	end
	return (times, [susceptibles, infectives, recovereds])
end

# ╔═╡ 3e2cd644-1f53-4f51-891b-c54ae816f557
# Figure 17 -- SIR model with infectious period equal to 3 serial
# passage times.

sirq_graph_3p0 =
plot(sirq(0.000001, 0.6, 3.0, 0.1, 100),
	linecolor = [:green "#4100eb" "#dd4411"],
	linewidth = [2.0 3.5 2.0],
	label = false,
	fontfamily = "helvetica",
	annotationfontsize = 10,
	annotationhalign = :left,
	annotations = [(0, 0.97, "susceptibles"),
				   (60, 0.79, "recovereds"),
				   (12, 0.18, "infectives"),
				   (75, 0.97, "duration = 3.0 TSP")],
	xticks = 0:20:100,
	yticks = 0.0:0.20:1.0,
	xlabel = "serial passage times",
	ylabel = "proportion of population"
	)

# ╔═╡ 7cc0d1e9-3cab-48cc-b873-181e006a620e
# savefig(sirq_graph_3p0, "sirq_0p000001_0p6_3p0_0p1_100.svg")

# ╔═╡ 462e8b4d-5bf1-4153-b9d5-46b74f308721
# Figure 18 -- SIR model with infectious period equal to 2 serial
# passage times.

sirq_graph_2p0 =
plot(sirq(0.000001, 0.6, 2.0, 0.1, 100),
	linecolor = [:green "#4100eb" "#dd4411"],
	linewidth = [2.0 3.5 2.0],
	label = false,
	fontfamily = "helvetica",
	annotationfontsize = 10,
	annotationhalign = :left,
	annotations = [(0, 0.97, "susceptibles"),
				   (85, 0.35, "recovereds"),
				   (62, 0.05, "infectives"),
				   (75, 0.97, "duration = 2.0 SPT")],
	xticks = 0:20:100,
	yticks = 0.0:0.20:1.0,
	xlabel = "serial passage times",
	ylabel = "proportion of population"
	)

# ╔═╡ bff6a040-8d4a-4f00-a4e9-9f8147a21b41
# savefig(sirq_graph_2p0, "sirq_0p000001_0p6_2p0_0p1_100.svg")

# ╔═╡ f24f1556-cf68-4f54-bb35-7ac9b01d0a85
# SIR model with vaccination

function sirv(i0, β, γ, v, v_date, dt, tmax)
	steps = floor(Int, tmax / dt)

	times = zeros(steps)
	susceptibles = zeros(steps)
	infectives = zeros(steps)
	recovereds = zeros(steps)
	vaccinateds = zeros(steps)

	tt = 0
	ss = 1 - i0
	ii = i0
	rr = 0
	vv = 0

	for count in 1:steps

		times[count] = tt
		susceptibles[count] = ss
		infectives[count] = ii
		recovereds[count] = rr
		vaccinateds[count] = vv

		new_infections = dt * β * ss * ii
		recoveries = dt * γ * ii
		new_vaccinations = (vv == 0 && tt > v_date) ? v : 0

		vv = vv + new_vaccinations
		ss = ss - new_infections - new_vaccinations
		ii = ii + new_infections - recoveries
		rr = rr + recoveries
		tt = tt + dt
	end
	return (times, [susceptibles, infectives, recovereds, vaccinateds])
end

# ╔═╡ 4ae90a2a-67f1-4dc9-b6ed-d2e2e85aadd7
# Figure 23

sirv_graph_0p6_0p2_0p5_0 =
plot(sirv(0.000001, 0.6, 0.2, 0.5, 0, 0.1, 100),
	linecolor = [:green "#4100eb" "#dd4411" :orange],
	linewidth = [3.5 3.5 2.0 2.0],
	# label = false,
	label = ["susceptibles" "infectives" "recovereds" "vaccinateds"],
	annotationfontsize = 10,
	annotationhalign = :left,
	# annotations = [(3, 0.96, "susceptibles"),
				   # (62, 0.61, "recovereds"),
				   # (17, 0.18, "infectives"),
				   # (62, 0.37, "vaccinateds")],
	xticks = 0:20:100,
	yticks = 0.0:0.20:1.0,
	xlabel = "serial passage times",
	ylabel = "proportion of population"
	)

# ╔═╡ 09f9e4c9-315f-4433-b021-08150d6ce413
# savefig(sirv_graph_0p6_0p2_0p5_0, "sirv_graph_0p6_0p2_0p5_0.svg")

# ╔═╡ f0e6437c-609e-48f9-abc8-f3d325f35ec3
# Figure 24

sirv_graph_0p6_0p1_0p5_0 =
plot(sirv(0.000001, 0.6, 0.1, 0.5, 0, 0.1, 100),
	linecolor = [:green "#4100eb" "#dd4411" :orange],
	linewidth = [3.5 3.5 2.0 2.0],
	# label = false,
	label = ["susceptibles" "infectives" "recovereds" "vaccinateds"],
	annotationfontsize = 10,
	annotationhalign = :left,
	# annotations = [(3, 0.96, "susceptibles"),
				   # (62, 0.61, "recovereds"),
				   # (17, 0.18, "infectives"),
				   # (62, 0.37, "vaccinateds")],
	xticks = 0:20:100,
	yticks = 0.0:0.20:1.0,
	xlabel = "serial passage times",
	ylabel = "proportion of population"
	)

# ╔═╡ cb0c1428-5146-4402-93ef-27c3cd4fd9d4
# savefig(sirv_graph_0p6_0p1_0p5_0, "sirv_graph_0p6_0p1_0p5_0.svg")

# ╔═╡ 3438d796-9efc-44d0-9c08-a6c90305dee0
md"""
#### Random mini-epidemics

The last topic to be taken up here is the possibility that the observed profile of the Covid incidence curve might be the result of summing many small, randomly timed mini-epidemics.
"""

# ╔═╡ e8139300-8097-4ba0-96e3-5135047c70d7
# Generate a standard SIR curve with a specified starting time
# 0 ≤ tstart ≤ tmax.

function asynchSIR(i0, β, γ, dt, tmax, tstart)
	steps = floor(Int, tmax / dt)

	times = zeros(steps)
	susceptibles = zeros(steps)
	infectives = zeros(steps)
	recovereds = zeros(steps)

	tt = 0.0
	ss = 1.0 - i0
	ii = i0
	rr = 0.0

	for count in 1:steps

		times[count] = tt
		tt += dt

		if tt >= tstart
			susceptibles[count] = ss
			infectives[count] = ii
			recovereds[count] = rr

			new_infections = dt * β * ss * ii
			recoveries = dt * γ * ii
			ss = ss - new_infections
			ii = ii + new_infections - recoveries
			rr = rr + recoveries
		end
	end
	return infectives
end

# ╔═╡ 8103f855-a617-438b-8cb9-8fa0e7fde319
# Generate N SIR curves with start times chosen uniformly at
# random from the interval 0 ≤ tstart ≤ 0.8 * tmax. The parameter
# β is also allowed to vary around the specied value by an amount
# drawn from a normal distribution with mean 0 and sdev 0.1.

function multiSIR(N, i0, β, γ, dt, tmax)
	steps = floor(Int, tmax / dt)
	curves = zeros(steps, N)
	times = collect(0.0:dt:tmax-dt)

	for i in 1:N
		tstart = rand() * (0.8 * tmax)
		rand_β = β + (randn() * 0.1)
		this_curve = asynchSIR(i0, rand_β, γ, dt, tmax, tstart)
		curves[1:end, i] .= this_curve
	end

	sumcurve = zeros(steps)
	for j in 1:steps
		sumcurve[j] = sum(curves[j, 1:end])
	end

	scale_factor = 1 / maximum(sumcurve)
	sumcurve .= sumcurve * scale_factor
	curves .= curves * scale_factor

	return ((times, curves), (times, sumcurve))
end

# ╔═╡ 3fdd5a77-45df-494e-9786-c8aa22daa82e
# Figure 19 -- Draw N randomly timed SIR curves and their sum curve.
# The published figure shows results with N = 6 and N = 50. Because of
# the randomness involved, rerunning this procedure will not replicate
# the published figure, but the character of the result should be
# the same.

asynch_sir_graph =
begin
	the_curves = multiSIR(20, 0.001, 0.6, 0.2, 0.1, 100)
	plot(the_curves[1],
		linecolor = ["#dd4411"],
		linewidth = 1.0,
		label = false,
		xticks = 0:20:100,
		yticks = 0.0:0.20:1.0,
		xlabel = "serial passage times",
		ylabel = "proportion of population")
	plot!(the_curves[2],
		linecolor = ["#4100eb"],
		linewidth = 3.0,
		label = false,
		legend = false)
end

# ╔═╡ 341c32a3-3fae-472b-8771-c7f9fbaf4704
# savefig(asynch_sir_graph, "miltiSIR_12.svg")

# ╔═╡ 0bb71f07-9381-4e77-9fe3-1c59854d18f5
# Figure 20. -- Generate multiSIR plots, without scales, but
# with a label indicating the number of component curves.

function multiSIRplot(N, i0, β, γ, dt, tmax, filename_prefix)
	results = multiSIR(N, i0, β, γ, dt, tmax)
	filename = filename_prefix * "_" * "$N" *".svg"

	p =	plot(results[1],
			linecolor = ["#dd4411"],
			linewidth = 1.0,
			label = false,
			framestyle = :box,
			annotationfontsize = 10,
			annotationhalign = :left,
			annotations = (0.0, 0.90, "N = $N"),
			xticks = false,
			yticks = false)
			# xticks = 0:20:100,
			# yticks = 0.0:0.20:1.0,
			# xlabel = "serial passage times",
			# ylabel = "proportion of population")
	plot!(p, results[2],
			linecolor = ["#4100eb"],
			linewidth = 3.0,
			label = false,
			legend = false)
	savefig(p, filename)
	return p
end

# ╔═╡ 20fd7570-5c1f-4316-87f3-b972a1a0364f
multiSIRplot(12, 0.001, 0.6, 0.2, 0.1, 100, "multiSIRplot")

# ╔═╡ 8bfed4f2-724c-44ea-b6f4-02c836391be7
# Figure 20 -- create a tableau of multiple multiSIR graphs.

multiSIR_tableau =
[multiSIRplot(N, 0.001, 0.6, 0.2, 0.1, 100, "multiSIR_tableau_A") for N in [3, 4, 5, 6, 8, 10, 15, 20, 30, 40, 50, 60, 100]]

# ╔═╡ 64460f92-6dd3-4c13-b3b9-bb2660436b40
# for i in 1:length(multiSIR_tableau)
# 	savefig(multiSIR_tableau[i], "multiSIR_tableau_A_$i.svg")
# end

# ╔═╡ 7b8a3922-21a9-4905-84ad-e2d8edf51cce
# Written to gather some statistics on the number of peaks and
# troughs in a curve representing the sum of N mini-epidemics.

function count_peaks_and_troughs(curve)
	deriv = diff(curve)
	crossings = map(sign, deriv)
	peaks = 0
	troughs = 0
	i = 1
	while crossings[i] == 0
		i += 1
	end
	previous = crossings[i]
	for j in i+1:length(crossings)
		if crossings[j] == -previous
			if crossings[j] < 0
				peaks += 1
			else
				troughs += 1
			end
			previous = -previous
		end
	end
	return (peaks, troughs)
end

# ╔═╡ bbf05eef-188d-4e31-ae40-f85a8269a3b6

function collect_peak_counts(N, reps)
	counts = zeros(Int, 20)
	for i in 1:reps
		multiSIRrecord = multiSIR(N, 0.001, 0.6, 0.2, 0.1, 100)
		peaks, troughs = count_peaks_and_troughs(multiSIRrecord[2][2])
		counts[peaks+1] += 1
	end
	return counts
end

# ╔═╡ 16e5e214-d5b7-480e-8243-9cbf36ababf8
peak_data = [collect_peak_counts(n, 1000) for n in [4, 5, 6, 8, 10, 15, 30, 50, 100]]

# ╔═╡ e252ca21-48d3-49f4-9bee-649a5610240a
# Figure 21

peak_data_plot =
plot(peak_data,
	seriestype = :bar,
	layout = (9, 1),
	size = (400, 600),
	fillcolor = "#4100eb",
	framestyle = :grid,
	xlims = [1, 12],
	# xticks = (1:1:12, ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"]),
	xticks = false,
	legend = false,
	yticks = false)

# ╔═╡ 933d49c9-31fc-4d02-9b83-d523bf828476
# savefig(peak_data_plot, "peak_numbers_bar_graph.svg")

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"

[compat]
StatsPlots = "~0.14.26"
"""

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

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

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

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

[[Arpack]]
deps = ["Arpack_jll", "Libdl", "LinearAlgebra"]
git-tree-sha1 = "2ff92b71ba1747c5fdd541f8fc87736d82f40ec9"
uuid = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97"
version = "0.4.0"

[[Arpack_jll]]
deps = ["Libdl", "OpenBLAS_jll", "Pkg"]
git-tree-sha1 = "e214a9b9bd1b4e1b4f15b22c0994862b66af7ff7"
uuid = "68821587-b530-5797-8361-c406ea357684"
version = "3.5.0+3"

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

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

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

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c3598e525718abcc440f69cc6d5f60dda0a1b61e"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.6+5"

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

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

[[Clustering]]
deps = ["Distances", "LinearAlgebra", "NearestNeighbors", "Printf", "SparseArrays", "Statistics", "StatsBase"]
git-tree-sha1 = "75479b7df4167267d75294d14b58244695beb2ac"
uuid = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5"
version = "0.14.2"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random", "StaticArrays"]
git-tree-sha1 = "ed268efe58512df8c7e224d2e170afd76dd6a417"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.13.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 = "344f143fa0ec67e47917848795ab19c6a455f32c"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.32.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 = "ee400abb2298bd13bfc3df1c412ed228061a2385"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.7.0"

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

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

[[DataValues]]
deps = ["DataValueInterfaces", "Dates"]
git-tree-sha1 = "d88a19299eba280a6d062e135a43f00323ae70bf"
uuid = "e7dc6d0d-1eca-5fa6-8ad6-5aecde8b7ea5"
version = "0.4.13"

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

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

[[Distances]]
deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
git-tree-sha1 = "abe4ad222b26af3337262b8afb28fab8d215e9f8"
uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
version = "0.10.3"

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

[[Distributions]]
deps = ["FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SparseArrays", "SpecialFunctions", "Statistics", "StatsBase", "StatsFuns"]
git-tree-sha1 = "3889f646423ce91dd1055a76317e9a1d3a23fff1"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.11"

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

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

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "92d8f9f208637e8d2d28c664051a00569c01493d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.1.5+1"

[[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", "LibVPX_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 = "3cc57ad0a213808473eafef4845a74766242e05f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.3.1+4"

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

[[FFTW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3676abafff7e4ff07bbd2c42b3d8201f31653dcc"
uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a"
version = "3.3.9+8"

[[FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "8c8eac2af06ce35973c3eadb4ab3243076a408e7"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.12.1"

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

[[Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "35895cf184ceaab11fd778b4590144034a167a2f"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.1+14"

[[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 = "cbd58c9deb1d304f5a245a0b7eb841a2560cfec6"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.1+5"

[[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 = "9f473cdf6e2eb360c576f9822e7c765dd9d26dbc"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.58.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 = "eaf96e05a880f3db5ded5a5a8a7817ecba3c7392"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.58.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"

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

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

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

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

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

[[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 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.1"

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

[[KernelDensity]]
deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"]
git-tree-sha1 = "591e8dc09ad18386189610acafb970032c519707"
uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
version = "0.6.3"

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

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

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

[[LibVPX_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "12ee7e23fa4d18361e7c2cde8f8337d4c3101bc7"
uuid = "dd192d2f-8180-539f-9fb4-cc70b1dcf69a"
version = "1.10.0+0"

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

[[LogExpFunctions]]
deps = ["DocStringExtensions", "LinearAlgebra"]
git-tree-sha1 = "7bd5f6565d80b6bf753738d2bc40a5dfea072070"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.2.5"

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

[[MKL_jll]]
deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "Pkg"]
git-tree-sha1 = "c253236b0ed414624b083e6b72bfe891fbd2c7af"
uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7"
version = "2021.1.1+1"

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

[[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 = "4ea90bd5d3985ae1f9a908bd4500ae88921c5ce7"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.0"

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

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

[[MultivariateStats]]
deps = ["Arpack", "LinearAlgebra", "SparseArrays", "Statistics", "StatsBase"]
git-tree-sha1 = "8d958ff1854b166003238fe191ec34b9d592860a"
uuid = "6f286f6a-111f-5878-ab1e-185364afe411"
version = "0.8.0"

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

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

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

[[Observables]]
git-tree-sha1 = "fe29afdef3d0c4a8286128d4e45cc50621b1e43d"
uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
version = "0.4.0"

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

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

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

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

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

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

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

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

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

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

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

[[Plots]]
deps = ["Base64", "Contour", "Dates", "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 = "1e72752052a3893d0f7103fbac728b60b934f5a5"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.19.4"

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

[[QuadGK]]
deps = ["DataStructures", "LinearAlgebra"]
git-tree-sha1 = "12fbe86da16df6679be7521dfb39fbc861e1dc7b"
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
version = "2.4.1"

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

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

[[Ratios]]
git-tree-sha1 = "37d210f612d70f3f7d57d488cb3b6eff56ad4e41"
uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
version = "0.4.0"

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

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

[[Reexport]]
git-tree-sha1 = "5f6c21241f0f655da3952fd60aa18477cf96c220"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.1.0"

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

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

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

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

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

[[SentinelArrays]]
deps = ["Dates", "Random"]
git-tree-sha1 = "35927c2c11da0a86bcd482464b93dadd09ce420f"
uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c"
version = "1.3.5"

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

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

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

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

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

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

[[SpecialFunctions]]
deps = ["ChainRulesCore", "LogExpFunctions", "OpenSpecFun_jll"]
git-tree-sha1 = "508822dca004bf62e210609148511ad03ce8f1d8"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.6.0"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "885838778bb6f0136f8317757d7803e0d81201e4"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.9"

[[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 = "fed1ec1e65749c4d96fc20dd13bea72b55457e62"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.9"

[[StatsFuns]]
deps = ["LogExpFunctions", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "30cd8c360c54081f806b1ee14d2eecbef3c04c49"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "0.9.8"

[[StatsPlots]]
deps = ["Clustering", "DataStructures", "DataValues", "Distributions", "Interpolations", "KernelDensity", "LinearAlgebra", "MultivariateStats", "Observables", "Plots", "RecipesBase", "RecipesPipeline", "Reexport", "StatsBase", "TableOperations", "Tables", "Widgets"]
git-tree-sha1 = "e7d1e79232310bd654c7cef46465c537562af4fe"
uuid = "f3b207a7-027a-5e70-b257-86293d7955fd"
version = "0.14.26"

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

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

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

[[TableOperations]]
deps = ["SentinelArrays", "Tables", "Test"]
git-tree-sha1 = "a7cf690d0ac3f5b53dd09b5d613540b230233647"
uuid = "ab02a1b2-a7df-11e8-156e-fb1833f50b87"
version = "1.0.0"

[[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 = "8ed4a3ea724dac32670b062be3ef1c1de6773ae8"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.4.4"

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

[[Widgets]]
deps = ["Colors", "Dates", "Observables", "OrderedCollections"]
git-tree-sha1 = "eae2fbbc34a79ffd57fb4c972b08ce50b8f6a00d"
uuid = "cc8bc4a8-27d6-5769-a93b-9d913e69aa62"
version = "0.6.3"

[[WoodburyMatrices]]
deps = ["LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "59e2ad8fd1591ea019a5259bd012d7aee15f995c"
uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
version = "0.5.3"

[[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", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "acc685bcf777b2202a904cdcb49ad34c2fa1880c"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.14.0+4"

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

[[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 = "d713c1ce4deac133e3334ee12f4adff07f81778f"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2020.7.14+2"

[[x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "487da2f8f2f0c8ee0e83f39d13037d6bbf0a45ab"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.0.0+3"

[[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:
# ╠═50cd5676-cdba-425d-84d3-7ad6fe21a2d5
# ╠═8ae7bb38-5803-41d0-98e3-6d9dc4936406
# ╠═03eb200d-c3b4-43d6-b86f-017f5454bc86
# ╠═035ad037-d231-4a7b-a451-213085be2eb4
# ╟─243d8dbd-0f8f-4604-bdb9-9c9078e906be
# ╠═7fa5fed0-63a9-4332-a808-95e2b7bcf09d
# ╠═b1f928d8-7175-4bd3-9adc-9cb93b0dbb40
# ╠═ad7091a0-399f-49ee-b273-4976ecb1b75e
# ╠═13abedaa-7df4-48c6-8d61-d81c8d585be8
# ╠═ef8592b6-7474-410c-9171-187893239151
# ╠═e3a0e0c3-1a96-4a9d-bbe3-a25061b1ee12
# ╠═228a6067-bddf-4e6e-b3d8-2a6ac47d3904
# ╠═08be5eef-4bdc-4ee6-8e5e-507c8055c07e
# ╠═890fe7ce-d6cd-49c2-b888-421971118d8d
# ╠═7ba339f1-b03e-49d7-9821-bd731ffe664d
# ╠═e8657ff4-4c7f-401c-a3e7-5098a90d99e5
# ╠═798a62d7-700b-400e-9bc9-efbe0b7758bc
# ╠═cb4c6cbb-5e44-49da-b82f-3551a1b3abbf
# ╠═235956e8-e4d6-49bc-b175-d7c2aed732a8
# ╠═3e2cd644-1f53-4f51-891b-c54ae816f557
# ╠═7cc0d1e9-3cab-48cc-b873-181e006a620e
# ╠═462e8b4d-5bf1-4153-b9d5-46b74f308721
# ╠═bff6a040-8d4a-4f00-a4e9-9f8147a21b41
# ╠═f24f1556-cf68-4f54-bb35-7ac9b01d0a85
# ╠═4ae90a2a-67f1-4dc9-b6ed-d2e2e85aadd7
# ╠═09f9e4c9-315f-4433-b021-08150d6ce413
# ╠═f0e6437c-609e-48f9-abc8-f3d325f35ec3
# ╠═cb0c1428-5146-4402-93ef-27c3cd4fd9d4
# ╟─3438d796-9efc-44d0-9c08-a6c90305dee0
# ╠═e8139300-8097-4ba0-96e3-5135047c70d7
# ╠═8103f855-a617-438b-8cb9-8fa0e7fde319
# ╠═3fdd5a77-45df-494e-9786-c8aa22daa82e
# ╠═341c32a3-3fae-472b-8771-c7f9fbaf4704
# ╠═20fd7570-5c1f-4316-87f3-b972a1a0364f
# ╠═0bb71f07-9381-4e77-9fe3-1c59854d18f5
# ╠═8bfed4f2-724c-44ea-b6f4-02c836391be7
# ╠═64460f92-6dd3-4c13-b3b9-bb2660436b40
# ╠═7b8a3922-21a9-4905-84ad-e2d8edf51cce
# ╠═bbf05eef-188d-4e31-ae40-f85a8269a3b6
# ╠═16e5e214-d5b7-480e-8243-9cbf36ababf8
# ╠═e252ca21-48d3-49f4-9bee-649a5610240a
# ╠═933d49c9-31fc-4d02-9b83-d523bf828476
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


function adjacency_matrix(adj::AbstractMatrix, T::DataType=eltype(adj))
    m, n = size(adj)
    (m == n) || throw(DimensionMismatch("adjacency matrix is not a square matrix: ($m, $n)"))
    T.(adj)
end

function degrees(adj::AbstractMatrix; dir::Symbol=:out)
    if issymmetric(adj)
        d = vec(sum(adj, dims=1))
    else
        if dir == :out
            d = vec(sum(adj, dims=1))
        elseif dir == :in
            d = vec(sum(adj, dims=2))
        elseif dir == :both
            d = vec(sum(adj, dims=1)) + vec(sum(adj, dims=2))
        else
            throw(DomainError(dir, "invalid argument, only accept :in, :out and :both"))
        end
    end
    d
end

degrees(adj::AbstractMatrix, T::DataType; dir::Symbol=:out) = degrees(T.(adj); dir=dir)

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

Degree matrix of graph `g`. Return a matrix which contains degrees of each vertex in its diagonal.
The values other than diagonal are zeros.

# Arguments

- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from Graphs)
    or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).

# Examples

```jldoctest
julia> using GraphLaplacians

julia> m = [0 1 1; 1 0 0; 1 0 0];

julia> GraphLaplacians.degree_matrix(m)
3×3 LinearAlgebra.Diagonal{Int64, Vector{Int64}}:
 2  ⋅  ⋅
 ⋅  1  ⋅
 ⋅  ⋅  1
```
"""
function degree_matrix(adj::AbstractMatrix, T::DataType=eltype(adj);
                       dir::Symbol=:out, squared::Bool=false, inverse::Bool=false)
    d = degrees(adj, T, dir=dir)
    squared && (d .= sqrt.(d))
    inverse && (d .= inv.(d); replace!(d, typemax(T)=>zero(T)))
    return Diagonal(T.(d))
end

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

Laplacian matrix of graph `g`.

# Arguments

- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from Graphs)
    or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).
"""
Graphs.laplacian_matrix(adj::AbstractMatrix, T::DataType=eltype(adj); dir::Symbol=:out) =
    degree_matrix(adj, T, dir=dir) - SparseMatrixCSC(T.(adj))

"""
    normalized_laplacian(g[, T]; dir=:both, selfloop=false)

Normalized Laplacian matrix of graph `g`.

# Arguments

- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from Graphs)
    or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `selfloop`: adding self loop while calculating the matrix (optional).
- `dir`: direction of graph; should be `:in` or `:out` (optional).
"""
function normalized_laplacian(adj::AbstractMatrix, T::DataType=eltype(adj);
                              dir::Symbol=:both, selfloop::Bool=false)
    if dir == :both
        selfloop && (adj += I)
        inv_sqrtD = degree_matrix(adj, T, dir=:both, squared=true, inverse=true)
        return T.(I - inv_sqrtD * adj * inv_sqrtD)
    else
        return T.(I - degree_matrix(adj, T, dir=dir, inverse=true) * adj)
    end
end

@doc raw"""
    scaled_laplacian(g[, T])

Scaled Laplacien matrix of graph `g`,
defined as ``\hat{L} = \frac{2}{\lambda_{max}} L - I`` where ``L`` is the normalized Laplacian matrix.

# Arguments

- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from Graphs)
    or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
"""
function scaled_laplacian(adj::AbstractMatrix, T::DataType=eltype(adj))
    @assert issymmetric(adj) "scaled_laplacian only works with symmetric matrices"
    E = eigen(Symmetric(Array(adj))).values
    T(2. / maximum(E)) * normalized_laplacian(adj, T) - I
end

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

Random walk normalized Laplacian matrix of graph `g`.

# Arguments

- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from Graphs)
    or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).
"""
function random_walk_laplacian(adj::AbstractMatrix, T::DataType=eltype(adj); dir::Symbol=:out)
    d = degrees(adj, dir=dir)
    inv_d = 1 ./ d
    replace!(inv_d, typemax(float(T)) => zero(float(T)))  # avoid degree to be zero
    P = Diagonal(inv_d) * adj
    SparseMatrixCSC(T.(I - P))
end

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

Signless Laplacian matrix of graph `g`.

# Arguments

- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from Graphs)
    or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).
"""
signless_laplacian(adj::AbstractMatrix, T::DataType=eltype(adj); dir::Symbol=:out) =
    degree_matrix(adj, T, dir=dir) + SparseMatrixCSC(T.(adj))


"""
    Dirac(x)

A *Dirac distribution* is parameterized by its only value `x`, and takes its value with probability 1.

```math
P(X = \\hat{x}) = \\begin{cases}
1 & \\quad \\text{for } \\hat{x} = x, \\\\
0 & \\quad \\text{for } \\hat{x} \\neq x.
\\end{cases}
```

```julia
Dirac(2.5)   # Dirac distribution with value x = 2.5
```

External links:

* [Dirac measure on Wikipedia](http://en.wikipedia.org/wiki/Dirac_measure)
"""
struct Dirac{T} <: DiscreteUnivariateDistribution
    value::T
end

Base.eltype(::Type{Dirac{T}}) where {T} = T

insupport(d::Dirac, x::Real) = x == d.value
minimum(d::Dirac) = d.value
maximum(d::Dirac) = d.value
support(d::Dirac) = (d.value,)

#### Properties
mean(d::Dirac) = d.value
var(d::Dirac{T}) where {T} = zero(T)

mode(d::Dirac) = d.value

entropy(d::Dirac{T}) where {T} = zero(T)

#### Evaluation

pdf(d::Dirac, x::Real) = insupport(d, x) ? 1.0 : 0.0
logpdf(d::Dirac, x::Real) = insupport(d, x) ? 0.0 : -Inf

cdf(d::Dirac, x::Real) = x < d.value ? 0.0 : 1.0
cdf(d::Dirac, x::Integer) = x < d.value ? 0.0 : 1.0

quantile(d::Dirac{T}, p::Real) where {T} = 0 <= p <= 1 ? d.value : T(NaN)

mgf(d::Dirac, t) = exp(t * d.value)
cf(d::Dirac, t) = cis(t * d.value)

#### Sampling

rand(rng::AbstractRNG, d::Dirac) = d.value


using Revise
	using SparseArrays, LinearAlgebra, DiffEqOperators, Setfield, Parameters
	using PseudoArcLengthContinuation
	using Plots
	const PALC = PseudoArcLengthContinuation
################################################################################
# case of the SH equation
norminf(x) = norm(x, Inf64)
Nx = 200; Lx = 30.;
X = -Lx .+ 2Lx/Nx*(0:Nx-1) |> collect
hx = X[2]-X[1]

Q = Neumann0BC(hx)
# Q = Dirichlet0BC(hx |> typeof)
Dxx = sparse(CenteredDifference(2, 2, hx, Nx) * Q)[1]
Lsh = -(I + Dxx)^2

function R_SH(u, par)
	@unpack p, b, L1 = par
	out = similar(u)
	out .= L1 * u .- p .* u .+ b .* u.^3 - u.^5
end

Jac_sp = (u, par) -> par.L1 + spdiagm(0 => -par.p .+ 3*par.b .* u.^2 .- 5 .* u.^4)

sol0 = 1.65cos.(X) .* exp.(-X.^2/(2*5^2))
	parSH = (p = 0.7, b = 2., L1 = Lsh)
	optnew = PALC.NewtonPar(verbose = true, tol = 1e-12)
	# allocations 26.47k, 0.038s, tol = 1e-10
	sol1, hist, flag = @time PALC.newton(
		R_SH, Jac_sp,
		sol0, parSH, optnew, normN = norminf)
	Plots.plot(X, sol1)

opts = PALC.ContinuationPar(dsmin = 0.0005, dsmax = 0.0055, ds = -0.001,
		newtonOptions = optnew,
		maxSteps = 1200,
		theta = .6, plotEveryNsteps = 200, computeEigenValues = true)
	br, u1 = @time PALC.continuation(R_SH, Jac_sp, sol1, parSH, (@lens _.p), opts,
		verbosity = 2,
		plot = true,
		# tangentAlgo = BorderedPred(),
		linearAlgo  = MatrixBLS(),
		plotSolution = (x, p;kwargs...)->(plot!(X, x; ylabel="solution", label="", kwargs...)), normC = norminf)
	brs = [br]
#####################################################
# case with computation of eigenvalues
# optnew = PALC.NewtonPar(linsolver = Default(),	eigsolver = eig_KrylovKit{Float64}())
plot(brs, label = "")

####################################################################################################
sol0 = 1.1cos.(X) .* exp.(-0X.^2/(2*5^2))
	optnew = PALC.NewtonPar(verbose = true, tol = 1e-12)
	# allocations 26.47k, 0.038s, tol = 1e-10
	sol1, hist, flag = @time PALC.newton(
	R_SH, Jac_sp,
	sol0, (@set parSH.p = -1.95), optnew)
	Plots.plot(X, sol1)

opts = PALC.ContinuationPar(dsmin = 0.001, dsmax = 0.005, ds = 0.001,
		newtonOptions = setproperties(optnew; maxIter = 30, tol = 1e-11), pMin = -2.,
		maxSteps = 1000, theta = .4, plotEveryNsteps = 200, computeEigenValues = true)
	@assert opts.a<=1.5 "sinon ca peut changer le sens du time step"

	br, u1 = @time PALC.continuation(
		R_SH, Jac_sp, sol1, (@set parSH.p = -1.), (@lens _.p), opts,
		verbosity = 2,
		plot = true,
		# tangentAlgo = BorderedPred(),
		# linearAlgo  = MatrixBLS(),
		plotSolution = (x, p;kwargs...)->(plot!(X, x; ylabel="solution", label="", kwargs...)))

push!(brs, br)

plot(brs)


uniform(u::Real) = 1//2 * float64(abs(u) <= 1.0)
uniform_unnormalized(u::Real) = 1//2 * float64(abs(u) <= 1.0)
Base.@vectorize_1arg Real uniform
Base.@vectorize_1arg Real uniform_unnormalized

triangular(u::Real) = (1 - abs(u)) * float64(abs(u) <= 1.0)
triangular_unnormalized(u::Real) = (1 - abs(u)) * float64(abs(u) <= 1.0)
Base.@vectorize_1arg Real triangular
Base.@vectorize_1arg Real triangular_unnormalized

epanechnikov(u::Real) = 3//4 * (1 - u^2) * float64(abs(u) <= 1.0)
epanechnikov_unnormalized(u::Real) = (1 - u^2) * float64(abs(u) <= 1.0)
Base.@vectorize_1arg Real epanechnikov
Base.@vectorize_1arg Real epanechnikov_unnormalized

biweight(u::Real) = 15//16 * (1 - u^2)^2 * float64(abs(u) <= 1.0)
biweight_unnormalized(u::Real) = (1 - u^2)^2 * float64(abs(u) <= 1.0)
Base.@vectorize_1arg Real biweight
Base.@vectorize_1arg Real biweight_unnormalized

triweight(u::Real) = 35//32 * (1 - u^2)^3 * float64(abs(u) <= 1.0)
triweight_unnormalized(u::Real) = (1 - u^2)^3 * float64(abs(u) <= 1.0)
Base.@vectorize_1arg Real triweight
Base.@vectorize_1arg Real triweight_unnormalized

tricube(u::Real) = 70//81 * (1 - abs(u)^3)^3 * float64(abs(u) <= 1.0)
tricube_unnormalized(u::Real) = (1 - abs(u)^3)^3 * float64(abs(u) <= 1.0)
Base.@vectorize_1arg Real tricube
Base.@vectorize_1arg Real tricube_unnormalized

gaussian(u::Real) = (1 / sqrt(2 * pi)) *  exp(-1//2 * u^2)
gaussian_unnormalized(u::Real) = exp(-1//2 * u^2)
Base.@vectorize_1arg Real gaussian
Base.@vectorize_1arg Real gaussian_unnormalized

cosine(u::Real) = (pi / 4) * cos((pi / 2) * u) * float64(abs(u) <= 1.0)
cosine_unnormalized(u::Real) = cos((pi / 2) * u) * float64(abs(u) <= 1.0)
Base.@vectorize_1arg Real cosine
Base.@vectorize_1arg Real cosine_unnormalized

logistic(u::Real) = 1 / (exp(u) + 2 + exp(-u))
logistic_unnormalized(u::Real) = 1 / (exp(u) + 2 + exp(-u))
Base.@vectorize_1arg Real logistic
Base.@vectorize_1arg Real logistic_unnormalized


using MAT, Plots

@views function generate_grf3D(lx=100.0, ly=100.0, lz=100.0, sf=1.0, cl=(10.0, 8.0, 5.0), k_m=100.0, nh=10000, nx=64, ny=64, nz=64;
                               cov_typ="expon", do_reset=true, do_viz=true, do_save=false)
    # Derived numerics
    dx, dy, dz  = lx/nx, ly/ny, lz/nz  # numerical grid step size
    me = 0
    co1, co2, co3 = 0, 0, 0
    # Array allocation
    Yf      = @zeros(nx, ny, nz)
    # Visu init
    if do_viz
        y_sl    = Int(ceil(ny/2))
        X, Y, Z = -lx/2:dx:lx/2, -ly/2:dy:ly/2, -lz/2:dz:lz/2
    end

    if cov_typ=="expon"
        # Generate the 3D exponential covariance function
        wtime_it = grf3D_expon!(Yf, sf, cl, nh, nx, ny, nz, dx, dy, dz; me, co1, co2, co3, do_reset)
    elseif cov_typ=="gauss"
        # Generate the 3D Gaussian covariance function
        wtime_it = grf3D_gauss!(Yf, sf, cl[1]/2.0, nh, k_m, nx, ny, nz, dx, dy, dz; me, co1, co2, co3, do_reset)
    else
        error("Undefined covariance function.")
    end

    # Performance
    A_eff    = 2/1e9*nx*ny*nz*sizeof(Data.Number)  # Effective main memory access per iteration [GB] (Lower bound of required memory access: H and dHdτ have to be read and written (dHdτ for damping): 4 whole-array memaccess; B has to be read: 1 whole-array memaccess)
    T_eff    = A_eff/wtime_it                      # Effective memory throughput [GB/s]
    if (me==0) @printf("T_eff = %1.2f GB/s \n", round(T_eff, sigdigits=2)) end

    # Visualisation
    if do_viz
        display(heatmap(X, Z, Array(Yf)[:,y_sl,:]', aspect_ratio=1, xlims=(X[1],X[end]), ylims=(Z[1],Z[end]), c=:hot, title="3D RandomField (y-slice)"))
    end
    if do_save  file = matopen("grf3D_$(cov_typ).mat", "w"); write(file, "grf3D", Array(Yf)); close(file)  end

    return Yf
end


using GNSSTools
using JLD
using PyPlot
pygui(true)

GM = 3.986004418e14  # m³s⁻²
eop = get_eop();

# N_rand = 25
# lats = rand(-90:0.001:90, N_rand)  # degrees
# longs = rand(-180:0.001:180, N_rand)  # degrees
# heights = fill(100, N_rand)  # meters
# llas = (lats, longs, heights)
# user_lla = llas
freq = L1_freq
# user_lla = (40.01, -105.2437, 1655);

file_name = "/home/sjbilardi/Dropbox/Apps/Overleaf/MastersThesis/ch1_doppler_curves_user_lla.jld"
# file_name = "ch1_doppler_curves_user_lla.jld"
user_lla = load(file_name, "user_lla")

Δt = 1  # seconds
total_time = 1*24*60*60  # seconds
t_range = Array(0:Δt:total_time);
min_elevation = 10  # degrees

# GPS
a = 26_560e3;  # m
plane_num = 6;
sat_per_plane = 4;
incl = 56;
ΔΩ = 360/plane_num;
Δf_per_plane = 360/sat_per_plane/2;  # degrees
orbital_period = 2π*sqrt(a^3 / GM)  # seconds
doppler_gps, doppler_rate_gps, doppler_bounds_gps,
doppler_rate_bounds_gps, raw_dopplers_gps = doppler_distribution(a, plane_num,
                                           sat_per_plane, incl,
                                           t_range, user_lla,
                                           freq, show_plot=false,
                                           print_steps=true,
                                           eop=eop,
                                           min_elevation=min_elevation,
                                           ΔΩ=ΔΩ,
                                           Δf_per_plane=Δf_per_plane,
										   return_constellation=false,
                                           return_raw_dopplers=true);
doppler_raw_gps, doppler_rate_raw_gps,
elevations_gps, max_elevations_gps = raw_dopplers_gps

# Iridium
a = Rₑ+780*1000;  # meters
plane_num = 6;
sat_per_plane = 11;
incl = 86;
ΔΩ = 360/plane_num/2;
Δf_per_plane = 360/sat_per_plane/2;  # degrees
orbital_period = 2π*sqrt(a^3 / GM)  # seconds
doppler_iridium, doppler_rate_iridium, doppler_bounds_iridium,
doppler_rate_bounds_iridium, raw_dopplers_iridium = doppler_distribution(a, plane_num,
                                            sat_per_plane, incl,
                                            t_range, user_lla,
                                            freq, show_plot=false,
                                            print_steps=true,
                                            eop=eop,
                                            ΔΩ=ΔΩ,
                                            Δf_per_plane=Δf_per_plane,
                                            return_raw_dopplers=true);
doppler_raw_iridium, doppler_rate_raw_iridium,
elevations_iridium, max_elevations_iridium = raw_dopplers_iridium

# Starlink
a = Rₑ+550*1000;  # meters
plane_num = 24;
sat_per_plane = 66;
incl = 53;
ΔΩ = 360/plane_num;
Δf_per_plane = 360/sat_per_plane/2;  # degrees
orbital_period = 2π*sqrt(a^3 / GM)  # seconds
doppler_starlink, doppler_rate_starlink, doppler_bounds_starlink,
doppler_rate_bounds_starlink, raw_dopplers_starlink = doppler_distribution(a, plane_num,
                                            sat_per_plane, incl,
                                            t_range, user_lla,
                                            freq, show_plot=false,
                                            print_steps=true,
                                            eop=eop,
                                            ΔΩ=ΔΩ,
                                            Δf_per_plane=Δf_per_plane,
                                            return_raw_dopplers=true);
doppler_raw_starlink, doppler_rate_raw_starlink,
elevations_starlink, max_elevations_starlink = raw_dopplers_starlink

# OneWeb
a = Rₑ+1200*1000;  # meters
plane_num = 18;
sat_per_plane = 36;
incl = 87.9;  # degrees
ΔΩ = 360/plane_num/2;
Δf_per_plane = 360/sat_per_plane/2;  # degrees
orbital_period = 2π*sqrt(a^3 / GM)  # seconds
doppler_oneweb, doppler_rate_oneweb, doppler_bounds_oneweb,
doppler_rate_bounds_oneweb, raw_dopplers_oneweb = doppler_distribution(a, plane_num,
                                            sat_per_plane, incl,
                                            t_range, user_lla,
                                            freq, show_plot=false,
                                            print_steps=true,
                                            eop=eop,
                                            ΔΩ=ΔΩ,
                                            Δf_per_plane=Δf_per_plane,
                                            return_raw_dopplers=true);
doppler_raw_oneweb, doppler_rate_raw_oneweb,
elevations_oneweb, max_elevations_oneweb = raw_dopplers_oneweb

save("ch1_doppler_curves.jld",
     "doppler_gps", doppler_gps,
	 "doppler_iridium", doppler_iridium,
	 "doppler_starlink", doppler_starlink,
	 "doppler_oneweb", doppler_oneweb,
	 "doppler_rate_gps", doppler_rate_gps,
	 "doppler_rate_iridium", doppler_rate_iridium,
	 "doppler_rate_starlink", doppler_rate_starlink,
	 "doppler_rate_oneweb", doppler_rate_oneweb,
	 "doppler_bounds_gps", doppler_bounds_gps,
	 "doppler_bounds_iridium", doppler_bounds_iridium,
	 "doppler_bounds_starlink", doppler_bounds_starlink,
	 "doppler_bounds_oneweb", doppler_bounds_oneweb,
	 "doppler_raw_gps", doppler_raw_gps,
	 "doppler_raw_iridium", doppler_raw_iridium,
	 "doppler_raw_starlink", doppler_raw_starlink,
	 "doppler_raw_oneweb", doppler_raw_oneweb,
	 "doppler_rate_raw_gps", doppler_rate_raw_gps,
	 "doppler_rate_raw_iridium", doppler_rate_raw_iridium,
	 "doppler_rate_raw_starlink", doppler_rate_raw_starlink,
	 "doppler_rate_raw_oneweb", doppler_rate_raw_oneweb,
	 "freq", freq,
	 "user_lla", user_lla,
	 "t_range", t_range,
	 "min_elevation", min_elevation,
	 "elevations_gps", elevations_gps,
	 "elevations_iridium", elevations_iridium,
	 "elevations_starlink", elevations_starlink,
	 "elevations_oneweb", elevations_oneweb,
	 "max_elevations_gps", max_elevations_gps,
	 "max_elevations_iridium", max_elevations_iridium,
	 "max_elevations_starlink", max_elevations_starlink,
	 "max_elevations_oneweb", max_elevations_oneweb)

# file_name = "/home/sjbilardi/Dropbox/Apps/Overleaf/MastersThesis/ch1_doppler_curves.jld"
# data = load(file_name,
#             "doppler_gps",
# 	       "doppler_iridium",
# 	       "doppler_starlink",
# 	       "doppler_oneweb",
# 	       "doppler_rate_gps",
# 	       "doppler_rate_iridium",
# 	       "doppler_rate_starlink",
# 	       "doppler_rate_oneweb",
# 	       "doppler_bounds_gps",
# 	       "doppler_bounds_iridium",
# 	       "doppler_bounds_starlink",
# 	       "doppler_bounds_oneweb",
# 	       "doppler_raw_gps",
# 	       "doppler_raw_iridium",
# 	       "doppler_raw_starlink",
# 	       "doppler_raw_oneweb",
# 	       "doppler_rate_raw_gps",
# 	       "doppler_rate_raw_iridium",
# 	       "doppler_rate_raw_starlink",
# 	       "doppler_rate_raw_oneweb",
# 	       "freq",
# 	       "user_lla",
# 	       "t_range",
# 	       "min_elevation")

# doppler_gps, doppler_iridium, doppler_starlink, doppler_oneweb,
# doppler_rate_gps, doppler_rate_iridium, doppler_rate_starlink, doppler_rate_oneweb,
# doppler_bounds_gps, doppler_bounds_iridium, doppler_bounds_starlink, doppler_bounds_oneweb,
# doppler_raw_gps, doppler_raw_iridium, doppler_raw_starlink, doppler_raw_oneweb,
# doppler_rate_raw_gps, doppler_rate_raw_iridium, doppler_rate_raw_starlink, doppler_rate_raw_oneweb,
# freq, user_lla, t_range, min_elevation = data

# # Make plots
# fig = figure(figsize=(8.5,9))
# # GPS
# ax1 = fig.add_subplot(4,2,1)
# ax1.hist(Array(doppler_gps.edges[1])[2:end]./1000,
#          Array(doppler_gps.edges[1])./1000, weights=doppler_gps.weights,
#          color="k");
# xlabel("Doppler (kHz)")
# ylabel("Probability")
# title("GPS")
# ax2 = fig.add_subplot(4,2,2)
# ax2.hist(Array(doppler_rate_gps.edges[1])[2:end],
#          Array(doppler_rate_gps.edges[1]), weights=doppler_rate_gps.weights,
#          color="k");
# xlabel("Doppler Rate (Hz/s)")
# ylabel("Probability")
# title("GPS")
# # Iridium
# ax3 = fig.add_subplot(4,2,3)
# ax3.hist(Array(doppler_iridium.edges[1])[2:end]./1000,
#          Array(doppler_iridium.edges[1])./1000, weights=doppler_iridium.weights,
#          color="k");
# xlabel("Doppler (kHz)")
# ylabel("Probability")
# title("Iridium")
# ax4 = fig.add_subplot(4,2,4)
# ax4.hist(Array(doppler_rate_iridium.edges[1])[2:end],
#          Array(doppler_rate_iridium.edges[1]), weights=doppler_rate_iridium.weights,
#          color="k");
# xlabel("Doppler Rate (Hz/s)")
# ylabel("Probability")
# title("Iridium")
# # Starlink
# ax5 = fig.add_subplot(4,2,5)
# ax5.hist(Array(doppler_starlink.edges[1])[2:end]./1000,
#          Array(doppler_starlink.edges[1])./1000, weights=doppler_starlink.weights,
#          color="k");
# xlabel("Doppler (kHz)")
# ylabel("Probability")
# title("Starlink")
# ax6 = fig.add_subplot(4,2,6)
# ax6.hist(Array(doppler_rate_starlink.edges[1])[2:end],
#          Array(doppler_rate_starlink.edges[1]), weights=doppler_rate_starlink.weights,
#          color="k");
# xlabel("Doppler Rate (Hz/s)")
# ylabel("Probability")
# title("Starlink")
# # OneWeb
# ax7 = fig.add_subplot(4,2,7)
# ax7.hist(Array(doppler_oneweb.edges[1])[2:end]./1000,
#          Array(doppler_oneweb.edges[1])./1000, weights=doppler_oneweb.weights,
#          color="k");
# xlabel("Doppler (kHz)")
# ylabel("Probability")
# title("OneWeb")
# ax8 = fig.add_subplot(4,2,8)
# ax8.hist(Array(doppler_rate_oneweb.edges[1])[2:end],
#          Array(doppler_rate_oneweb.edges[1]), weights=doppler_rate_oneweb.weights,
#          color="k");
# xlabel("Doppler Rate (Hz/s)")
# ylabel("Probability")
# title("OneWeb")

# subplots_adjust(hspace=0.7, wspace=0.35, top=0.93, left=0.1, right=0.9)
# savefig("/home/sjbilardi/Dropbox/Apps/Overleaf/MastersThesis/figures/ch1_doppler_distributions.svg", dpi=300)

# using PyCall
# basemap = pyimport("mpl_toolkits.basemap")
# # lon_0 is central longitude of projection.
# # resolution = 'c' means use crude resolution coastlines.
# fig = figure(figsize=(7.5, 2.5))
# ax1 = fig.add_subplot(1,1,1)
# m = basemap.Basemap(projection="robin", lon_0=0, resolution="c")
# m.drawcoastlines(color="black", linewidth=0.25)
# m.fillcontinents(color="#69b2a2",lake_color="#A6CAE0")
# # draw parallels and meridians.
# m.drawparallels(range(-90., 120., step=30.), linewidth=0.5, color="dimgrey")
# m.drawmeridians(range(0., 360., step=60.), linewidth=0.5, color="dimgrey")
# m.drawmapboundary(fill_color="#A6CAE0")
# # m.fillcontinents(color="grey", alpha=0.3)
# color = "tomato"
# marker = "."
# markersize = 5
# N = min(length(user_lla[1]), length(user_lla[2]), length(user_lla[3]))
# for i in 1:N
# 	lat = user_lla[1][i]
# 	lon	= user_lla[2][i]
# 	xpt, ypt = m(lon,lat)
# 	m.plot(xpt, ypt, color=color, marker=marker, markersize=markersize)
# end
# # title("$N Random Observation Locations")
# subplots_adjust(bottom=0.01, left=0.1, right=0.9, top=0.99)
# savefig("/home/sjbilardi/Dropbox/Apps/Overleaf/MastersThesis/figures/ch1_doppler_curves_random_location_map.pdf",
#         dpi=300)


using SpecialFunctions

"""
    G(x::Real)

Computes the second synchrotron function at a given frequency ratio ``x = \\frac{\\nu}{\\nu_0}``.

``G(x) = x K_{\\frac{2}{3}}(x)``
"""
G(x::Real) = x * besselk(2/3, x)

"""
    𝒢(x::Real)

Computes the second synchrotron function at a given frequency ratio ``x = \\frac{\\nu}{\\nu_0}``.

``G(x) = x K_{\\frac{2}{3}}(x)``

Wrapper for [`G`](@ref).
"""
𝒢(x::Real) = G(x)


function sphcesaro1(A::AbstractMatrix{T}) where T
    M, N = size(A)
    m, n = M, N÷2
    C = zeros(T, m, 2n+1)
    @inbounds for i = 1:m
        C[i,1] = (m+one(T)-i)/m
    end
    @inbounds for j = 1:n
        for i = 1:m-j
            C[i,2j] = C[i+j,1]
            C[i,2j+1] = C[i+j,1]
        end
    end
    C
end

function sphcesaro2(A::AbstractMatrix{T}) where T
    M, N = size(A)
    m, n = M, N÷2
    C = zeros(T, m, 2n+1)
    @inbounds for i = 1:m
        C[i,1] = (m+2one(T)-i)*(m+one(T)-i)/((m+one(T))*m)
    end
    @inbounds for j = 1:n
        for i = 1:m-j
            C[i,2j] = C[i+j,1]
            C[i,2j+1] = C[i+j,1]
        end
    end
    C
end

function sphcesaro(A::AbstractMatrix{T}, κ::Int) where T
    M, N = size(A)
    m, n = M, N÷2
    C = zeros(T, m, 2n+1)
    @inbounds for i = 1:m
        C[i,1] = binomial(m+κ-i, m-i)/binomial(m+κ-1, m-1)
    end
    @inbounds for j = 1:n
        for i = 1:m-j
            C[i,2j] = C[i+j,1]
            C[i,2j+1] = C[i+j,1]
        end
    end
    C
end


# Given the row and column indices of the lower left
# vertex, add the location where the contour level
# crosses the specified edge.
function interpolate(x, y, z::AbstractMatrix, h::Number, ind, edge::UInt8, ::Type{VT}) where {VT}
    xi, yi = ind
    @inbounds if edge == W
        y_interp = y[yi] + (y[yi + 1] - y[yi]) * (h - z[xi, yi]) / (z[xi, yi + 1] - z[xi, yi])
        x_interp = x[xi]
    elseif edge == E
        y_interp = y[yi] + (y[yi + 1] - y[yi]) * (h - z[xi + 1, yi]) / (z[xi + 1, yi + 1] - z[xi + 1, yi])
        x_interp = x[xi + 1]
    elseif edge == N
        y_interp = y[yi + 1]
        x_interp = x[xi] + (x[xi + 1] - x[xi]) * (h - z[xi, yi + 1]) / (z[xi + 1, yi + 1] - z[xi, yi + 1])
    elseif edge == S
        y_interp = y[yi]
        x_interp = x[xi] + (x[xi + 1] - x[xi]) * (h - z[xi, yi]) / (z[xi + 1, yi] - z[xi, yi])
    end

    return VT(x_interp, y_interp)
end

function interpolate(x::AbstractRange, y::AbstractRange, z::AbstractMatrix, h::Number, ind, edge::UInt8, ::Type{VT}) where {VT}
    xi, yi = ind
    @inbounds if edge == W
        y_interp = y[yi] + step(y) * (h - z[xi, yi]) / (z[xi, yi + 1] - z[xi, yi])
        x_interp = x[xi]
    elseif edge == E
        y_interp = y[yi] + step(y) * (h - z[xi + 1, yi]) / (z[xi + 1, yi + 1] - z[xi + 1, yi])
        x_interp = x[xi + 1]
    elseif edge == N
        y_interp = y[yi + 1]
        x_interp = x[xi] + step(x) * (h - z[xi, yi + 1]) / (z[xi + 1, yi + 1] - z[xi, yi + 1])
    elseif edge == S
        y_interp = y[yi]
        x_interp = x[xi] + step(x) * (h - z[xi, yi]) / (z[xi + 1, yi] - z[xi, yi])
    end

    return VT(x_interp, y_interp)
end

function interpolate(x::AbstractMatrix, y::AbstractMatrix, z::AbstractMatrix, h::Number, ind, edge::UInt8, ::Type{VT}) where {VT}
    xi, yi = ind
    @inbounds if edge == W
        Δ = [y[xi,  yi+1] - y[xi,  yi  ], x[xi,  yi+1] - x[xi,  yi  ]].*(h - z[xi,  yi  ])/(z[xi,  yi+1] - z[xi,  yi  ])
        y_interp = y[xi,yi] + Δ[1]
        x_interp = x[xi,yi] + Δ[2]
    elseif edge == E
        Δ = [y[xi+1,yi+1] - y[xi+1,yi  ], x[xi+1,yi+1] - x[xi+1,yi  ]].*(h - z[xi+1,yi  ])/(z[xi+1,yi+1] - z[xi+1,yi  ])
        y_interp = y[xi+1,yi] + Δ[1]
        x_interp = x[xi+1,yi] + Δ[2]
    elseif edge == N
        Δ = [y[xi+1,yi+1] - y[xi,  yi+1], x[xi+1,yi+1] - x[xi,  yi+1]].*(h - z[xi,  yi+1])/(z[xi+1,yi+1] - z[xi,  yi+1])
        y_interp = y[xi,yi+1] + Δ[1]
        x_interp = x[xi,yi+1] + Δ[2]
    elseif edge == S
        Δ = [y[xi+1,yi  ] - y[xi,  yi  ], x[xi+1,yi  ] - x[xi,  yi  ]].*(h - z[xi,  yi  ])/(z[xi+1,yi  ] - z[xi,  yi  ])
        y_interp = y[xi,yi] + Δ[1]
        x_interp = x[xi,yi] + Δ[2]
    end

    return VT(x_interp, y_interp)
end


function quadgk_PV(f, a, c, b; kwargs...)
    if b > c > a
        fc = f(c)
        g(x) = 2 * x * (-c^2 + x^2)^-1 * (f(x) - fc)
        return quadgk(g, a, prevfloat(c); kwargs...)[1] +
               quadgk(g, nextfloat(c), b; kwargs...)[1] +
               fc * log(abs((b^2 - c^2) / (a^2 - c^2)))
    else
        return quadgk(x -> 2 * x * (-c^2 + x^2)^-1 * f(x), a, b; kwargs...)[1]
    end
end

function quadgk_PV2(f, a, c, b; initdiv=1,kwargs...)
    fc = f(c)
    function g(x)
        if x == c
            2 * x * (-nextfloat(c)^2 + x^2)^-1 * (f(x) - fc)
        else
            return 2 * x * (-c^2 + x^2)^-1 * (f(x) - fc)
        end
    end
    return quadgk(g, a, b; order=ceil(Int64,initdiv/5),kwargs...)[1] + fc * log(abs((b^2 - c^2) / (a^2 - c^2)))
end

function quadgk_PV3(f, a, c, b;initdiv=1,kwargs...)
    if a < c < b
        fc = f(c)
        function g(x)
            if x == c
                2 * x * (-nextfloat(c)^2 + x^2)^-1 * (f(x) - fc)
            else
                return 2 * x * (-c^2 + x^2)^-1 * (f(x) - fc)
            end
        end
        return quadgk(g, a, b; order=ceil(Int64,initdiv/5),kwargs...)[1]
        # + fc * log(abs((b^2 - c^2) / (a^2 - c^2)))
    else
        fc2 = f(nextfloat(a))
        function g2(x)
            if x == c
                2 * x * (-nextfloat(c)^2 + x^2)^-1 * (f(x) - fc2)
            else
                return 2 * x * (-c^2 + x^2)^-1 * (f(x) - fc2)
            end
        end
        return quadgk(g2, a, b; order=ceil(Int64,initdiv/5),kwargs...)[1]
        # + fc * log(abs((b^2 - c^2) / (a^2 - c^2)))
    end
end

function hquadrature_PV(f, a, c, b; kwargs...)
    fc = f(c)
    g(x) = 2 * x * (-c^2 + x^2)^-1 * (f(x) - fc)
    return hquadrature(g, a, prevfloat(c); kwargs...)[1] +
           hquadrature(g, nextfloat(c), b; kwargs...)[1] +
           fc * log(abs((b^2 - c^2) / (a^2 - c^2)))
end

# function quadgk_PV2(f, c; kwargs...)
#     g(x) = (2*f(x))/(x)
#     return quadgk(g, 0.0, Inf; kwargs...)[1]
# end

# quadgk_PV(x->sin(x),10.0,15.0,20.0,atol=0.0,rtol=1e-4)
# PvdkF1Tildeps(p0, k, kprim, m, T)


@doc """
Calculates the stationarity KPSS test statistic of Nyblom (1989) and Kwiatkowski et all (1992).

```math
 y_t = \\xi_t + r_t + \\epsilon_t
```
```math
 r_t = r_{t-1} + u_t, \\quad u_t\\sim\\mathcal{N}(0, \\sigma^2_u)
```

The KPSS test has a LM-type statistic given by

```math
  LM = \\frac{\\sum^T_t S_t}{\\epsilon'\\epsilon},
```

where Sₜ is the partial sum of resids up to time t

```math
  S_t = \\sum^t_{j=1} \\epsilon_j
```

Required Input Parameters
----------------------------------
`y::Vector{Float64}`: Observed time series in vector form.

Optional Input Parameters
----------------------------------

`trend::String`: Deterministic trend specification. It determines the asymptotic distribution of the t-statistic. Possible values are `"constant"`, which considers a constant deterministic term; amd `"linear"`, which also considers a linear time trend.

Output
----------------------------------
`kpss_stat::Float64`: KPSS test statistic.

`kpss_pval::Float64`: p-value obtained from 10000 simulations.

```@example 1
  series = simul_arma()
  kpss_test(series)
```

"""->
function kpss_test(y::Vector{Float64}; trend::String="constant")
  T = size(y,1)
  if trend=="constant"
    x = ones(T)
    println("constant deteriministic detrending.")
  elseif trend=="linear"
    x = [ones(T) collect(1:T)]
    println("constant plus linear deteriministic detrending.")
  else
    error(" trend must be either, constant, or linear")
  end
  β, t_stat, resid = kpss_ols(y, x[:,:])
  S = cumsum(resid, 1)
  nominator = sum(S.^2)/(T^2)
  denominator = resid'resid/T
  kpss_stat = nominator./denominator
  kpss_pval = mean(kpss_stat.<kpss_dist(T, trend=trend))
  return kpss_stat[1], kpss_pval
end
#
#
function wiener2(nobs)
    e0 = rand(Normal(), nobs);
    x = ones(nobs,1);
    e1 = e0 - mean(e0);
    b = inv(x'x)*(x'e0);
    e2 = e0-b.*e0;
    y1 = cumsum(e1);
    y2 = cumsum(e2);
    intW2_1 = nobs^(-2.)*sum(y1.^2, 1);
    intW2_2 = nobs^(-2.)*sum(y2.^2, 1);
    return intW2_1, intW2_2
end

function kpss_dist(T; trend="constant")
  nsim = 10000
  kpss = Vector{Float64}(nsim)
  for isim ∈ 1:nsim
    c_tmp, trnd_tmp = wiener2(T)
    (trend=="constant") && (kpss[isim, :] = c_tmp)
    (trend=="linear") && (kpss[isim, :] = trnd_tmp)
  end
  return kpss
end

function kpss_ols(y::Array{Float64,}, x::Array{Float64,})
  T, k = size(x)
  β = inv(x'x)*(x'y)
  ϵ = y - x*β
  yhat = x*β
  sse = ϵ'ϵ
  σ2_y = sse/(T-k)
  Σₓ = inv(x'x)
  Σᵦ = σ2_y.*Σₓ
  t_stat = β./sqrt.(diag(Σᵦ))
  bic = (T-k).*log(sse./(T-k)) .+ k.*log(T-k)
  resid = y - x*β
  return β, t_stat, resid
end

#kpss_test(randn(200))


module ConfidenceIntervals

using Distributions

export binomialCI

# TODO Clopper-Pearson
# TODO Convert to string documentation
# TODO Multinomial CI calculation

## Binomial proportion confidence interval bounds calculation
# Variables
#   - X (int)             = successes
#   - n (int)             = trial
#   - method              = CI calculation method
#   - confidence (float)  = confidence for the interval, 1-α
# Returns
#   lower bound, upper bound
function binomialCI(X::Int, n::Int, method=:wilson, confidence::AbstractFloat=0.95)
  ## Variable checks
  0 <= X || error("Successes must be positive")
  X < n || error("Trials must be larger than successes")
  0 <= confidence <= 1 || error("Confidence must lie between 0 and 1. Typically 0.95")

  ## Method selection
  if method == :agrestiCoull
    return agrestiCoull_CI(X, n, confidence)
  elseif method == :arcsineTransformation
    return arcsineTransformation_CI(X, n, confidence)
  elseif method == :jeffreys
    return jeffreys_CI(X, n, confidence)
  elseif method == :wald
    return wald_CI(X, n, confidence)
  elseif method == :wilson
    return wilson_CI(X, n, confidence)
  elseif method == :wilsonCC
    return wilsonCC_CI(X, n, confidence)
  else
    error("No method found with such name.")
  end
end

function zCalc(confidence)
  return quantile(Normal(), 1-(1-confidence)/2);
end

function agrestiCoull_CI(X, n, confidence)
  ## Variable precalculations
  z = zCalc(confidence)
  m = n+z^2
  μ = (X+z^2/2)/m

  ## Bounds calculation
  σ = sqrt(q*(1-q)/m)
  lower = μ-z*σ
  upper = μ+z*σ

  return lower, upper
end

function arcsineTransformation_CI(X, n, confidence)
  ## Variable precalculations
  p = X/n
  z = zCalc(confidence)

  ## Bounds calculation
  s = z/(2*sqrt(n))
  lower = sin(asin(sqrt(p))-s)^2
  upper = sin(asin(sqrt(p))+s)^2

  return lower, upper
end

function jeffreys_CI(X, n, confidence)
  α = 1-confidence
  lower = quantile(Beta(x+1/2, n-X+1/2), α/2)
  upper = quantile(Beta(x+1/2, n-X+1/2), 1-α/2)
  return lower, upper
end

# Normal approximation
#   - Extremely conservative
function wald_CI(X, n, confidence)
  ## Variable precalculations
  p = X/n
  z = zCalc(confidence)

  ## Bounds calculation
  A = z*sqrt(p*(1-p)/n)
  lower = p-A
  upper = p+A

  return lower, upper
end

# Calculates the Wilson interval bounds, as developed by Edwin Bidwell Wilson (1927, JSTOR 2276774).
#   - Improvement over the normal approximation interval
#   - Good for even small number of trials
#   - Not equal-tailed (systemic bias to centre)
function wilson_CI(X, n, confidence)
  ## Variable precalculations
  neg = n-X
  p = X/n
  z = zCalc(confidence)

  ## Bounds calculation
  #TODO convert to p-X formulas
  A = z*sqrt(X*neg/n+(z^2)/4)
  B = n+z^2
  lower = (X+(z^2)/2-A)/B
  upper = (X+(z^2)/2+A)/B

  return lower, upper
end

# Continuity correction derived from Newcombe (1998, PMID 9595616).
function wilsonCC_CI(X, n, confidence)
  ## Variable precalculations
  p = X/n
  z = zCalc(confidence)

  ## Bounds calculation
  A = 2*n*p+z^2
  B = (z^2)/n+4*n*p*(1-p)
  C = 4*p-2
  D = 2*(n+z^2)
  # Lower bound calculation
  if p == 0
    lower = 0
  else
    lower = maximum([0.0 A-(z*sqrt(B+C)+1)/D])
  end

  # Upper bound calculation
  if p == 1
    upper = 1
  else
    upper = minimum([1.0 A+(z*sqrt(B-C)+1)/D]);
  end

  return lower, upper
end

end


using SpecialFunctions

"""
    mc_volume(i, xs, nmc, searcher)

Estimate the area and volume of the `i`-th Voronoi cell from the Voronoi Diagram generated by `xs`
by a Monte Carlo estimate from random rays
"""
function mc_volume(i, xs, nmc=1000, searcher = Raycast(xs))
    y, δy, V, A = mc_integrate(x->1, i, xs, nmc, 0, searcher)
    A, V
end

"""
    mc_volumes(xs::Points, nmc=1000)

Estimate the areas and volumes of the Voronoi Cells generated by `xs` using `nmc` Monte Carlo samples.

# Returns
- `SparseMatrix`: the areas of the common boundaries of two cells
- `Vector`: the volumes of each cell
"""
function mc_volumes(xs::Points, nmc=1000)
    V = zeros(length(xs))
    A = spzeros(length(xs), length(xs))

    searcher = Raycast(xs)

    for i in 1:length(xs)
        a, v = mc_volume(i, xs, nmc, searcher)
        V[i] = v
        A[:, i] = a
    end
    #A = (A + A') / 2
    A, V
end

"""
    mc_volumes(sig::Vertices, xs::Points, nmc=1000)

In the case when the simplicial complex is already known this information can be used
to speed up the Monte-Carlo sampling by restricting the search space
"""
function mc_volumes(σ::Vertices, xs::Points, nmc=1000)
    V = zeros(length(xs))
    A = spzeros(length(xs), length(xs))

    searcher = Raycast(xs)
    neigh = neighbors(σ)

    for i in 1:length(xs)
        ix = [i; neigh[i]]
        a, v = mc_volume(1, xs[ix], nmc, RaycastBruteforce())
        V[i] = v
        A[ix, i] = a
    end
    #A = (A + A') / 2
    A, V
end

"""
    neighbors(sig::Vertices) --> Dict{Int, Vector{Int}}
Compute the neighbors of all generators.
"""
neighbors(σ::Vertices) = neighbors(keys(σ))
function neighbors(σ)
    d = Dict{Int, Vector{Int}}()
    for s in σ
        for i in s
            v = get!(d, i, Vector{Int}())
            append!(v, s)
        end
    end
    for i in keys(d)
        unique!(d[i])
        filter!(x->x != i, d[i])
    end
    return d
end

"""
    mc_integrate(f::Function, i::Int, xs::Points, nmc=1000, nmc2=1000, searcher=Raycast(xs))
Integrate function `f` over cell `i` and its boundary using `nmc` rays per cell and `nmc2` points per ray for the volume integral.

# Returns:
- `Vf::Real`: the volume integral of `f`
- `Af::SparseVector`: Af[j] is the surface integral of `f` over the intersection between cells i and j.
- `V::Real`: the volume of cell `i`
- `A::SparseVector`: A[j] is the surface area of the intersection between cells i and j.
"""
function mc_integrate(f::Function, i::Int, xs::Vector, nmc=1000, nmc2=1000, searcher = Raycast(xs))
    x = xs[i]
    d = length(x)

    # volume/area computations are basically for free, leaving them commented for now
    V = 0.
    A = spzeros(length(xs))

    y = 0.
    δy = spzeros(length(xs))

    for _ in 1:nmc
        u = normalize(randn(d))
        (j, t) = raycast([i], x, u, xs, searcher)

        V += t^d
        for _ in 1:nmc2
            r = t * rand()
            x′ = x + u * r
            y += f(x′) * r^(d-1) * t
        end

        if t < Inf
            j = j[1] == i ? j[2] : j[1]
            normal = normalize(xs[j] - xs[i])
            dA = t ^ (d-1) / abs(dot(normal, u))
            A[j] += dA
            δy[j] += dA * f(x + t*u)
        end
    end

    c_vol = pi^(d/2) / gamma(d/2 + 1)
    c_area = d * c_vol

    V *= c_vol / nmc
    A *= c_area / nmc

    y *= c_area / nmc / nmc2
    δy *= c_area / nmc

    y, δy, V, A
end

function mc_integrate(f, σ::Vertices, xs::Points, nmc, nmc2)
    return mc_integrate_i((x,i)->f(x), σ, xs, nmc, nmc2)
end

# MC integration using f(x, i) function which is also passed the index of the cell
function mc_integrate_i(f, σ::Vertices, xs::Points, nmc, nmc2)
    n = length(xs)
    ys = zeros(n)
    ∂ys = zeros(n)

    neigh = neighbors(σ)

    for i in 1:n
        ix = [i; neigh[i]]
        y, ∂y, V, A = mc_integrate(x->f(x, i), 1, xs[ix], nmc, nmc2, RaycastBruteforce())
        ys[i] = y
        ∂ys[i] = sum(∂y)
    end
    return ys, ∂ys
end