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

using Markdown
using InteractiveUtils

# ╔═╡ 346a0b1f-0c48-4837-99e0-7bfd8b6d9def
begin
	using PlutoUI
end

# ╔═╡ 23304c9e-2f57-11ec-1412-896477910082
md"""$(PlutoUI.LocalResource("./figs/Fig-ThreeUnitCycle.png"))
__Fig 1__. Schematic of a hypothetical three unit thermodynamic cycle (units=1,2,3) connected by streams 1,2 and 3.
"""

# ╔═╡ 70a9bb50-7f81-446c-a2d4-69cc97734536
md"""

Consider the hypothetical three unit cycle shown in Fig 1. Each unit has a single input and a single output.

__Assumptions__
* the cycle operates at steady-state;
* the working fluid has a mass flow rate of $\dot{m}$ = 1.5 kg s$^{-1}$;
* each unit can have both heat and work terms;
* neglect changes in the kinetic and potential energy in the system and streams.

"""

# ╔═╡ 2172328e-0de6-4ede-9acb-cd89cf545e9a
md"""

##### Problem:
Apply the steady-state open energy balance equation to each unit:

$$\begin{equation}
	\dot{Q}_{\star}+\dot{W}_{sh,\star} + \sum_{s\in{streams}}v_{s}\dot{m}_{s}H_{s} = 0
\end{equation}$$
where $\dot{Q}_{\star}$ and $\dot{W}_{sh,\star}$ denote the rate of heat and work for unit $\star$.
Show that the _total_ rate of heat transfer must equal the _total_ rate of work for the cycle:

$$\begin{equation}
\sum_{cycle}\dot{Q}_{\star} + \sum_{cycle}\dot{W}_{sh,\star} = 0
\end{equation}$$

Is the summation relationship given above true for an _arbitrary cyclic process_ in which each unit has a single input and a single output (explain your answer)?
"""

# ╔═╡ 659ab1d4-1015-465d-af19-d896070367e8
md"""

##### Solution:

First, because each unit has a single input and a single ouput we know that $\dot{m}_{1}=\dot{m}_{2}=\dot{m}_{3}\equiv\dot{m}$. Next, apply the open energy balance to each of the hypothetical process units in Fig 1.

$$\begin{eqnarray}
i = 1\qquad~\dot{Q}_{1}+\dot{W}_{sh,1}+\dot{m}\left(H_{3} - H_{1}\right) & = & 0\\
i = 2\qquad~\dot{Q}_{2}+\dot{W}_{sh,2}+\dot{m}\left(H_{1} - H_{2}\right) & = & 0\\
i = 3\qquad~\dot{Q}_{3}+\dot{W}_{sh,3}+\dot{m}\left(H_{2} - H_{3}\right) & = & 0\\
\end{eqnarray}$$

If we sum these equations over the units ($\star_{u}$) in the cycle, we get the _total_ work and heat or:

$$\sum_{u=1}^{3}\dot{Q}_{u}+\sum_{u=1}^{3}\dot{W}_{sh,u}+\dot{m}\left(H_{3} - H_{1} + H_{1} - H_{2} + H_{2} - H_{3}\right) = 0$$

Because we have a cycle, the enthalpy terms sum to 0 i.e., our system state ends where it starts, which leaves:

$$\sum_{u=1}^{3}\dot{Q}_{u}+\sum_{u=1}^{3}\dot{W}_{sh,u} = 0$$

"""

# ╔═╡ 828b05ea-2f9d-4c09-9dc4-d0332a64e607
md"""
###### Is the summation relationship given above true for an arbitrary cyclic process?
Yes, for a cyclic process the summation relationship is always true.

__Why__? Enthaply is a _state function_ that is path independent, meaning, changes in enthalpy do not depend upon the path taken only on the endpoints. Thus, since we have a cyclic process we always end where we start, hence no overall enthalpy change.

__Hmmm__: I don't know that about Enthalpy (yet), so how should we approach this problem?
If you create _any_ steady-state cyclic process where which unit has a single input, and a single output, the enthaply terms will sum to zero, hence the heat and work summation expression holds. In symbols, for _any_ steady-state cyclic process where which unit has a single input, and a single output, the following must be true:

$$\sum_{u}\sum_{s}v_{su}\dot{m}_{s}H_{s} = 0$$

where $v_{su}$ denotes a direction parameter that relates stream $s$ and unit $u$;

* if stream $s$ __enters__ unit $u$ then $v_{su}$ = 1
* if stream $s$ __exits__ unit $u$ then $v_{su}$ = -1
* if stream $s$ __is not connected to__ unit $u$ then $v_{su}$ = 0

Since each unit has a single input and output, $\dot{m}_{1}=\dot{m}_{2}=\dots\equiv{\dot{m}}$ which gives:

$$\dot{m}\left(\sum_{u}\sum_{s}v_{su}H_{s}\right) = 0$$

We know that $\dot{m}\neq{0}$, thus:

$$\left(\sum_{u}\sum_{s}v_{su}H_{s}\right) = 0$$
"""

# ╔═╡ df49d1e4-6ba1-4e8f-80fa-4d9833ef6d0a
md"""
##### Test cases:
"""

# ╔═╡ a80b3623-ebdc-4f32-a120-4f24087c446d
md"""$(PlutoUI.LocalResource("./figs/Fig-Linear-TwoNode-F21.png"))
__Test case I__: Linear two node network
"""

# ╔═╡ ac4ee87c-9b2b-441c-b084-5fdd80a16b02
md"""
Let's run out the sum for the linear two-node network test case. For this case:

$$\sum_{u=1}^{2}\sum_{s=1}^{3}v_{su}H_{s} = \left(v_{11}+v_{12}\right)H_{1} + \left(v_{21}+v_{22}\right)H_{2}+\left(v_{31}+v_{32}\right)H_{3}$$

* Value for $$\left(v_{11}+v_{12}\right)$$ = 1
* Value for $$\left(v_{21}+v_{22}\right)$$ = 0
* Value for $$\left(v_{31}+v_{32}\right)$$ = -1

which gives:

$$\sum_{u=1}^{2}\sum_{s=1}^{3}v_{su}H_{s} = \left(H_{1} - H_{3}\right)\neq{0}$$

"""

# ╔═╡ 61e17d6d-a898-408a-9d22-6c74ca8a8d8f
md"""$(PlutoUI.LocalResource("./figs/Fig-Cycle-TwoNode-F21.png"))
__Test case II__: Two node cyclic network
"""

# ╔═╡ 5ff6b6c8-b772-48b6-9030-c2911a2753ae
md"""
Let's run out the sum for the cyclic two-node network test case. For this case:

$$\sum_{u=1}^{2}\sum_{s=1}^{2}v_{su}H_{s} = \left(v_{11}+v_{12}\right)H_{1} + \left(v_{21}+v_{22}\right)H_{2}$$

* Value for $$\left(v_{11}+v_{12}\right)$$ = 0
* Value for $$\left(v_{21}+v_{22}\right)$$ = 0

which gives:

$$\sum_{u=1}^{2}\sum_{s=1}^{2}v_{su}H_{s} = 0$$

We could extend this to higher-order cycles, but the summation relationship will continue to hold.

"""

# ╔═╡ b80e0292-e9cf-4653-b63c-8fbf48bb47c9

# ╔═╡ 8b76c6e5-3623-4dc4-a0bd-bf442f0e5195
html"""
<style>
main {
    max-width: 1200px;
    width: 95%;
    margin: auto;
    font-family: "Roboto, monospace";
}

a {
    color: blue;
    text-decoration: none;
}

.H1 {
    padding: 0px 30px;
}
</style>"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
PlutoUI = "~0.7.16"
"""

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

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

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

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

[[HypertextLiteral]]
git-tree-sha1 = "f6532909bf3d40b308a0f360b6a0e626c0e263a8"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.1"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# ╔═╡ Cell order:
# ╟─23304c9e-2f57-11ec-1412-896477910082
# ╟─70a9bb50-7f81-446c-a2d4-69cc97734536
# ╟─2172328e-0de6-4ede-9acb-cd89cf545e9a
# ╟─659ab1d4-1015-465d-af19-d896070367e8
# ╟─828b05ea-2f9d-4c09-9dc4-d0332a64e607
# ╟─df49d1e4-6ba1-4e8f-80fa-4d9833ef6d0a
# ╟─a80b3623-ebdc-4f32-a120-4f24087c446d
# ╟─ac4ee87c-9b2b-441c-b084-5fdd80a16b02
# ╟─61e17d6d-a898-408a-9d22-6c74ca8a8d8f
# ╟─5ff6b6c8-b772-48b6-9030-c2911a2753ae
# ╠═b80e0292-e9cf-4653-b63c-8fbf48bb47c9
# ╟─346a0b1f-0c48-4837-99e0-7bfd8b6d9def
# ╟─8b76c6e5-3623-4dc4-a0bd-bf442f0e5195
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# Sparsification sketch of a matrix
#
# Danny Perry (dperry@cs.utah.edu)
# April 2015
#

using Debug

# Random sparsification that preserves eigen spectrum of A
# Theorem 1 from Achlioptas, "Fast computation of low rank matrix approximations", STOC 2001
# http://web.stanford.edu/class/ee378b/papers/achlioptas.pdf
#
# A - matrix to sparsify
# sparsity - controls amount sparsity, 1 <= s <= (m+n)/(log(m+n)^6)
#            sparse terms will be chosen w.p. (1-1/s)
#
function RandomSparsify(A, sparsity)
	n = size(A,1)
	d = size(A,2)
	s = sparsity

	entries = n*d
	I = zeros(Int32,int(.5*entries))
	J = zeros(Int32,int(.5*entries))
	V = zeros(typeof(A[1,1]), int(.5*entries))
	k = int(0)

	for i=1:n
		for j=1:d
			if rand() < 1.0/s
				#B[i,j] = s * A[i,j]
				k += 1
				if k > size(I,1)
					append!(I, zeros(typeof(I[1]),int(.25*size(I,1))))
					append!(J, zeros(typeof(J[1]),int(.25*size(J,1))))
					append!(V, zeros(typeof(V[1]),int(.25*size(V,1))))
				end
				I[k] = i
				J[k] = j
				V[k] = s*A[i,j]
			#else
			# B[i,j] = 0  # implied via sparse matrix
			end
		end
	end
	println("sparse k: ", k)

	I = I[1:k]
	J = J[1:k]
	V = V[1:k]

	B = sparse(I,J,V,n,d)
end

# Random sparsification that preserves eigen spectrum of A
# Theorem 2 from Achlioptas, "Fast computation of low rank matrix approximations", STOC 2001
# http://web.stanford.edu/class/ee378b/papers/achlioptas.pdf
#
# A - matrix to sparsify
#
# returns sparse binary matrix B with non-zero entries = 2,
# however estimates should use matrix  b*(B .- 1), so that each
# entry is either +b or -b.
#
# returns B,b
function RandomRoundSparsify(A)
	n = size(A,1)
	d = size(A,2)

	entries = n*d
	I = zeros(Int32,int(.5*entries))
	J = zeros(Int32,int(.5*entries))
	V = zeros(typeof(A[1,1]), int(.5*entries))
	k = int(0)

	b = maximum(abs(A))
	for i=1:n
		for j=1:d
			p = .5 + A[i,j] / (2*b)
			if rand() < p
				#B[i,j] = 2
				k += 1
				if k > size(I,1)
					append!(I, zeros(typeof(I[1]),int(.25*size(I,1))))
					append!(J, zeros(typeof(J[1]),int(.25*size(J,1))))
					append!(V, zeros(typeof(V[1]),int(.25*size(V,1))))
				end
				I[k] = i
				J[k] = j
				V[k] = 2

			#else
			# B[i,j] = 0  # implied via sparse matrix
			end
		end
	end

	I = I[1:k]
	J = J[1:k]
	V = V[1:k]

	B = sparse(I,J,V,n,d)
	B,b
end


#=

  Euler #22 in Julia.

  Problem 22:
  """
  Using names.txt (right click and 'Save Link/Target As...'), a 46K
  text file containing over five-thousand first names, begin by sorting
  it into alphabetical order. Then working out the alphabetical value
  for each name, multiply this value by its alphabetical position in the
  list to obtain a name score.

  For example, when the list is sorted into alphabetical order, COLIN,
  which is worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in
  the list. So, COLIN would obtain a score of 938 53 = 49714.

  What is the total of all the name scores in the file?")
  """

  This Julia program was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#

include("Euler.jl")

function to_code(s)
    return [Int(s[i])-64 for i in 1:length(s)]
end

# 0.00231866s
function euler22a()
    words = sort(split(readline("euler22_names.txt"),","))
    a = 1;
    s = 0;
    for word in words
        word = replace(word,"\""=>"")
        s += a*sum(to_code(word));
        a += 1;
    end
    return s;

end

# 0.02438064s
function euler22b()
    a = 1
    return sum(map(word->(word=replace(word,"\""=>"");a=a+1; (a-1)*sum(to_code(word))
                   ),
                  sort(split(readline("euler22_names.txt"),","))))

end

# Nicer chaining.
# Note the () before |>sum
# 0.02813539s
function euler22c()
    a = 1
    return (sort(split(readline("euler22_names.txt"),",")).|>word->(
                    word=replace(word,"\""=>"");
                    a=a+1; (a-1)*sum(to_code(word))
                    ))|>sum
end

run_euler(euler22a)
# run_euler(euler22b)
# run_euler(euler22c)


# 2020.02.17, Антонюк В.А.

M(P) = [Int(sign(P[i]-P[j])) for i=1:length(P), j=1:length(P)]

function TrueIndices!(P::Vector{T}) where T<:Int
    i = length(P)-1
    while i > 0
        Curr = P[i]
        for j = i+1:length(P)
            P[j] += (Curr<=P[j])
        end
        i -= 1
    end
end

function HeuristicIterations!(R::Matrix{T}) where T<:Int
    @assert size(R)[1] == size(R)[2]
    Alt = Vector{T}()
    while !isempty(R)
        Size = size(R)[1]
        (RMin,Pos) = findmin([sum(R[i,:]) for i=1:Size])
        push!(Alt, Pos)
        R = reshape([R[i,j] for i=1:Size, j=1:Size
                              if i!=Pos && j!=Pos],(Size-1,Size-1))
    end
    TrueIndices!(Alt)
    return Alt
end

function OptimizeRanking!(P::Vector{T}, R::Matrix{T}) where T<:Int
    N = size(P)[1]
    @assert size(R)[1] == size(R)[2] == N
    k = N-1
    while k > 0
        if R[P[k],P[k+1]] > R[P[k+1],P[k]]    # 2020.04.01
            P[k], P[k+1] = P[k+1], P[k]
            k -= 1         # 2020.04.01
        end
        k -= 1
    end
    return P
end

# 2020.02.27, Антонюк В.А.

# 2020.03.07
#using LinearAlgebra
@static if VERSION >= v"0.7" using LinearAlgebra end
#

function HeuristicIterations(R::Matrix{T}) where T<:Int
    @assert size(R)[1] == size(R)[2]
    (N, _), TMax = size(R), typemax(T)
    E, Dropped = ones(T, N), Vector{T}()
    while length(Dropped) < N
        V = [(E[k]==0 ? TMax : R[k,:]⋅E) for k = 1:N]
        (_, Pos) = findmin(V)
        E[Pos] = 0
        push!(Dropped, Pos)
    end
    return Dropped
end


module ContinuedFractions

import Base: iterate, IteratorSize, IteratorEltype, HasLength, HasEltype,
length, eltype, collect

export ContinuedFraction, quotients, convergents, ConvergentIterator

struct ContinuedFraction{T}
	quotients::Vector{T}
	ContinuedFraction{T}(qs::Vector{T}) where {T<:Integer} = new{T}(qs)
end

eltype(it::ContinuedFraction{T}) where {T} = T

quotients(cf::ContinuedFraction) = cf.quotients

struct ConvergentIterator{T}
    qs::Vector{T}
	ConvergentIterator{T}(qs::Vector{T}) where {T<:Integer} = new{T}(qs)
end
ConvergentIterator(qs::Vector{T}) where {T<:Integer} = ConvergentIterator{T}(qs)

_done(it::ConvergentIterator, state::Int) = state > length(it.qs)
function Base.iterate(it::ConvergentIterator, state::Int = 1)
	_done(it, state) && return nothing
	convergent = Rational(ContinuedFraction(it.qs[1:state]))
    convergent, state + 1
end

IteratorSize(::Type{ConvergentIterator}) = HasLength()
IteratorEltype(::Type{ConvergentIterator}) = HasEltype()

length(it::ConvergentIterator) = length(it.qs)
eltype(::Type{ConvergentIterator{T}}) where {T} = Rational{T}
eltype(it::ConvergentIterator{T})     where {T} = Rational{T}
collect(it::ConvergentIterator{T})    where {T} = collect(Rational{T}, it)

convergents(cf::ContinuedFraction) = convergents(quotients(cf))
convergents(qs::Vector{<:Integer}) = ConvergentIterator(qs)

function Base.Rational(cf::ContinuedFraction)
    qs = quotients(cf)
    isempty(qs) && return 0 // 1
    length(qs) == 1 && return qs[1] // 1

    remainder = qs[2:end]
    rat = Rational(ContinuedFraction(remainder))
    (qs[1] * rat.num + rat.den) // rat.num
end

function ContinuedFraction(rat::Rational{T}) where {T<:Integer}
    a = div(rat.num, rat.den)
    a * rat.den == rat.num && return ContinuedFraction(T[a])  # Exact!

    cf = ContinuedFraction(rat.den//(rat.num - a*rat.den))
    pushfirst!(quotients(cf), a) # insert at index 1
    cf
end

# let rationalize handle conversion from floating point
ContinuedFraction(x::BigFloat) = ContinuedFraction(rationalize(BigInt, x))
ContinuedFraction(x::AbstractFloat) = ContinuedFraction(rationalize(x))

ContinuedFraction(x::Integer) = ContinuedFraction([x])
ContinuedFraction(qs::Vector{T}) where {T<:Integer} = ContinuedFraction{T}(qs)

end # module ContinuedFractions


using CubicSplineNR
using Base.Test

x = collect(linspace(0, 2pi, 30))
fx = sin.(x)
fx2 = cos.(x)

sin_itp = CubicSpline(x, fx)
sin_mut = CubicSpline(x, fx, mutable=true)

sin_itp(3.14)
sin_mut(3.14)

@time update!(sin_mut, fx2)

sin_mut(3.14)


## Methods for root finding which use a bracket

## Bisection_method for floats and int

# type to throw on succesful convergence
type StateConverged
    x0::Real
end

# type to throw on failure
type ConvergenceFailed
    reason::AbstractString
end

## From Jason Merrill https://gist.github.com/jwmerrill/9012954
## cf. http://squishythinking.com/2014/02/22/bisecting-floats/
# Alternative "mean" definition that operates on the binary representation
# of a float. Using this definition, bisection will never take more than
# 64 steps.
function _middle(x::Float64, y::Float64)
  # Use the usual float rules for combining non-finite numbers
  if !isfinite(x) || !isfinite(y)
    return x + y
  end

  # Always return 0.0 when inputs have opposite sign
  if sign(x) != sign(y) && x != 0.0 && y != 0.0
    return 0.0
  end

  negate = x < 0.0 || y < 0.0

  xint = reinterpret(UInt64, abs(x))
  yint = reinterpret(UInt64, abs(y))
  unsigned = reinterpret(Float64, (xint + yint) >> 1)

  return negate ? -unsigned : unsigned
end

"""

Find zero using modified bisection method for Float64 arguments.

This is guaranteed to take no more than 64 steps. The `a42` alternative usually has
fewer iterations, but this seems to find the value with fewer function evaluations.

Terminates with `x1` when the bracket length of `[x0,x2]` is `<= max(xtol, xtolrel*abs(x1))` where `x1` is the midpoint . The tolerances can be set to 0, in which case, the termination occurs when `nextfloat(x0) = x2`.

The bracket `[a,b]` must be bounded.

"""
function find_zero(f, a::Float64, b::Float64; xtol::Real=0.0, xtolrel::Real=0.0, verbose::Bool=false)

    if (xtol < 0.0) | (xtolrel < 0.0)
        error("Tolerances must be non-negative")
    end

    x0, y0 = a, f(a)
    x2, y2 = b, f(b)

    y0 == 0 && return a
    y2 == 0.0 && return b
    if sign(y0) * sign(y2) > 0
        error("[a,b] is not a bracket")
    end

    x1 = _middle(x0, x2)
    y1 = f(x1)

    while x0 < x1 && x1 < x2
        if sign(y0) == sign(y1)
            x0, x2 = x1, x2
            y0, y2 = y1, y2
        else
            x0, x2 = x0, x1
            y0, y2 = y0, y1
        end

        x1 = _middle(x0, x2)
        y1 = f(x1)

        sign(y1) == 0 && return x1
        abs(x2 - x0) <= max(xtol, xtolrel*abs(x1)) && return(x1)

        verbose && println(@sprintf("xi =%18.15f,\t f(xi) = %18.15f", x1, f(x1)))
    end

    return abs(y0) < abs(y2) ? x0 : x2
end

"""

find_zero using Algorithm 4.2  of Alefeld, Potra and Shi for Big numbers

"""
typealias BigSomething @compat  Union{BigFloat, BigInt}
function find_zero(f, a::BigSomething, b::BigSomething;
                   xtol=zero(a),
                   maxeval::Int=100,
                   verbose::Bool=false
                   )
    a42(f, a, b; xtol=xtol, maxeval=maxeval, verbose=verbose)
end

"""

Finds the root of a continuous function within a provided
interval [a, b], without requiring derivatives. It is based on algorithm 4.2
described in: 1. G. E. Alefeld, F. A. Potra, and Y. Shi, "Algorithm 748:
enclosing zeros of continuous functions," ACM Trans. Math. Softw. 21,
327–344 (1995).

input:
    `f`: function to find the root of
    `a`, `b`: the initial bracket, with: a < b, f(a)*f(b) < 0
    `xtol`: acceptable error (it's safe to set zero for machine precision)
    `maxeval`:  maximum number of iterations

output:
    an estimate of the zero of f

By John Travers

"""
function a42(f, a, b;
                   xtol=zero(float(a)),
                   maxeval::Int=15,
                   verbose::Bool=false
                   )
    if a >= b || sign(f(a))*sign(f(b)) >= 0
        error("on input a < b and f(a)f(b) < 0 must both hold")
    end
    if xtol < 0.0
        error("tolerance must be >= 0.0")
    end

    c = secant(f, a, b)
    a42a(f, a, b, c,
         xtol=xtol, maxeval=maxeval, verbose=verbose)
end

"""

Split Alefeld, F. A. Potra, and Y. Shi algorithm 4.2 into a function
where `c` is passed in.

Solve f(x) = 0 over bracketing interval [a,b] starting at c, with a < c < b

"""
function a42a(f, a, b, c=(0.5)*(a+b);
                   xtol=zero(float(a)),
                   maxeval::Int=15,
                   verbose::Bool=false
              )

    try
        # re-bracket and check termination
        a, b, d = bracket(f, a, b, c, xtol)
        ee = d
        for n = 2:maxeval
            # use either a cubic (if possible) or quadratic interpolation
            if n > 2 && distinct(f, a, b, d, ee)
                c = ipzero(f, a, b, d, ee)
            else
                c = newton_quadratic(f, a, b, d, 2)
            end
            # re-bracket and check termination
            ab, bb, db = bracket(f, a, b, c, xtol)
            eb = d
            # use another cubic (if possible) or quadratic interpolation
            if distinct(f, ab, bb, db, eb)
                cb = ipzero(f, ab, bb, db, eb)
            else
                cb = newton_quadratic(f, ab, bb, db, 3)
            end
            # re-bracket and check termination
            ab, bb, db = bracket(f, ab, bb, cb, xtol)
            # double length secant step; if we fail, use bisection
            u = abs(f(ab)) < abs(f(bb)) ? ab : bb
            cb = u - 2*f(u)/(f(bb) - f(ab))*(bb - ab)
            ch = abs(cb - u) > (bb - ab)/2 ? ab + (bb - ab)/2 : cb
            # re-bracket and check termination
            ah, bh, dh = bracket(f, ab, bb, ch, xtol)
            # if not converging fast enough bracket again on a bisection
            if bh - ah < 0.5*(b - a)
                a = ah
                b = bh
                d = dh
                ee = db
            else
                ee = dh
                a, b, d = bracket(f, ah, bh, ah + (bh - ah)/2,
                                  xtol)
            end

            verbose && println(@sprintf("a=%18.15f, n=%s", a, n))

            if nextfloat(ch) * prevfloat(ch) <= 0
                throw(StateConverged(ch))
            end
            if nextfloat(a) >= b
                throw(StateConverged(a))
            end
        end
        throw(ConvergenceFailed("More than $maxeval iterations before convergence"))
    catch ex
        if isa(ex, StateConverged)
            return ex.x0
        else
            rethrow(ex)
        end
    end
    throw(ConvergenceFailed("More than $maxeval iterations before convergence"))
end

# calculate a scaled tolerance
# based on algorithm on page 340 of [1]
function tole(a, b, fa, fb, tol)
    u = abs(fa) < abs(fb) ? abs(a) : abs(b)
    2u*eps(1.0) + tol
end

# bracket the root
# inputs:
#     - f: the function
#     - a, b: the current bracket with a < b, f(a)f(b) < 0
#     - c within (a,b): current best guess of the root
#     - tol: desired accuracy
#
# if root is not yet found, return
#     ab, bb, d
# with:
#     - [ab, bb] a new interval within [a, b] with f(ab)f(bb) < 0
#     - d a point not inside [ab, bb]; if d < ab, then f(ab)f(d) > 0,
#       and f(d)f(bb) > 0 otherwise
#
# if the root is found, throws a StateConverged instance with x0 set to the
# root.
#
# based on algorithm on page 341 of [1]
function bracket(f, a, b, c, tol)

    fa = f(a)
    fb = f(b)

    if !(a <= c <= b)
        error("c must be in (a,b)")
    end
    delta = 0.7*tole(a, b, fa, fb, tol)
    if b - a <= 4delta
        c = (a + b)/2
    elseif c <= a + 2delta
        c = a + 2delta
    elseif c >= b - 2delta
        c = b - 2delta
    end
    fc = f(c)
    if fc == 0
        throw(StateConverged(c))
    elseif sign(fa)*sign(fc) < 0
        aa = a
        bb = c
        db = b
    else
        aa = c
        bb = b
        db = a
    end
    faa = f(aa)
    fbb = f(bb)
    if bb - aa < 2*tole(aa, bb, faa, fbb, tol)
        x0 = abs(faa) < abs(fbb) ? aa : bb
        throw(StateConverged(x0))
    end
    aa, bb, db
end

# take a secant step, if the resulting guess is very close to a or b, then
# use bisection instead
function secant(f, a, b)
    c = a - f(a)/(f(b) - f(a))*(b - a)
    tol = eps(1.0)*5
    if c <= a + abs(a)*tol || c >= b - abs(b)*tol
        return a + (b - a)/2
    end
    return c
end

# approximate zero of f using quadratic interpolation
# if the new guess is outside [a, b] we use a secant step instead
# based on algorithm on page 330 of [1]
function newton_quadratic(f, a, b, d, k::Int)
    fa = f(a)
    fb = f(b)
    fd = f(d)
    B = (fb - fa)/(b - a)
    A = ((fd - fb)/(d - b) - B)/(d - a)
    if A == 0
        return secant(f, a, b)
    end
    r = A*fa > 0 ? a : b
    for i = 1:k
        r -= (fa + (B + A*(r - b))*(r - a))/(B + A*(2*r - a - b))
    end
    if isnan(r) || (r <= a || r >= b)
        r = secant(f, a, b)
    end
    return r
end

# approximate zero of f using inverse cubic interpolation
# if the new guess is outside [a, b] we use a quadratic step instead
# based on algorithm on page 333 of [1]
function ipzero(f, a, b, c, d)
    fa = f(a)
    fb = f(b)
    fc = f(c)
    fd = f(d)
    Q11 = (c - d)*fc/(fd - fc)
    Q21 = (b - c)*fb/(fc - fb)
    Q31 = (a - b)*fa/(fb - fa)
    D21 = (b - c)*fc/(fc - fb)
    D31 = (a - b)*fb/(fb - fa)
    Q22 = (D21 - Q11)*fb/(fd - fb)
    Q32 = (D31 - Q21)*fa/(fc - fa)
    D32 = (D31 - Q21)*fc/(fc - fa)
    Q33 = (D32 - Q22)*fa/(fd - fa)
    c = a + (Q31 + Q32 + Q33)
    if (c <= a) || (c >= b)
        c = newton_quadratic(f, a, b, d, 3)
    end
    return c
end

# floating point comparison function
function almost_equal(x, y)
    const min_diff = realmin(Float64)*32
    abs(x - y) < min_diff
end

# check that all interpolation values are distinct
function distinct(f, a, b, d, e)
    f1 = f(a)
    f2 = f(b)
    f3 = f(d)
    f4 = f(e)
    !(almost_equal(f1, f2) || almost_equal(f1, f3) || almost_equal(f1, f4) ||
      almost_equal(f2, f3) || almost_equal(f2, f4) || almost_equal(f3, f4))
end

"""
Searches for zeros  of `f` in an interval [a, b].

Basic algorithm used:

* split interval [a,b] into `no_pts` subintervals.
* For each bracketing interval finds a bracketed zero.
* For other subintervals does a quick search with a derivative-free method.

If there are many zeros relative to the number of points, the process
is repeated with more points, in hopes of finding more zeros for
oscillating functions.

"""
function find_zeros(f, a::Real, b::Real, args...;
                    no_pts::Int=100,
                    ftol::Real=10*eps(), reltol::Real=10*eps(),
                    kwargs...)

    a, b = a < b ? (a,b) : (b,a)
    rts = eltype(promote(float(a),b))[]
    xs = vcat(a, a + (b-a)*sort(rand(no_pts)), b)

    ## Look in [ai, bi)
    for i in 1:(no_pts+1)
        ai,bi=xs[i:i+1]
        if isapprox(f(ai), 0.0, rtol=reltol, atol=ftol)
            push!(rts, ai)
        elseif sign(f(ai)) * sign(f(bi)) < 0
            push!(rts, fzero(f, ai, bi))
        else
            try
                x = fzero(f, ai + (0.5)* (bi-ai), order=8, maxeval=10, ftol=ftol, reltol=reltol)
                if ai < x < bi
                    push!(rts, x)
                end
            catch e
            end
        end
    end
    ## finally, b?
    isapprox(f(b), 0.0, rtol=reltol, atol=ftol) && push!(rts, b)

    ## redo if it appears function oscillates alot in this interval...
    if length(rts) > (1/4) * no_pts
        return(find_zeros(f, a, b, args...; no_pts = 10*no_pts, ftol=ftol, reltol=reltol, kwargs...))
    else
        return(sort(rts))
    end
end


# ---
# title: 33. Search in Rotated Sorted Array
# id: problem33
# author: zhwang
# date: 2022-03-18
# difficulty: Medium
# categories: Array, Binary Search
# link: <https://leetcode.com/problems/search-in-rotated-sorted-array/description/>
# hidden: true
# ---
#
# You are given an integer array `nums` sorted in ascending order, and an
# integer `target`.
#
# Suppose that `nums` is rotated at some pivot unknown to you beforehand (i.e.,
# `[0,1,2,4,5,6,7]` might become `[4,5,6,7,0,1,2]`).
#
# _If`target` is found in the array return its index, otherwise, return `-1`._
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [4,5,6,7,0,1,2], target = 0
#     Output: 4
#
#
# **Example 2:**
#
#
#
#     Input: nums = [4,5,6,7,0,1,2], target = 3
#     Output: -1
#
#
# **Example 3:**
#
#
#
#     Input: nums = [1], target = 0
#     Output: -1
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 5000`
#   * `-10^4 <= nums[i] <= 10^4`
#   * All values of `nums` are **unique**.
#   * `nums` is guranteed to be rotated at some pivot.
#   * `-10^4 <= target <= 10^4`
#
#
## @lc code=start
using LeetCode

function search_left_border(left::Int, right::Int, key::Function)
    ## e.g. search_left_border(1, 10, >(3)) | returns 4
    while left <= right
        mid = left + (right - left) >> 1
        if key(mid) ## lies in the right part
            right = mid - 1
        else
            left = mid + 1
        end
    end
    return left
end

function search_in_rotated_array(nums::Vector{Int}, target::Int)::Int
    n, rightmost = length(nums), last(nums)
    if target > rightmost ## lies in the right side
        pos = search_left_border(1, n, i -> (nums[i] <= rightmost || nums[i] >= target))
        return pos <= n && nums[pos] == target ? pos - 1 : -1
    else
        pos = search_left_border(1, n, i -> (target <= nums[i] <= rightmost))
        return pos <= n && nums[pos] == target ? pos - 1 : -1
    end
end
## @lc code=end


nums = parse.(Int, readlines("day9/input.txt"))

function satisfies_preamble(line_num)
    for i in line_num-25:line_num-1, j in i+1:line_num-1
        if nums[i]+nums[j] == nums[line_num]
            return true
        end
    end
    return false
end

println(nums[25+findfirst(!satisfies_preamble, 26:length(nums))])


#    Copyright (c) 2021 J.A. Duffek
#    Copyright (c) 2000 D.M. Spink
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, see <http://www.gnu.org/licenses/>.

"""
    vecangle(num::Matrix{F},den::Matrix{F}
            )::Matrix{F} where {F<:AbstractFloat}

or

    vecangle(num::F,den::F)::Vector{F} where {F<:AbstractFloat}

An alternative to atan, returning an arctangent in the range `0` to `2*π`.

The components of the vector ang are the arctangent of the corresponding enties
of `num./den`. This function is an alternative for atan, returning an angle in
the range `0` to `2*π`.

# Arguments:
- `num`: Numerator, vector of size `(1,nv)`.
- `den`: Denominator, vector of size `(1,nv)`.

# Output:
- `ang`: Arctangents, row vector of angles.

# Examples:
```julia
julia> ang = vecmag2(num,dum)
```

Find the `atan(1.2,2.0)` and `atan(1.5,3.4)` using `vecangle`.
```julia
julia> ang = vecangle([1.2 1.5], [2.0 3.4])
```
"""
function vecangle(num::Matrix{F},den::Matrix{F}
                 )::Matrix{F} where {F<:AbstractFloat}
ang = atan.(num,den);
ang[ang .< 0.0] .+= 2*π;
return ang;
end # vecangle

# overloaded function for float input to get a float instead of a vector
function vecangle(num::F,den::F)::F where {F<:AbstractFloat}
ang = atan(num,den);
if ang < 0.0
  ang += 2*π;
end # if
return ang;
end # vecangle


"""
License: MIT
Julia 1.02
Dynamic programming formulation to count number of matchings of maximum cardinality
"""

# function from the paper
# INPUT:
# G and E - can be provided by read_Graph(filename)
# maxCard - can be prodived by OptimalMatching(G,E)
function Count_Mat_Paper(G, maxCard,E,r,s,aux)
    # reset table of results
    if aux == 1
        global F_table = Dict()
    end
    # do not repeat computations:
    try
        # if aux == 1 then that entry wont exist
        return F_table[r,maxCard,[if s[v]>0 1 else 0 end for v in sort(collect(keys(G)))]]
    catch
        # if any of the entries maxCard, r or s is negative return 0
        if maxCard<0 || r<0 || minimum(values(s))<0
            F_table[r,maxCard,[if s[v]>0 1 else 0 end for v in sort(collect(keys(G)))]]=0
            # aux =1 means we are in the end of the recurssion so return table
            if aux ==1
                return F_table, 0
            end
            # if not in end of the recurssion return 0 (no need to return table since it is a global variable)
            return 0
        end
        # matchings containing more edges than the ones available
        if maxCard>r
            F_table[r,maxCard,[if s[v]>0 1 else 0 end for v in sort(collect(keys(G)))]]=0
            # aux =1 means we are in the end of the recurssion so return table
            if aux ==1
                return F_table, 0
            end
            # if not in end of the recurssion return 0 (no need to return table since it is global variable)
            return 0
        end
        # if we want a matching of cardinality 0, the only choice is M= empty set, i.e., there only one solution
        if maxCard ==0 && r >=0 && minimum(values(s))>=0
            F_table[r,maxCard,[if s[v]>0 1 else 0 end for v in sort(collect(keys(G)))]]=1
            # aux =1 means we are in the end of the recurssion so return table
            if aux ==1
                return F_table, 1
            end
            # if not in end of the recurssion return 0 (no need to return table since it is global variable)
            return 1
        # if there are no edges available, i.e., r=0, and maxCard>0 then there is no matching under these condition
        elseif maxCard >0 && r ==0 && minimum(values(s))>=0
            F_table[r,maxCard,[if s[v]>0 1 else 0 end for v in sort(collect(keys(G)))]]=0
            # aux =1 means we are in the end of the recurssion so return table
            if aux ==1
                return F_table, 0
            end
            # if not in end of the recurssion return 0 (no need to return table since it is global variable)
            return 0
        # OPT >0 and r>0 and min(s)>=0
        else
            No_r = Count_Mat_Paper(G,maxCard,E,r-1,s,0)
            Yes_r = Count_Mat_Paper(G,maxCard-1,E,r-1,Dict(if v in E[r] v=>s[v]-1 else v=>s[v] end for v in sort(collect(keys(G)))),0)
            F_table[r,maxCard,[if s[v]>0 1 else 0 end for v in sort(collect(keys(G)))]] = No_r+Yes_r
            if aux ==1
                return F_table,No_r+Yes_r
            end
            return No_r+Yes_r
        end
    end
end

# read graphs

function Read_Graph(filename)
    G_tpm = Dict()
    f = open(filename)
    lines =  readlines(f)
    close(f)
    num_V, num_E = split(lines[1])
    num_V = parse(Int,num_V)
    num_E = parse(Int,num_E)
    for i=1:num_E
        v1,v2,w = split(lines[1+i])
        v1 = parse(Int,v1)
        v2 = parse(Int,v2)
        if !(v2 in keys(G_tpm))
            G_tpm[v2] = []
        end
        if !(v1 in keys(G_tpm))
            G_tpm[v1] = [v2]
        else
            append!(G_tpm[v1],v2)
        end
    end
    G = Dict(v=>[] for v in keys(G_tpm))
    E = []
    for v1 in keys(G_tpm)
        for v2 in G_tpm[v1]
            if v1 in G_tpm[v2] && !(v1 in G[v2]) && !(v2 in G[v1])
                append!(G[v1],v2)
                append!(G[v2],v1)
                append!(E,[Set([v1,v2])])
            end
        end
    end
    if length(keys(G))==0
        G = Dict(v=>[] for v in 1:num_V)
    end
    return G,E
end

using CPLEX, JuMP
function OptimalMatching(G,E)
    #m = Model(solver=CplexSolver())
    m = Model(with_optimizer(CPLEX.Optimizer))
    # only one core:
    CPX_PARAM_THREADS = 1
    CPX_PARAM_MIPDISPLAY =0
    CPX_PARAM_TILIM = 3600
    num_E = length(E)
    if num_E==0
        return 0
    end
    @variable(m, x[1:num_E], Bin)
    for v in keys(G)
        @constraint(m,sum(x[j[1]] for j in enumerate(E) if v in j[2])<=1)
    end
    @objective(m, Max, sum(x[j[1]] for j in enumerate(E)))
    optimize!(m)
    println("Objective value: ", objective_value(m))
    #println("Objective value: ", getobjectivevalue(m))
    return objective_value(m)
end


struct LeastActionResult
    path
    lam # Least action energy
    res
end

@with_kw struct curve_ind
    integral_func
    func! # ODE function (du, u,p, t)
    DiffusionMatrix
    dt
    dim
    n_points
    p
    t = nothing # for ode: placeholder
end

function (self::curve_ind)(x)
    points = reshape(x, (self.dim, self.n_points) )

    intd = self.integral_func(points, self.func!, self.DiffusionMatrix, self.dt, self.p; t=self.t )
    return intd
end

"""
Least Action Method

Arguements
----------
- `n_points` {Integer}: Number of time points
- `tmax` {Float}: maximum time
- `point_start` {Array}: initial variables
- `point_end` {Array}: final state
- `ode!` {Function}: ODE function (non-allocation type) f(du,u,p,t)
- `p` {Array}: parameters `p` of `ode!`

Return
------
- `op` {Optim.MultivariateOptimizationResults} : keywords. `ls_success` (success of optimization). Use `Optim.minimizer` function to get the arg minimum.
"""
function action(n_points, tmax, point_start, point_end, ode!, p, Jacobian, DiffusionMatrix)

    dim = length(point_start)
    dt = tmax/n_points

    # A straight line from A → B
    initpath = LeastAction.linearpath(point_start, point_end, n_points)
    init_vec = reshape(initpath, 1, :)

    # Function of curve integration
    curve_func = curve_ind(integral, ode!, DiffusionMatrix, dt, dim, n_points, p, nothing)
    g! = grad!(Jacobian, dt, dim, n_points)

    op = optimize(curve_func, g!, init_vec, ConjugateGradient())

    path = reshape(Optim.minimizer(op), size(initpath))
    lam = op.minimum

    return LeastActionResult(path, lam, op)

end

"""
Integration along the path
"""
function integral(points, func!, DiffusionMatrix, dt, p; t=nothing)
    dim = size(points)[1]
    integral = 0
    for k = 2: size(points)[end]
        du = get_dxdt(func!, points[:,k-1], p; t=nothing)

        Tmp = Δ(points, k, dt) .- du #eq18.
        integral = integral + Tmp' * DiffusionMatrix(dim)^(-1) * Tmp * dt
    end
    integral = integral/4

    return integral
end

"""
Gradient for optimzation
"""
struct grad!
    Jacobian
    dt
    dim
    N
end

function (self::grad!)(G, x)
    x_ = reshape(x, self.dim, self.N)

    m_jac = self.Jacobian(self.dt, x_)

    G[:] = reshape(m_jac, size(x))
end


module SavitzkyGolay

export savitzky_golay, SGolay, SGolayResults

using LinearAlgebra

struct SGolay{T1 <: Signed, T2 <: Real}
    w::T1        # Window size
    order::T1    # Polynomial order
    deriv::T1    # Derivative order
    rate::T2      # Rate
    function SGolay(w::T1, order::T1, deriv::T1, rate::T2) where {T1 <: Signed, T2 <: Real}
        isodd(w) || throw(ArgumentError("w must be an odd number."))
        w ≥ 1 || throw(ArgumentError("w must greater than or equal to 1."))
        w ≥ order + 2 || throw(ArgumentError("w too small for the polynomial order chosen (w ≥ order + 2)."))
        return new{T1, T2}(w, order, deriv, rate)
    end
end

SGolay(w, order) = SGolay(w, order, 0, 1.0)
SGolay(w, order, deriv) = SGolay(w, order, deriv, 1.0)

struct SGolayResults{T <: Float64}
    y::Vector{T}
    params::SGolay
    coeff::Vector{T}
    Vdm::Matrix{T}
end

function (p::SGolay)(y::AbstractVector)
    return _savitzky_golay(y, p)
end

function savitzky_golay(
    y::AbstractVector, window_size::T0, order::T0;
    deriv::T0=0, rate::T1=1.0,
    ) where {T0 <: Signed, T1 <: Real}
    y_, p = _check_input_sg(y, window_size, order, deriv, rate)
    return _savitzky_golay(y_, p)
end

function _savitzky_golay(y::AbstractVector, p::SGolay)
    order_range = 0 : p.order
    hw = Int64((p.w - 1) / 2) # half-window size
    V = zeros(2*hw + 1, length(order_range))
    _vandermonde!(V, hw, order_range)
    c = _coefficients(V, order_range, p)
    y_ = _padding_signal(y, hw)
    y_conv = _convolve_1d(y_, c)
    return SGolayResults(y_conv, p, c, V)
end

function _check_input_sg(y::Vector, w, order, deriv, rate)
    isodd(w) || throw(ArgumentError("w must be an even number."))
    w ≥ 1 || throw(ArgumentError("w must greater than or equal to 1."))
    w ≥ order + 2 || throw(ArgumentError("w too small for the polynomial order chosen (w ≥ order + 2)."))
    length(y) > 1 || throw(ArgumentError("vector x must have more than one element."))
    return Float64.(y), SGolay(w, order, deriv, rate)
end

function _convolve_1d(u::AbstractVector, v::Vector)
    m = length(u)
    n = length(v)
    m > n || throw(ArgumentError("length of signal u must be greater than length of kernel v."))
    w = zeros(m + n - 1)
    @inbounds for j in 1:m, k in 1:n
        w[j+k-1] += u[j]*v[k]
    end
    return w[n:end-n+1]
end

function _vandermonde!(V::Matrix{T1}, hw::T2, order_range::UnitRange{T2}) where {T1 <: Float64, T2 <: Int64}
    @inbounds for i in -hw:hw, j in order_range
        V[i+hw+1, j+1] = i^j
    end
end

function _coefficients(V::Matrix{T1}, order_range::UnitRange{T2}, p::SGolay) where {T1 <: Float64, T2 <: Int64}
    Vqr = qr(V')
    c = Vqr.R \ (Vqr.Q' * _onehot(p.deriv + 1, length(order_range)))
    c .*= (p.rate)^(p.deriv) * factorial(p.deriv)
    return reverse(c)
end

function _onehot(i::T, m::T) where T <: Int64
    m > i || throw(ArgumentError("length of vector must be greater than the position"))
    oh = zeros(m)
    oh[i] = 1.0
    return oh
end

function _padding_signal(y::AbstractVector, hw::Int64)
    initvals = y[1] .- abs.(reverse(y[2:hw+1]) .- y[1])
    endvals = y[end] .+ abs.(reverse(y[end-hw:end-1] .- y[end]))
    return vcat(initvals, y, endvals)
end

end  # module SavitzkyGolay


using Base.Test
using QuantLib

# build a fixed rate bond to test cash flows
settlement_date = Date(2008, 9, 18)
set_eval_date!(settings, settlement_date - Dates.Day(3))
bbCurveRate = 0.055
bbDayCount = QuantLib.Time.ActualActualBond()
bbIR = InterestRate(bbCurveRate, bbDayCount, CompoundedCompounding(), QuantLib.Time.Semiannual())
ts = FlatForwardTermStructure(settlement_date, bbIR.rate, bbIR.dc, bbIR.comp, bbIR.freq)
settlement_days = 3
faceAmount = 100.0
fixed_schedule = QuantLib.Time.Schedule(Date(2007, 5, 15), Date(2017, 5, 15),
                       QuantLib.Time.TenorPeriod(QuantLib.Time.Semiannual()), QuantLib.Time.Unadjusted(),
                       QuantLib.Time.Unadjusted(), QuantLib.Time.DateGenerationBackwards(), false,
                       QuantLib.Time.USGovernmentBondCalendar())

pe = DiscountingBondEngine(ts)
fixedrate_bond = FixedRateBond(settlement_days, faceAmount, fixed_schedule, 0.045,
                        QuantLib.Time.ISMAActualActual(), QuantLib.Time.ModifiedFollowing(), 100.0,
                        Date(2007, 5, 15), fixed_schedule.cal, pe)

# generate a zero-coupon bond
zcb_pe = DiscountingBondEngine(ts)
zcb_cal = QuantLib.Time.USGovernmentBondCalendar()
zcb = ZeroCouponBond(3, zcb_cal, 100.0, Date(2013, 8, 15), QuantLib.Time.Following(), 116.92, Date(2003, 8, 15), zcb_pe)

@test notional(fixedrate_bond, Date(2016, 4, 3)) == 100.0
@test accrued_amount(fixedrate_bond, Date(2016, 4, 3)) == 1.7307692307692246
@test maturity_date(fixedrate_bond) == Date(2017, 5, 15)
@test QuantLib.yield(fixedrate_bond, bbIR.dc, bbIR.comp, bbIR.freq) == 0.05506693320721388
@test duration(fixedrate_bond, bbIR, ModifiedDuration(), bbIR.dc, QuantLib.settlement_date(fixedrate_bond)) == 6.899980120600781
@test npv(fixedrate_bond) == 94.67245111530346
@test dirty_price(fixedrate_bond) == 94.67245111530346
@test clean_price(fixedrate_bond) == 93.13169024573823

@test QuantLib.get_redemption(fixedrate_bond) == fixedrate_bond.cashflows[end]
@test QuantLib.get_frequency(fixedrate_bond) == QuantLib.Time.Semiannual()

# Zero coupon bond calculations
@test npv(zcb) == 89.54351524038537
@test clean_price(zcb) == 89.54351524038537
@test dirty_price(zcb) == 89.54351524038537
@test accrued_amount(zcb, settlement_date) == 0.0
@test QuantLib.yield(zcb, clean_price(zcb), QuantLib.Time.Actual360(), CompoundedCompounding(), QuantLib.Time.Annual(), settlement_date) == 0.05505323890596629

## Testing callable bonds ##
todaysDate = Date(2007, 10, 16)
set_eval_date!(settings, todaysDate)

ts = FlatForwardTermStructure(todaysDate, bbIR.rate, bbIR.dc, bbIR.comp, bbIR.freq)

# set up call schedule
callSchedule = CallabilitySchedule(24)
callPrice = 100.0
callDate = Date(2006, 9, 15)

for i in eachindex(callSchedule)
  nullCal = QuantLib.Time.NullCalendar()
  myPrice = Price(callPrice, CleanCall())

  callSchedule[i] = Callability(myPrice, Call(), callDate)
  callDate = QuantLib.Time.advance(Dates.Month(3), nullCal, callDate)
end

# set up callable bond
dated = Date(2004, 9, 16)
issue = dated
mat = Date(2012, 9, 15)

settlementDays = 3
bondCal = QuantLib.Time.USGovernmentBondCalendar()
coupon = 0.0465
freq = QuantLib.Time.Quarterly()
redemption = 100.0

bondDayCount = QuantLib.Time.ActualActualBond()
accrualConvention = QuantLib.Time.Unadjusted()
paymentConvention = QuantLib.Time.Unadjusted()

schedule = QuantLib.Time.Schedule(dated, mat, QuantLib.TenorPeriod(freq), accrualConvention, accrualConvention, QuantLib.Time.DateGenerationBackwards(), false, bondCal)

maxIter = 1000
accuracy = 1e-8
gridIntervals = 40
reversionParameter = 0.03

sigma = eps()
hw0 = HullWhite(ts, reversionParameter, sigma)
engine0 = TreeCallableFixedRateEngine(hw0, gridIntervals)

callableBond = CallableFixedRateBond(settlementDays, faceAmount, schedule, coupon, bondDayCount, paymentConvention, redemption, issue, callSchedule, engine0)

@test clean_price(callableBond) == 96.46794584230878
@test QuantLib.yield(callableBond, bondDayCount, CompoundedCompounding(), freq, accuracy, maxIter) == 0.054753332138061515


abstract type AbstractRadialBasisFunction <: InterpolationMethod end
abstract type RadialBasisFunction <: AbstractRadialBasisFunction end
abstract type GeneralizedRadialBasisFunction <: AbstractRadialBasisFunction end

Base.iterate(x::AbstractRadialBasisFunction) = (x, nothing)
Base.iterate(x::AbstractRadialBasisFunction, ::Any) = nothing

export  Gaussian,
        Multiquadratic,
        InverseQuadratic,
        InverseMultiquadratic,
        Polyharmonic,
        ThinPlate,
        GeneralizedMultiquadratic,
        GeneralizedPolyharmonic

# Define types for the different kinds of radial basis functions
for rbf in (:Gaussian,
            :Multiquadratic,
            :InverseQuadratic,
            :InverseMultiquadratic,
            :Polyharmonic)
    @eval begin
        struct $rbf{T} <: RadialBasisFunction where T <: Real
            ɛ::T
        end

        # Define default constructors
        $rbf() = $rbf(1)
    end
end

# Generalized RBF:s
struct GeneralizedMultiquadratic{T<:Real, S<:Real, U<:Integer} <: GeneralizedRadialBasisFunction
    ε::T
    β::S
    degree::U
end

struct GeneralizedPolyharmonic{S<:Integer, U<:Integer} <: GeneralizedRadialBasisFunction
    k::S
    degree::U
end

@doc "
    Gaussian(ɛ = 1)

Define a Gaussian Radial Basis Function

```math
ϕ(r) = e^{-(ɛr)^2}
```
" Gaussian
(rbf::Gaussian)(r) = exp(-(rbf.ɛ*r)^2)

@doc "
    Multiquadratic(ɛ = 1)

Define a Multiquadratic Radial Basis Function

```math
ϕ(r) = \\sqrt{1 + (ɛr)^2}
```
" Multiquadratic
(rbf::Multiquadratic)(r) = (sqrt(1 + (rbf.ɛ*r)^2))

@doc "
    InverseQuadratic(ɛ = 1)

Define an Inverse Quadratic Radial Basis Function

```math
ϕ(r) = \\frac{1}{1 + (ɛr)^2}
```
" InverseQuadratic
(rbf::InverseQuadratic)(r) = (1/(1 + (rbf.ɛ*r)^2))

@doc "
    InverseMultiquadratic(ɛ = 1)

Define an Inverse Multiquadratic Radial Basis Function

```math
ϕ(r) = \\frac{1}{\\sqrt{1 + (ɛr)^2}}
```
" InverseMultiquadratic
(rbf::InverseMultiquadratic)(r) = (1/sqrt(1 + (rbf.ɛ*r)^2))

@doc "
    Polyharmonic(k = 1)

Define a Polyharmonic Spline Radial Basis Function

```math
ϕ(r) = r^k, k = 1, 3, 5, ...
\\\\
ϕ(r) = r^k ln(r), k = 2, 4, 6, ...
```
" Polyharmonic
function (rbf::Polyharmonic{T})(r) where T <: Integer
    @assert rbf.ɛ > 0

    # Distinguish odd and even cases
    expr = if rbf.ɛ % 2 == 0
        (r > 0 ? r^rbf.ɛ*log(r) : 0.0)
    else
        (r^rbf.ɛ)
    end

    expr
end

@doc "
    ThinPlate()

Define a Thin Plate Spline Radial Basis Function

```math
ϕ(r) = r^2 ln(r)
```

This is a shorthand for `Polyharmonic(2)`.
" ThinPlate
ThinPlate() = Polyharmonic(2)

@doc "
    GeneralizedMultiquadratic(ɛ, β, degree)

Define a generalized Multiquadratic Radial Basis Function

```math
ϕ(r) = (1 + (ɛ*r)^2)^β
```
Results in a positive definite system for a 'degree' of ⌈β⌉ or higher.

" GeneralizedMultiquadratic
(rbf::GeneralizedMultiquadratic)(r) = (1 + (rbf.ε*r)^2)^rbf.β

@doc "
    GeneralizedPolyharmonic(k, degree)

Define a generalized Polyharmonic Radial Basis Function

```math
ϕ(r) = r^k, k = 1, 3, 5, ...
\\\\
ϕ(r) = r^k ln(r), k = 2, 4, 6, ...
```
Results in a positive definite system for a 'degree' of ⌈k/2⌉ or higher for k = 1, 3, 5, ...
and of exactly k + 1 for k = 2, 4, 6, ...
" GeneralizedPolyharmonic
function (rbf::GeneralizedPolyharmonic)(r)
    @assert rbf.k > 0

    # Distinguish odd and even cases
    expr = if rbf.k % 2 == 0
        (r > 0 ? r^rbf.k*log(r) : 0.0)
    else
        (r^rbf.k)
    end

    expr
end

abstract type RadialBasisInterpolant <: ScatteredInterpolant end

struct RBFInterpolant{T1, T2, F, M} <: RadialBasisInterpolant where {T1 <: AbstractArray, T2 <: AbstractMatrix{<:Real}}

    w::T1
    points::T2
    rbf::F
    metric::M
end

struct GeneralizedRBFInterpolant{T1, T2, F, M} <: RadialBasisInterpolant where {T1 <: AbstractArray, T2 <: AbstractMatrix{<:Real}}

    w::T1
    λ::T1
    points::T2
    rbf::F
    metric::M
end

function interpolate(rbf::Union{T, AbstractVector{T}} where T <: AbstractRadialBasisFunction,
                     points::AbstractArray{<:Real,2},
                     samples::AbstractArray{<:Number,N};
                     metric = Euclidean(), returnRBFmatrix::Bool = false,
                     smooth::Union{S, AbstractVector{S}} = false) where {N} where {S<:Number}

    #hinder smooth from being set to true and interpreted as the value 1
    @assert smooth != true "set the smoothing value as a number or vector of numbers"

    # Compute pairwise distances, apply the Radial Basis Function
    # and optional smoothing (ridge regression)
    A = pairwise(metric, points;dims=2)

    A = evaluateRBF!(A, rbf, smooth)

    # Solve for the weights
    itp = solveForWeights(A, points, samples, rbf, metric)

    # Create and return an interpolation object
    if returnRBFmatrix    # Return matrix A
        return itp, A
    else
        return itp
    end

end

@inline function evaluateRBF!(A, rbf, symmetric::Bool = false)
    A .= rbf.(A)

    if symmetric
        A = Symmetric(A)
    end

    return A
end
@inline function evaluateRBF!(A, rbfs::AbstractVector{<:AbstractRadialBasisFunction}, symmetric::Bool = false)
    for (j, rbf) in enumerate(rbfs)
        A[:,j] .= rbf.(@view A[:,j])
    end
    return A
end
@inline function evaluateRBF!(A, rbf, smooth)
    A = evaluateRBF!(A, rbf)
    A = addSmoothing!(A, smooth)
    return A
end

@inline function addSmoothing!(A, smooth::Vector{T}) where T <: Number
    for i = 1:size(A, 1)
        A[i,i] += smooth[i]
    end
    return A
end
@inline function addSmoothing!(A, smooth::T) where T <: Number
    for i = 1:size(A, 1)
        A[i,i] += smooth
    end
    return A
end

@inline function solveForWeights(A, points, samples,
                                    rbf::Union{T, AbstractVector{T}} where T <: RadialBasisFunction,
                                    metric)
    w = A\samples
    RBFInterpolant(w, points, rbf, metric)
end
@inline function solveForWeights(A, points, samples,
                                    rbf::Union{T, AbstractVector{T}} where T <: Union{GeneralizedRadialBasisFunction, RadialBasisFunction},
                                    metric)
    # Use the maximum degree among the generalized RBF:s
    P = getPolynomial(rbf, points)

    # Solve for the weights and polynomial coefficients
    # We end up with a blocked system, so we don't have to form the full matrix
    Af = factorize(A)
    B = -P'*(Af\P)
    E = B\(P'*(Af\samples))

    w = A\(I*samples + P*E)
    λ = -E

    GeneralizedRBFInterpolant(w, λ, points, rbf, metric)
end

function evaluate(itp::RadialBasisInterpolant, points::AbstractArray{<:Real, 2})

    # Compute distance matrix and evaluate the RBF
    A = pairwise(itp.metric, points, itp.points; dims=2)
    A = evaluateRBF!(A, itp.rbf, false)

    # Compute polynomial matrix for generalized RBF:s
    P = getPolynomial(itp.rbf, points)

    # Compute the interpolated values
    return computeInterpolatedValues(A, P, itp)
end

@inline getPolynomial(rbf::Union{T, AbstractVector{T}} where T <: RadialBasisFunction, points) = nothing
@inline function getPolynomial(rbf::Union{T, AbstractVector{T}} where T <: Union{GeneralizedRadialBasisFunction, RadialBasisFunction}, points)

    # Use the maximum degree among the generalized RBF:s
    degree = maximum(x isa GeneralizedRadialBasisFunction ? x.degree : 0 for x in rbf)
    P = generateMultivariatePolynomial(points, degree)
end

@inline computeInterpolatedValues(A, P, itp::RBFInterpolant) = A*itp.w
@inline computeInterpolatedValues(A, P, itp::GeneralizedRBFInterpolant) = A*itp.w + P*itp.λ

# Helper function to generate matrices defining complete homogenic symmetric polynomials
function generateMultivariatePolynomial(points::AbstractArray{<:Real, 2}, degree::Integer)

    # How big should the matrix be?
    nDimensions, nPoints = size(points)
    nTerms = binomial(degree + nDimensions, degree)

    P = ones(nPoints, nTerms)

    # Start with the lowest orders and work upwards
    position = 2
    while position <= nTerms
        for order = 1:degree
            for combination in with_replacement_combinations(1:nDimensions, order)
                for var in combination
                    P[:, position] .*= points[var, :]
                end
                position += 1
            end
        end
    end

    P
end


include("../utility.jl")
using Distributions, Turing, Test

N = 5
setchunksize(4*N)
alg = HMC(1000, 0.2, 4)

@model vdemo() = begin
  v = Vector{Matrix{Real}}(undef, N)
  v ~ [Wishart(7, [1 0.5; 0.5 1])]
end

t_vec = @elapsed res_vec = sample(vdemo(), alg)

@model vdemo() = begin
  v = Vector{Matrix{Real}}(undef, N)
  for i = 1:N
    v[i] ~ Wishart(7, [1 0.5; 0.5 1])
  end
end

t_loop = @elapsed res = sample(vdemo(), alg)

println("Time for")
println("  Loop : $t_loop")
println("  Vec  : $t_vec")


module DarcyTests

using Test
using Gridap
import Gridap: ∇, divergence
using LinearAlgebra

u(x) = VectorValue(2*x[1],x[1]+x[2])

divergence(::typeof(u)) = (x) -> 3

p(x) = x[1]-x[2]

∇p(x) = VectorValue(1,-1)

∇(::typeof(p)) = ∇p

f(x) = u(x) + ∇p(x)

domain = (0,1,0,1)
partition = (4,4)
order = 1
model = CartesianDiscreteModel(domain,partition)

V = FESpace(
  reffe=:RaviartThomas, order=order, valuetype=VectorValue{2,Float64},
  conformity=:Hdiv, model=model, dirichlet_tags=[5,6])

Q = FESpace(
  reffe=:QLagrangian, order=order, valuetype=Float64,
  conformity=:L2, model=model)

U = TrialFESpace(V,u)
P = TrialFESpace(Q)

Y = MultiFieldFESpace([V, Q])
X = MultiFieldFESpace([U, P])

trian = Triangulation(model)
degree = 2
quad = CellQuadrature(trian,degree)

neumanntags = [7,8]
btrian = BoundaryTriangulation(model,neumanntags)
degree = 2*(order+1)
bquad = CellQuadrature(btrian,degree)
nb = get_normal_vector(btrian)

function a(x,y)
  u, p = x
  v, q = y
  u⋅v - p*(∇⋅v) + q*(∇⋅u)
end

function l(y)
  v, q = y
  v⋅f + q*(∇⋅u)
end

function l_Γ(y)
  v, q = y
  -(v⋅nb)*p
end

t_Ω = AffineFETerm(a,l,trian,quad)
t_Γ = FESource(l_Γ,btrian,bquad)
op = AffineFEOperator(X,Y,t_Ω,t_Γ)
xh = solve(op)
uh, ph = xh

eu = u - uh
ep = p - ph

l2(v) = v⋅v
h1(v) = v*v + ∇(v)⋅∇(v)

eu_l2 = sum(integrate(l2(eu),trian,quad))
ep_l2 = sum(integrate(l2(ep),trian,quad))
ep_h1 = sum(integrate(h1(ep),trian,quad))

tol = 1.0e-9
@test eu_l2 < tol
@test ep_l2 < tol
@test ep_h1 < tol

end # module


## Before we got started
# I added the CSV package via
# typing "]" then "add CSV"
# alternatively, we could use
# using Pkg; Pkg.add("CSV")

## Let's start by reading our house prices
using CSV
data = CSV.read("1-julia-intro/houses.csv")

## Let's get the first house info
data[1,:]'

## See the column names
names(data)

##
scatter(data[:latitude], data[:longitude])

## Let's add house prices
scatter(data[:latitude], data[:longitude], marker_z=Int.(data[:price]))

## Let's see zipcodes
# how many are there
@show unique(Int.(data[:zip]))

## TODO - show a true categorical vector plot of zipcodes.
## Plot by zip
scatter(data[:latitude], data[:longitude], marker_z=Int.(data[:zip]))

## Let's plot house-prices by sqft.
scatter(data[:latitude], data[:longitude],
    marker_z=Int.(data[:price])./Int.(data[:sq__ft]))

##
datar = data[data[:type] .== "Residential",:]
datar = datar[datar[:sq__ft] .> 0, :]
##
scatter(datar[:latitude], datar[:longitude],
    marker_z=Int.(datar[:price])./Int.(datar[:sq__ft]))

## Get the average price per sqft based on zipcodes.
# Let's use Julia's data frames first
using Statistics
using DataFrames
datar.price_per_sqft = Int.(datar[:price])./Int.(datar[:sq__ft])
by(datar, :zip, :price_per_sqft => mean)

##
"""
This takes in a set of data in terms of zipcodes
and price_per_sqrt data for a house sold in that zipcode.
    So the two vectors zipcodes, price_per_sqft should have
    the same length. Then we will return a Dictionary
    which is the average price per sqft in each zipcode.
"""
function avgprice_by_zip(zipcodes, price_per_sqft)
    zipprices = Dict{Int,Float64}()
    zipcounts = Dict{Int,Int}() # this counts the number of vals
    @assert length(zipcodes) == length(price_per_sqft)
    for i in 1:length(zipcodes)
        if !(haskey(zipprices, zipcodes[i]))
            # initialize
            zipprices[zipcodes[i]] = 0
            zipcounts[zipcodes[i]] = 0
        end
        zipprices[zipcodes[i]] += price_per_sqft[i]
        zipcounts[zipcodes[i]] += 1 # add one to our count
    end
    for k in keys(zipprices)
        zipprices[k] /= zipcounts[k]
    end
    return zipprices
end
avgs = avgprice_by_zip(Int.(datar[:zip]), datar[:price_per_sqft])

##
bar(collect(keys(avgs)), collect(values(avgs)))

## Our goal -- is there really a relationship between
## prices and zip codes?
## LEt's pick an arbitrary zipcode
using Random
testzip = 95690
# Here the avg prices
testavg = avgs[testzip]

function permtest_on_zip(N::Int)
    # note that this is going to use
    # global variables from outside,
    # you can just use them!
    zipcodes = Int.(datar[:zip])
    prices = datar[:price_per_sqft]
    rand_prices = zeros(N)
    for i=1:N
        p = randperm(length(zipcodes))
        zipcodes = zipcodes[p]
        randavgs = avgprice_by_zip(zipcodes, prices)
        rand_prices[i] = randavgs[testzip]
    end
    return rand_prices
end
rand_prices = permtest_on_zip(1000)
@show sum(rand_prices .>= testavg)
histogram(rand_prices)
vline!([testavg])

## Let's look at a max-vs-min price by zip
diffs = maximum(values(avgs)) - minimum(values(avgs))

## Differs under perm test
function permtest_diff_on_zip(N::Int)
    # note that this is going to use
    # global variables from outside,
    # you can just use them!
    zipcodes = Int.(datar[:zip])
    prices = datar[:price_per_sqft]
    rand_diffs = zeros(N)
    for i=1:N
        p = randperm(length(zipcodes))
        zipcodes = zipcodes[p]
        randavgs = avgprice_by_zip(zipcodes, prices)
        rand_diffs[i] = maximum(values(randavgs)) - minimum(values(randavgs))
    end
    return rand_diffs
end
rand_diffs = permtest_diff_on_zip(1000)
@show sum(rand_diffs .>= diffs)
histogram(rand_prices)
vline!([diffs])


using SigmoidNumbers

if length(ARGS) < 2
  print("""
  USE:
  julia fft.jl [vector_length] [posit_precision] [posit_es] [distribution = {-1:0:1}]

  executes a fast fourier transform/inverse transform of a random vector and
  measures the deviation from the original in standard floats or posits.

  julia fft.jl 1024 32
  julia fft.jl 256  16 randn
  """)

  exit()
end

vector_size = parse(ARGS[1])
posit_precision = parse(ARGS[2])
posit_es = parse(ARGS[3])

F = posit_precision <= 16 ? Float16 : Float32
P = Posit{posit_precision, posit_es}

if length(ARGS) < 4
  randomizer() = rand(-1:1, vector_size)
elseif ARGS[4] == "randn"
  randomizer() = randn(vector_size)
elseif ARGS[4] == "rand"
  randomizer() = rand(vector_size)
end

function ffttest()

  sv = randomizer()

  v = F.(sv)
  t = ifft(fft(v))
  d = sum(abs.(Complex{Float64}.(t .- v)))
  println("$F deviation: ", d)

  vp = P.(sv)
  tp = ifft(fft(vp))
  dp = sum(abs.(Complex{Float64}.(tp .- vp)))
  println("$P deviation: ", dp)

  tp = ifft(fft(vp, normalize=:always), normalize=:always)
  dp = sum(abs.(Complex{Float64}.(tp .- vp)))
  println("$P deviation: ", dp)

  tp = ifft(fft(vp, normalize=:continuous), normalize=:continuous)
  dp = sum(abs.(Complex{Float64}.(tp .- vp)))
  println("$P deviation: ", dp)

end

ffttest()


const Position = Tuple{Int,Int}
const FindClosestResult = Union{Position,Nothing}

"Calculate the manhattan distance between two `Position`s"
manhattan_distance(a::Position, b::Position) = abs.(a .- b) |> sum

"""
    closest_coin(goblin::Position, coins::Set{Position})::FindClosestResult

Given a `Position` representing the goblin and a list of `Position`s
representing the coins that have not yet been collected, return the coin
nearest to the goblin's current position. If more than one coin is nearest
the goblin, return `nothing`.
"""
function closest_coin(goblin::Position, coins::Set{Position})::FindClosestResult
    found_coin   = nothing      # The nearest coin found so far
    found_times  = 0            # How many times the current closest coin has been found
    min_distance = typemax(Int) # Distance to the nearest coin found so far

    for coin in coins
        distance = manhattan_distance(goblin, coin)

        # We've seen this distance before...
        if distance == min_distance
            found_times += 1
        end

        # The new closest coin
        if distance < min_distance
            found_coin = coin
            found_times = 1
            min_distance = distance
        end
    end

    return found_times > 1 ? nothing : found_coin
end

"""
    path(goblin::Position, coins::Vector{Position})

Given a `Position` representing the goblin and a list of `Position`s
representing coins, return a list of `Position`s, in order, that represent
the path taken by the goblin from nearest coin to nearest coin. Returns an
empty list if the goblin ever finds itself stuck between two equidistant coins.
"""
function path(goblin::Position, coins::Vector{Position})
    found_path    = []
    coin_set      = Set(coins)
    curr_position = goblin

    while !isempty(coin_set)
        curr_position = closest_coin(curr_position, coin_set)
        isnothing(curr_position) && return []
        push!(found_path, curr_position)
        delete!(coin_set, curr_position)
    end

    return found_path
end

#=------------------------------------------------------------------------------
| Test Cases
------------------------------------------------------------------------------=#

using Test

@testset "Should find a path when positions are negative" begin
    goblin = (1, 1)
    coins  = [(1, 2), (-5, -5), (1, 3)]
    @test path(goblin, coins) == [(1, 2), (1, 3), (-5, -5)]
end

@testset "Should find a path when positions are large numbers" begin
    goblin = (34, 51)
    coins  = [(1050, -34), (-8809080, 2), (-46788, -67683)]
    @test path(goblin, coins) == [(1050, -34), (-46788, -67683), (-8809080, 2)]
end

@testset "Should find a path when positions are all positive" begin
    goblin = (0, 2)
    coins  = [(0, 4), (1, 0), (2, 0), (3, 2), (2, 4)]
    @test path(goblin, coins) == [(0, 4), (2, 4), (3, 2), (2, 0), (1, 0)]
end

@testset "Should return an empty list when the goblin freaks out." begin
    goblin = (100, 100)
    coins  = [(99, 100), (101, 100), (100, 99), (100, 101)]
    @test path(goblin, coins) == []
end


__precompile__()
module MyStats

export hist, hist_indices, histND, histND_indices, min_max, min_max_ind, condmean, Bins, dbkhist_indices, dbkBins, threaded_sum, tmean, tstd, histstd

function hist(field::AbstractArray, min::Real, max::Real, nbins::Integer=100, pdf::Bool=true)
    dx = max - min
    indices = Vector{Float64}(nbins)

    @inbounds for i in linearindices(field)
        n = trunc(Int, ((field[i] - min)/dx - eps())*nbins) + 1
        indices[n] += 1.
    end

    x = Bins(min,max,nbins)
    if pdf
        area = sum(indices)*step(x)
        indices ./= area
    end

    return [x indices]
end

hist(field::AbstractArray, nbins::Integer=100, pdf::Bool=true) = hist(field, min_max(field)..., nbins, pdf)

hist(field::AbstractArray, indices::AbstractVector{<:Integer}, nbins::Integer=100, pdf::Bool=true) = hist(view(field,indices), nbins, pdf)

# return a Vector of Vectors of indices corresponding to the bin.
function hist_indices(field::AbstractArray,min::Real,max::Real,nbins::Integer=30)
    dx = max - min
    indices = Vector{Vector{Int}}(nbins)
    hint = length(field)÷nbins
    @inbounds for i in linearindices(indices)
        indices[i] = Vector{Int}()
        sizehint!(indices[i],hint)
    end

    @inbounds for i in linearindices(field)
        n = trunc(Int, ((field[i] - min)/dx - eps())*nbins) + 1
        (1 <= n <= nbins) && push!(indices[n],i)
    end

    return indices
end

hist_indices(field::AbstractArray,nbins::Integer=30) = hist_indices(field,min_max(field)...,nbins)

function histND_indices(field::NTuple{N,AbstractArray},min::NTuple{N,Real},max::NTuple{N,Real},nbins::NTuple{N,Integer}) where {N}
    dx = max .- min
    indices = Array{Vector{Int},N}(nbins)

    @inbounds for i in linearindices(indices)
        indices[i] = Vector{Int}()
    end

    @inbounds for i in linearindices(field[1])
        n = @. trunc(Int, ((getindex(field,i) - min)/dx - eps())*nbins) + 1
        all(1 .<= n .<= nbins) && push!(indices[n...],i)
    end

    return indices
end

histND_indices(field::NTuple{N,AbstractArray},nbins::NTuple{N,Integer}) where {N} = histND_indices(field,min_max(field)...,nbins)

histND_indices(field::NTuple{3,AbstractArray}) = histND_indices(field,(5,5,5))

histND_indices(field::NTuple{2,AbstractArray}) = histND_indices(field,(12,12))

###################################

function histND(field::NTuple{N,AbstractArray},min::NTuple{N,Real},max::NTuple{N,Real},nbins::NTuple{N,Integer},pdf::Bool=true) where {N}
    dx = max .- min
    indices = zeros(Float64,nbins)

    @inbounds for i in linearindices(field[1])
        n = @. trunc(Int, ((getindex(field,i) - min)/dx - eps())*nbins) + 1
        indices[n...] += 1.
    end
    x = Bins.(min,max,nbins)
    barea = prod((max .- min) ./ nbins)
    if pdf
        area = sum(indices)*barea
        indices ./= area
    end

    return x, indices
end

histND(field::NTuple{N,AbstractArray},nbins::NTuple{N,Integer}) where {N} = histND(field,min_max(field)...,nbins)

histND(field::NTuple{3,AbstractArray}) = histND(field,(5,5,5))

histND(field::NTuple{2,AbstractArray}) = histND(field,(12,12))

function min_max(f::NTuple{N,AbstractArray}) where N
    aux = min_max.(f)
    return (getindex.(aux,1), getindex.(aux,2))
end

function min_max(f::AbstractArray)
    nt::Int = Threads.nthreads()
    const pmin = zeros(eltype(f),nt)
    const pmax = zeros(eltype(f),nt)

    Threads.@threads for i in 1:nt
        get_min_max(f,pmin,pmax,i)
    end

    return minimum(pmin), maximum(pmax)
end

function get_min_max(f,pmi,pma,i)
    nt::Int = Threads.nthreads()

    r = divide_range(length(f), nt)[i]

    minv = f[r[1]]
    maxv = f[r[1]]

    @inbounds for j in r
        if f[j] > maxv
            maxv = f[j]
        elseif f[j] < minv
            minv = f[j]
        end
    end

    pmi[i] = minv
    pma[i] = maxv
    return nothing
end

function divide_range(l::Integer,np::Integer)
    p = l÷np

    m = p
    a = Vector{UnitRange{Int}}(np)
    a[1] = 1:m
    @inbounds for i=2:(np-1)
        a[i] = (m+1):(m+p)
        m = m+p
    end
    a[np] = (m+1):l
    return a
end

function min_max_ind(f::AbstractArray)
    nt::Int = Threads.nthreads()
    const imin = zeros(Int,nt)
    const imax = zeros(Int,nt)

    Threads.@threads for i in 1:nt
        get_min_max_ind(f,imin,imax,i)
    end

    return compare_ind(f,imin,Base.:<), compare_ind(f,imax,Base.:>)
end

function get_min_max_ind(f,pmi,pma,i)
    nt::Int = Threads.nthreads()

    r = divide_range(length(f), nt)[i]

    minv = r[1]
    maxv = r[1]

    @inbounds for j in r
        if f[j] > f[maxv]
            maxv = j
        elseif f[j] < f[minv]
            minv = j
        end
    end

    pmi[i] = minv
    pma[i] = maxv
    return nothing
end

function compare_ind(v::AbstractArray,ind,f::Function)
    tv = ind[1]

    @inbounds for j in ind
        if f(v[j],v[tv])
            tv = j
        end
    end

    return tv
end

function condmean(field::AbstractArray,condindices)
    const result = zeros(length(condindices))
    const err = zeros(length(condindices))
    Threads.@threads for i in linearindices(condindices)
        @inbounds begin
            ind = condindices[i]
            l = length(ind)
            for j in ind
                result[i] += field[j]
            end
            m = result[i]/l
            result[i] = m
            for j in ind
                err[i] += (field[j] - m)^2
            end
            err[i] = sqrt(err[i]/(l*(l-1)))
        end
    end
    return result, err
end

struct Bins <: AbstractVector{Float64}
    minv::Float64
    maxv::Float64
    n::Int
    dx::Float64
    Bins(minv,maxv,n) = new(minv,maxv,n,(maxv-minv)/n)
end

Base.length(a::Bins) = a.n
Base.size(a::Bins) = (a.n,)
Base.step(a::Bins) = a.dx
Base.IndexStyle(::Type{Bins}) = IndexLinear()

@inline function Base.getindex(a::Bins,i::Integer)
    @boundscheck((1 <= i <= a.n) || throw(BoundsError(a,i)))
    dx = a.dx
    return (a.minv + 0.5*dx) + (i-1)*dx
end

function dbkhist_indices(field::AbstractArray, nbins::Integer=30)

    indicesp = sortperm(vec(field))

    indices = Vector{Vector{Int}}(nbins)

    ranges = divide_range(length(field),nbins)
    @inbounds for i in 1:nbins
        indices[i] = indicesp[ranges[i]]
    end

    return indices

end

struct dbkBins <: AbstractVector{Float64}
    edges::Vector{Float64}
    n::Int
    dbkBins(edges) = new(edges,length(endges)-1)
end

Base.length(a::dbkBins) = a.n
Base.size(a::dbkBins) = (a.n,)
Base.IndexStyle(::Type{dbkBins}) = IndexLinear()

@inline function Base.getindex(a::dbkBins,i::Integer)
    @boundscheck((1 <= i <= a.n) || throw(BoundsError(a,i)))
    ed = a.edges
    return 0.5*(ed[i] + ed[i+1])
end

function threaded_sum(v,func::Function=Base.identity)
    nt::Int = Threads.nthreads()
    const result = zeros(eltype(v),nt)
    ranges = divide_range(length(v),nt)
    Threads.@threads for i in 1:nt
        get_sum!(result,v,func,ranges[i],i)
    end

    return sum(result)
end

function get_sum!(result::AbstractVector,v::AbstractArray,f::Function,ran,j::Integer)
    @inbounds begin
        r = zero(eltype(v))
        @simd for i in ran
            r += f(v[i])
        end
        result[j] = r
    end
    return nothing
end

tmean(v::AbstractArray,func::Function=Base.identity) = threaded_sum(v,func)/length(v)

function tstd(v::AbstractArray,func::Function=Base.identity,m::Number=tmean(v,func))
    f = x->((func(x)-m)^2)
    return sqrt(threaded_sum(v,f)/(length(v)-1))
end

tstd(v::AbstractArray,m::Number) = tstd(v,Base.identity,m)

function histstd(field::AbstractArray, min::Real, max::Real, nbins::Integer=100, pdf::Bool=true)
    dx = max - min
    indices = Vector{Float64}(nbins)
    @inbounds for i in linearindices(field)
        n = trunc(Int, ((field[i] - min)/dx - eps())*nbins) + 1
        (1 <= n <= nbins) && (indices[n] += 1.)
    end

    x = Bins(min,max,nbins)
    if pdf
        area = sum(indices)*step(x)
        indices ./= area
    end

    return [x indices]
end

histstd(field::AbstractArray,m::Real,stand::Real,ntimes::Real,nbins::Integer=100,pdf::Bool=true) = histstd(field,m-ntimes*stand,m+ntimes*stand,nbins,pdf)

function histstd(field::AbstractArray,ntimes::Real,nbins::Integer=100,pdf::Bool=true)
    m = tmean(field)
    stand = tstd(field,m)
    histstd(field,m-ntimes*stand,m+ntimes*stand,nbins,pdf)
end

end # module


@testset "SME" begin
    res = BRS.sme(collect(1:10:100), collect(1:10:100))
    @test res.value == 1
    @test res.nUp == 1
    @test res.nDown == 0
end

@testset "RMSSD ratio" begin
    res = BRS.rmssdr(collect(1:10:100), collect(1:10:100))
    @test res == 1
end

@testset "xBRS" begin
    res = BRS.xbrs(collect(1000:1000:10000), collect(1:10:100))
    @test res.value ≈ 100
end

@testset "tfBRS" begin
    res = BRS.tfbrs(collect(100:100:10000), collect(1:1:100), n = 40)
    @test res.value ≈ 100
end

@testset "prsaBRS" begin
    res = BRS.prsabrs(collect(100:100:1000000), collect(1:1:10000))
    @test isapprox(res.value, 100, atol = 0.01)
end

@testset "arBRS" begin
    res = BRS.arbrs(collect(1.0:1000.0), collect(1.0:1000.0))
    @test isapprox(res.αHF, 1)
    @test isnan(res.αLF)
end

@testset "getBRS" begin

    res = getBRS(collect(1.0:1000.0), collect(1.0:1000.0))
    @test length(res) == 7
end


using DifferentialEquations
using OrdinaryDiffEq
using MomentClosure
using ModelingToolkit
using DiffEqJump
using DataFrames
using Tables
using Statistics
using Plots
using StatsPlots
using Latexify
using BenchmarkTools

@parameters t β c γ
@variables S(t) I(t);

rxs = [Reaction(β*c, [S,I], [I], [1,1], [2])
       Reaction(γ, [I], nothing)]
rs  = ReactionSystem(rxs, t, [S,I], [β,c,γ]);

δt = 0.1
tmax = 40.0
tspan = (0.0,tmax)
ts = 0:δt:tmax;

u0 = [S => 990.0, I => 10.0];

u0v = [x[2] for x in u0];

p = [β=>0.00005, c=>10.0, γ=>0.25];

central_eqs = generate_central_moment_eqs(rs, 4, combinatoric_ratelaw=false);

closure_methods = ["zero","normal","log-normal","gamma","derivative matching"];

closed_central_eqs = Dict(cm=>moment_closure(central_eqs,cm) for cm in closure_methods);

u0map = Dict(cm=> deterministic_IC(u0v,closed_central_eqs[cm]) for cm in closure_methods);

closed_central_eqs_df = Dict{String,DataFrame}()
for cm in closure_methods
    prob = ODEProblem(closed_central_eqs[cm], u0map[cm], tspan, p)
    sol = solve(prob)
    df = DataFrame(sol(ts)')
    rename!(df,[replace(string(x[1]),"(t)" => "") for x in u0map[cm]])
    df[!,:t] = ts
    closed_central_eqs_df[cm] = df
end;

jumpsys = convert(JumpSystem, rs)
u0i = [S => 990, I => 10]
dprob = DiscreteProblem(jumpsys, u0i, tspan, p)
jprob = JumpProblem(jumpsys, dprob, Direct());

ensemble_jprob = EnsembleProblem(jprob)
ensemble_jsol = solve(ensemble_jprob,SSAStepper(),trajectories=10000)
ensemble_summary = EnsembleSummary(ensemble_jsol,ts);

ensemble_u = DataFrame(ensemble_summary.u')
rename!(ensemble_u,["μ₁₀","μ₀₁"])
ensemble_v = DataFrame(ensemble_summary.v')
rename!(ensemble_v,["M₂₀","M₀₂"])
ensemble_uv = hcat(ensemble_u,ensemble_v)
ensemble_uv[!,:t] = ts;

jplot = @df ensemble_uv plot(:t,[:μ₁₀,:μ₀₁],
     ribbon=[2*sqrt.(:M₂₀),
             2*sqrt.(:M₀₂)],
     label=["S" "I"],
     xlabel="Time",
     ylabel="Number",
     title="Jump process")

pltlist = []
for cm in closure_methods
     plt = @df closed_central_eqs_df[cm] plot(:t,[:μ₁₀,:μ₀₁],
          ribbon=[2*sqrt.(:M₂₀),
                  2*sqrt.(:M₀₂)],
          label=["S" "I"],
          xlabel="Time",
          ylabel="Number",
          title=cm)
    push!(pltlist,plt)
end;

l = @layout [a b c; d e f]
plot(vcat(jplot,pltlist)...,
     layout=l,
     legendfontsize=8,
     titlefontsize=10)

m = []
c = []
v = []
for moment in [:μ₁₀,:μ₀₁,:M₂₀,:M₀₂]
    for cm in closure_methods
        push!(m,moment)
        push!(c,cm)
        push!(v,mean(abs.(closed_central_eqs_df[cm][!,moment] - ensemble_uv[!,moment])))
    end
end
df = DataFrame(Dict(["Moment" => m,"Method" => c,"Normalized L1" => v]))
table = latexify(df,latex=false,env=:mdtable)
table

@benchmark solve(ensemble_jprob,SSAStepper(),trajectories=20000)

prob = ODEProblem(closed_central_eqs["normal"], u0map["normal"], tspan, p)
@benchmark sol = solve(prob)


##
## Fun with pi!
##
## Tomáš Kalvoda, KAM FIT ČVUT, 2015
##

##
## References: J. Gibbons, Unbounded Spigot Algorithms for the Digits of pi
##
## http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/spigot.pdf
##

function nextdigit(state)
    q,r,t,i = state
    u, y = (3*(3*i+1)*(3*i+2), div(q*(27*i-12)+5*r, 5*t))
    return (y, [10*q*i*(2*i-1), 10*u*(q*(5*i-2)+r-y*t), t*u, i+1])
end

function pidigits(n)
    dig = Int[]
    d, state = nextdigit(BigInt[1, 180, 60, 2])
    for j in 1:n
        push!(dig,d)
        d, state = nextdigit(state)
    end
    return dig
end

##
## Progression
##

const ndigits = 2000

info("Computing first $(ndigits) digits of pi...")
mypi = pidigits(ndigits)

info("Counting number occurances of each digit...")
dcount = Dict{Int64,Int64}({i => 0 for i in 0:9})
ddrawn = Dict{Int64,Int64}({i => 0 for i in 0:9})

for d in mypi
    dcount[d] += 1
end

digit_angle = 0.85 * (360. / 10.)
space_angle = 0.15 * (360. / 10.)
delta_angle = Dict{Int64,Float64}({d => digit_angle / (dcount[d] - 1) for d in 0:9})

radius  = 4
opacity = 0.7

# tex head

texout = "\\documentclass{standalone}
\\usepackage[T1]{fontenc}
\\usepackage{tikz}

\\definecolor{d0}{RGB}{253,253,150}
\\definecolor{d1}{RGB}{174,198,207}
\\definecolor{d2}{RGB}{244,154,194}
\\definecolor{d3}{RGB}{255,105,97}
\\definecolor{d4}{RGB}{130,105,83}
\\definecolor{d5}{RGB}{255,179,71}
\\definecolor{d6}{RGB}{3,192,60}
\\definecolor{d7}{RGB}{207,207,196}
\\definecolor{d8}{RGB}{119,158,203}
\\definecolor{d9}{RGB}{203,153,201}

\\begin{document}
\\begin{tikzpicture}\n"

# connections

for i in 1:(length(mypi)-1)
    d1 = mypi[i]
    d2 = mypi[i+1]
    angled1 = d1*(36.) + ddrawn[d1] * delta_angle[d1]
    outd1 = angled1 + 180.
    angled2 = d2*(36.) + ddrawn[d2] * delta_angle[d2]
    outd2 = angled2 + 180.
    texout = string(texout, "\\draw[thin, opacity=$(opacity), d$(d1)] ($(angled1):$(radius)) to[out=$(outd1),in=$(outd2)] ($(angled2):$(radius));\n")
    ddrawn[d1] += 1
end

# legend

for d in 0:9
    angle1 = d*36
    angle2 = (d+1)*36 - space_angle
    texout = string(texout, "\\draw[very thick, d$(d)] ($(angle1):$(radius)) arc ($(angle1):$(angle2):$(radius));\n")
    texout = string(texout, "\\node at ($(angle1 + 18):{1.1*$(radius)}) {$(d)};\n")
end

# tail

texout = string(texout,"\\end{tikzpicture}\n\\end{document}\n")

# output

info("Writing .tex file...")
fileout = open("pi.tex","w")
write(fileout, texout)
close(fileout)


function restart(backcolor)
    empty_figure(filename = joinpath(@__DIR__, "test_24.png"))
    background(backcolor)
    sethue(PALETTE[8])
end
"""
    prline_1(vx, vy)

This is not for use in the final algorithm, just for debugging.
Plots a polyline until NaN or end of vectors.
"""
function prline_1(origo, vx, vy)
    move(origo + (vx[1], vy[1]))
    for (x, y) in zip(vx, vy)
        isnan(x) && break
        isnan(y) && break
        line(origo + (x, y))
    end
    do_action(:stroke)
end

"""
    euler_step_complex_1!(f, vx, vy, h, n)

Coordinates (vx[n + 1], vy[n + 1]) are updated with the estimated position,
along function f.
     f is a complex-valued gradient in two dimensions
     vx and vy are vectors representing coordinates like (vx[n], vy[n])
     h is a step-size factor
"""
function euler_step_complex_1!(f, vx, vy, h, n)
    @assert length(vx) == length(vy)
    @assert n < length(vx)
    f0 = f(vx[n], vy[n])
    vx[n + 1] = vx[n] + real(f0) * h
    vy[n + 1] = vy[n] + imag(f0) * h
end
"""
    euler_forwardsteps_1!(f, vx, vy, h)

Coordinates (vx[n > 1], vy[n > 1]) are updated with the estimated positions,
along function f.
     f is a complex-valued gradient in two dimensions
     vx and vy are vectors representing coordinates like (vx[n], vy[n])
     h is a step-size factor
"""
function euler_forwardsteps_1!(f, vx, vy, h)
    @assert length(vx) == length(vy)
    for n in 1:(length(vx) - 1)
        euler_step_complex_1!(f, vx, vy, h, n)
    end
end

"""
    rk4_step_complex_1!(f, vx, vy, h, n)

Coordinates (vx[n + 1], vy[n + 1]) are updated with the estimated position,
along function f.
     f is a complex-valued gradient in two dimensions
     vx and vy are vectors representing coordinates like (vx[n], vy[n])
     h is a step-size quantity. If the integration variable is time,
     it defines the length of time betweeen points.
"""
function rk4_step_complex_1!(f, vx, vy, h, n)
    @assert length(vx) == length(vy)
    @assert n < length(vx)
    f0 = f(vx[n], vy[n])
    x1 = vx[n] + real(f0) * h * 0.5
    y1 = vy[n] + imag(f0) * h * 0.5
    f1 = f(x1, y1)
    x2 = vx[n] + real(f1) * h * 0.5
    y2 = vy[n] + imag(f1) * h * 0.5
    f2 = f(x2, y2)
    x3 = vx[n] + real(f2) * h
    y3 = vy[n] + imag(f2) * h
    f3 = f(x3, y3)
    vx[n + 1] = vx[n] + 1/6 * h * real( f0  + 2∙f1 + 2∙f2 + f3 )
    vy[n + 1] = vy[n] + 1/6 * h * imag( f0  + 2∙f1 + 2∙f2 + f3 )
end

"""
    rrk4_forwardsteps_1!(f, vx, vy, h)

Coordinates (vx[n > 1], vy[n > 1]) are updated with the estimated positions,
along function f.
     f(x,y) returns a complex-valued gradient in two dimensions
     vx and vy are vectors representing coordinates like (vx[n], vy[n])
     h is a step-size quantity. If the integration variable is time,
     it defines the length of time betweeen points.
"""
function rrk4_forwardsteps_1!(f, vx, vy, h)
    @assert length(vx) == length(vy)
    for n in 1:(length(vx) - 1)
        rk4_step_complex_1!(f, vx, vy, h, n)
    end
end
@inline function euler_step_complex_2!(f, vx, vy, h, n)
    @assert length(vx) == length(vy)
    @assert n < length(vx)
    f0 = f(vx[n], vy[n])
    vx[n + 1] = vx[n] + real(f0) * h
    vy[n + 1] = vy[n] + imag(f0) * h
end
@inline function rk4_step_complex_2!(f, vx, vy, h, n)
    @assert length(vx) == length(vy)
    @assert n < length(vx)
    f0 = f(vx[n], vy[n])
    x1 = vx[n] + real(f0) * h * 0.5
    y1 = vy[n] + imag(f0) * h * 0.5
    f1 = f(x1, y1)
    x2 = vx[n] + real(f1) * h * 0.5
    y2 = vy[n] + imag(f1) * h * 0.5
    f2 = f(x2, y2)
    x3 = vx[n] + real(f2) * h
    y3 = vy[n] + imag(f2) * h
    f3 = f(x3, y3)
    vx[n + 1] = vx[n] + 1/6 * h * real( f0  + 2∙f1 + 2∙f2 + f3 )
    vy[n + 1] = vy[n] + 1/6 * h * imag( f0  + 2∙f1 + 2∙f2 + f3 )
end
function euler_forwardsteps_2!(f, vx, vy, h)
    @assert length(vx) == length(vy)
    for n in 1:(length(vx) - 1)
        euler_step_complex_2!(f, vx, vy, h, n)
    end
end
function rrk4_forwardsteps_2!(f, vx, vy, h)
    @assert length(vx) == length(vy)
    for n in 1:(length(vx) - 1)
        rk4_step_complex_2!(f, vx, vy, h, n)
    end
end
# Inconclusive inlining for both . Redefine the inner functions without assertions

function euler_step_complex_3!(f, vx, vy, h, n)
    f0 = f(vx[n], vy[n])
    vx[n + 1] = vx[n] + real(f0) * h
    vy[n + 1] = vy[n] + imag(f0) * h
end
function rk4_step_complex_3!(f, vx, vy, h, n)
    f0 = f(vx[n], vy[n])
    x1 = vx[n] + real(f0) * h * 0.5
    y1 = vy[n] + imag(f0) * h * 0.5
    f1 = f(x1, y1)
    x2 = vx[n] + real(f1) * h * 0.5
    y2 = vy[n] + imag(f1) * h * 0.5
    f2 = f(x2, y2)
    x3 = vx[n] + real(f2) * h
    y3 = vy[n] + imag(f2) * h
    f3 = f(x3, y3)
    vx[n + 1] = vx[n] + 1/6 * h * real( f0  + 2∙f1 + 2∙f2 + f3 )
    vy[n + 1] = vy[n] + 1/6 * h * imag( f0  + 2∙f1 + 2∙f2 + f3 )
end
function euler_forwardsteps_3!(f, vx, vy, h)
    @assert length(vx) == length(vy)
    for n in 1:(length(vx) - 1)
        euler_step_complex_3!(f, vx, vy, h, n)
    end
end
function rrk4_forwardsteps_3!(f, vx, vy, h)
    @assert length(vx) == length(vy)
    for n in 1:(length(vx) - 1)
        rk4_step_complex_3!(f, vx, vy, h, n)
    end
end
# Removing inner assertions was infavourable for Euler, favourable for RK4.

# Redefine the outer functions too without assertions

function euler_forwardsteps_4!(f, vx, vy, h)
    for n in 1:(length(vx) - 1)
        euler_step_complex_3!(f, vx, vy, h, n)
    end
end
function rrk4_forwardsteps_4!(f, vx, vy, h)
    for n in 1:(length(vx) - 1)
        rk4_step_complex_3!(f, vx, vy, h, n)
    end
end

# Removing outer assertions was favourable for Euler, not for RK4.

# Proceed with the optimal versions so far.
# Use @fastmath

@fastmath function euler_step_complex_4!(f, vx, vy, h, n)
    @assert length(vx) == length(vy)
    @assert n < length(vx)
    @fastmath f0 = f(vx[n], vy[n])
    @fastmath vx[n + 1] = vx[n] + real(f0) * h
    @fastmath vy[n + 1] = vy[n] + imag(f0) * h
end
@fastmath function rk4_step_complex_4!(f, vx, vy, h, n)
    @fastmath f0 = f(vx[n], vy[n])
    @fastmath x1 = vx[n] + real(f0) * h * 0.5
    @fastmath y1 = vy[n] + imag(f0) * h * 0.5
    @fastmath f1 = f(x1, y1)
    @fastmath  x2 = vx[n] + real(f1) * h * 0.5
    @fastmath y2 = vy[n] + imag(f1) * h * 0.5
    @fastmath f2 = f(x2, y2)
    @fastmath x3 = vx[n] + real(f2) * h
    @fastmath y3 = vy[n] + imag(f2) * h
    @fastmath f3 = f(x3, y3)
    @fastmath vx[n + 1] = vx[n] + 1/6 * h * real( f0  + 2∙f1 + 2∙f2 + f3 )
    @fastmath vy[n + 1] = vy[n] + 1/6 * h * imag( f0  + 2∙f1 + 2∙f2 + f3 )
end
@fastmath function euler_forwardsteps_5!(f, vx, vy, h)
    @fastmath for n in 1:(length(vx) - 1)
        euler_step_complex_4!(f, vx, vy, h, n)
    end
end
@fastmath function rrk4_forwardsteps_5!(f, vx, vy, h)
    @assert length(vx) == length(vy)
    @fastmath for n in 1:(length(vx) - 1)
        rk4_step_complex_4!(f, vx, vy, h, n)
    end
end

# Below, we're reusing code to generate the same flow field as in test_23. The cutoff value is no longer NaN.
if !@isdefined m²
    @import_expand(m, s)
end
ϕ_vortex_23 =  generate_complex_potential_vortex(; pos = complex(0.0, 1.0)m, vorticity = 1.0m²/s / 2π)
ϕ_source_23 = generate_complex_potential_source(; pos = complex(3.0, 0.0)m, massflowout = 1.0m²/s)
ϕ_sink_23 = generate_complex_potential_source(; pos = -complex(3.0, 0.0)m, massflowout = -1.0m²/s)
ϕ_23(p) = ϕ_vortex_23(p) + ϕ_source_23(p) + ϕ_sink_23(p)

PHYSWIDTH_23 = 10.0m
HEIGHT_RELATIVE_WIDTH_23 = 0.4
PHYSHEIGHT_23 = PHYSWIDTH_23 * HEIGHT_RELATIVE_WIDTH_23
SCREEN_WIDTH_FRAC_23 = 2 / 3
CUTOFF_23 = 0.5m/s
set_scale_sketch(PHYSWIDTH_23, round(Int, SCREEN_WIDTH_FRAC_23 * WI))

"""
The test flow field from test_23 as a matrix of complex velocities with one element per pixel.
We're filling the above-cutoff values with the cutoff value instead of with NaN.
"""
function flowfield_23()
    unclamped = ∇_rectangle(ϕ_23,
        physwidth = PHYSWIDTH_23,
        physheight  = PHYSWIDTH_23 * HEIGHT_RELATIVE_WIDTH_23);
    map(unclamped) do u
        hypot(u) > CUTOFF_23 ? CUTOFF_23∙u / hypot(u) : u
    end
end


module _DistEn
export DistEn
using StatsBase: Histogram, fit, skewness
    """
        Dist, Ppi = DistEn(Sig)

    Returns the distribution entropy estimate (`Dist`) and the
    corresponding distribution probabilities (`Ppi`) estimated from
    the data sequence (`Sig`) using the default  parameters:
    embedding dimension = 2, time delay = 1, binning method = 'Sturges',
    logarithm = base 2, normalisation = w.r.t # of histogram bins

        Dist, Ppi = DistEn(Sig::AbstractArray{T,1} where T<:Real; m::Int=2, tau::Int=1, Bins::Union{Int,String}="Sturges", Logx::Real=2, Norm::Bool=true)

    Returns the distribution entropy estimate (`Dist`) estimated from
    the data sequence (`Sig`) using the specified 'keyword' arguments:

    # Arguments:
    `m`     - Embedding Dimension, a positive integer  \n
    `tau`   - Time Delay, a positive integer  \n
    `Bins`  - Histogram bin selection method for distance distribution,
              one of the following:  \n
              an integer > 1 indicating the number of bins, or one of the
              following strings {'sturges','sqrt','rice','doanes'}
              [default: 'sturges']
    `Logx`  - Logarithm base, a positive scalar (enter 0 for natural log)   \n
    `Norm`  - Normalisation of DistEn value:  \n
              [false]  no normalisation.
              [true]   normalises w.r.t # of histogram bins - default

    # See also `XDistEn`, `DistEn2D`, `MSEn`, `K2En`

    # References:
        [1] Li, Peng, et al.,
            "Assessing the complexity of short-term heartbeat interval
            series by distribution entropy."
            Medical & biological engineering & computing
            53.1 (2015): 77-87.

    """
    function DistEn(Sig::AbstractArray{T,1} where T<:Real; m::Int=2, tau::Int=1,
        Bins::Union{Int,String}="Sturges", Logx::Real=2, Norm::Bool=true)

    Logx == 0  ? Logx = exp(1) : nothing

    N = size(Sig)[1]
    (N>10) ? nothing : error("Sig:  must be a numeric vector")
    (m > 0) ? nothing : error("m:   must be an integer > 0")
    (tau > 0) ? nothing : error("tau:   must be an integer > 0")
    (Logx>0) ? nothing :  error("Logx:  must be a positive number > 0")
    if typeof(Bins)<:Int
        (Bins>1) ? nothing :
        error("Bins:  must be an integer > 1 (or name of binning method)")
    elseif typeof(Bins)<:String
        (lowercase(Bins) in ["sturges","sqrt","rice","doanes"]) ? nothing :
        error("Bins:    must be one of the following strings
                'sturges', 'sqrt', 'rice', 'doanes' (or an integer >1)")
    end

    Nx = size(Sig)[1] - ((m-1)*tau)
    Zm = zeros(Nx,m)
    for n = 1:m
        Zm[:,n] = Sig[(n-1)*tau + 1:Nx+(n-1)*tau]
    end

    DistMat = zeros(Int(Nx*(Nx-1)/2))
    for k = 1:Nx-1
        Ix = [Int((k-1)*(Nx - k/2)+1), Int(k*(Nx-((k+1)/2)))]
        DistMat[Ix[1]:Ix[2]] = maximum(abs.(transpose(Zm[k,:]) .- Zm[k+1:end,:]),dims=2)
        # DistMat[Ix[1]:Ix[2]] = maximum(abs.(repeat(Zm[k,:],outer=(1,Nx-k)) .- Zm[k+1:end,:]'),dims=2)
    end

    Ny = size(DistMat)[1]
    if eltype(Bins)<:Char
        if lowercase(Bins) == "sturges"
            Bx = ceil(log2(Ny) + 1)
        elseif lowercase(Bins) == "rice"
            Bx = ceil(2*(Ny^(1/3)))
        elseif lowercase(Bins) == "sqrt"
            Bx = ceil(sqrt(Ny))
        elseif lowercase(Bins) == "doanes"
            sigma = sqrt(6*(Ny-2)/((Ny+1)*(Ny+3)))
            Bx = ceil(1+log2(Ny)+log2(1+abs(skewness(DistMat)/sigma)))
        else
            error("Please enter a valid binning method")
        end
    else
        Bx = Bins
    end
    By = collect(range(minimum(DistMat),maximum(DistMat),length=Int(Bx+1)))
    By[end] += 1; By[1]-= 1
    Ppi = fit(Histogram, DistMat, By).weights/Ny

    if round(sum(Ppi),digits=6) != 1
        @warn("Potential error estimating probabilities (p = $(sum(Ppi))")
        Ppi = Ppi[Ppi.!=0]
    elseif any(Ppi.==0)
        print("Note: $(sum(Ppi.==0))/$(length(Ppi)) bins were empty \n")
        Ppi = Ppi[Ppi.!=0]
    end
    Dist = -sum(Ppi.*log.(Logx, Ppi))
    if Norm
        Dist = Dist/(log(Logx, Bx));
    end

    return Dist, Ppi
    end

end

"""
Copyright 2021 Matthew W. Flood, EntropyHub

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

For Terms of Use see https://github.com/MattWillFlood/EntropyHub
"""


# three dimension vector
import Base:+,-,*,/,^

struct Vec3{T<:Real}
    x::T
    y::T
    z::T
    Vec3{T}(x::T, y::T, z::T) where {T<:Real} = new(x, y, z)
end

Vec3(x::T, y::T, z::T) where {T<:Real} = Vec3{T}(x, y, z)
Vec3(x, y, z) = Vec3(promote(x, y, z)...)
vec3(x, y, z) = Vec3(x, y, z)
vec3_from_spherical(r::Float64, θ::Float64, φ::Float64) = Vec3(
    r*sin(θ)*cos(φ), r*sin(θ)*sin(φ), r*cos(θ)
)

Base.show(io::IO, v::Vec3) = begin
    print(io, "($(v.x), $(v.y), $(v.z))")
end

Base.eltype(::Vec3{T}) where {T<:Real} = T
Base.float(v::Vec3) = Vec3{Float64}(float(v.x), float(v.y), float(v.z))

+(v::Vec3) = v
-(v::Vec3) = Vec3(-v.x, -v.y, -v.z)
+(v1::Vec3, v2::Vec3) = Vec3(v1.x+v2.x, v1.y+v2.y, v1.z+v2.z)
-(v1::Vec3, v2::Vec3) = Vec3(v1.x-v2.x, v1.y-v2.y, v1.z-v2.z)
*(v1::Vec3, v2::Vec3) = v1.x*v2.x + v1.y*v2.y + v1.z*v2.z
*(k::Real, v::Vec3) = Vec3(k*v.x, k*v.y, k*v.z)
*(v::Vec3, k::Real) = k * v
/(v::Vec3, k::Real) = Vec3(v.x/k, v.y/k, v.z/k)
×(v1::Vec3, v2::Vec3) = Vec3(
    v1.y * v2.z - v1.z * v2.y,
    v1.z * v2.x - v1.x * v2.z,
    v1.x * v2.y - v1.y * v2.x
)

Base.abs2(v::Vec3) = v.x^2 + v.y^2 + v.z^2
Base.abs(v::Vec3) = hypot(v.x, v.y, v.z)
Base.length(v::Vec3) = abs(v)
distance(v1::Vec3, v2::Vec3) = abs(v1 - v2)

Base.getindex(v::Vec3, i::Integer) = begin
    i == 1 && return v.x
    i == 2 && return v.y
    i == 3 && return v.z
    error("out of range for Vec3 getindex i = $i")
end


using RDatasets, Plots, Measures; pyplot()

data = dataset("datasets", "iris")
println("Number of rows: ", nrow(data))

insertSpace(name) = begin
    i = findlast(isuppercase,name)
    name[1:i-1]*" "*name[i:end]
end

featureNames = insertSpace.(string.(names(data)))[1:4]
println("Names of features:\n\t", featureNames)

speciesNames = unique(data.Species)
speciesFreqs = [sn => sum(data.Species .== sn) for sn in speciesNames]
println("Frequency per species:\n\t", speciesFreqs)

default(msw = 0, ms = 3)

scatters = [
    scatter(data[:,i], data[:,j], c=[:blue :red :green], group=data.Species,
        xlabel=featureNames[i], ylabel=featureNames[j], legend = i==1 && j==1)
    for i in 1:4, j in 1:4 ]

plot(scatters..., size=(1200,800), margin = 4mm)