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

using Markdown
using InteractiveUtils

# ╔═╡ 67477abe-0b56-49a6-818d-3fccc669c6ce
begin
	# using Pkg; Pkg.add("Primes")
	using Primes
end

# ╔═╡ 727bf063-5fef-4126-a615-8a483aab9c99
begin
	# using Pkg; Pkg.add("Plots")
	using Plots
end

# ╔═╡ 80fc74b2-e038-11eb-39c8-fbd84caf7338
md"
# Fun with Arithmetic Derivatives

In this notebook I use Julia and Pluto to entertain the concept of the [Arithmetic Derivative](https://en.wikipedia.org/wiki/Arithmetic_derivative), also known as **number derivative**. It is named due to its axiomatic definition, which shares some of the properties of [real derivatives](https://en.wikipedia.org/wiki/Derivative), except it is a function acting on numbers.

**Definition (Arithmetic Derivative).** _Let $n\in \mathbb{N}$. Then $D:\mathbb{N}\rightarrow\mathbb{N}$ is a function with the following properties:_

* _$D(p)=1$ for any prime $p$, and_

* _$D(pq)=D(p)q+D(q)p$ for any $p,q\in\mathbb{N}$._

Thus we can generalize the arithmetic derivative to the set of natural numbers, ie. if we take $p=q=0$, we obtain that $D(0)=0$, and if we take $p=q=1$ we also obtain that $D(1)=0$. Thus we can calculate the arithmetic derivative for any non-prime natural by means of prime factorization, using the \"power rule\"

$$D(p^n)=np^{n-1}D(p)$$

for $p,n\in\mathbb{N}$.

**Proof (Power rule).** _The \"power rule\" can be proven in a straight-forward manner by means of mathematical induction. We take $n=0$ and see easily that the equation holds, for $D(1)=0$. Next we assume that the equation holds for a given $n$ and apply the induction step $n\rightarrow n+1$:_

$\begin{align}
D(p^{n+1})&=D(p^np)\\
&=D(p^n)p+p^nD(p)\\
&=np^{n-1}D(p)p + p^nD(p)\\
&=np^{n}D(p) + p^nD(p)\\
&=(n+1)p^{n}D(p)
\end{align}$

_Thus the proof is concluded._

Therefore we conclude that the arithmetic derivatie of any natural number $q$ greater than $1$ can be determined using its prime factorization, as in

$$D(q)=D\left(\prod_{j=1}^Np_j^{m_j}\right),$$

where $p_1,...,p_N$ are the prime factors of $q$ and $m_1,...,m_N$ their multiplicities.

Now we have all the building blocks to implement the arithmetic derivative in Julia. To this end, we make use of the Julia package `Primes.jl`, which provides optimized routines to calculate prime numbers and perform prime factorization of `Integer` values.
"

# ╔═╡ 8b83eccb-c629-45ff-b8cb-4b4879664ace
md"
Next we implement $D(p)$ as the Julia function `arithmetic_derivative(::Integer)`. We also implement the generalization upon the set of whole numbers $\mathbb{Z}$, which will be useful later. This generalization rests upon the fact that we consider $D(-q)=-D(q)$. This statement can be proven, but we will simply pretend that the proof is trivial, as in the prime factorization of a negative integer simply yields a factor of $-1$ multiplied with the other (positive) prime factors.
"

# ╔═╡ c0d8d582-364c-4b06-8b08-cf902a359897
function arithmetic_derivative(n::Integer)
	if (n < 0)
		return -arithmetic_derivative(-n)
	end

	if (n < 2)
		return 0
	elseif (isprime(n))
		return 1
	end

	fac = factor(n)
	sum = 0

	map(collect(fac)) do pair
		m = pair.second
		p = pair.first
		sum += m * p^(m-1) * (n ÷ p^m)
	end
	sum
end

# ╔═╡ fa3e00d3-7f8e-4158-98ee-4e22285952d7
md"
Thus we have implemented the basic algorithm that determines the arithmetic derivative of an integer number. Indeed, the arithmetic derivative is sometimes generalized to the set of rational numbers $\mathbb{Q}$, and in this generalization the special case of a negative integer is indeed treated that way (see later).

To test our implementation, we let Julia compute `arithmetic_derivative(n)` for $0\le n \le 25$. We make sure that the values calculated are correct by calculating them by hand ([see this handwritten document](https://cdn.calza.xyz/pics/notebooks/maths/arithmetic_derivatives/Primes_Test.pdf) to check the results). While the standard output is not shown in Pluto, the reader is welcome to download the notebook and run it locally to double-check the test output with the results calculated by hand.
"

# ╔═╡ 49eea834-b3bf-426b-993b-d2ea01da742d
md"
At this point it would be interesting to show the values in a `scatter` plot. Indeed, the arithmetic derivative is a very interesting construction from a number theory standpoint since it is a reasonably simble and yet fun axiomatic construction, however it is difficult to visualize. A plot will hopefully shed more light on the nature of the function $D(p)$.

To this end, we plot the first $80$ values for $D(p)$ against their corresponding values $p$:
"

# ╔═╡ 5fbd5946-9e1e-444b-9ab6-03bfed725620
md"
This is perhaps less useful than we had hoped. We can try to connect the dots to visualize the path of the function $D(n)$:
"

# ╔═╡ 5166fe7d-50fc-4e0e-b839-c0db8bf73b01
md"
This makes a moderate amount of sense because we expect some kind of oscillation as the derivative is bound to rise as $n$ grows, but will keep falling to $1$ as primes are encountered every now and again in order to rise again. The \"amplitude\" of the oscillation (as in, the maximum) is also expected to rise as larger natural numbers are likelier to have a longer array of prime factors and higher multiplicities, which means that the sum due to the power rule can reach higher values.

Julia is more than capable of doing better, though, such that we can venture into higher bounds. Let's try larger values of $N$ (note that the code cells containing the calls to `plot_derivatives(n, connect_dots = true)` have been hidden for aesthetic purposes):
"

# ╔═╡ a3a14843-7b62-4f6f-8e81-ed772e4a27f3
md"
These plots share in the character described earlier, but fail to provide further insights. Interestingly enough, [a generalization over the rational numbers $\mathbb{Q}$ is given](https://en.wikipedia.org/wiki/Arithmetic_derivative#Extensions_beyond_natural_numbers) by the well-known quotient rule

$$D\left(\frac pq\right)=\frac{D(p)q-D(q)p}{q^2}$$

for any rational number expressed as the fraction $\frac pq$. We implement the function `arithmetic_derivative(::Rational)` which calls `arithmetic_derivative(::Integer)`:
"

# ╔═╡ 8af8ac5e-616d-407b-8001-6fa7529c5729
begin
	function arithmetic_derivative(r::Rational)
		p = numerator(r)
		q = denominator(r)
		(arithmetic_derivative(p) * q - arithmetic_derivative(q) * p) / q^2
	end
end

# ╔═╡ 3c9f9459-9828-47ec-bdfc-8bd8a0187553
for n in 0:25
	println("n=$n\tD(n)=$(arithmetic_derivative(n))")
end

# ╔═╡ 1f2295eb-188f-4498-9d8c-1c0f303b2396
function plot_derivatives(N; connect_dots = false, plot_label = nothing, new_plot = true, dots_color = :lightblue)
	mode_func = nothing
	if (connect_dots)
		mode_func = new_plot ? plot : plot!
	else
		mode_func = new_plot ? scatter : scatter!
	end

	if (plot_label === nothing)
		mode_func(0:N, arithmetic_derivative.(0:N), legend=false, color = dots_color)
	else
		mode_func(0:N, arithmetic_derivative.(0:N), label=plot_label, legend=:topleft, color = dots_color)
	end

	xlabel!("Natural number n")
	ylabel!("Number derivative D(n)")
	title!("Plot of the first $N derivatives")
end

# ╔═╡ 9ebc2997-8db4-4aeb-a2e1-4614302d4f56
plot_derivatives(80)

# ╔═╡ f9b2636f-231c-44f4-95a3-013ba47df516
plot_derivatives(80, connect_dots = true)

# ╔═╡ 652bf146-8059-4c52-8f9a-63c3b5a22487
plot_derivatives(100, connect_dots = true)

# ╔═╡ 9cbc71f0-d902-4276-8a3d-b025355dddef
plot_derivatives(200, connect_dots = true)

# ╔═╡ 9c06d571-24e9-4678-9c0f-a7ac01f7d8f6
plot_derivatives(500, connect_dots = true)

# ╔═╡ 0374c7ce-c485-4712-a95b-55e2f3c30a67
plot_derivatives(3000, connect_dots = true)

# ╔═╡ c3248ade-6106-47df-8e6f-38847aee52ce
md"
We can plot this function and overlay it with the scatter plot for $N=100$:
"

# ╔═╡ 2ae73301-1458-4d49-80f4-249706d2eb09
begin
	# Calculate array of rationals to use:

	N_max = 101
	rationals = zeros(Rational, N_max^2 + 1)
	for i in 0:N_max, j in 1:N_max
		rationals[i * (N_max-1) + j] = i//j
	end
	sort!(rationals)

	# Plot the rational derivatives:

	plot(rationals, arithmetic_derivative.(rationals), label = "Rational generalization", color = :lightblue)

	# ----------------------------
	# Overlay integer derivatives:

	plot_derivatives(100, connect_dots = false, plot_label = "Integer derivatives", new_plot = false, dots_color = :lightgreen)
end

# ╔═╡ 7f491dc8-d921-4637-acc9-09ccc3d2b9d6
md"
With this interesting plot we conclude our small exploration.

About this very last plot a few questions could be asked, for example why does the line belonging to the rational generalization become less dense after $n=50$? Could it be due to the way the `rationals` array is generated? If so, are the rationals in the array all equidistant from one another? Or does it depend on the nature of the generalized function $D(p/q)$?
"

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"

[compat]
Plots = "~1.18.1"
Primes = "~0.5.0"
"""

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "cbd58c9deb1d304f5a245a0b7eb841a2560cfec6"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.1+5"

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

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

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

[[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 = "e14907859a1d3aee73a019e7b3c98e9e7b8b5b3e"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.57.3+0"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[Primes]]
git-tree-sha1 = "afccf037da52fa596223e5a0e331ff752e0e845c"
uuid = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
version = "0.5.0"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[[x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d713c1ce4deac133e3334ee12f4adff07f81778f"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2020.7.14+2"

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

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

# ╔═╡ Cell order:
# ╟─80fc74b2-e038-11eb-39c8-fbd84caf7338
# ╠═67477abe-0b56-49a6-818d-3fccc669c6ce
# ╟─8b83eccb-c629-45ff-b8cb-4b4879664ace
# ╠═c0d8d582-364c-4b06-8b08-cf902a359897
# ╟─fa3e00d3-7f8e-4158-98ee-4e22285952d7
# ╠═3c9f9459-9828-47ec-bdfc-8bd8a0187553
# ╟─49eea834-b3bf-426b-993b-d2ea01da742d
# ╠═727bf063-5fef-4126-a615-8a483aab9c99
# ╠═1f2295eb-188f-4498-9d8c-1c0f303b2396
# ╠═9ebc2997-8db4-4aeb-a2e1-4614302d4f56
# ╟─5fbd5946-9e1e-444b-9ab6-03bfed725620
# ╠═f9b2636f-231c-44f4-95a3-013ba47df516
# ╟─5166fe7d-50fc-4e0e-b839-c0db8bf73b01
# ╟─652bf146-8059-4c52-8f9a-63c3b5a22487
# ╟─9cbc71f0-d902-4276-8a3d-b025355dddef
# ╟─9c06d571-24e9-4678-9c0f-a7ac01f7d8f6
# ╟─0374c7ce-c485-4712-a95b-55e2f3c30a67
# ╟─a3a14843-7b62-4f6f-8e81-ed772e4a27f3
# ╠═8af8ac5e-616d-407b-8001-6fa7529c5729
# ╟─c3248ade-6106-47df-8e6f-38847aee52ce
# ╠═2ae73301-1458-4d49-80f4-249706d2eb09
# ╟─7f491dc8-d921-4637-acc9-09ccc3d2b9d6
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# ---
# title: 446. Arithmetic Slices II - Subsequence
# id: problem446
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/arithmetic-slices-ii-subsequence/description/>
# hidden: true
# ---
#
# A sequence of numbers is called arithmetic if it consists of at least three
# elements and if the difference between any two consecutive elements is the
# same.
#
# For example, these are arithmetic sequences:
#
#
#
#     1, 3, 5, 7, 9
#     7, 7, 7, 7
#     3, -1, -5, -9
#
# The following sequence is not arithmetic.
#
#
#
#     1, 1, 2, 5, 7
#
#
#
# A zero-indexed array A consisting of N numbers is given. A **subsequence**
# slice of that array is any sequence of integers (P0, P1, ..., Pk) such that 0
# ≤ P0 < P1 < ... < Pk < N.
#
# A **subsequence** slice (P0, P1, ..., Pk) of array A is called arithmetic if
# the sequence A[P0], A[P1], ..., A[Pk-1], A[Pk] is arithmetic. In particular,
# this means that k ≥ 2.
#
# The function should return the number of arithmetic subsequence slices in the
# array A.
#
# The input contains N integers. Every integer is in the range of -231 and 231-1
# and 0 ≤ N ≤ 1000\. The output is guaranteed to be less than 231-1.
#
#
#
# **Example:**
#
#
#
#     Input: [2, 4, 6, 8, 10]
#
#     Output: 7
#
#     Explanation:
#     All arithmetic subsequence slices are:
#     [2,4,6]
#     [4,6,8]
#     [6,8,10]
#     [2,4,6,8]
#     [4,6,8,10]
#     [2,4,6,8,10]
#     [2,6,10]
#
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


include("fisher_yates!.jl")

"""
    simplexpt(unif)

Takes a vector of length `d-1` of numbers between `0` and `1` and converts it a point on the standard `d-1` dimensional simplex.
"""
function simplexpt(unif)
    d = length(unif) + 1
    T = eltype(unif)
    w = zeros(T, d + 1)
    w[2:d] .= sort(unif)
    w[d+1] = one(T)
    diff(w)
end

"""
    randprobvec([rng], d::Int)

Generates a random probability vector of length `d` using an algorithm by [Smith and Tromble](http://www.cs.cmu.edu/~nasmith/papers/smith+tromble.tr04.pdf).
"""
randprobvec(rng::AbstractRNG, ::Type{N}, d::Int) where {N<:Number} =
    simplexpt(rand(rng, N, d - 1))

randprobvec(rng::AbstractRNG, d::Int) = simplexpt(rand(rng, Float64, d - 1))

randprobvec(::Type{N}, d::Int) where {N<:Number} = randprobvec(Random.GLOBAL_RNG, N, d)

randprobvec(d::Int) = randprobvec(Random.GLOBAL_RNG, Float64, d)

"""
    randprobvec([rng], d::Int, N::T) where {T <: Integer}

Generates a random probability vector of length `d` whose entries are rational numbers with denominator at most `N` using an algorithm by [Smith and Tromble](http://www.cs.cmu.edu/~nasmith/papers/smith+tromble.tr04.pdf).
"""
function randprobvec(rng::AbstractRNG, d::Int, N::T) where {T<:Integer}
    unif = zeros(T, d - 1)
    fisher_yates_sample!(rng, 1:N, unif)
    unif = unif .// N
    simplexpt(unif)
end

randprobvec(d::Int, N::T) where {T<:Integer} = randprobvec(Random.GLOBAL_RNG, d, N)

"""
    randunitary([rng], d::Int)

Generates a unitary matrix of dimension `d` at random according to the Haar measure, using an algorithm described by Maris Ozols in ["How to generate a random unitary matrix"](http://home.lu.lv/~sd20008/papers/essays/Random%20unitary%20%5Bpaper%5D.pdf).
"""
function randunitary(rng::AbstractRNG, d::Int)
    RG = randn(rng, d, d) + im * randn(rng, d, d)
    Q, R = qr!(RG)
    r = diag(R)
    L = Diagonal(r ./ abs.(r))
    return Q * L
end

randunitary(d::Int) = randunitary(Random.GLOBAL_RNG, d)

"""
    randdm([rng], d::Int)

Generates a density matrix of dimension `d` at random.
"""
function randdm(rng, d::Int)
    eigs = Diagonal(randprobvec(rng, d))
    U = randunitary(rng, d)
    ρ = U * eigs * (U')
    return Hermitian((ρ + ρ') / 2)
end

randdm(d::Int) = randdm(Random.GLOBAL_RNG, d)

"""
    randmajpair([rng], [T], d::Int, [N])

Generates a pair of probability vectors `p`, `q` of length `d`
such that `p ≺ q` by rejection sampling. Supply a type `T` to
choose the numeric type of the elements of the vectors, and an
integer `N` to use rational numbers with maximum denominator `N`.
"""
function randmajpair(args...)
    while true
        p = randprobvec(args...)
        q = randprobvec(args...)
        p ≺ q && return p, q
        p ≻ q && return q, p
    end
end

"""
    randincomppair([rng], [T], d::Int, [N]; sortby = entropy)

Generates a pair of probability vectors `p`, `q` of length `d`
such that `!(p ≺ q)` and `!(q ≺ p)` by rejection sampling. Supply
a type `T` to choose the numeric type of the elements of the vectors,
and an integer `N` to use rational numbers with maximum denominator `N`.

Optionally, pass a function (from probability vectors to an
ordered set) to the keyword argument `sortby` to sort the results.
"""
function randincomppair(args...; sortby = entropy)
    while true
        p = randprobvec(args...)
        q = randprobvec(args...)
        p ≺ q && continue
        p ≻ q && continue
        if sortby(p) >= sortby(q)
            return p, q
        else
            return q, p
        end
    end
end


using Plots
using LinearAlgebra

function plot_plane(center, spanning_vector_a, spanning_vector_b, color=:grey, ε = 1/2, frame=false, frame_color=:blue)
	x = [center[1] + ε * spanning_vector_a[1] + ε * spanning_vector_b[1],
	     center[1] - ε * spanning_vector_a[1] + ε * spanning_vector_b[1],
	     center[1] + ε * spanning_vector_a[1] - ε * spanning_vector_b[1],
	     center[1] - ε * spanning_vector_a[1] - ε * spanning_vector_b[1]]

	y = [center[2] + ε * spanning_vector_a[2] + ε * spanning_vector_b[2],
	     center[2] - ε * spanning_vector_a[2] + ε * spanning_vector_b[2],
	     center[2] + ε * spanning_vector_a[2] - ε * spanning_vector_b[2],
	     center[2] - ε * spanning_vector_a[2] - ε * spanning_vector_b[2]]

	z = [center[3] + ε * spanning_vector_a[3] + ε * spanning_vector_b[3],
	     center[3] - ε * spanning_vector_a[3] + ε * spanning_vector_b[3],
	     center[3] + ε * spanning_vector_a[3] - ε * spanning_vector_b[3],
	     center[3] - ε * spanning_vector_a[3] - ε * spanning_vector_b[3]]

	if frame
		path3d!(x,y,z;color=frame_color)
	end
	mesh3d!(x,y,z;color,connections=([0,3],[1,2],[3,0]))
end


using QuadGK, HCubature

"""
Create a new directory `dirname` in the current path.
Append a number to the name if the directory already exists.
"""
function make_new_dir(dirname::String)
    if ispath(dirname)
        k=1
        while ispath(dirname*"_$k")
            k+=1
        end
        return mkdir(dirname*"_$k")
    else
        return mkdir(dirname)
    end
end

"""
    nice_number(x,K::Int)

Return a string which is the rounded value of `x` with `K` (decimal) digits and
if necessary, pad `"0"` to the right so that there are exactly `K` decimal places.
"""
function nice_float2string(x,K::Int)
    integer_part = trunc(Int,x)
    y = x - integer_part
    decimal_part = rpad(round(Int,y*10^K),K,"0")
    return "$(integer_part).$decimal_part"
end

"""
Return the coefficients `c1,c2,λ` of the SOH model computed for the `model` `"Fokker-Planck"` (default)
or `"BGK"` with the concentration parameters `κ`.
These coefficients are given by

c₁ = ∫\\_[0,π] cos(θ)exp(κcos(θ))dθ / ∫\\_[0,π] exp(κcos(θ))dθ

c₂ = ∫\\_[0,π] cos(θ)sin(θ)g(θ)exp(κcos(θ))dθ / ∫\\_[0,π] sin(θ)g(θ)exp(κcos(θ))dθ

λ = 1/κ

where

g(θ) = θ/κ - π/κ * ∫\\_[0,θ] exp(-κcos(ψ))dψ / ∫\\_[0,π] exp(-κcos(ψ))dψ in the Fokker-Planck case

and

g(θ) = 1 in the BGK case.
"""
function coefficients_Vicsek(κ;model="Fokker-Planck")
    λ = 1/κ
    I1 = quadgk(θ->cos(θ)*exp(κ*cos(θ)),0,pi,rtol=1e-5)[1]
    Z1 = quadgk(θ->exp(κ*cos(θ)),0,pi,rtol=1e-5)[1]
    c1 = I1/Z1
    if model=="BGK"
        I2 = quadgk(θ->cos(θ)*sin(θ)^2*exp(κ*cos(θ)),0,pi,rtol=1e-5)[1]
        Z2 = quadgk(θ->sin(θ)^2*exp(κ*cos(θ)),0,pi,rtol=1e-5)[1]
        c2 = I2/Z2
        return c1,c2,λ
    elseif model=="Fokker-Planck"
        Zinv = quadgk(θ->exp(-κ*cos(θ)),0,pi,rtol=1e-5)[1]
        I21 = quadgk(θ->θ*cos(θ)*sin(θ)*exp(κ*cos(θ)),0,pi,rtol=1e-5)[1]/κ
        I22 = pi/(κ*Zinv) * hcubature(x->cos(x[1])*sin(x[1])*exp(κ*cos(x[1]))*exp(-κ*cos(x[2]))*(x[2]<x[1]),(0,0),(pi,pi),rtol=1e-3)[1]
        I2 = I21 - I22
        Z21 = quadgk(θ->θ*sin(θ)*exp(κ*cos(θ)),0,pi,rtol=1e-5)[1]/κ
        Z22 = pi/(κ*Zinv) * hcubature(x->sin(x[1])*exp(κ*cos(x[1]))*exp(-κ*cos(x[2]))*(x[2]<x[1]),(0,0),(pi,pi),rtol=1e-3)[1]
        Z2 = Z21 - Z22
        c2 = I2/Z2
        return c1,c2,λ
    else
        ArgumentError("Model not defined!")
    end
end


# calculate eigen values/vectors
# plot eigen vectors on the contour

module CalcPlotEigen
    using Plots, Base.Iterators, LinearAlgebra
    pyplot()

    function pdf(x, mu, cov_mat)
        # determinant
        det_mat = det(cov_mat)

        diff_x = x - mu
        return exp(-((diff_x'/cov_mat)*diff_x)/2)/(2*pi*sqrt(det_mat))
    end

    function main(is_test=false)
        # x-y grid
        vx = 0:200
        vy = 0:100

        # mu, covariance matrix
        mu = [150; 50]
        cov = [100 -25*sqrt(3); -25*sqrt(3) 50]

        # calculate eigen values/vectors
        eig_vals, eig_vecs = eigen(cov)
        println("Eigen Values: $(eig_vals)")
        println("Eigen Vectors: $(eig_vecs)")
        println("Eigen Vector1: $(eig_vecs[:, 1])")
        println("Eigen Vector2: $(eig_vecs[:, 2])")

        # long or short
        if eig_vals[1] > eig_vals[2]
            long_val = eig_vals[1]
            long_vec = eig_vecs[:, 1]
            short_val = eig_vals[2]
            short_vec = eig_vecs[:, 2]
        else
            long_val = eig_vals[2]
            long_vec = eig_vecs[:, 2]
            short_val = eig_vals[1]
            short_vec = eig_vecs[:, 1]
        end
        println("Long Value: $(long_val)")
        println("Long Vector: $(long_vec)")
        println("Short Value: $(short_val)")
        println("Short Vector: $(short_vec)")

        # plot
        z = [pdf([x; y], mu, cov) for x in vx, y in vy]
        contour(vx, vy, z', c=:haline, aspect_ratio=:equal)
        long = 2 * sqrt(long_val) * long_vec
        short = 2 * sqrt(short_val) * short_vec
        quiver!([mu[1]], [mu[2]], quiver=([long[1]], [long[2]]),
                c=:red, aspect_ratio=:equal)
        quiver!([mu[1]], [mu[2]], quiver=([short[1]], [short[2]]),
                c=:blue, aspect_ratio=:equal)

        if is_test == false
            save_path = joinpath(split(@__FILE__, "src")[1], "src/prob_stats/multi_dim_gauss_dist/eigen/contour_eigen.png")
            savefig(save_path)
        end
    end
end


@registerN Hahn AbstractCDOP3 α β 𝐍
export Hahn

"""
    Hahn{α,β,𝐍}

References: [Koekoek and Swarttouw §1.5](https://arxiv.org/pdf/math/9602214.pdf)

!!! note
    In  [Koekoek and Swarttouw](https://arxiv.org/pdf/math/9602214.pdf) sections 1.3, 1.4, and 1.6  are other  Hahn-type polynomials,  not  implemented here.

"""
Hahn

abcde(::Type{<:Hahn{α,β,𝐍}})  where {α,β,𝐍} = NamedTuple{(:a,:b,:c,:d,:e)}((1, -(β+𝐍+1), 0,  α+β+2, -𝐍*(α+1)))

basis_symbol(::Type{<:Hahn{α,β,𝐍}}) where {α,β,𝐍} = "Q⁽ᵅᵝ⁾"
Polynomials.domain(::Type{<:Hahn{α, β, 𝐍}}) where {α, β,  𝐍} = Polynomials.Interval(0, 𝐍)
weight_function(::Type{<:Hahn{α,β,𝐍}}) where {α,β,𝐍} = x -> generalized_binomial(α+x,x) * generalized_binomial(𝐍+β-x,𝐍-x)

# use   recurrence relation  1.5.3 of Koekoek and Swarttouw
# -xQᵢ = AQᵢ₊₁ -  (A+C)Qᵢ + CQᵢ₋₁
#  or Qᵢ₊₁ = (1/A ⋅ x - (1 + C/A)) Qᵢ + C/A ⋅ Qᵢ₋₁
# Using kᵢ = A⁻¹ᵢ₋₁ ⋅ A⁻¹ᵢ₋₂ ⋯ A⁻¹₀; so kᵢ₊₁/kᵢ = A⁻¹ᵢ, kᵢ₊₁/kᵢ₋ᵢ = A⁻¹ᵢA⁻¹ᵢ₋₁
#
# function Aᵢ⁻¹(n,α,β,𝐍)
#     num = (2n+α+β + 1) * (2n+α+β+2)
#     den = (n+α + β + 1)*(n + α + 1) *(𝐍-n)
#     num/den
# end

# define kn, k1k_1 through defaults
function k1k0(P::Type{<:Hahn{α,β, 𝐍}}, n::Int) where {α,β,𝐍}

    num  = (2*n + α + β + 1)*(2*n + α + β + 2)
    den = (-n + 𝐍)*(n + α + 1)*(n + α + β + 1)

    -(one(eltype(P))*num)/den

end

# end

function classical_hypergeometric(::Type{<:Hahn{α,β,𝐍}}, n::Int, x)  where {α,β,𝐍}
    pFq((-n, -x, n+1+α+β),  (α+1, -𝐍),  1)
end


using Tables
using DataFrames
using StatsPlots
using BenchmarkTools

function sir_ode!(du,u,p,t)
    (S,I,R) = u
    (β,c,γ) = p
    N = S+I+R
    @inbounds begin
        du[1] = -β*c*I/N*S
        du[2] = β*c*I/N*S - γ*I
        du[3] = γ*I
    end
    nothing
end;

t0 = 0.0
δt = 0.1
tmax = 40.0
u0 = [990.0,10.0,0.0] # S,I,R
p = [0.05,10.0,0.25]; # β,c,γ

function euler(f, u0, p, δt, t0, tmax)
    t = t0 # Initialize time
    u = copy(u0) # Initialize struct parametric inherited
    du = zeros(length(u0)) # Initialize derivatives
    f(du,u,p,t)
    sol = [] # Store output
    times = [] # Store times
    push!(sol,copy(u))
    push!(times,t)
    # Main loop
    while t < tmax
        t = t + δt # Update time
        u .= u .+ du.*δt # Update state
        sir_ode!(du,u,p,t) # Update derivative
        push!(sol,copy(u)) # Store output
        push!(times,t) # Store time
    end
    sol = hcat(sol...) # Convert to matrix
    return times, sol
end;

times, sol = euler(sir_ode!, u0, p, δt, t0, tmax);

df = DataFrame(Tables.table(sol'))
rename!(df,["S","I","R"])
df[!,:t] = times;

@df df plot(:t,
    [:S :I :R],
    label=["S" "I" "R"],
    xlabel="Time",
    ylabel="Number")

@benchmark euler(sir_ode!, u0, p, δt, t0, tmax)


# Nonlinear curve fitting.

"""
# Nonlinear multi-variable least squares

Uses a Newton like algorithm to compute the least squares fit
of a model

## Parameters:

 * `x` an array where each colum is a variable
 * `fun` The function that should be fitted (more later on)
 * `a0` An initial guess for each fitting parameter
 * `eps` Convergence criterion
 * `maxiter` Maximum number of iterations

## Specifying the model

The model is specified in argument `fun` that should be callable according
to the following signature:

`r = fun(x, a)`

where both `x` and `a` are vectors

The way the algorithm is implemented allows for the function to be implicit. This means
that we are trying to fit the relationship `fun(x, a) = 0`.

## Initial guess

The initial guess is important and should be as close as possible to the expeted values. It should, at least, give an order of magnitude of each parameter. Zero values are not recommended since in this
case no order of magnitude is available and it is assumed. The initial step is assumed to be `a0/10`.

## Convergence criterion

The initial guess `a0` will probably not fit the data very well. The largest value
of `maxerr = maxabs(fun(x, a0))` (maximum error) is used as a reference and
every time a new approximation is computed, if the change in paramaters is compared to this
reference error. If it is small, `maxabs(da) < eps*maxerr`, the algorithm converged.

## Return values

A tuple containing:

 * The estimated coefficients
 * A `Bool` stating whether the algorithm converged
 * Number of iterations it took to converge.

"""
function nonlinear_fit(x, fun, a0, eps=1e-8, maxiter=200)

    na = length(a0)
    np = size(x, 1)
    nv = size(x, 2)
    for i in 1:na
        if a0[i] == 0
            a0[i] = 0.01
        end
    end

    da = a0 / 10
    a1 = zeros(na)
    a = zeros(na)
    for k = 1:na
        a1[k] = a0[k] - da[k]
    end
    xp = zeros(nv)
    A = zeros(np, na)
    r0 = zeros(np)
    r1 = zeros(np)
    for p = 1:np
        for k = 1:nv
            xp[k] = x[p,k]
        end
        r0[p] = fun(xp, a0)
        r1[p] = fun(xp, a1)
    end
    maxerr = maximum(abs, [maximum(abs, r0), maximum(abs, r1)])
    iter = 1
    convergence = false
    for i = 1:maxiter
        iter = i
        for p = 1:np
            for k = 1:nv
                xp[k] = x[p,k]
            end
            for k = 1:na
                aa = a1[k]
                a1[k] = a1[k] + da[k]
                r = fun(xp, a1)
                a1[k] = aa

                A[p,k] = -(r1[p] - r) / da[k]
            end
        end
        da = A \ r1
        for k = 1:na
            a1[k] = a1[k] - da[k]
        end
        for p = 1:np
            r0[p] = r1[p]
            for k = 1:nv
                xp[k] = x[p,k]
            end
            r1[p] = fun(xp, a1)
        end
        if maximum(abs, r1) < eps * maxerr
            convergence = true
            break
        end
    end

    return a1, convergence, iter
end

"""
   a = gauss_newton_fit(x, y, fun, ∇fun!, a0[[, eps,] maxiter])

Gauss-Newton nonlinear least squares. Given vectors `x` and `y`, the tries to fit parameters `a` to
a function `f` using least squares approximation:

 ``y = f(x, a₁, ..., aₙ)``

For more general approximations, see [`gauss_newton_fit`](@ref).

### Arguments:

 * `x` Vector with x values
 * `y` Vector with y values
 * `fun` a function that is called as `fun(x, a)` where `a` is a vector of parameters.
 * `∇fun!` A function that calculares the derivatives with respect to parameters `a`
 * `a0` Vector with the initial guesses of the parameters
 * `eps` Maximum residual for convergence
 * `maxiter` Maximum number of iterations for convergence

## Return value

A vector with the convrged array. If no convergence is achieved, the function throws an error.

## Specification of the fitting function

The function that should be fitted shoud be specified by Julia funcion with the following signature:

```julia
fun(x::T, a::AbstractVector{T}) where {T<:Number}
```

The derivatives with respect to each fitting parameter `a[i]` should have the following signature:

```julia
∇fun!(x::T, a::AbstractVector{T}, df::AbstractVector{T}) where {T<:Number}
```

No return value is expected and the derivatives are returned in argument `df`.

## Initial approximation (guess)

If the initial approximation is not good enough, divergence is possible.

**Careful** with parameters close to 0. The initial guess should never be 0.0 because the initial
value of the parameter is used as reference value for computing resiuduals.

## Convergence criteria

The argumento `maxiter` specifies the maximum number of iterations that should be carried out.
At each iteration,

``aₖⁿ⁺¹ = aₖⁿ + δₖ``

Convergence is achieved when

``|δᵏ / aₖ⁰| < ε``

## Example
```julia
x = 1.0:10.0
a = [3.0, 2.0, 1.0]
y = a[1] + a[2]*x + a[3]*x^2
fun(x, a) = a[1] + a[2]*x + a[3]*x^2

function ∇fun!(x, a, df)
    df[1] = 1.0
    df[2] = x
    df[3] = x^2
end

a = gauss_newton_fit(x, y, fun, ∇fun!, [0.5, 0.5, 0.5], 1e-8, 30)
```
"""
function gauss_newton_fit(x::AbstractVector{T}, y::AbstractVector{T}, fun, ∇fun!, a0::AbstractVector{T},
                          eps=1e-8, maxiter=200) where {T<:Number}
    P = length(x) # Number of points
    N = length(a0) # Number of parameters

    xi = zero(T)
    df = zeros(T, N)
    a = zeros(T, N)
    for i in 1:N
        a[i] = a0[i]
        if a[i] == 0
            a[i] = 0.01
        end
    end

    A = zeros(T, N, N)
    b = zeros(T, N)

    δref = abs.(a)
    maxerr = zero(T)
    for iter = 1:maxiter
        A .= zero(T)
        b .= zero(T)
        for i in 1:P
            xi = x[i]
            yi = y[i]
            f = fun(xi, a) - yi
            ∇fun!(xi, a, df)

            # Assemble LHS
            for k in 1:N
                for j in 1:N
                    A[j,k] += df[j] * df[k]
                end
            end
            # Assemble RHS
            for j in 1:N
                b[j] -= f * df[j]
            end
        end

        δ = A\b
        a .+= δ

        # Verify convergence:
        maxerr = maximum(abs, δ./δref)
        if maxerr < eps
            return(a)
        end

    end

    error("gauss_newton_fit failed to converge in $maxiter iterations with relative residual of $maxerr !")

    return(a)
end

"""
   a = gauss_newton_generic_fit(x, y, fun, ∇fun!, a0[[, eps,] maxiter])

Gauss-Newton nonlinear least squares. Given matrix `x` the function tries to fit parameters `a` to
an implicit function `f` using least squares approximation:

``f(x₁,..., xₘ, a₁, ..., aₙ) = 0``

This function is very similar to [`gauss_newton_fit`](@ref) but more generic. It doesn't limit the number of
independent variables and doesn't require a `y` LHS.

### Arguments:

 * `x` Matrix where each column is a variable
 * `
 * `fun` a function that is called as `fun(x, a)` where `a` is a vector of parameters.
 * `∇fun!` A function that calculares the derivatives with respect to parameters `a`
 * `a0` Vector with the initial guesses of the parameters
 * `eps` Maximum residual for convergence
 * `maxiter` Maximum number of iterations for convergence

## Return value

A vector with the convrged array. If no convergence is achieved, the function throws an error.

## Specification of the fitting function

The function that should be fitted shoud be specified by Julia funcion with the following signature:

```julia
fun(x::T, a::AbstractVector{T}) where {T<:Number}
```

The derivatives with respect to each fitting parameter `a[i]` should have the following signature:

```julia
∇fun!(x::T, a::AbstractVector{T}, df::AbstractVector{T}) where {T<:Number}
```

No return value is expected and the derivatives are returned in argument `df`.

## Initial approximation (guess)

If the initial approximation is not good enough, divergence is possible.

**Careful** with parameters close to 0. The initial guess should never be 0.0 because the initial
value of the parameter is used as reference value for computing resiuduals.

## Convergence criteria

The argumento `maxiter` specifies the maximum number of iterations that should be carried out.
At each iteration,

``aₖⁿ⁺¹ = aₖⁿ + δₖ``

Convergence is achieved when

``|δᵏ / aₖ⁰| < ε``

## Example
```julia
x = 1.0:10.0
a = [3.0, 2.0, 1.0]
y = a[1] + a[2]*x + a[3]*x^2
fun(x, a) = a[1] + a[2]*x + a[3]*x^2

function ∇fun!(x, a, df)
    df[1] = 1.0
    df[2] = x
    df[3] = x^2
end

a = gauss_newton_fit(x, y, fun, ∇fun!, [0.5, 0.5, 0.5], 1e-8, 30)
```

"""
function gauss_newton_generic_fit(x::AbstractMatrix{T}, fun, ∇fun!, a0::AbstractVector{T},
                          eps=1e-8, maxiter=200) where {T<:Number}
    P = size(x, 1) # Number of points
    M = size(x, 2) # Number of columns (variables)
    N = length(a0) # Number of parameters

    xi = zeros(T, M)
    df = zeros(T, N)
    a = zeros(T, N)
    for i in 1:N
        a[i] = a0[i]
        if a[i] == 0
            a[i] = 0.01
        end
    end

    A = zeros(T, N, N)
    b = zeros(T, N)

    δref = abs.(a)
    maxerr = zero(T)
    for iter = 1:maxiter
        A .= zero(T)
        b .= zero(T)
        for i in 1:P
            for k in 1:M
                xi[k] = x[i,k]
            end
            f = fun(xi, a)

            ∇fun!(xi, a, df)
            # Assemble LHS
            for k in 1:N
                for j in 1:N
                    A[j,k] += df[j] * df[k]
                end
            end
            # Assemble RHS
            for j in 1:N
                b[j] -= f * df[j]
            end
        end

        δ = A\b
        a .+= δ

        # Verify convergence:
        maxerr = maximum(abs, δ./δref)
        if maxerr < eps
            return(a)
        end

    end

    error("gauss_newton_fit failed to converge in $maxiter iterations with relative residual of $maxerr !")

    return(a)
end

"""
   a = secant_nls_fit(x, y, fun, ∇fun!, a0[[, eps,] maxiter])

Secant/Gauss-Newton nonlinear least squares. DOESN'T NEED A DERIVATIVE FUNCTION. Given vectors `x` and `y`, the tries to fit parameters `a` to
a function `f` using least squares approximation:

 ``y = f(x, a₁, ..., aₙ)``

For more general approximations, see [`gauss_newton_fit`](@ref).

### Arguments:

 * `x` Vector with x values
 * `y` Vector with y values
 * `fun` a function that is called as `fun(x, a)` where `a` is a vector of parameters.
 * `∇fun!` A function that calculares the derivatives with respect to parameters `a`
 * `a0` Vector with the initial guesses of the parameters
 * `eps` Maximum residual for convergence
 * `maxiter` Maximum number of iterations for convergence

## Return value

A vector with the convrged array. If no convergence is achieved, the function throws an error.

## Specification of the fitting function

The function that should be fitted shoud be specified by Julia funcion with the following signature:

```julia
fun(x::T, a::AbstractVector{T}) where {T<:Number}
```

The derivatives with respect to each fitting parameter `a[i]` should have the following signature:

```julia
∇fun!(x::T, a::AbstractVector{T}, df::AbstractVector{T}) where {T<:Number}
```

No return value is expected and the derivatives are returned in argument `df`.

## Initial approximation (guess)

If the initial approximation is not good enough, divergence is possible.

**Careful** with parameters close to 0. The initial guess should never be 0.0 because the initial
value of the parameter is used as reference value for computing resiuduals.

## Convergence criteria

The argumento `maxiter` specifies the maximum number of iterations that should be carried out.
At each iteration,

``aₖⁿ⁺¹ = aₖⁿ + δₖ``

Convergence is achieved when

``|δᵏ / aₖ⁰| < ε``

## Example
```julia
x = 1.0:10.0
a = [3.0, 2.0, 1.0]
y = a[1] + a[2]*x + a[3]*x^2
fun(x, a) = a[1] + a[2]*x + a[3]*x^2

a = secant_nls_fit(x, y, fun, ∇fun!, [0.5, 0.5, 0.5], 1e-8, 30)
```
"""

function secant_nls_fit(x::AbstractVector{T}, y::AbstractVector{T}, fun, aguess::AbstractVector{T},
                          eps=1e-8, maxiter=200) where {T<:Number}
    P = length(x) # Number of points
    N = length(aguess) # Number of parameters

    xi = zero(T)
    df = zeros(T, N)
    a = zeros(T, N)
    for i in 1:N
        a[i] = aguess[i]
        if a[i] == 0
            a[i] = 0.01
        end
    end

    δ = a .* (one(T)/20)
    f1 = zeros(T, P)
    a .+= δ
    A = zeros(T, N, N)
    b = zeros(T, N)

    δref = abs.(a)
    maxerr = zero(T)
    for iter = 1:maxiter

        A .= zero(T)
        b .= zero(T)
        f1 .= fun.(x, Ref(a))
        for i in 1:P
            xi = x[i]
            yi = y[i]
            f = f1[i] - yi
            for k in 1:N
                a[k] -= δ[k]
                df[k] = (f1[i] - fun(xi, a)) / δ[k]
                a[k] += δ[k]
            end
            # Assemble LHS
            for k in 1:N
                for j in 1:N
                    A[j,k] += df[j] * df[k]
                end
            end
            # Assemble RHS
            for j in 1:N
                b[j] -= f * df[j]
            end
        end
        δ = A\b
        a .+= δ
        # Verify convergence:
        maxerr = maximum(abs, δ./δref)
        if maxerr < eps
            return(a)
        end

    end

    error("gauss_newton_fit failed to converge in $maxiter iterations with relative residual of $maxerr !")

    return(a)
end


# Generate a random graph with n vertices where each edge is included with probability p.
function erdos_renyi_graph(g::GT, n::Integer, p::Real; has_self_loops=false) where {GT<:AbstractGraph}
    for i=1:n
        start_ind = is_directed(g) ? 1 : i
        for j=start_ind:n
            if(rand() <= p && (i != j || has_self_loops))
                add_edge!(g, i, j)
            end
        end
    end
    return g
end

# Convenience function with a default graph type.
function erdos_renyi_graph(n::Integer, p::Real; is_directed=true, has_self_loops=false)
    g = simple_inclist(n, is_directed=is_directed)
    erdos_renyi_graph(g, n, p, has_self_loops=has_self_loops)
end

# Generate a 'small world' random graph based on the Watts-Strogatz model.
# Written with much reference to the implementation from GraphStream <http://graphstream-project.org>.
# The resulting graph has n vertices,
#   Each vertex has a base degree of k  (n > k, k >= 2, k must be even.)
#   There is a beta chance of each edge being 'rewired'
function watts_strogatz_graph(g::GT, n::Integer, k::Integer, beta::Real) where {GT<:AbstractGraph}
    # When attributes are restored, we can enable placing the vertices around a circle.
    # This is nice for explicit visualization of the model.
    #space = linspace(0,2*pi,n+1)
    #for v in vertices(g)
    #    attrs = attributes(v)
    #    x = round((cos(space[i]) + 1)*100,2)
    #    y = round((sin(space[i]) + 1)*100,2)
    #    attrs["pos"] = "$(x),$(y)"
    #end

    # Link each node to the k/2 nodes next to it in each direction.
    # For each vertex in the graph:
    for i in 1:n
        # For the next k/2 higher indexed nodes:
        for j in 1:div(k,2)
            # Either make an edge to the neighbor or a long edge across the graph.
            if rand() > beta
                add_edge!(g, i, ((i+j - 1) % n) + 1 )
            else
                # Add a random link across the graph and delete the link to the neighbor.
                while true
                    target = rand(1:(n-1))
                    if (target >= i)
                        target += 1
                    end
                    if !(target in out_neighbors(i,g))
                        add_edge!(g,i, target)
                        break
                    end
                end
            end
        end
    end
    g
end

# Convenience function with a default graph type.
function watts_strogatz_graph(n::Integer, k::Integer, beta::Real)
    g = simple_inclist(n, is_directed=false)
    watts_strogatz_graph(g,n,k,beta)
end


#All these examples of path are in 4 stages

using Distributions, Random
rng = MersenneTwister(01012019);

"""
	gaussian_path1D()

Returns a '4x1' dimensional array of Gaussian random walk
"""
function gaussian_path1D()
    return vcat(0.0, cumsum(randn(rng, 3, 1), dims = 1)) #4 stages
end

"""
	gaussian_path2D()

Returns a '4x2' dimensional array of Gaussian random walk
"""
function gaussian_path2D()
    gsmatrix = randn(rng, 4, 2) * [1.0 0.0 ; 0.9 0.3] #will create an (dimension x nstages) matrix
    gsmatrix[1,:] .= 0.0
    return cumsum(gsmatrix .+ [1.0 0.0], dims = 1)
end

"""
	running_maximum1D()

Returns a '4x1' dimensional array of Running Maximum process.
"""
function running_maximum1D()
    rmatrix = vcat(0.0, cumsum(randn(rng, 3, 1), dims = 1))
    for i = 2 : 4
        rmatrix[i] = max.(rmatrix[i-1], rmatrix[i])
    end
    return rmatrix
end

"""
	running_maximum2D()

Returns a '4x2' dimensional array of Running Maximum process.
"""
function running_maximum2D()
    rmatrix = vcat(0.0, cumsum(randn(rng, 3, 1), dims = 1))
    rmatrix2D = zeros(4, 2)
    rmatrix2D[:,1] .= vec(rmatrix)
    for j = 2 : 2
        for i = 2 : 4
            rmatrix2D[i,j] = max.(rmatrix[i-1], rmatrix[i])
        end
    end
    return rmatrix2D * [1.0 0.0; 0.9 0.3]
end

"""
	path()

Returns a sample of stock prices following the a simple random random process.
"""
function path()
    return  100 .+ 50 * vcat(0.0, cumsum(randn(rng, 3, 1), dims = 1))
end


export color, chromatic_number

"""
`color(G,k)`: Return a `k`-coloring of `G` (or error if none exists).
"""
function color(G::SimpleGraph, k::Int)
    VV = vlist(G)
    EE = elist(G)
    n = NV(G)
    m = NE(G)

    if k<1
        error("Number of colors must be positive")
    end

    err_msg = "This graph is not " * string(k) * " colorable"

    # Special cases that don't require integer programming

    # see if we know the chromatic number already
    if cache_check(G,:chromatic_number)
        chi = cache_recall(G,:chromatic_number)
        if k<chi
            error(err_msg)
        end
    end

    result = Dict{vertex_type(G),Int}()

    if k==1
        if NE(G) > 0
            error(err_msg)
        end
        for v in VV
            result[v] = 1
        end
        return result
    end

    if k==2
        return two_color(G)
    end

    MOD = Model(get_solver())

    @variable(MOD, x[VV,1:k], Bin)

    for v in VV
        @constraint(MOD, sum(x[v,i] for i=1:k) == 1)
    end

    for e in EE
        u = e[1]
        v = e[2]
        for i=1:k
            @constraint(MOD, x[u,i] + x[v,i] <= 1)
        end
    end

    optimize!(MOD)
    status = Int(termination_status(MOD))

    if status != 1
        error(err_msg)
    end

    X = value.(x)

    for v in VV
        for c = 1:k
            if X[v,c] > 0
                result[v] = c
            end
        end
    end

    return result
end

function chromatic_number(G::SimpleGraph, verb::Bool=false)::Int
    if cache_check(G,:chromatic_number)
        return cache_recall(G,:chromatic_number)
    end

    if NV(G) == 0
        return 0
    end
    if NE(G) == 0
        return 1
    end

    # lower bound: larger of clique size or n/alpha
    n = NV(G)
    alf = length(max_indep_set(G))
    lb1 = Int(floor(n/alf))
    lb2 = length(max_clique(G))
    lb = max(lb1,lb2)

    # upper bound: try a random greedy coloring
    f = greedy_color(G)
    ub = maximum(values(f))

    k =  chromatic_number_work(G,lb,ub,verb)
    cache_save(G,:chromatic_number,k)
    return k
end

function chromatic_number_work(G::SimpleGraph, lb::Int, ub::Int, verb::Bool)::Int
    if verb
        print("$lb <= chi(G) <= $ub")
    end
    if lb == ub
        if verb
            println("\tand we're done")
        end
        return lb
    end

    mid = Int(floor((ub+lb)/2))

    if verb
        print("\tlooking for a $mid coloring")
    end

    try
        f = color(G,mid)  # success
        if verb
            println("\tfound")
        end
        return chromatic_number_work(G,lb,mid,verb)
    catch
    end
    if verb
        println("\tno such coloring")
    end
    return chromatic_number_work(G,mid+1,ub,verb)
end


@model function gmm_tarray(x, K)
    N = length(x)

    # Cluster centers.
    μ ~ filldist(Normal(), K)

    # Cluster association prior.
    w ~ Dirichlet(K, 1.0)

    # Cluster assignments.
    z = tzeros(Int, N)
    for n = 1:N
        z[n] ~ DiscreteNonParametric(1:K, w)
    end

    # Observations.
    for n = 1:N
        x[n] ~ Normal(μ[z[n]], 1.0)
    end
end

gmm_tarray_example(x = [0.1, -0.05, 1.0], K = 2) = gmm_tarray(x, K)

# K clusters, each one around i for i = 1:K with variance 0.5
@model function hmm_tarray(x, K, ::Type{T}=Float64) where {T<:Real}
    N = length(x)

    # State sequence.
    s = tzeros(Int, N)

    # Emission matrix.
    m = Vector{T}(undef, K)

    # Transition matrix.
    T = Vector{Vector{T}}(undef, K)

    # Assign distributions to each element
    # of the transition matrix and the
    # emission matrix.
    for i = 1:K
        T[i] ~ Dirichlet(K, 1.0)
        m[i] ~ Normal(i, 0.5)
    end

    # Observe each point of the input.
    s[1] ~ Categorical(K)
    x[1] ~ Normal(m[s[1]], 0.1)

    for i = 2:N
        s[i] ~ Categorical(T[s[i-1]])
        x[i] ~ Normal(m[s[i]], 0.1)
    end
end

hmm_tarray_example(x = [0.1, -0.05, 1.0], K = 2) = hmm_tarray(x, K)

function stickbreak(v)
    K = length(v) + 1
    cumprod_one_minus_v = cumprod(1 .- v)

    return map(1:K) do k
        if k == 1
            v[1]
        elseif k == K
            cumprod_one_minus_v[K - 1]
        else
            v[k] * cumprod_one_minus_v[k - 1]
        end
    end
end

@model function imm_stick_tarray(y, α, K)
    N = length(y)
    crm = DirichletProcess(α)

    v ~ filldist(StickBreakingProcess(crm), K - 1)
    w = stickbreak(v)

    # Cluster assignments
    z = tzeros(Int, N)
    for n = 1:N
        z[n] ~ Categorical(w)
    end

    # Cluster centers
    L = identity(K)
    μ ~ filldist(Normal(), L)

    # Observations
    for n = 1:N
        y[n] ~ Normal(μ[z[n]], 1.0)
    end
end

imm_stick_tarray_example(y = data_neal, α = α_neal, K = 10) = imm_stick_tarray()


using LinearAlgebra
using SpecialFunctions
using OffsetArrays

const Γ = gamma

# Coefficients in the Jacobi polynomial recurrence relations.
a(α, β, n) = 2/(2n+α+β) * √(n * (n+α+β) * (n+α) * (n+β) / (2n+α+β-1) / (2n+α+β+1))
b(α, β, n) = -(α^2 - β^2) / (2n+α+β) / (2n+α+β+2)

" Evaluates the Jacobi polynomial Pₙᵅᵝ(x). "
function jacobi(x, α, β, n::Int)
    Pᵅᵝ = n <= 1 ? OffsetArray(zeros(2), 0:1) : OffsetArray(zeros(n+1), 0:n)
    Pᵅᵝ[0] = √(2.0^-(α+β+1) * Γ(α+β+2) / Γ(α+1) / Γ(β+1))
    Pᵅᵝ[1] = Pᵅᵝ[0]/2 * √((α+β+3) / (α+1) / (β+1)) * ((α+β+2)*x + α - β)
    for n′ in 1:n-1
        Pᵅᵝ[n′+1] = ((x - b(α,β,n′)) * Pᵅᵝ[n′] - a(α,β,n′) * Pᵅᵝ[n′-1]) / a(α, β, n′+1)
    end
    return Pᵅᵝ[n]
end

" Evaluates the derivative of the Jacobi polynomial Pₙᵅᵝ(x). "
∂jacobi(x, α, β, n::Int) = n == 0 ? 0 : √(n * (n+α+β+1)) * jacobi(x, α+1, β+1, n-1)

" Guassian quadrature points and weights for the Jacobi polynomial Pₙᵅᵝ. "
function jacobi_gauss_quadrature(α, β, N)
    N == 0 && return [(α-β) / (α+β+2)], [2]

    # Form symmetric matrix from recurrence.
    dv = OffsetArray(zeros(N+1), 0:N)  # diagonal vector
    ev = OffsetArray(zeros(N+1), 0:N)  # sub/super-diagonal vector

    for n in 0:N
        dv[n] = b(α, β, n)
        ev[n] = a(α, β, n)
    end

    # Create full matrix combining the two.
    # Need to pass arrays that are not offset.
    J = SymTridiagonal(dv[0:N], ev[1:N])
    (α + β) ≈ 0 && (J[1, 1] = 0)

    # Compute quadrature points and weights by eigenvalue solve.
    x, V = eigen(J)
    w = @. V[1, :]^2 * 2^(α+β+1) / (α+β+1)
    @. w *= factorial(α) * factorial(β) / factorial(α+β)

    return x, w
end

" Guass-Labatto quadrature points for the Jacobi Polynomial Pₙᵅᵝ. "
function jacobi_gauss_lobatto(α, β, N)
    N == 0 && error("What are you doing? Gauss-Lobatto points only make sense if N >= 1.")
    N == 1 && return [-1, 1]

    x = zeros(N+1)
    x[1], x[N+1] = -1, 1

    x_GQ, _ = jacobi_gauss_quadrature(α+1, β+1, N-2)
    x[2:N] .= x_GQ

    return x
end


function bs(O::Option)
    d1 = (log(O.s / O.k) + (O.r + O.σ * O.σ / 2) * O.t) / (O.σ * √O.t)
    d2 = d1 - O.σ * √O.t

    if O.call_or_put == "Put"
        return CND(-d2) * O.k * exp(-O.r * O.t) - CND(-d1) * O.s
    else
        return O.s * CND(d1) - exp(-O.r * O.t) * O.k * CND(d2)
    end
end

function CND(x::Float64)
    a1 = 0.31938153
    a2 = -0.356563782
    a3 = 1.781477937
    a4 = -1.821255978
    a5 = 1.330274429
    l = abs(x)
    k = 1 / (1 + 0.2316419 * l)

    CND = 1 - 1 / sqrt(2 * π) * exp(-l^2 / 2) * (a1 * k + a2 * k^2 + a3 * k^3 + a4 * k^4 + a5 * k^5)

    if x < 0
        return 1 - CND
    end

    return CND
end