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

# ## Learning with a single neuron
#
# In this notebook, we'll build a neuron that classifies an image as an apple or as a banana using multiple features from the image. We'll **train** our neuron using data from many images that have already been correctly categorised; our neuron will thereby **learn** what parameters to use, by minimizing a loss function using gradient descent.
#
# We'll do this with almost the simplest possible neuron, namely one that takes just two inputs:

include(datapath("scripts/draw_neural_net.jl"))
number_inputs, number_neurons = 2, 1
draw_network([number_inputs, number_neurons])

# ### Roadmap
#
# To use a neuron with two inputs, we will use just two of the data (numbers) for each image, say columns 3 and 4, the mean amount of red and the mean amount of green; each data point will then be a 2-dimensional vector, and the data points lie on a two-dimensional plane. We will have many input data, labelled with an index $i$. We will denote the $i$th data point as  $\mathbf{x}^{(i)}$.
#
# The goal is that our neuron will take a single point on the two-dimensional plane as its input, and should return a single output that **classifies** it as an apple ($0$) or a banana ($1$).
#
# To do so, it must "**learn**" the correct values of its parameters $\mathbf{w}$ and $b$. For this learning to take place, we'll need **labels** for each of the input data, which identify them as either an apple (0) or as a banana (1).
#
# These labels will, in turn, allow us to create a loss function, which will allow our algorithm to learn to determine if a given choice of parameters does a good or a poor job of classifying our fruit images.
#
# **So what do we have left to do?**
#
# The above might sound complex, but luckily we can break it down into a series of actionable steps:
#
# 1. Clean our input data (amounts of red and green) to get it into a usable format;
# 2. Create a sequence of labels that we can use to identify correct and incorrect classifications;
# 3. Define a loss function that contains parameters;
# 4. Implement an algorithm to pick parameters for our neuron by minimizing the loss function with respect to the parameters;
# 5. Use all of the above to train our neuron how to classify images as apples or bananas!

#-

# #### Note:
#
# Note that *in general we cannot expect that a single neuron will be adequate for classification.*
#
# If a single neuron struggles to classify our images, we may need to use a more complicated neural network structure (which corresponds to using a more complicated function).

#-

# To do this, we need to work with and *clean* some real data. Let's get started!

#-

# ## Loading in some data

#-

# Let's load in some real data! We'll use data that we have prepared from photos of apples and bananas; it turns out to be stored on disk in data files as "tab-separated values". We can read this data in with the `CSV.jl` package, as follows.

#nb ;head $(datapath("data/Apple_Golden_1.dat"))
#jl run(`head $(datapath("data/Apple_Golden_1.dat"))`)

#-

#nb ;head $(datapath("data/bananas.dat"))
#jl run(`head $(datapath("data/bananas.dat"))`)

#-

using CSV, DataFrames

apples = DataFrame(CSV.File(datapath("data/Apple_Golden_1.dat"), delim='\t', allowmissing=:none, normalizenames=true))
bananas = DataFrame(CSV.File(datapath("data/bananas.dat"), delim='\t', allowmissing=:none, normalizenames=true));

# CSV.jl defaults to creating a `DataFrame` from DataFrames.jl to store the information:

apples

#-

typeof(apples)

#-

names(apples)

# Each of the two data sets is stored in a `DataFrame` (from the `DataFrames.jl` package).

#-

# ## Cleaning the data

#-

# Usually it will be necessary to "clean" the data in some way, i.e. pre-process it, before it can be used for whichever task you are interested in.

#-

# Our next *meta*-exercise will be to collect all the data from columns 3 and 4 into a *single* Julia vector `x` (of which each entry is itself a vector), and the labels into a single vector `y`. Let's do this in a series of steps!

#-

# #### Exercise 1
#
# First, let's practice grabbing a single column of a `DataFrame`. To grab a column, you can index into the `DataFrame` with the name of the column you want, passed as a symbol. In Julia, symbols are names preceded by a `:`. For example, we could grab the "height" column from `apples` by indexing into `apples` with the symbol, `:height`:
#
# ```julia
# apples[:height]
# ```
#
# Index into `apples` to get the "red" column. What is the type of the object returned? How many entries does it have?
#
# A) Array, 5

# C) Array, 64

# E) Array, 492

# F) Float64, 492

#-

# #### Solution
#
# To find the answer, grab the `red` column via

apples[:red]

# #### Exercise 2
#
# We can grab an individual entry of a `DataFrame` by specifying the row index of the entry and the column symbol. For example, to access the height of the 4th image of an apple, we would execute
#
# ```julia
# apples[4, :height]
# ```
#
# How much red is there in the 63rd image of a banana?

#-

# #### Solution
#
# We determine the amount of red in the 63rd banana via

bananas[63, :red]

# #### Exercise 3
#
# We want to reorganize data from the 3rd and 4th columns of `apples` and `bananas` to put that data in a single array. Let's start by organizing the data from the 3rd and 4th columns of `apples` into a single array, `x_apples`. Create `x_apples` such that there is a single element in `x_apples` for each image in `apples`. The $i^\mathrm{th}$ element in `x_apples` should be a `Vector`, i.e. a 1D `Array`, with two elements - the amount of red and the amount of blue in the $i^\mathrm{th}$ image from `apples`.
#
# Similarly create the `Array` `x_bananas` using data from `bananas`.

#-

# #### Solution
#
# Create `x_apples` and `x_bananas` via

x_apples = [[row.red, row.green] for row in eachrow(apples)]
x_bananas = [[row.red, row.green] for row in eachrow(bananas)]

# **Tests**

@assert isapprox(x_apples[404][1],  0.6774070822358786)
@assert isapprox(x_apples[404][2], 0.5832823897939495)
@assert isapprox(x_bananas[404][1], 0.5384643155305847)
@assert isapprox(x_bananas[404][2], 0.4738283806042557)

# #### Exercise 4
#
# Next we want to combine the elements of `x_apples` and `x_bananas` into a single array, `xs`. `xs` should contain, first, all the elements of `x_apples`, and then all the elements of `x_bananas`. Use the `vcat` function to create `xs`.

#-

# #### Solution
#
# The following will produce the desired `xs`:

xs = vcat(x_apples, x_bananas)

# **Tests**

isapprox(xs[404][1], 0.6774070822358786)
isapprox(xs[404][2], 0.5832823897939495)
isapprox(xs[808][1], 0.5771131704577359)
isapprox(xs[808][2], 0.4821497596511228)

# #### Exercise 5
#
# If you've gotten this far, our data is in the format we want for learning. Now we need labels! We want to store a label (either a `0` or a `1` for every apple or banana image in our data set in an array called `ys`. Recall that "0" refers to an apple and "1" refers to a banana.
#
# Create an array `ys` where the $i^\mathrm{th}$ element of `ys` is a `0` if the $i^\mathrm{th}$ element of `xs` is an apple, and where the $i^\mathrm{th}$ element of `ys` is a `1` if the $i^\mathrm{th}$ element of `xs` is a banana.

#-

# #### Solution
#
# You can generate `ys` via
# ```julia
# ys = [0 for i in 1:length(x_apples)]
# ys = vcat(ys, [1 for i in 1:length(x_bananas)])
# ```
#
# **Tests**
#
# ```julia
# ys[1] == 0
# ys[492] == 0
# ys[493] == 1
# ys[982] == 1
# ```

ys = vcat(fill(0, size(x_apples)), fill(1, size(x_bananas)))

# #### Exercise 6
#
# Add data points for all apple and banana images in our data sets to a plot using `scatter`. Plot data points for apples in one color and use a different color for banana data points.
#
# Hint: You may want to use the `first` and `last` functions.

#-

# #### Solution
#
# The following code will produce the desired plot:
#
# ```julia
# scatter(first.(x_apples), last.(x_apples), m=:cross, label="apples")
# scatter!(first.(x_bananas), last.(x_bananas), label="bananas")
# ```

using Plots
scatter(first.(x_apples), last.(x_apples), m=:cross, label="apples")
scatter!(first.(x_bananas), last.(x_bananas), label="bananas")

# ## "Learning" by hand

#-

# Intuitively, looking at the plot of the data, we see that it should be "easy" to find a function that separates the data into bananas on one side and apples on the other: we just need to draw a straight line that divides the two clouds of data. We can do this "by hand", as follows.

#-

# In the code below, the neuron will learn a function of the form $\sigma(\mathbf{w} \cdot \mathbf{x} + b)$. Since $\sigma$ looks like a smooth version of a step function, we can think of $\sigma$ classifying based on whether the value of its output argument is less than `0.5` or greater than `0.5`.
#
# **Game**: Use the interactive visualization to find suitable values of $\mathbf{w}$ and $b$ such that the hyperplane $\sigma(w_1 x_1 + w_2 x_2 + b) = 0.5$ divides the data. This is the same as the hyperplane for which $w_1 x_1 + w_2 x_2 + b = 0$ ! (Note that there are many such values!)

#-

# We can solve for $x_2$ via
#
# $$x_2 = -(w_1 x_1 + b) / w_2,$$
#
# and use this to draw the corresponding hyperplane.

#-

w1 = 3.0 # Try manipulating w1 to have values between -2 and 6
w2 = 3.0 # Try manipulating w2 to have values between -2 and 6
b  = -2.5 # Try manipulating b  to have values between -5 and 0
scatter(first.(x_apples), last.(x_apples), m=:cross, label="apples", xlim=(.3, .9), ylim=(.2,.8))
scatter!(first.(x_bananas), last.(x_bananas), label="bananas")
plot!(x -> -(w1*x + b) / w2)
## w1 = 2.22
## w2 = 1.05
## b  = -2.0

# ## How can the neuron *learn* to classify the data?

#-

# We are now ready for our first experience of **machine learning**: we will let the neuron learn automatically by processing data and tuning model parameters accordingly (the process we call "learning")!

#-

# For given values of the parameters $w_1$, $w_2$ and $b$, the function $f_{\mathbf{w}, b}$ maps a vector of length $2$ to a number between $0$ and $1$ (due to the definition of $\sigma$). Now we want to have a neuron *learn* suitable values of these parameters.
#
# We want to discover (learn!) the parameters such that $f$ models the relationship between the data we explored above about the fruit images, and outputs a classification of the fruit: $0$ if it corresponds to an apple, and $1$ if it is a banana.
#
# So the neuron's input will be a vector of 2 pieces of information about an image; let's call the data about the $i$th image $\mathbf{x}^{(i)}$.
# We also are given the label that says which type of fruit it is: $0$ for an apple, and $1$ for a banana; let's call this *desired* output number $y^{(i)}$. When we feed in the $i$th data, $\mathbf{x}^{(i)}$, we want the neuron to give an output that is *as close as possible* to the desired output $y^{(i)}$; i.e. it should **minimize** the mean-squared distance
#
# $$L_i = [f_{\mathbf{w}, b}(\mathbf{x}^{(i)}) - y^{(i)} ]^2.$$

#-

# However, now we see a key difference from what we did previously: the neuron should vary its parameters in such a way that it manages to minimize this distance for *all* of the input data, simultaneously!
#
# How can we express this mathematically? We once again define a loss function, $L(\mathbf{w}, b)$, which tells us "how wrong" we are when the parameters take on the given values, and then **minimize** this loss function with respect to all of its parameters.
#
# One way to take account of all the data at once is to use the "mean-squared error" loss function, which is the mean (squared) over all the differences between the output of the network, $f_{\mathbf{w}, b}(\mathbf{x}^{(i)})$ on the $i$th data, and the desired output $y^{(i)}$:
#
# $$L_\mathrm{total}(\mathbf{w}, b) = \frac{1}{N} \sum_i L_i = \frac{1}{N} \sum_i [f_{\mathbf{w}, b}(\mathbf{x}^{(i)}) - y^{(i)} ]^2,$$
#
# where $N$ is the total number of data in the training set.
#
# Why do we choose this particular loss function? Because the minimum possible value of this loss function is $0$ (since it is a sum of squares), and this is reached only when the neural network perfectly predicts the output. If we can find a way to minimize this loss function, we will get as close as possible to this perfect prediction. (In general, though, we won't be able to get an exact prediction.)

#-

# ## Minimizing the loss function: *stochastic* gradient descent

#-

# We already know how to minimise loss functions on a computer: we just calculate the gradient, and do gradient descent! But here we hit a problem: the function $L_\mathrm{total}$ usually has a *lot* of terms, and so calculating the gradient of that function will be very time-consuming.
#
# Instead, we will use a variant of gradient descent, called *stochastic* gradient descent. Here, the idea is that we will not use the complete loss function; instead, at each step we will choose a random data point, number $i$, and do a step of gradient descent for the partial loss function $L_i$ *corresponding to only that data point*.

#-

# **Exercise 7:**
#
# Write functions for the partial loss function `L(w, b, x, y)`.
#
# To do this, recall
#
# $$
# \mathbf{x} = \begin{pmatrix} x_1 \\ x_2 \end{pmatrix};
# \qquad
# \mathbf{w} = \begin{pmatrix} w_1 \\ w_2 \end{pmatrix};
# \qquad
# f_{\mathbf{w}, b}(\mathbf{x}) = \sigma(\mathbf{w} \cdot \mathbf{x} + b),$$
#
# and declare `f(x, w, b)` as in notebook 8.

#-

# #### Solution

## Setup:
σ(x) = 1 / (1 + exp(-x))
f(x, w, b) = σ(w'x + b)
## The loss function:
L(w, b, x, y) = (f(x, w, b) - y)^2

# **Tests**

@assert isapprox(L([-1.5, 1.5], .5, [.3, .4], 1), 0.11764182271544733)
@assert isapprox(L([-1, 1], 0, [-.4, -.3], 0), 0.27560314728604807)

# #### Exercise 8
#
# Write a function for the gradient of `L`, i.e. `∇L(w, b, x, y)`, with respect to the parameters $(w_1, w_2, b)$, using finite differences. $∇L$ will be a vector with one component per parameter:
#
# $$∇L = \left( \frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2}, \frac{\partial L}{\partial b} \right).$$

#-

# #### Solution

## A separate function for each partial:
partial_w1(w, b, x, y, h = 0.001) = (L(w .+ [h, 0], b, x, y) - L(w, b, x, y))/h
partial_w2(w, b, x, y, h = 0.001) = (L(w .+ [0, h], b, x, y) - L(w, b, x, y))/h
partial_b(w, b, x, y, h = 0.001) = (L(w, b + h, x, y) - L(w, b, x, y))/h

## The combined gradient:
∇L(w, b, x, y) = [partial_w1(w, b, x, y), partial_w2(w, b, x, y), partial_b(w, b, x, y)]

# **Tests**

@assert isapprox(∇L([-1.5, 1.5], .5, [.3, .4], 1)[1], -0.04636839045066288)
@assert isapprox(∇L([-1.5, 1.5], .5, [.3, .4], 1)[2], -0.061821518678797927)
@assert isapprox(∇L([-1.5, 1.5], .5, [.3, .4], 1)[3], -0.15450877075851022)
@assert isapprox(∇L([-1, 1], 0, [-.4, -.3], 0)[1], -0.10472487950158316)
@assert isapprox(∇L([-1, 1], 0, [-.4, -.3], 0)[2], -0.07854532958612159)
@assert isapprox(∇L([-1, 1], 0, [-.4, -.3], 0)[3], 0.2618900851394379)

# #### Exercise 9
#
# Implement stochastic gradient descent in the function `stochastic_gradient_descent(L, w, b, xs, ys, N = 1000)`. Use this function to minimize the function $L_\mathrm{total}$.
#
# The algorithm: For each of `N` steps, randomly select an index $i$ into the vector `xs` storing your image data. Calculate the gradient of the cost function, $L_i$, for this image and update each of the parameters, $p_j$, of $L_i$ according to
#
# $$p_j = p_j - 0.01 * ∇L_j$$
#
# (Here, $j$ signifies the $j^{th}$ parameter of $L$ and similarly the $j^{th}$ component of $∇L$.)
#
# `stochastic_gradient_descent` should return the updated values for vector $\mathbf{w}$ and scalar $b$.
#
# Optional: Keep track of the value of $L_\mathrm{total}$ over time if you want to visualize the learning process.

#-

# #### Solution

function stochastic_gradient_descent(∇L, w, b, xs, ys, N=1000)

    η = 0.01

    for step in 1:N

        i = rand(1:length(xs))  # choose a data point

        x = xs[i]
        y = ys[i]

        grad = ∇L(w, b, x, y)
        w = w .- η .* grad[1:2]
        b = b - η * grad[3]
    end

    return w, b

end

# **Tests**

w, b = ([0.736019, 0.518619], 0.03408673204690893)
w, b = stochastic_gradient_descent(∇L, w, b, xs, ys, 1e6)
@assert -18.8 < w[1] < -18.4 # roughly -18.6
@assert -19.4 < w[2] < -19.0 # roughly -19.2
@assert 21 < b < 23 # roughly 22

# #### Exercise 10
#
# Use the values of `w` and `b` from the last exercise to see how `f` is classifying a couple of the images in out dataset.
#
# In particular, calculate `f` using the 1st and the 900th image in `xs`. For which image is the output of `f` closer to the value of its label?
#
# A) The output of `f` for the 1st image in `xs` is closer to its label
#
# B) The output of `f` for the 900th image in `xs` is closer to its label.

#-

# #### Solution
#
# A)
#
# ```julia
# f(xs[900], w, b) # yields ~0.9565428318826446
# ys[900] - f(xs[900], w, b) # yields ~.041 (4% difference)
# f(xs[1], w, b) # yields ~0.027378537417953305
# ys[1] - f(xs[1], w, b) # yields ~ -0.030 (3% difference)
# ```

@show f(xs[1], w, b) # yields ~0.027378537417953305
@show ys[1] - f(xs[1], w, b) # yields ~ -0.030 (3% difference)

@show f(xs[900], w, b) # yields ~0.9565428318826446
@show ys[900] - f(xs[900], w, b) # yields ~.041 (4% difference);

# We see that *with sufficient training*, the single neuron is approximately able to learn the function for most of the data, but not all:

#-

# #### Exercise 11
#
# Use the `maximum` function to determine the maximum squared distance of the prediction from the true value. (For each image, this formula is $y_i - f_{w, b}(x_i)$.)

#-

# #### Solution

@show maximum((ys .- f.(xs, [w], [b])).^2)

# #### Exercise 12
#
# Use `w` and `b` from stochastic gradient descent to draw the function that the network has learned, as before, as the hyperplane $w_1 x + w_2 y + b = 0$. Overlay this with the data.
#
#
# Does this hyperplane correctly separate the data? (I.e., is the data for all apples on one side of the line, and is the data for all bananas on the other side of the line?)
#
# A) Yes
# B) No

#-

# #### Solution
#
# A) Yes

using Plots; gr()

plot(x ->(-w[1]*x - b) / w[2])
scatter!(first.(x_apples), last.(x_apples), m=:cross)
scatter!(first.(x_bananas), last.(x_bananas))
xlims!(0.45, 0.75)
ylims!(0.35, 0.75)

# Since the neuron has two input variables and one output variable, we can also use a "heatmap" to visualize the value of $f_{\mathbf{w}, b}(x_1, x_2)$ as a function of the input data pairs $(x_1, x_2)$:

heatmap(0:0.01:1, 0:0.01:1, (x,y) -> f([x,y], w, b))

scatter!(first.(x_apples), last.(x_apples), m=:cross)
scatter!(first.(x_bananas), last.(x_bananas))

xlims!(0.45, 0.75)
ylims!(0.35, 0.75)

# We see that the single neuron has indeed *learned* to separate the data using a hyperplane.

Courses/Foundations of machine learning/1100.Intro-to-neurons-quiz.jl


@testset "661.image-smoother.jl" begin
    @test image_smoother([1 1 1; 1 0 1; 1 1 1]) == zeros(Int, 3, 3)
    @test image_smoother([100 200 100; 200 50 200; 100 200 100]) ==
        [137 141 137; 141 138 141; 137 141 137]
    @test image_smoother([1 2; 2 3]) == [2 2; 2 2]
    @test image_smoother([1 2 3; 2 3 4]) == [2 2 3; 2 2 3]
    @test image_smoother([1 2]) == [1 1]
    @test image_smoother([1, 2][:, :]) == [1, 1][:, :] ## transpose
end


# area plots

# Vega
using Vega, KernelDensity, Distributions

x = rand(Beta(3.0, 2.0), 10)
k = kde(x)

areaplot(x = k.x, y = k.density)

# stacked area

x = [0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9]
y = [28, 43, 81, 19, 52, 24, 87, 17, 68, 49, 55, 91, 53, 87, 48, 49, 66, 27, 16, 15]
g = [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1]

a = areaplot(x = x, y = y, group = g, stacked = true)

# Plotly
using PlotlyJS

x = ["Winter", "Spring", "Summer", "Fall"]

plot([
    scatter(
        x=x, y=[40, 60, 40, 10],
        stackgroup="one", mode="lines", hoverinfo="x+y",
        line=attr(width=0.5, color="rgb(131, 90, 241)")
    ),
    scatter(
        x=x, y=[20, 10, 10, 60],
        stackgroup="one", mode="lines", hoverinfo="x+y",
        line=attr(width=0.5, color="rgb(111, 231, 219)")
    ),
    scatter(
        x=x, y=[40, 30, 50, 30],
        stackgroup="one", mode="lines", hoverinfo="x+y",
        line=attr(width=0.5, color="rgb(184, 247, 2121)")
    ),
], Layout(yaxis_range=(0, 100)))


"""
    tab(df::DataFrame,vars::Symbol...; label_dict::Union{Nothing,Dict} = nothing, rmna::Bool = true, weights::AbstractVector = UnitWeights())

Produce an one-way or two-way frequency table from a DataFrame or any type of arrays.
Any weights defined under AbstractWeights (e.g., fweights, aweights, or pweights)
can be used to obtain weighted frequencies.

`tab` is mainly a wrapper for the excellent `FreqTables` package.
Use `skipmissing = true` to obtain frequencies that include `missing` values.
The returned table is a `NamedArray`. Frequencies are in an n-dimensional array `na.array`
where `na` is the returned NamedArray. `na.dimnames` contain row and column values.
"""
function tab(A::AbstractArray,B::AbstractArray; skipmissing::Bool=false,weights::AbstractWeights=fweights(ones(Int8,length(A))))

    # check lengths of arrays
    alen = length(A)
    blen = length(B)
    wlen = length(weights)

    if alen != blen
        error("Lengths of two vectors are not equal")
    end

    if alen != wlen
        error("Input vectors and `weights` vector are not of the same lengths")
    end

    # from FreqTables.jl/src/freqtable.jl
    d = Dict{Tuple{eltype(A),eltype(B)},Int64}()
    @inbounds @simd for i in 1:alen

        el = (A[i],B[i])

        index = Base.ht_keyindex(d, el)

        if index > 0
            @inbounds d.vals[index] += weights[i]
        else
            @inbounds d[el] = weights[i]
        end
    end

    # construct labels
    alev = Set{eltype(A)}()
    blev = Set{eltype(B)}()
    for (k1,k2) in keys(d)
        push!(alev,k1)
        push!(blev,k2)
    end

    alev = sort!(collect(alev))
    blev = sort!(collect(blev))
    if skipmissing
        alev = collect(skipmissing(alev))
        blev = collect(skipmissing(blev))
    end

    cnt = Array{Int64,2}(undef,length(alev),length(blev))
    @inbounds @simd for i=1:length(alev)
        @inbounds @simd for j=1:length(blev)
            el = (alev[i],blev[j])
            if haskey(d,el)
                cnt[i,j] = d[el]
            else
                cnt[i,j] = 0
            end
        end
    end

    return NamedArray(cnt,(alev,blev),("A","B"))
end
function tab(A::AbstractArray; skipmissing::Bool=true,weights::AbstractWeights=fweights(ones(Int8,length(A))))

    alen = length(A)
    wlen = length(weights)

    if alen != wlen
        error("The `weights` vector does not have the same length as the input vector `A`")
    end

    # from FreqTables.jl/src/freqtable.jl
    d = Dict{eltype(A),Int64}()
    @simd for i in 1:alen
        index = Base.ht_keyindex(d, A[i])

        if index > 0
            @inbounds d.vals[index] += weights[i]
        else
            @inbounds d[A[i]] = weights[i]
        end
    end

    # construct labels
    alev = sort(collect(keys(d)))

    cnt = Vector{Int64}(undef,length(alev))
    @inbounds @simd for i=1:length(alev)
        cnt[i] = d[alev[i]]
    end

    if skipmissing == true && alev[end] == missing
        return NamedArray(reshape(cnt[1:end-1],length(alev)-1,1),(alev[1:end-1],["Frequency"]),("A","Stat"))
    end

    return NamedArray(reshape(cnt,length(alev),1),(alev,["Frequency"]),("A","Stat"))
end
function tab(df::DataFrame,vars::Symbol...;  skipmissing::Bool=false, weights::AbstractWeights=fweights(ones(Int8,size(df,1))))
    nvec = length(vars)
    if nvec == 0
        error("At least one variable is required")
    elseif nvec > 2
        error("More than two variables are not supported")
    end

    # oneway table
    if nvec == 1
        na = tab(df[vars[1]], skipmissing = skipmissing, weights = weights)
        setdimnames!(na,(vars[1],"Stat"))
        return na
    end

    # two-way table
    na = tab(df[vars[1]], df[vars[2]], skipmissing = skipmissing, weights = weights)

    setdimnames!(na,(vars[1],vars[2]))
    return na
end

struct XsqResult
    chisq::Float64
    dof::Int
    p::Float64
end
function chi2test(a::Array{Float64,2})

    if ndims(t) != 2
        error("Only two dimensional arrays are supported")
    end

    rowsum = Array(sum(t,dims=2))
    colsum = Array(sum(t,dims=1))
    total = sum(t)

    ncol = length(colsum)
    nrow = length(rowsum)

    chisq = 0.
    for i = 1:nrow
        for j = 1:ncol
            expected = rowsum[i]*colsum[j]/total
            chisq += ((t[i,j] - expected)^2)/expected
        end
    end

    # degress of freedom
    df = (ncol-1)*(nrow-1)

    # return a tuple of chisq, df, p-value
    return XsqResult(chisq,df,Distributions.ccdf(Distributions.Chisq(df),chisq))
end

function rowpct(na::NamedArray)
    return 100. * na ./ sum(na.array,dims=2)
end

function colpct(na::NamedArray)
    return 100. * na ./ sum(na.array,dims=1)
end

function cellpct(na::NamedArray)
    return 100. * na ./ sum(na)
end

function chi2(t::NamedArray)
    if ndims(t) != 2
        error("Only two dimensional arrays are supported")
    end

    rowsum = sum(t,dims=2)
    colsum = sum(t,dims=1)
    total = sum(t)

    ncol = length(colsum)
    nrow = length(rowsum)

    chisq = 0.
    for i = 1:nrow
        for j = 1:ncol
            expected = rowsum[i]*colsum[j]/total
            chisq += ((t[i,j] - expected)^2)/expected
        end
    end

    # degress of freedom
    df = (ncol-1)*(nrow-1)

    # return a tuple of chisq, df, p-value
    return (chisq,df,Distributions.ccdf(Distributions.Chisq(df),chisq))
end

function rowpercent(tab::NamedArray)

    totrow = sum(tab,dims=2)

    array = 100 * tab.array ./ totrow

    return NamedArray(array,tuple(names(tab)...),tuple(dimnames(tab)...))

end

function colpercent(tab::NamedArray)

    totcol = sum(tab,dims=1)

    array = 100 * tab.array ./ tocol

    return NamedArray(array,tuple(names(tab)...),tuple(dimnames(tab)...))
end

function cellpercent(tab::NamedArray)

    total = sum(tab.array)

    array = 100 * tab.array ./ total

    return NamedArray(array,tuple(names(tab)...),tuple(dimnames(tab)...))
end

function tabprint(na::NamedArray; precision=2, labels::Union{Nothing,Label} = nothing, chisq=true, row=true, col=true, cell=false, all=false, pagewidth=78)

    ndim = ndims(na)
    if ndim == 2 && length(names(na,2)) == 1
        return tabprint1(na, precision = precision, labels = labels)
    elseif ndim == 3
        for (i,v) in enumerate(names(na,3))
            vlab = labels == nothing ? "" : string(" (",varlab(labels,dimnames(na,3)), " = ",vallab(labels,dimnames(na,3), v),")")
            print("\n",dimnames(na,3)," = ",v,vlab,"\n\n")
            tabprint(NamedArray(na.array[:,:,i],(names(na,1),names(na,2)),tuple(dimnames(na,1),dimnames(na,2))),
                precision=precision,labels=labels,chisq=chisq,row=row,col=col,cell=cell,all=all,pagewidth=pagewidth)
        end
        return
    elseif ndim > 2
        error("Only up to three dimensional arrays are currently supported")
    end

    if labels == nothing
        print(dimnames(na,1), " \\ ", dimnames(na,2),"\n")
    else
        print(varlab(labels,dimnames(na,1)), " \\ ", varlab(labels,dimnames(na,2)),"\n")
    end

    # all is true when row, col, or cell is true
    if all
        row = col = cell = true
    end

    # row and column names
    if labels == nothing
        rownames = string.(names(na,1))
        colnames = string.(names(na,2))
    else # labels defined
        rownames = findvlab(labels,dimnames(na,1),names(na,1))
        colnames = findvlab(labels,dimnames(na,2),names(na,2))
    end

    # maximum widths
    maxrowname = maximum(vcat(5,length.(rownames))) # minimum width is 5
    maxcolname = maximum(vcat(3,length.(colnames))) # minimum width is 3

    # width of data columns - the same as the greater of the length of the grand total
    # and 4 + precision
    tot = sum(na.array) # grand total
    colwidth = max(length(digits(Int(floor(tot)))),4+precision)

    # number of columns
    ncols = length(colnames)

    # number of rows
    nrows = length(rownames)

    # floating point numbers with three digits after decimal point
    if eltype(na.array) <: AbstractFloat
        colwidth += 3
    end

    # determine column widths
    colwidth = max(maxcolname,colwidth)

    #---------------------------------------------------
    # column totals
    colsum = sum(na.array,dims=1)

    # row totals
    rowsum = sum(na.array,dims=2)

    #---------------------------------------------------
    # determine how many columns can be printed within
    # the pagewidth
    maxcol = Int(floor((pagewidth - maxrowname) / colwidth))
    niter = Int(ceil(ncols / maxcol))
    nlastrow = ncols % maxcol

    for i in 1:niter

        #---------------------------------------------------
        # print header
        print(repeat(" ",maxrowname)," |")

        for j = 1:maxcol
            c = Int(maxcol*(i-1))+j
            if c > ncols
                break
            end
            print(" ",lpad(string(colnames[c]),colwidth))
        end

        print(" | ",lpad("Total",colwidth),"\n")

        print(repeat("-",maxrowname),"-+-",repeat("-",(colwidth+1)*(i == niter ? nlastrow : maxcol)),"+-",repeat("-",colwidth),"\n")

        #----------------------------------------------------
        # print values
        for r = 1:nrows

            # row name
            print(rpad(string(rownames[r]),maxrowname)," |")

            for j = 1:maxcol
                c = Int(maxcol*(i-1))+j
                if c > ncols
                    break
                end
                val = na.array[r,c]
                print(" ",lpad(val,colwidth))
            end

            # row total
            print(" |")

            val = rowsum[r]
            print(" ",lpad(val,colwidth),"\n")

            # row percentages
            if row
                print(repeat(" ",maxrowname)," |")
                for j = 1:maxcol
                    c = Int(maxcol*(i-1)) + j
                    if c > ncols
                        break
                    end
                    val = strval(100 * na.array[r,c] / rowsum[r],precision)
                    print(" ",lpad(val,colwidth," "))
                end

                # row percentage
                print(" |")

                val = strval(100.0,precision)
                print(" ",lpad(val,colwidth),"\n")
            end

            # column percentages
            if col
                print(repeat(" ",maxrowname)," |")
                for j = 1:maxcol
                    c = Int(maxcol*(i-1)) + j
                    if c > ncols
                        break
                    end
                    val = strval(100 * na.array[r,c] / colsum[c],precision)
                    print(" ",lpad(val,colwidth))
                end

                # column percent
                print(" |")

                val = strval(100 * rowsum[r] / tot,precision)
                print(" ",lpad(val,colwidth),"\n")
            end

            # cell percentages
            if cell
                print(repeat(" ",maxrowname)," |")
                for j = 1:maxcol
                    c = Int(maxcol*(i-1)) + j
                    if c > ncols
                        break
                    end
                    val = strval(100 * na.array[r,c] / tot,precision)
                    print(" ",lpad(val,colwidth))
                end

                # column percent
                print(" |")

                val = strval(100 * rowsum[r] / tot,precision)
                print(" ",lpad(val,colwidth),"\n")
            end

            if row || col || cell
                print(repeat("-",maxrowname),"-+-",repeat("-",(colwidth+1)*(i == niter ? nlastrow : maxcol)),"+-",repeat("-",colwidth),"\n")
            end

        end

        #----------------------------------------------------
        # Total
        if !(row || col || cell)
            print(repeat("-",maxrowname+1),"+",repeat("-",(colwidth+1)*(i == niter ? nlastrow : maxcol)),"-+-",repeat("-",colwidth),"\n")
        end
        print(rpad("Total",maxrowname," ")," |")

        for j = 1:maxcol
            c = Int(maxcol*(i-1)) + j
            if c > ncols
                break
            end
            val=colsum[c]
            print(" ",lpad(val,colwidth," "))
        end

        # Grand total
        val = strval(tot)
        print(" | ",lpad(val,colwidth," "),"\n")

        #----------------------------------------------------
        # row percentages
        if row
            print(repeat(" ",maxrowname)," |")
            for j = 1:maxcol
                c = Int(maxcol*(i-1)) + j
                if c > ncols
                    break
                end
                val = strval(100 * colsum[c] / tot,precision)
                print(" ",lpad(val,colwidth," "))
            end

            # column percent for the total column
            val = strval(100.0,precision)
            print(" | ",lpad(val,colwidth," "),"\n")
        end

        # column percentages
        if col
            print(repeat(" ",maxrowname)," |")
            for j = 1:maxcol
                c = Int(maxcol*(i-1)) + j
                if c > ncols
                    break
                end
                val = strval(100.0,precision)
                print(" ",lpad(val,colwidth," "))
            end

            # column percent for the total column
            val = strval(100.0,precision)
            print(" | ",lpad(val,colwidth," "),"\n")

        end

        # cell percentages
        if cell
            print(repeat(" ",maxrowname)," |")
            for j = 1:maxcol
                c = Int(maxcol*(i-1)) + j
                if c > ncols
                    break
                end
                val = strval(100.0 * colsum[c] / tot,precision)
                print(" ",lpad(val,colwidth," "))
            end

            # column percent
            val = strval(100.0,precision)
            print(" | ",lpad(val,colwidth," "),"\n")
        end

        # separator
        print("\n\n")
    end

    if chisq
        # chisq test
        dof = (nrows-1)*(ncols-1)
        chisqval = 0.
        for i = 1:nrows
            for j = 1:ncols
                expected = rowsum[i]*colsum[j]/tot
                chisqval += ((na.array[i,j] - expected)^2)/expected
            end
        end

        # degress of freedom
        dof = (ncols-1)*(nrows-1)

        # return a tuple of chisq, df, p-value
        pval = Distributions.ccdf(Distributions.Chisq(dof),chisqval)

        # chisquare output
        println("Pearson χ² (",dof,") = ",@sprintf("%.5f",chisqval)," Pr = ",@sprintf("%.5f",pval))
    end
end

function tabprint1(na::NamedArray; precision=2, labels::Union{Nothing,Label} = nothing)

    if ndims(na) == 2 && names(na,2)[1] != "Frequency"
        error("Cannot print 2 or higher dimensions")
    end

    # variable name
    dname = dimnames(na,1)

    # value names
    if labels == nothing
        rownames = string.(names(na,1))
    else
        rownames = findvlab(labels,dname,names(na,1)) # [ ismissing(x) ? "missing" : vallab(labels,dname,x) for x in names(na,1) ]
    end

    # maximum width for row labels
    if skipmissing == true && rownames[end] == "missing"
        maxrowname = maximum(vcat(5,length(string(dname)),length.(rownames[1:end-1]))) # minimum width is 5
    else
        maxrowname = maximum(vcat(5,length(string(dname)),length.(rownames))) # minimum width is 5
    end

    # number of rows
    nrows = length(rownames)

    # column names
    colnames = ["Frequency","Percent","Cum Percent"]

    # grand total
    if skipmissing == true && rownames[end] == "missing"
        tot = sum(na.array[1:end-1])
    else
        tot = sum(na)
    end

    # maximum column width
    maxcolwidth = maximum(vcat(length(string(tot)),length.(colnames)))

    # header
    print("\n")
    print(lpad(dname,maxrowname)," |")
    for i = 1:length(colnames)
        print(lpad(colnames[i],maxcolwidth+1))
    end
    print("\n")

    # separator
    print(repeat("-",maxrowname),"-+")
    for i = 1:length(colnames)
        print(repeat("-",maxcolwidth + 1))
    end
    print("\n")

    # rows
    cumpct = 0.
    for i = 1:nrows

        if skipmissing == true && rownames[i] == "missing"
            continue
        end

        print(lpad(rownames[i],maxrowname)," |")

        # frequency
        print(lpad(na.array[i],maxcolwidth + 1))

        # percent
        pct = na.array[i] / tot
        print(lpad(strval(100*pct,precision),maxcolwidth + 1))

        # cumulative percent
        cumpct += pct
        println(lpad(strval(100*cumpct,precision),maxcolwidth + 1))
    end

    # separator
    print(repeat("-",maxrowname),"-+")
    for i = 1:length(colnames)
        print(repeat("-",maxcolwidth + 1))
    end
    print("\n")

    # total
    print(lpad("Total",maxrowname)," |")

    # total frequency
    print(lpad(tot,maxcolwidth + 1))

    # percent
    print(lpad(strval(100.0,precision),maxcolwidth + 1))

    # cumulative percent
    println(lpad(strval(100.0,precision),maxcolwidth + 1))

    print("\n")
end

function findvlab(labels::Label,v::Symbol,val::Vector)

    lname = lblname(labels,v)

    if lname == nothing
        return val
    end

    return [ismissing(x) ? "missing" : vallab(labels,v,x) for x in val]
end

# function tab(x::AbstractVector...; allowmissing=false,weights::AbstractWeights = fweights(ones(Int8,length(x[1]))))
#
#     ncols = length(x)
#     nrows = length(x[1])
#     for i = 2:ncols
#         if nrows != length(x[i])
#             error("Columns do not have the same rows.")
#         end
#     end
#
#     vectypes = Missings.T.(eltype.(x))
#     println(vectypes)
#
#     # # create output arrays
#     # vdims = Vector{Vector}(ncols)
#     # vnums = zeros(Int64,ncols)
#     # naidx = falses(ncols)
#     # for i = 1:ncols
#     #     if sum(x[i].na) > 0 # there are NA values in this vector
#     #         vdims[i] = sort(levels(dropna(x[i])))
#     #         vnums[i] = size(vdims[i],1) + 1 # one for the NA
#     #         naidx[i] = true
#     #     else
#     #         vdims[i] = sort(levels(x[i]))
#     #         vnums[i] = size(vdims[i],1)
#     #     end
#     # end
#     # from FreqTables.jl/src/freqtable.jl
#     d = Dict{Tuple{Missings.T(eltype(a)),Missings.T(eltype(b))},Int64}()
#     @inbounds @simd for i in 1:alen
#         el = (a[i],b[i])
#         index = Base.ht_keyindex(d, el)
#
#         if index > 0
#             @inbounds d.vals[index] += weights[i]
#         else
#             @inbounds d[el] = weights[i]
#         end
#     end
#
#     # output array
#     cnt = Array{Int64,2}(length(alev),length(blev))
#     @inbounds @simd for i=1:length(alev)
#         @inbounds @simd for j=1:length(blev)
#             cnt[i,j] = d[alev[i],blev[j]]
#         end
#     end
#
#
#     return NamedArray(a, tuple(dimnames...), ntuple(i -> "Dim$i", ncols))
# end


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

using Markdown
using InteractiveUtils

# ╔═╡ 521890de-ab23-11ec-0c2f-2dcaee6dc1bc
using Plots, Distributions, PlutoUI

# ╔═╡ dbf8cdd6-7815-49f8-9dd6-5987000792ce
md"""
# 3월24일 강의영상
"""

# ╔═╡ 20697867-c74a-4485-bb97-c6a31060669a
html"""

"""

# ╔═╡ 64e29d20-aaf5-4fa0-ad03-b283dac52dce
md"""
# 3월24일 강의노트
"""

# ╔═╡ 3f43a098-0653-4cb3-a48e-673e342ae48b
Plots.plotly()

# ╔═╡ ddae88bf-0b17-40f4-ae33-5cd70aa8a0de
md"""
### 포아송분포 ($X \sim Poi(\lambda)$)
"""

# ╔═╡ c0e571b7-580a-4b25-ba5b-6cb729ec36b9
md"""
`-` 포아송분포의 요약
- X의의미: 발생횟수의 평균이 λ인 분포에서 실제 발생횟수를 X라고 한다.
- X의범위: 발생안할수도 있으므로 X=0이 가능. 따라서 X=0,1,2,3,...
- 파라메터의 의미와 범위: λ = 평균적인 발생횟수; λ>0.
- pdf:
- mgf:
- E(X): λ
- V(X): λ
"""

# ╔═╡ 7807af0d-012a-432a-9a64-5df4a633333b
md"""
`-` [포아송분포의 예시](https://www.statology.org/poisson-distribution-real-life-examples/#:~:text=Example%201%3A%20Calls%20per%20Hour,receives%2010%20calls%20per%20hour.)
- Calls per Hour at a Call Center
- Number of Arrivals at a Restaurant
- Number of Website Visitors per Hour
- Number of Bankruptcies Filed per Month
- Number of Network Failures per Week
"""

# ╔═╡ 713fc6aa-3cdb-49ad-903c-9127452e69b9
md"""
`-` 평균이 3인 포아송분포에서 100개의 샘플을 뽑는 방법
"""

# ╔═╡ 443fc984-8109-45c3-a6e5-e6e565dd0491
md"""
(방법1)
"""

# ╔═╡ 6d30adc5-4da9-4e85-bd47-e128de6fffb2
rand(Poisson(3),100)

# ╔═╡ 9a9ed77b-903e-4587-967c-518343a62e54
md"""
(방법2) 이항분포의 포아송근사를 이용
- 이론: 이항분포에서 (1) $n\to \infty$ (2) $p\to 0$ (3) $np=\lambda$ 이면 이것은 평균이 $\lambda$인 포아송분포로 근사함.
- 평균이 $\lambda$인 포아송분포는 $B(n,\frac{\lambda}{n})$로 근사할 수 있다. 이때 $n$이 커질수록 더 정확해짐.
"""

# ╔═╡ b0ca1c8f-272d-4a2f-ac4b-10bd659fceda
let
	samplesize_of_poisson = 10000
	λ=3
	n=10000
	p=λ/n
	X = rand(Binomial(n,p),samplesize_of_poisson)
	Y = rand(Poisson(λ),samplesize_of_poisson)
	p1= histogram(X)
	p2= histogram(Y)
	plot(p1,p2,layout=(2,1))
end

# ╔═╡ 1e943933-7266-4696-9388-6060d0d173e8
md"""
-  $n=10000$ 정도이면 꽤 비슷함.
- 방법2는 근사방법이므로 엄밀히 말하면 분포를 뽑는 기법이라고 볼 수는 없음.
"""

# ╔═╡ 4520bf81-0491-4a47-99c4-45680964654c
let
	λ=3
	n=600
	p=λ/n
	Δt = (60/n) # 단위가 60초니까 60

	poi_samplesize = 10000
	X = [(rand(n) .
 sum for k in 1:poi_samplesize]
	p1= histogram(X)
	p2= rand(Poisson(λ),poi_samplesize) |> histogram
	_p = plot(p1,p2,layout=(2,1))

	md"""
	(방법3) 균등분포 -> 베르누이 -> 이항분포 -> 포아송근사
	- 1분동안 맥도날드에 평균 3명이 온다고 생각하자.
	- 이건 사실 1초에 성공확률이 0.05인 베르누이 시행을 1번 시행하여 1분동안 총 60회 반복한 것으로 이해할 수 있음.
	- 아니야, 이건 사실 $(Δt) 초에 성공확률이 $p 인 베르누이 시행을 1번 시행하여 1분동안 총 $n 회 반복한 것으로 이해할 수 있음.
	- 아니야 아냐야, 이건 사실.... (무한반복)
	- 느낌: 하여튼 엄청 작은 시간에 엄청 작은 확률의 베르누이 시행이 독립적으로 엄청 많이 반복되는 느낌을 기억하세요!
	$(_p)
	- 위: 유니폼 -> 베르누이 -> 이항분포 -> 포아송
	- 아래: 포아송
	"""
end

# ╔═╡ ea7e4bb9-5eca-4046-8209-51489614c636
md"""
(방법4) 균등분포 -> inverse cdf method를 이용해서 생성할 수 있음.
"""

# ╔═╡ 8352dd50-1f7d-4e3d-9365-5af24d43547c
md"""
`-` 포아송분포의 합은 다시 포아송분포가 된다.
- 이론: $X \sim Poi(\lambda_1), Y\sim Poi(\lambda_2),~ X \perp Y \Rightarrow X + Y \sim P(\lambda_1 + \lambda_2)$
- 의미? (1) 1분동안 맥도날드 매장에 들어오는 남자의 수는 평균이 5인 포아송 분포를 따름 (2) 1분동안 맥도날드 매장에 들어오는 여자의 수는 평균이 4.5인 포아송분포를 따름 (3) 남자와 여자가 매장에 오는 사건은 독립 => 1분동안 맥도날드 매장에 오는 사람은 평균이 9.5인 포아송 분포를 따른다는 의미.
"""

# ╔═╡ af1c928e-ab6c-4212-88df-e3109503a157
md"""
(실습)
"""

# ╔═╡ 5d376188-fad1-48fb-86a2-7dcc908c337f
let
	n= 1000
	X = rand(Poisson(5),n)
	Y = rand(Poisson(4.5),n)
	p1 = X.+Y |> histogram
	p2 = rand(Poisson(9.5),n) |> histogram
	plot(p1,p2,layout=(2,1))
end

# ╔═╡ 0ab7a2d2-0d17-4523-8e91-b28624303524
let
	n=1000
	λ=5
	X = rand(Poisson(λ),n)
	md"""
	`-` 평균과 분산의 추정
	- 평균: $λ
	- 평균의 추정치: $(mean(X))
	- 분산: $λ
	- 분산의 추정치: $(var(X))
	"""
end

# ╔═╡ f140fb8e-308f-40e4-a199-b0e0d20dc7da
md"""
- 생각해보니까 왜 평균추정값과 분산추정값이 달라야하나?
"""

# ╔═╡ a222e7a1-dd18-4416-92eb-320f926d1d77
let
	n = 10000
	λ = 5
	p1=[mean(rand(Poisson(λ),n)) for k in 1:100] |> histogram
	p2=[var(rand(Poisson(λ),n)) for k in 1:100] |> histogram
	#p3=[(mean(rand(Poisson(λ),n))+var(rand(Poisson(λ),n)))/2  for k in 1:100] |> histogram
	_p=plot(p1,p2,layout=(2,1))
	md"""
	mean(X),var(X)로 λ를 추정
	$(_p)
	- 위: mean(X)
	- 아래: var(X)
	"""
end

# ╔═╡ ebe47018-7a6d-42a4-a61a-0079b5c077f8
md"""
- 히스토그램을 그려보니까 누가봐도 mean(X)로 λ를 추정하는것이 var(X)로 λ를 추정하는것 보다 좋아 보인다.
- 그냥 mean(X)=평균추정량=분산추정량 이라고 주장하면 안되나? => 됩니다! 이게 바로 MLE에요!
"""

# ╔═╡ 29a2e115-37a7-4f67-a040-a4c1648fdfa6
md"""
### 지수분포 ($X \sim Exp(1/\lambda)$)
"""

# ╔═╡ a185e773-f51e-449b-8d40-de819bd4badf
md"""
`-` 지수분포의 요약
- X의의미: 시간1에 평균적으로 λ번 발생하는 시건이 있을때 첫 번째 이벤트가 발생할때 까지 걸리는 시간.
- X의범위: 시간은 양수이므로 X ≥ 0
- 파라메터의 의미와 범위: (1) λ = 시간1에 평균적으로 발생하는 이벤트의 수 (2) 1/λ = 한번의 이벤트가 발생할때까지 평균적으로 걸리는 시간; λ>0
- pdf: $f(x)=\lambda e^{-\lambda x}$
- mgf:
- cdf: $F(x)=1-e^{-\lambda x}$
- E(X): $\frac{1}{\lambda}$
- V(S): $\frac{1}{\lambda^2}$
"""

# ╔═╡ 885941da-8604-4af0-8b69-6d564348f116
md"""
`-` 평균이 10인 지수분포에서 100개의 샘플을 뽑는 방법
"""

# ╔═╡ 2134273b-fca8-4238-b906-f05948fae7d2
md"""
(방법1)
"""

# ╔═╡ 1da1e41d-73a6-43f4-9d69-3e28b83da8e0
rand(Exponential(10),100)

# ╔═╡ caa4ea34-e2ae-455a-88a7-6324ff2cc8ef
md"""
(방법2) 포아송프로세스 -> 지수분포
- 맥도날드에 시간1당 0.1명씩 평균적으로 방문한다. 1명 방문하는데에는 평균적으로 시간이 10이 걸린다고 볼 수 있음.
- 따라서 언뜻생각하면 포아송과 지수분포는 역의 관계라서 포아송분포를 만들고 역수를 취하면 지수분포를 쉽게 만들 수 있을 것 같다.
"""

# ╔═╡ eb7572bd-63eb-4792-b982-9f320358a99c
rand(Poisson(0.1),100) # 0이 나오네?

# ╔═╡ 1c902aac-d633-4cde-95e1-62e42e0ffe87
md"""
- 0이 나온다?
- 생각해보니까 0이 없다고 쳐도 나올 수 있는 값은 1, 1/2, 1/3, 1/4, ... 따위임 (애초에 틀린 접근)
- 아이디어: 극한의 베르누이로 포아송을 만들때, 몇번 성공했는지 관심을 가지고 카운팅 했음 => 조금 응용해서 첫 성공까지 몇번의 시도를 해야하는지 카운팅을 한다고 생각하면 시간계산이 가능할것 같다.
- 결국 포아송프로세스 -> 지수분포로 가야함
"""

# ╔═╡ 09a0f94b-e90c-4233-933c-7c1237f11658
md"""
(예비학습)
"""

# ╔═╡ 8a1a0dd9-a5fc-4ea1-b026-c47490db1bc3
rand() # 유니폼에서 1개의 샘플 추출

# ╔═╡ 011a3508-939b-47b6-a062-9d61c6ea9eec
let
	i=0
	while i <= 5
		i=i+1
	end
	i
end

# ╔═╡ aef02ab9-4e12-4030-b428-f4aa36f65ba6
md"""
(풀이시작)
"""

# ╔═╡ e5ac4d16-20ed-4c35-832f-b6fd5cfad2dd
function try_until_you_succeed(p) # 성공할때까지 시도하는 함수
	n_of_try=0
	u=0
	while u < (1-p) # p=0이면 무한반복
		u = rand()
		n_of_try = n_of_try + 1
	end
	return n_of_try
end

# ╔═╡ b0d76b5d-cefa-489e-9963-830d6fb710a0
[try_until_you_succeed(0.1) for k in 1:10000] |> histogram

# ╔═╡ a27efaae-2793-4d8a-8dd6-4900caa80a4a
let
	exp_samplesize = 10000
	λ=0.1
	n=10000
	p=λ/n
	Δt = (1/n) # 단위가 시간1이니까
	X = [try_until_you_succeed(p) for k in 1:exp_samplesize] .* Δt
	p1 = X|> histogram
	p2 = rand(Exponential(10),exp_samplesize) |> histogram
	plot(p1,p2,layout=(2,1))
end

# ╔═╡ 829ad68b-7352-4d18-bcfd-90919b18648c
md"""
- 불평: 샘플하나뽑는데 시간이 오래걸림. (정확도를 올릴수록 더 오래걸림)
"""

# ╔═╡ fc1acb74-f856-47c7-b9e1-8d635f0e35ca
md"""
(방법3) inverse cdf method
- 이론적인 pdf를 알고 있다는 전제가 필요함.
- 자세하게 살펴보자.
"""

# ╔═╡ c2c198a7-4597-49e2-98d7-8776603db7fa
md"""
##### Inverse cdf method를 활용하여 지수분포에서 샘플추출
"""

# ╔═╡ cf6a955e-d684-4a16-bc0f-14409f83536a
md"""
`-` 아래와 같은 2개의 지수분포의 pdf를 고려하자.

$$f(x)=e^{-x}$$

$$g(x)=\frac{1}{5}e^{-\frac{1}{5}x}$$
"""

# ╔═╡ ed777a37-6351-41a1-aed3-86412c9edaac
md"""
`-` 각각의 pdf를 그려보면 아래와 같다.
"""

# ╔═╡ db26ed1c-b01a-4f06-886d-4079cf2c139f
let
	p1= plot(x-> exp(-x), 0, 20)
	p2= plot(x-> 1/5* exp(-x/5),0,20)
	plot(p1,p2,layout=(2,1))
end

# ╔═╡ 7889eeaa-a7b5-4cdd-8b26-c0de2d2981e0
md"""
`-` 이번에는 각각의 cdf를 그려보자.

$$F(x)=\int_0^x f(\tau)d\tau=\int_0^x e^{-\tau} d\tau = \left[-e^{-\tau}\right]_0^x=1-e^{-x}$$

$$G(x)=\int_0^x g(\tau)d\tau=\int_0^x \frac{1}{5}e^{-\tau/5} d\tau = \left[-e^{-\tau/5}\right]_0^x=1-e^{-x/5}$$

"""

# ╔═╡ 23abf65d-276d-4a5d-ae70-b41255b35b48
let
	p1= plot(x -> 1-exp(-x), 0, 20)
	p2= plot(x -> 1-exp(-x/5), 0, 20)
	plot(p1,p2,layout=(2,1))
end

# ╔═╡ ac1c2222-04b5-4af0-a55c-723b1ad57dec
md"""
`-` cdf 해석
- 위(평균이1인지수분포) = 5정도면 거의 cdf의 값이 1에 가까워짐
- 아래(평균이5인지수분포) = 5정도에서 값이 거의 0.63정도임 => 100번뽑으면 5보다 작은게 63개정도..

`-` cdf의 y축에서 랜덤변수를 발생시킨다음 $\rightarrow \downarrow$ 와 같이 이동하여 $x$축에 내린다고 생각해보자.
- 위: 대부분 5이하에 떨어짐
- 아래: 약 63% 정도만 5이하에 떨어짐.
"""

# ╔═╡ 9c2f4080-9dd5-4f85-83f7-42bf3c719e6e
# F(x) = 1-exp(-x)
# F: x-> *(-1) -> exp -> *(-1) -> +1
# Finv: y-> -1 -> *(-1) -> log -> *(-1)
Finv(x) = -log(-(x-1))
 Ginv(x) =-5*log(-(x-1))

# ╔═╡ 23118be8-96de-4df1-8b01-297f6ac3d1be
 Ginv(x) =-5*log(-(x-1))

# ╔═╡ 7520499e-e499-4873-b96a-acbdb777ca80

# ╔═╡ 2a78805b-6f73-4a44-b862-8fa15be1a20a

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
Distributions = "~0.25.52"
Plots = "~1.27.2"
PlutoUI = "~0.7.37"
"""

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

julia_version = "1.7.2"
manifest_format = "2.0"

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

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

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

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

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

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

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

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

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

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

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

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

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

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "96b0bc6c52df76506efc8a441c6cf1adcb1babc4"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.42.0"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# ╔═╡ Cell order:
# ╟─dbf8cdd6-7815-49f8-9dd6-5987000792ce
# ╟─20697867-c74a-4485-bb97-c6a31060669a
# ╟─64e29d20-aaf5-4fa0-ad03-b283dac52dce
# ╠═521890de-ab23-11ec-0c2f-2dcaee6dc1bc
# ╠═3f43a098-0653-4cb3-a48e-673e342ae48b
# ╟─ddae88bf-0b17-40f4-ae33-5cd70aa8a0de
# ╠═c0e571b7-580a-4b25-ba5b-6cb729ec36b9
# ╟─7807af0d-012a-432a-9a64-5df4a633333b
# ╟─713fc6aa-3cdb-49ad-903c-9127452e69b9
# ╟─443fc984-8109-45c3-a6e5-e6e565dd0491
# ╠═6d30adc5-4da9-4e85-bd47-e128de6fffb2
# ╟─9a9ed77b-903e-4587-967c-518343a62e54
# ╠═b0ca1c8f-272d-4a2f-ac4b-10bd659fceda
# ╟─1e943933-7266-4696-9388-6060d0d173e8
# ╠═4520bf81-0491-4a47-99c4-45680964654c
# ╟─ea7e4bb9-5eca-4046-8209-51489614c636
# ╟─8352dd50-1f7d-4e3d-9365-5af24d43547c
# ╟─af1c928e-ab6c-4212-88df-e3109503a157
# ╠═5d376188-fad1-48fb-86a2-7dcc908c337f
# ╠═0ab7a2d2-0d17-4523-8e91-b28624303524
# ╠═f140fb8e-308f-40e4-a199-b0e0d20dc7da
# ╠═a222e7a1-dd18-4416-92eb-320f926d1d77
# ╟─ebe47018-7a6d-42a4-a61a-0079b5c077f8
# ╟─29a2e115-37a7-4f67-a040-a4c1648fdfa6
# ╟─a185e773-f51e-449b-8d40-de819bd4badf
# ╟─885941da-8604-4af0-8b69-6d564348f116
# ╟─2134273b-fca8-4238-b906-f05948fae7d2
# ╠═1da1e41d-73a6-43f4-9d69-3e28b83da8e0
# ╟─caa4ea34-e2ae-455a-88a7-6324ff2cc8ef
# ╠═eb7572bd-63eb-4792-b982-9f320358a99c
# ╟─1c902aac-d633-4cde-95e1-62e42e0ffe87
# ╟─09a0f94b-e90c-4233-933c-7c1237f11658
# ╠═8a1a0dd9-a5fc-4ea1-b026-c47490db1bc3
# ╠═011a3508-939b-47b6-a062-9d61c6ea9eec
# ╟─aef02ab9-4e12-4030-b428-f4aa36f65ba6
# ╠═e5ac4d16-20ed-4c35-832f-b6fd5cfad2dd
# ╠═b0d76b5d-cefa-489e-9963-830d6fb710a0
# ╠═a27efaae-2793-4d8a-8dd6-4900caa80a4a
# ╟─829ad68b-7352-4d18-bcfd-90919b18648c
# ╟─fc1acb74-f856-47c7-b9e1-8d635f0e35ca
# ╟─c2c198a7-4597-49e2-98d7-8776603db7fa
# ╟─cf6a955e-d684-4a16-bc0f-14409f83536a
# ╟─ed777a37-6351-41a1-aed3-86412c9edaac
# ╠═db26ed1c-b01a-4f06-886d-4079cf2c139f
# ╟─7889eeaa-a7b5-4cdd-8b26-c0de2d2981e0
# ╠═23abf65d-276d-4a5d-ae70-b41255b35b48
# ╟─ac1c2222-04b5-4af0-a55c-723b1ad57dec
# ╠═9c2f4080-9dd5-4f85-83f7-42bf3c719e6e
# ╠═23118be8-96de-4df1-8b01-297f6ac3d1be
# ╠═7520499e-e499-4873-b96a-acbdb777ca80
# ╠═2a78805b-6f73-4a44-b862-8fa15be1a20a
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


import Base.isempty

export AbstractCentrallySymmetric,
       center,
       an_element

"""
    AbstractCentrallySymmetric{N<:Real} <: LazySet{N}

Abstract type for centrally symmetric sets.

### Notes

Every concrete `AbstractCentrallySymmetric` must define the following functions:

- `center(::AbstractCentrallySymmetric{N})` -- return the center
    point

```jldoctest; setup = :(using LazySets: subtypes)
julia> subtypes(AbstractCentrallySymmetric)
3-element Array{Any,1}:
 Ball2
 Ballp
 Ellipsoid
```
"""
abstract type AbstractCentrallySymmetric{N<:Real} <: LazySet{N} end

isconvextype(::Type{<:AbstractCentrallySymmetric}) = false

"""
    dim(S::AbstractCentrallySymmetric)

Return the ambient dimension of a centrally symmetric set.

### Input

- `S` -- set

### Output

The ambient dimension of the set.
"""
@inline function dim(S::AbstractCentrallySymmetric)
    return length(center(S))
end

"""
    isbounded(S::AbstractCentrallySymmetric)

Determine whether a centrally symmetric set is bounded.

### Input

- `S` -- centrally symmetric set

### Output

`true` (since a set with a unique center must be bounded).
"""
function isbounded(::AbstractCentrallySymmetric)
    return true
end

"""
    an_element(S::AbstractCentrallySymmetric{N}) where {N<:Real}

Return some element of a centrally symmetric set.

### Input

- `S` -- centrally symmetric set

### Output

The center of the centrally symmetric set.
"""
function an_element(S::AbstractCentrallySymmetric{N}) where {N<:Real}
    return center(S)
end

"""
    isempty(S::AbstractCentrallySymmetric)

Return if a centrally symmetric set is empty or not.

### Input

- `S` -- centrally symmetric set

### Output

`false`.
"""
function isempty(::AbstractCentrallySymmetric)
    return false
end

"""
    isuniversal(S::AbstractCentrallySymmetric{N}, [witness]::Bool=false
               ) where {N<:Real}

Check whether a centrally symmetric set is universal.

### Input

- `S`       -- centrally symmetric set
- `witness` -- (optional, default: `false`) compute a witness if activated

### Output

* If `witness` option is deactivated: `false`
* If `witness` option is activated: `(false, v)` where ``v ∉ S``

### Algorithm

A witness is obtained by computing the support vector in direction
`d = [1, 0, …, 0]` and adding `d` on top.
"""
function isuniversal(S::AbstractCentrallySymmetric{N}, witness::Bool=false
                    ) where {N<:Real}
    if witness
        d = SingleEntryVector{N}(1, dim(S))
        w = σ(d, S) + d
        return (false, w)
    else
        return false
    end
end


# Calculator.jl
module Calculator

export interest, rate

"""
 interest(amount, rate)

Calculate interest from an `amount` and interest rate of `rate`.
"""
function interest(amount, rate)
 return amount * (1 + rate)
end

"""
 rate(amount, interest)

Calculate interest rate based on an `amount` and `interest`.
"""
function rate(amount, interest)
 return interest / amount
end

end # module


using RecipesBase
using ColorTypes
import PlotUtils: cgrad, palette, color_list
using LaTeXStrings

@userplot Staticpressure

@recipe function f(h::Staticpressure;ambient=Pressure(1u"atm"),numarrows=20)
    if length(h.args) != 2
      error("`staticpressure` should be given two arguments.  Got: $(typeof(h.args))")
     end
    depths, fluids = h.args

    _depths = typeof(depths) <: AbstractArray ? value.(depths) : [value(depths)]
    _fluids = typeof(fluids) <: AbstractArray ? fluids : [fluids]
    p = Pressure[]
    d = Depth[]
    fs = Depth(0u"m")
    push!(p,ambient)
    push!(d,fs)
    prepend!(_depths,value(fs))
    dd = ustrip(_depths[end])/numarrows
    for i in eachindex(_fluids)
        di = Depth.(range(0,ustrip(_depths[i+1]-_depths[i]),step=dd)*unit(_depths[i+1]))
        pi = Pressure.(p[end] .+ SpecificWeight(_fluids[i])*di)
        append!(d,Depth.(d[end] .+ di))
        append!(p,pi)
    end

    flip := true
    xguide := "Pressure"
    yguide := "Depth"
    widen := false
    ymirror := true
    @series begin
        p, d
    end
    @series begin
        seriestype := Plots.quiver
        quiver := (-ustrip.(p),zeros(length(p)))
        p,d
    end
end


module ApproxOperator

import Base: +, -, *, getindex, setindex!, getproperty, setproperty!, length, push!, fill!, issubset, intersect
import InteractiveUtils: subtypes

abstract type AbstractNode end
abstract type AbstractElement{T} end
abstract type SpatialPartition end

include("node.jl")
include("snode.jl")
include("approximation.jl")
include("approximation_mf.jl")
include("integration.jl")
include("operation.jl")
include("operation_thin_plate.jl")
include("approximation_rk.jl")
include("import.jl")

export Node, Element, SNode, ReproducingKernel, getnₚ
export importmsh
export RegularGrid
export Operator, prescribe!, issubset, intersect
export set𝓖!
export set𝝭!, set∇𝝭!, set∇²𝝭!, set∇³𝝭!, set∇̃𝝭!, set∇̃²𝝭!, set∇∇̃²𝝭!, set∇̄𝝭!, set𝒏!, set∇𝑢!, get∇𝑢, get𝝐, set_memory_𝝭!

#debug
include("littletools.jl")
export get𝐴,cal𝗠!,cal𝗚!,get𝒙,get∇𝝭,get𝝭,checkIC, checkCC, checkConsistency

end


# Unit tests for Distances

@testset "individual metrics" begin

a = 1
b = 2
@test sqeuclidean(a, a) == 0.
@test sqeuclidean(a, b) == 1.

@test euclidean(a, a) == 0.
@test euclidean(a, b) == 1.

@test cityblock(a, a) == 0.
@test cityblock(a, b) == 1.

@test chebyshev(a, a) == 0.
@test chebyshev(a, b) == 1.

@test chebyshev(a, a) == 0.

@test minkowski(a, a, 2) == 0.
@test minkowski(a, b, 2) == 1.

@test hamming(a, a) == 0
@test hamming(a, b) == 1

bt = [true, false, true]
bf = [false, true, true]
@test rogerstanimoto(bt, bt) == 0
@test rogerstanimoto(bt, bf) == 4./5

p = rand(12)
p[p .< 0.3] = 0.
q = rand(12)
a = [1., 2., 1., 3., 2., 1.]
b = [1., 3., 0., 2., 2., 0.]
for (x, y) in (([4., 5., 6., 7.], [3., 9., 8., 1.]),
                ([4., 5., 6., 7.], [3. 8.; 9. 1.]))
    @test sqeuclidean(x, x) == 0.
    @test sqeuclidean(x, y) == 57.

    @test euclidean(x, x) == 0.
    @test euclidean(x, y) == sqrt(57.)

    @test jaccard(x, x) == 0
    @test jaccard(x, y) == 13./28

    @test cityblock(x, x) == 0.
    @test cityblock(x, y) == 13.

    @test chebyshev(x, x) == 0.
    @test chebyshev(x, y) == 6.

    @test minkowski(x, x, 2) == 0.
    @test minkowski(x, y, 2) == sqrt(57.)

    @test cosine_dist(x, x) ≈ 0.0

    @test_throws DimensionMismatch cosine_dist(1.:2, 1.:3)
    @test cosine_dist(x, y) ≈ (1.0 - 112. / sqrt(19530.))

    @test corr_dist(x, x) < 1.0e-14
    @test corr_dist(x, y) ≈ cosine_dist(x .- mean(x), vec(y) .- mean(y))

    @test chisq_dist(x, x) == 0.
    @test chisq_dist(x, y) == sum((x - vec(y)).^2 ./ (x + vec(y)))

    klv = 0.
    for i = 1 : length(p)
        if p[i] > 0
            klv += p[i] * log(p[i] / q[i])
        end
    end
    @test kl_divergence(p, q) ≈ klv

    pm = (p + q) / 2
    jsv = kl_divergence(p, pm) / 2 + kl_divergence(q, pm) / 2
    @test js_divergence(p, p) ≈ 0.0
    @test js_divergence(p, q) ≈ jsv

    @test spannorm_dist(x, x) == 0.
    @test spannorm_dist(x, y) == maximum(x - vec(y)) - minimum(x - vec(y))

    w = ones(4)
    @test sqeuclidean(x, y) ≈ wsqeuclidean(x, y, w)

    w = rand(size(x))

    @test wsqeuclidean(x, x, w) == 0.
    @test wsqeuclidean(x, y, w) ≈ dot((x - vec(y)).^2, w)

    @test weuclidean(x, x, w) == 0.
    @test weuclidean(x, y, w) == sqrt(wsqeuclidean(x, y, w))

    @test wcityblock(x, x, w) == 0.
    @test wcityblock(x, y, w) ≈ dot(abs(x - vec(y)), w)

    @test wminkowski(x, x, w, 2) == 0.
    @test wminkowski(x, y, w, 2) ≈ weuclidean(x, y, w)

    w = rand(size(a))

    @test whamming(a, a, w) == 0.
    @test whamming(a, b, w) == sum((a .!= b) .* w)
end

end # testset

@testset "NaN behavior" begin

a = [NaN, 0]; b = [0, NaN]
@test isnan(chebyshev(a, b)) == isnan(maximum(a-b))
a = [NaN, 0]; b = [0, 1]
@test isnan(chebyshev(a, b)) == isnan(maximum(a-b))

end #testset

@testset "empty vector" begin

a = Float64[]
b = Float64[]
@test sqeuclidean(a, b) == 0.
@test isa(sqeuclidean(a, b), Float64)
@test euclidean(a, b) == 0.
@test isa(euclidean(a, b), Float64)
@test cityblock(a, b) == 0.
@test isa(cityblock(a, b), Float64)
@test chebyshev(a, b) == 0
@test isa(chebyshev(a, b), Float64)
@test minkowski(a, b, 2) == 0.
@test isa(minkowski(a, b, 2), Float64)
@test hamming(a, b) == 0.0
@test isa(hamming(a, b), Int)

w = Float64[]
@test isa(whamming(a, b, w), Float64)

end # testset

@testset "DimensionMismatch throwing" begin

a = [1, 0]; b = [2]
@test_throws DimensionMismatch sqeuclidean(a, b)
a = [1, 0]; b = [2.0] ; w = [3.0]
@test_throws DimensionMismatch wsqeuclidean(a, b, w)
a = [1, 0]; b = [2.0, 4.0] ; w = [3.0]
@test_throws DimensionMismatch wsqeuclidean(a, b, w)

end # testset

@testset "mahalanobis" begin

x, y = [4., 5., 6., 7.], [3., 9., 8., 1.]
a = [1., 2., 1., 3., 2., 1.]
b = [1., 3., 0., 2., 2., 0.]

Q = rand(length(x), length(x))
Q = Q * Q'  # make sure Q is positive-definite
@test sqmahalanobis(x, x, Q) == 0.
@test sqmahalanobis(x, y, Q) ≈ dot(x - y, Q * (x - y))

@test mahalanobis(x, x, Q) == 0.
@test mahalanobis(x, y, Q) == sqrt(sqmahalanobis(x, y, Q))

end #testset

@testset "bhattacharyya / hellinger" begin

x, y = [4., 5., 6., 7.], [3., 9., 8., 1.]
a = [1., 2., 1., 3., 2., 1.]
b = [1., 3., 0., 2., 2., 0.]
p = rand(12)
p[p .< 0.3] = 0.
q = rand(12)

# Bhattacharyya and Hellinger distances are defined for discrete
# probability distributions so to calculate the expected values
# we need to normalize vectors.
px = x ./ sum(x)
py = y ./ sum(y)
expected_bc_x_y = sum(sqrt(px .* py))
@test Distances.bhattacharyya_coeff(x, y) ≈ expected_bc_x_y
@test bhattacharyya(x, y) ≈ (-log(expected_bc_x_y))
@test hellinger(x, y) ≈ sqrt(1 - expected_bc_x_y)

pa = a ./ sum(a)
pb = b ./ sum(b)
expected_bc_a_b = sum(sqrt(pa .* pb))
@test Distances.bhattacharyya_coeff(a, b) ≈ expected_bc_a_b
@test bhattacharyya(a, b) ≈ (-log(expected_bc_a_b))
@test hellinger(a, b) ≈ sqrt(1 - expected_bc_a_b)

pp = p ./ sum(p)
pq = q ./ sum(q)
expected_bc_p_q = sum(sqrt(pp .* pq))
@test Distances.bhattacharyya_coeff(p, q) ≈ expected_bc_p_q
@test bhattacharyya(p, q) ≈ (-log(expected_bc_p_q))
@test hellinger(p, q) ≈ sqrt(1 - expected_bc_p_q)

# Ensure it is semimetric
@test bhattacharyya(x, y) ≈ bhattacharyya(y, x)

end #testset

macro test_colwise(dist, x, y)
    quote
        local n = size($x, 2)
        r1 = zeros(n)
        r2 = zeros(n)
        r3 = zeros(n)
        for j = 1 : n
            r1[j] = evaluate($dist, ($x)[:,j], ($y)[:,j])
            r2[j] = evaluate($dist, ($x)[:,1], ($y)[:,j])
            r3[j] = evaluate($dist, ($x)[:,j], ($y)[:,1])
        end
        @test colwise($dist, $x, $y) ≈ r1
        @test colwise($dist, ($x)[:,1], $y) ≈ r2
        @test colwise($dist, $x, ($y)[:,1]) ≈ r3
    end
end

@testset "column-wise metrics" begin

m = 5
n = 8
X = rand(m, n)
Y = rand(m, n)
A = rand(1:3, m, n)
B = rand(1:3, m, n)

P = rand(m, n)
Q = rand(m, n)
P[P .< 0.3] = 0.

@test_colwise SqEuclidean() X Y
@test_colwise Euclidean() X Y
@test_colwise Cityblock() X Y
@test_colwise Chebyshev() X Y
@test_colwise Minkowski(2.5) X Y
@test_colwise Hamming() A B

@test_colwise CosineDist() X Y
@test_colwise CorrDist() X Y

@test_colwise ChiSqDist() X Y
@test_colwise KLDivergence() P Q
@test_colwise JSDivergence() P Q
@test_colwise SpanNormDist() X Y

@test_colwise BhattacharyyaDist() X Y
@test_colwise HellingerDist() X Y

w = rand(m)

@test_colwise WeightedSqEuclidean(w) X Y
@test_colwise WeightedEuclidean(w) X Y
@test_colwise WeightedCityblock(w) X Y
@test_colwise WeightedMinkowski(w, 2.5) X Y
@test_colwise WeightedHamming(w) A B

Q = rand(m, m)
Q = Q * Q'  # make sure Q is positive-definite

@test_colwise SqMahalanobis(Q) X Y
@test_colwise Mahalanobis(Q) X Y

end # testset

macro test_pairwise(dist, x, y)
    quote
        local nx = size($x, 2)
        local ny = size($y, 2)
        rxy = zeros(nx, ny)
        rxx = zeros(nx, nx)
        for j = 1 : ny, i = 1 : nx
            rxy[i, j] = evaluate($dist, ($x)[:,i], ($y)[:,j])
        end
        for j = 1 : nx, i = 1 : nx
            rxx[i, j] = evaluate($dist, ($x)[:,i], ($x)[:,j])
        end
        @test pairwise($dist, $x, $y) ≈ rxy
        @test pairwise($dist, $x) ≈ rxx
    end
end

@testset "pairwise metrics" begin

m = 5
n = 8
nx = 6
ny = 8

X = rand(m, nx)
Y = rand(m, ny)
A = rand(1:3, m, nx)
B = rand(1:3, m, ny)

P = rand(m, nx)
Q = rand(m, ny)

@test_pairwise SqEuclidean() X Y
@test_pairwise Euclidean() X Y
@test_pairwise Cityblock() X Y
@test_pairwise Chebyshev() X Y
@test_pairwise Minkowski(2.5) X Y
@test_pairwise Hamming() A B

@test_pairwise CosineDist() X Y
@test_pairwise CorrDist() X Y

@test_pairwise ChiSqDist() X Y
@test_pairwise KLDivergence() P Q
@test_pairwise JSDivergence() P Q

@test_pairwise BhattacharyyaDist() X Y
@test_pairwise HellingerDist() X Y

w = rand(m)

@test_pairwise WeightedSqEuclidean(w) X Y
@test_pairwise WeightedEuclidean(w) X Y
@test_pairwise WeightedCityblock(w) X Y
@test_pairwise WeightedMinkowski(w, 2.5) X Y
@test_pairwise WeightedHamming(w) A B

Q = rand(m, m)
Q = Q * Q'  # make sure Q is positive-definite

@test_pairwise SqMahalanobis(Q) X Y
@test_pairwise Mahalanobis(Q) X Y

end #testset