import Base.show

using Colors
using CSV
using LinearAlgebra
using Plots

"""
    getpoints(path::String, [usingnames::Bool])

Read the x and y coordinates from a list in a CSV file at `path`.

Also read the names if `usingnames`.

# Example
```julia-repl
julia> getpoints("points.txt")
```
"""
function getpoints(path::String, usingnames::Bool = false)
    xs = Int64[]
    ys = Int64[]
    names = String[]
    for row ∈ CSV.Rows(path)
        push!(xs,parse(Int64,row.x))
        push!(ys,parse(Int64,row.y))
        if usingnames
            push!(names,row.name)
        end
    end
    return usingnames ? (names, xs, ys) : (xs, ys)
end

"""
    bruteforcevoronoi(xs::Vector{Int64}, ys::Vector{Int64})

Draw a Voronoi diagram from lists of coordinates `xs` and `ys` by individually colouring every square of a 1000x1000 grid.

# Example
```julia-repl
julia> bruteforcevoronoi(rand(1:1000, 10), rand(1:1000, 10))
```
"""
function bruteforcevoronoi(xs::Vector{Int64}, ys::Vector{Int64})
    m = length(xs)
    colours = distinguishable_colors(m, lchoices = 50:5:100)

    pointcolours = fill(RGB(0,0,0), (1000,1000))
    for y ∈ 1:1000
        for x ∈ 1:1000
            pointcolours[x,y] = colours[findmin(hypot.(xs .- x, ys .- y))[2]]
        end
    end

    diagram = scatter(
        [(x,y) for x ∈ 1:1000, y ∈ 1:1000][:];
        c = pointcolours[:],
        markersize = 1,
        markerstrokewidth = 0,
        legend = false,
        showaxis = false,
        ticks = false,
        size = (1000, 1000)
    )
    return scatter!(
        diagram,
        xs,
        ys;
        c = :black,
        markersize = 2,
        markerstrokewidth = 0
    )
end

"""
    montecarlovoronoi(n::Int64, xs::Vector{Int64}, ys::Vector{Int64})

Sample `n` times from a 1000x1000 grid to approximate a Voronoi diagram from lists of coordinates `xs` and `ys`.

# Example
```julia-repl
julia> montecarlovoronoi(100000, rand(1:1000, 10), rand(1:1000, 10))
```
"""
function montecarlovoronoi(n::Int64, xs::Vector{Int64}, ys::Vector{Int64})
    m = length(xs)
    colours = distinguishable_colors(m, lchoices = 50:5:100)

    randxs = Int64[]
    randys = Int64[]
    randcolours = RGB[]

    for _ ∈ 1:n
        x, y = rand(1:1000), rand(1:1000)
        push!(randxs, x)
        push!(randys, y)
        push!(randcolours, colours[findmin(hypot.(xs .- x, ys .- y))[2]])
    end

    diagram = scatter(
        randxs,
        randys;
        c = randcolours,
        markersize = 1,
        markerstrokewidth = 0,
        legend = false,
        showaxis = false,
        ticks = false,
        size = (1000, 1000)
    )
    return scatter!(
        diagram,
        xs,
        ys;
        c = :black,
        markersize = 2,
        markerstrokewidth = 0,
    )
end

"""
    VoronoiPoint(x::Real, y::Real, neighbours::Vector{VoronoiPoint})
    VoronoiPoint(x::Real, y::Real)

Construct a `VoronoiPoint` at the coordinates `x`, `y`, representing a point in a Voronoi diagram.

If no neighbours are specified, default to an empty `Vector{VoronoiPoint}`.

# Examples
```julia-repl
julia> p = VoronoiPoint(3,4)
(3,4)

julia> q = VoronoiPoint(4,5,[p])
(4,5)

julia> p.neighbours
VoronoiPoint[]

julia> q.neighbours
1-element Vector{VoronoiPoint}:
 (3,4)
```
"""
mutable struct VoronoiPoint
    x::Real
    y::Real
    neighbours::Vector{VoronoiPoint}
end
VoronoiPoint(x::T, y::T) where T <: Real = VoronoiPoint(x, y, VoronoiPoint[])

show(io::IO, p::VoronoiPoint) = print(io, "(", p.x, ",", p.y, ")")

"""
    pos(p::VoronoiPoint)

Return the `x` and `y` coordinates of the `VoronoiPoint` `p` as a `Vector`.

# Example
```julia-repl
julia> pos(VoronoiPoint(3,4))
2-element Vector{Int64}:
 3
 4
```
"""
pos(p::VoronoiPoint) = [p.x, p.y]

"""
    perpbisector(p::VoronoiPoint, q::VoronoiPoint)

Return the midpoint of `VoronoiPoint`s `p` and `q`, and direction vector along the perpendicular bisector between of the same length as `p - q` and direction such that `p` is to the right and `q` is to the left.

# Example
```julia-repl
julia> perpbisector(VoronoiPoint(0,1), VoronoiPoint(2,1))
([1.0, 1.0], [0, -2])
```
"""
function perpbisector(p::VoronoiPoint, q::VoronoiPoint)
    return ( (pos(p) + pos(q))/2 , [[0,1] [-1,0]] * (pos(p) - pos(q)) )
end

"""
    circumcentre(p::VoronoiPoint, q::VoronoiPoint, r::VoronoiPoint)

Return the circumcentre of the three `VoronoiPoint`s `p`, `q`, and `r`.

If they are collinear, return `[Inf, Inf]`.

# Example
```julia-repl
julia> circumcentre(VoronoiPoint(0,0), VoronoiPoint(1,0), VoronoiPoint(0,1))
2-element Vector{Float64}:
 0.5
 0.5
```
"""
function circumcentre(p::VoronoiPoint, q::VoronoiPoint, r::VoronoiPoint)
    (x₁, v₁) = perpbisector(p,q)
    (x₂, v₂) = perpbisector(p,r)
    det([v₁ v₂]) == 0 && return [Inf, Inf]
    M = [v₁ v₂]^-1
    t₁ = (M * (x₂ - x₁))[1]
    return x₁ + t₁*v₁
end

"""
    findnextpoint(p::VoronoiPoint, q::VoronoiPoint, z::Vector{T}, reversedirection::Bool) where T <: Real

Find the `r` from `q.neighbours` where the circumcentre of `p`, `q`, and `r` is closest to `z` in the direction determined by `perpbisector`.

If `reversedirection == true`, find `r` based on the opposite direction.

# Examples
```julia-repl
julia> p = VoronoiPoint(0,0)
(0,0)

julia> q = VoronoiPoint(0,1,[p, VoronoiPoint(3,4), VoronoiPoint(-3,4)])
(0,1)

julia> findnextpoint(p, q, [0, 0.5], false)
(3,4)

julia> findnextpoint(p, q, [0, 0.5], true)
(-3,4)
```
"""
function findnextpoint(p::VoronoiPoint, q::VoronoiPoint, z::Vector{T}, reversedirection::Bool) where T <: Real
    directionvector = perpbisector(p,q)[2] .* (-1)^reversedirection

    mindisplacement = Inf
    nextpoint = VoronoiPoint(Inf,Inf)
    for r ∈ q.neighbours
        r ∈ [p,q] && continue
        displacement = dot(directionvector, circumcentre(p,q,r) - z)
        displacement > 0 && displacement < mindisplacement && (mindisplacement = displacement; nextpoint = r)
    end
    return nextpoint
end

"""
    addpoint(x::Real, y::Real, allpoints::Vector{VoronoiPoint})

Add a new `VoronoiPoint` at coordinates `x` and `y`, including calculating all neighbours from `allpoints`.

Also, update neighbour lists of other points in `allpoints` to agree with the new addition.

# Example
```julia-repl
julia> p = VoronoiPoint(1,1)
(1,1)

julia> q = VoronoiPoint(2,2)
(2,2)

julia> p.neighbours = [VoronoiPoint(Inf,Inf), q, VoronoiPoint(Inf,Inf)]
3-element Vector{VoronoiPoint}:
 (Inf,Inf)
 (2,2)
 (Inf,Inf)

julia> q.neighbours = [VoronoiPoint(Inf,Inf), p, VoronoiPoint(Inf,Inf)]
3-element Vector{VoronoiPoint}:
 (Inf,Inf)
 (1,1)
 (Inf,Inf)

julia> r = addpoint(1, 2, [p,q])
(1,2)

julia> p.neighbours
4-element Vector{VoronoiPoint}:
 (Inf,Inf)
 (2,2)
 (1,2)
 (Inf,Inf)

julia> q.neighbours
4-element Vector{VoronoiPoint}:
 (Inf,Inf)
 (1,2)
 (1,1)
 (Inf,Inf)

julia> r.neighbours
4-element Vector{VoronoiPoint}:
 (Inf,Inf)
 (1,1)
 (2,2)
 (Inf,Inf)
```
"""
function addpoint(x::Real, y::Real, allpoints::Vector{VoronoiPoint})
    p = VoronoiPoint(x, y)
    nearestpoint = allpoints[findmin([hypot(x-q.x, y-q.y) for q ∈ allpoints])[2]]

    z = perpbisector(p, nearestpoint)[1]
    q = nearestpoint
    reversedirection = false
    while true
        push!(p.neighbours, q)
        r = findnextpoint(p, q, z, reversedirection)

        # Edge goes to infinity
        if pos(r) == [Inf,Inf]
            push!(p.neighbours, r)
            if reversedirection
                break
            else
                reversedirection = true
                q = nearestpoint
                z = perpbisector(p, nearestpoint)[1]
                pop!(reverse!(p.neighbours))
                continue
            end

        # Back to start
        elseif pos(r) == pos(nearestpoint)
            reverse!(p.neighbours)
            break

        else
            z = circumcentre(p,q,r)
            q = r
        end
    end

    n = length(p.neighbours)
    for i ∈ 1:n
        q = p.neighbours[i]
        pos(q) == [Inf, Inf] && continue
        j = findlast(r -> pos(r) == pos(p.neighbours[mod(i-1,1:n)]), q.neighbours)
        k = findfirst(r -> pos(r) == pos(p.neighbours[mod(i+1,1:n)]), q.neighbours)
        if j < k
            q.neighbours = vcat(q.neighbours[j:k], p)
        else
            q.neighbours = vcat(q.neighbours[1:k], p, q.neighbours[j:end])
        end
    end

    return p
end

"""
    voronoipoints(xs::Vector{T}, ys::Vector{T}) where T <: Real

Calculate the lists of neighbouring regions of a Voronoi diagram with points at coordinates given by `xs` and `ys`.

Return a list of `VoronoiPoint`s containing this information, with `Rational` parameters.

# Example
```julia-repl
voronoipoints([1,2,3],[2,1,3])
julia> points = voronoipoints([1,2,3],[2,1,3])
3-element Vector{VoronoiPoint}:
 (1//1,2//1)
 (2//1,1//1)
 (3//1,3//1)

julia> points[1].neighbours
4-element Vector{VoronoiPoint}:
 (Inf,Inf)
 (2//1,1//1)
 (3//1,3//1)
 (Inf,Inf)

julia> points[2].neighbours
4-element Vector{VoronoiPoint}:
 (Inf,Inf)
 (3//1,3//1)
 (1//1,2//1)
 (Inf,Inf)

julia> points[3].neighbours
4-element Vector{VoronoiPoint}:
 (Inf,Inf)
 (1//1,2//1)
 (2//1,1//1)
 (Inf,Inf)
```
"""
function voronoipoints(xs::Vector{T}, ys::Vector{T}) where T <: Real
    # Rational numbers!
    xs, ys = Rational{BigInt}.(xs), Rational{BigInt}.(ys)
    points = [VoronoiPoint(xs[1],ys[1],[VoronoiPoint(Inf,Inf)])]
    for i ∈ 2:length(xs)
        push!(points,addpoint(xs[i],ys[i],points))
    end
    return points
end

"""
    voronoi(xs::Vector{T}, ys::Vector{T}; [names::Vector{String}]) where T <: Real

Draw a Voronoi diagram from points at coordinates `xs` and `ys` in the region (x,y) ∈ [0,1000]².

Points given `names` in a legend if specified.

# Example
```julia-repl
julia> voronoi(rand(1:1000, 10), rand(1:1000, 10))
```
"""
function voronoi(xs::Vector{T}, ys::Vector{T}; names::Vector{String} = String[]) where T <: Real
    points = voronoipoints(xs,ys)
    m = length(xs)
    colours = distinguishable_colors(m, lchoices = 50:5:100)
    usingnames = !isempty(names)

    polygons = Shape[]
    for p ∈ points
        n = length(p.neighbours)
        vertices = Tuple{Rational{Int64}, Rational{Int64}}[]

        if pos(p.neighbours[1]) == [Inf,Inf]
            startindex, endindex = 2, n-2

            x₁, v₁ = perpbisector(p.neighbours[n-1],p)
            x₁, v₁ = Rational{Int64}.(x₁), Rational{Int64}.(v₁)
            # Values of t₁ where the line x₁ + t₁v₁ intersects the borders
            t₁vals = [-x₁[2]/v₁[2], (1000-x₁[1])/v₁[1], (1000-x₁[2])/v₁[2], -x₁[1]/v₁[1]]
            t₁ = minimum(t₁vals[t₁vals .> 0])
            push!(vertices,Tuple(x₁ + t₁*v₁))

            x₂, v₂ = perpbisector(p,p.neighbours[2])
            x₂, v₂ = Rational{Int64}.(x₂), Rational{Int64}.(v₂)
            t₂vals = [-x₂[2]/v₂[2], (1000-x₂[1])/v₂[1], (1000-x₂[2])/v₂[2], -x₂[1]/v₂[1]]
            t₂ = minimum(t₂vals[t₂vals .> 0])

            t₁index = findfirst(==(t₁),t₁vals)
            t₂index = findfirst(==(t₂),t₂vals)
            indexdifference = mod(t₂index - t₁index,4)
            corners = [(0,0), (1000,0), (1000,1000), (0,1000)]
            for j ∈ 1:indexdifference
                push!(vertices,corners[mod(t₁index+j, 1:4)])
            end

            push!(vertices,Tuple(x₂ + t₂*v₂))

        else
            startindex, endindex = 1, n
        end

        for j ∈ startindex:endindex
            q,r = p.neighbours[j], p.neighbours[mod(j+1,1:n)]
            push!(vertices,Tuple(circumcentre(p,q,r)))
        end

        push!(polygons, Shape(vertices))
    end

    diagram = plot(
        polygons,
        c = colours',
        label = (usingnames ? hcat(names...) : false),
        showaxis = false,
        ticks = false,
        size = (1000, 1000),
        xlims = (0, 1000),
        ylims = (0, 1000)
    )
    return scatter!(diagram, xs, ys, c = :black, markersize = 2, label = false)
end

"""
    voronoidistribution(points::Vector{VoronoiPoint})

Count the number of edges of each of the polygons from a Voronoi diagram, as represented by `points`.

`points` should usually be the output of the function `voronoipoints`.

Display the counts in a bar chart.

# Example
```julia-repl
voronoidistribution(voronoipoints(rand(1:1000, 10), rand(1:1000, 10)))
```
"""
function voronoidistribution(points::Vector{VoronoiPoint})
    neighbourcount = broadcast(p -> pos(p.neighbours[1]) == [Inf, Inf] ? 0 : length(p.neighbours), points)

    m = maximum(neighbourcount)
    total = count(!=(0), neighbourcount)
    return bar(
        1:m,
        [count(==(i), neighbourcount)//total for i ∈ 1:m],
        legend = false,
        xticks = 1:m,
        yticks = 0:0.1:1
    )
end

"""
    randomlattice(shape::Symbol, n::Int64, σ::Rational{Int64})

Create a lattice of `n^2` points, with each perturbed randomly both vertically and horizontally by a normal distribution of standard deviation `σ`.

If `shape == :square`, the lattice is a square lattice.

If `shape == :hexagonal`, the lattice is an approximately equilateral hexagonal lattice.

Return a bar chart of the distribution of edges of the polygons in the Voronoi diagram generated by these points.

# Examples
```julia-repl
julia> randomlattice(:square, 10, 1)

julia> randomlattice(:hexagonal, 20, 1//10)
```
"""
function randomlattice(shape::Symbol, n::Int64, σ::Rational{Int64})
	if shape == :square
		coords = [(x,y) for x ∈ 1:n, y ∈ 1:n]
	elseif shape == :hexagonal
		coords = hcat([(x,(6//7)*y) for x ∈ 1:n, y ∈ 1:2:n],
			[(x+1//2,(6//7)*y) for x ∈ 1:n, y ∈ 2:2:n])
	else
		error("Invalid shape")
	end

	xs,ys = [xy[1] for xy ∈ coords[:]], [xy[2] for xy ∈ coords[:]]
	randxs = xs .+ σ * round.(Int64, n * randn(n^2))//n
	randys = ys .+ σ * round.(Int64, n * randn(n^2))//n
	return voronoidistribution(voronoipoints(randxs,randys))
end


### A Pluto.jl notebook ###
# v0.12.16

using Markdown
using InteractiveUtils

# ╔═╡ e077ab16-22e5-11eb-15ed-5f0654bf7328
using Pkg, DrWatson

# ╔═╡ e75a9a06-22e5-11eb-2e36-8d4f62b830ed
begin
	@quickactivate "StatisticsWithJuliaPlutoNotebooks"
	using Random, Distributions, Plots, LaTeXStrings
	Random.seed!(0)
end;

# ╔═╡ ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
md"## Listing5.03"

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
function statPair(dist,n)
    sample = rand(dist,n)
    [mean(sample),var(sample)]
end

# ╔═╡ 1111d8c4-252c-11eb-3813-81de856f7fba
begin
	stdUni = Uniform(-sqrt(3),sqrt(3))
	n, N = 3, 10^4

	dataUni     = [statPair(stdUni,n) for _ in 1:N]
	dataUniInd  = [[mean(rand(stdUni,n)),var(rand(stdUni,n))] for _ in 1:N]
	dataNorm    = [statPair(Normal(),n) for _ in 1:N]
	dataNormInd = [[mean(rand(Normal(),n)),var(rand(Normal(),n))] for _ in 1:N]
end;

# ╔═╡ 11120d28-252c-11eb-1c1e-0dba64d8ae5c
begin
	p1 = scatter(first.(dataUni), last.(dataUni),
		c=:blue, ms=1, msw=0, label="Same group")
	p1 = scatter!(first.(dataUniInd), last.(dataUniInd),
		c=:red, ms=0.8, msw=0, label="Separate group",
		xlabel=L"\overline{X}", ylabel=L"S^2")

	p2 = scatter(first.(dataNorm), last.(dataNorm),
		c=:blue, ms=1, msw=0, label="Same group")
	p2 = scatter!(first.(dataNormInd), last.(dataNormInd),
		c=:red, ms=0.8, msw=0, label="Separate group",
		xlabel=L"\overline{X}", ylabel=L"$S^2$")

	plot(p1, p2, ylims=(0,5), size=(800, 400))
end

# ╔═╡ 475ff888-22e6-11eb-2354-09f8f40a8e12
md"## End of listing5.03"

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═1111d8c4-252c-11eb-3813-81de856f7fba
# ╠═11120d28-252c-11eb-1c1e-0dba64d8ae5c
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


#=
   Lotto Problem

   From some unknown source (but I quoted it here: http://www.hakank.org/sims/simulering.html)
   """
   One day I was asked

   What is the probability that no two numbers are consecutive in
   a lottery draw?

   Information:

   National Lottery in UK is to draw 6 different numbers from 1 to 49.
   so, for example,

    (a)   1  4  7   12  19  44    - no consecutive numbers
    (b)   3  6  17  18  44  46    - 17 and 18 are consecutive
    (c)   1  2  3   17  29  49    - 1, 2 and 3 are consecutive

   We are asking the probability that class (a) occurs. Hope this is clear.

   Observation shows that it is NOT a small number (actually near half-and-half).
   ......

   A Monte Carlo simulation experiment produced 50,558 sets of six numbers
   between 1 and 49 with none consecutive out of 100,000 trials giving an
   estimated probability of .50558.
   """

   Summary Statistics
   parameters      mean       std   naive_se      mcse          ess      rhat   ess_per_sec
      Symbol   Float64   Float64    Float64   Float64      Float64   Float64       Float64

        prob    0.5054    0.5000     0.0025    0.0034   22057.4684    1.0000     1884.6094

=#
using Turing, StatsPlots, Distributions
include("jl_utils.jl")

@model function lotto_not_two_consecutive_numbers(n=49,m=6)

    lotto ~ filldist(DiscreteUniform(1,n),m)
    # Ensure unicity
    true ~ Dirac(sum([lotto[i] == lotto[j] for i in 1:m for j in i+1:m]) == 0)
    # Are there any consecutive numbers?
    prob ~ Dirac(sum([ abs(lotto[i]-lotto[j]) == 1 for i in 1:m for j in i+1:m]) == 0)
end

n = 49
m = 6
model = lotto_not_two_consecutive_numbers(n,m)

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
chns = sample(model, PG(5), 40_000)
# chns = sample(model, SMC(), 10_000)
# chns = sample(model, IS(), 10_000)

display(chns[[:prob]])
# display(plot(chns))


"""
    householderqr(R1, R2) computes the QR decompsition of
    (m+l)-by-l block matrix [R1;R2]

```math
          l               l
    m  (  R1  )  =  m  (  Q1  ) * R
    l  (  R2  )     l  (  Q2  )
```

- Q1 is m-by-l orthogonal matrix,
- Q2 is l-by-l orthogonal matrix,
- R is l-by-l upper triangular matrix.

# arguments:
R1: m-by-l matrix,
R2: m-by-l matrix.

# returns:
Q1: m-by-l matrix,
Q2: l-by-l matrix,
R: l-by-l matrix.
"""
function householderqr(R1, R2)
    r, c = size(R1)
    F = qr!([R1;R2])
    Q = Matrix(F.Q)
    Q1 = Q[1:r,:]
    Q2 = Q[r+1:end,:]
    return Q1, Q2, F.R
end


"""
sigmoid activation function
"""
sigmoid(z::Number) = 1 / (1 + e^(-z))
sigmoid{T<:Number}(arr::Array{T}) = map(sigmoid, arr)

partial_sigmoid(z::Number) = z * (1 - z)

"""
tanh activation function
"""
tanh = Base.tanh

partial_tanh(z::Number) = 2 / (e^z + e^-z)

"""
ReLU activation function
"""
relu(x::Real) = max(0, x)
relu{T<:Real}(arr::Array{T}) = map(relu, arr)


using Distributions, Plots; pyplot()

mu, sig = 2, 3
eta = sqrt(3)*sig/pi
n, N = 15, 10^7
dNormal   = Normal(mu, sig)
dLogistic = Logistic(mu, eta)
xGrid = -8:0.1:12

sNormal   = [var(rand(dNormal,n)) for _ in 1:N]
sLogistic = [var(rand(dLogistic,n)) for _ in 1:N]

p1 = plot(xGrid, pdf.(dNormal,xGrid), c=:blue, label="Normal")
p1 = plot!(xGrid, pdf.(dLogistic,xGrid), c=:red, label="Logistic",
	xlabel="x",ylabel="Density", xlims= (-8,12), ylims=(0,0.16))

p2 = stephist(sNormal, bins=200, c=:blue, normed=true, label="Normal")
p2 = stephist!(sLogistic, bins=200, c=:red, normed=true, label="Logistic",
	xlabel="Sample Variance", ylabel="Density", xlims=(0,30), ylims=(0,0.14))

plot(p1, p2, size=(800, 400))


# method of moments estimators for a sample from Chi^2(theta)
# increase n to observe consistency
# note that the two estimators are different from one another
using Distributions
n = 30
theta = 3

# the distribution doesn't matter, what matters is
# that the moments are correctly specified
#y = rand(Chisq(theta), n)
y = rand(Normal(theta, sqrt(2*theta)), n)

# a MM estimator
thetahat = mean(y)
println("mm estimator based on mean: ", thetahat)

# a second MM estimator
thetahat = 0.5*var(y)
println("mm estimator based on variance: ", thetahat)


using MDP

# This example describes a Markov decision process where
#
# S = {1, 2}
# A = {1, 2}

# r = S x A matrix

r = [ 5  10
     -1  -Inf ]

# Transition matrix
# P(1) = [0.5, 0.5; 0.0, 1.0]
# P(2) = [0.0, 1.0; 0.5, 0.5]
P = Matrix[ [ 0.5  0.5
              0.0  1.0 ],
            [ 0.0  1.0
              0.5  0.5 ] ]

# Construct the MDP model
model = ProbModel(r, P; objective=:Max)

# Compute the optimal solution
(v,g) = valueIteration(model; discount=0.95, tolerance=1e-4)

println(g) # [1, 1]
println(v) # [-8.571401228528563,-19.999971289954992]

# For this model, the optimal solution is:

# g = [1, 1]
# w[1] = (5 - 5.5 β) / (1 -0.5 β)(1 - β)
# w[2] = -1/(1-β)

# which equals
#
# w = [ -8.57142857142855, -19.999999999999982]

# As expected, v is within 1e-4 of w (in sup-norm).


function main()
    A, B, C = parse.(Float64, split(readline()))
    g(t; A=A, B=B, C=C) = A * t + B * sin(C * t * π) - 100
    t̲, t̄ = 0, (B + 100) / A
    ε = 1e-6

    t = (t̄ + t̲) / 2
    while abs(g(t)) > ε
        if g(t) > 0
            t̄ = t
        else
            t̲ = t
        end
        t = (t̄ + t̲) / 2
    end

    println(t)
    println()
end

main()


# Computes the SVD of a 2-by-2 matrix M, returning U, S, and V such that A == U*S*V'.
# Reference:
# Blinn, J. (1996). Consider the lowly 2 x 2 matrix. IEEE Computer Graphics and Applications, 16(2), 82-88.
# https://scicomp.stackexchange.com/questions/8899/robust-algorithm-for-2-times-2-svd
function svd2x2(M::AbstractArray)
    E = (M[1,1] + M[2,2]) / 2
    F = (M[1,1] - M[2,2]) / 2
    G = (M[2,1] + M[1,2]) / 2
    H = (M[2,1] - M[1,2]) / 2
    Q = sqrt(E^2 +H^2)
    R = sqrt(F^2 + G^2)
    sx = Q + R
    sy = Q - R
    a₁ = atan2(G,F)
    a₂ = atan2(H,E)
    θ = (a₂ - a₁) / 2
    ϕ = (a₂ + a₁) / 2
    s = sign(sy)
    U = SMatrix{2,2,Float64}(cos(ϕ), sin(ϕ), -s*sin(ϕ), s*cos(ϕ))
    S = SMatrix{2,2,Float64}(sx, 0.0, 0.0, abs(sy))
    V = SMatrix{2,2,Float64}(cos(θ), -sin(θ), sin(θ), cos(θ))
    U,S,V
end

#  Computes the Moore-Penrose pseudoinverse for a 2-by-2 matrix M by only inverting
#  singular values above the threshold `tol = sqrt(eps(real(float(one(eltype(M))))))`
function pinv2x2(M::AbstractArray)
     tol = sqrt(eps(real(float(one(eltype(M))))))
     U,S,V = svd2x2(M)
     D = SMatrix{2,2,Float64}( S[1,1] > tol ?  1/S[1,1] : 0.0 , 0.0, 0.0, S[2,2] > tol ?  1/S[2,2] : 0.0 )
     U*D*V'
end


"""
Solve system of linear equations: L*x = b, where L is Laplacian matrix
"""
function linsolve_cg_v2!( LF::LF3dGrid, x::Array{Float64,1}, b::Array{Float64,1};
                          NiterMax=1000, verbose=false, TOL=5.e-10 )
  #
  Npoints = size(x)[1]
  #
  L_x = apply_Laplacian( LF, x )
  #
  r = b[:] - L_x[:]
  z = r[:]
  p = z[:]

  r_old = r[:]
  z_old = z[:]
  p_old = p[:]
  for iter = 1 : NiterMax
    #
    L_x = apply_Laplacian( LF, p )
    #
    α = dot( r, z ) / dot( p, L_x )
    #
    x[:] = x[:] + α * p[:]  # FIXME use x[:] to force x to be copied, not referenced
    r[:] = r[:] - α * L_x[:]
    #
    norm_res = sqrt( dot( r, r ) )
    if verbose
      @printf("%8d %20.10f\n", iter, norm_res)
    end
    #
    if norm_res < TOL
      if verbose
        @printf("#CONV in linsolve_cg_v2: iter, norm_res: %8d %10.5e\n", iter, norm_res)
      end
      break
    end
    #
    z = r[:]  #XXX call appropriate preconditioner here
    # Polak-Ribiere
    β = dot( z, r - r_old ) / dot( z_old, r_old )
    p = z[:] + β * p_old[:]
    #
    z_old = z[:]
    r_old = r[:]
    p_old = p[:]
  end
  #
  return
  #
end # of function


"""
    Ormsby(; <keyword arguments>)

Create a Ormsby wavelet sampled every dt seconds with corner frequencies
defined by the vector f = [f1, f2, f3, f4]. The final wavelet is multiplied by
a Hamming window.

# Arguments
- `dt::AbstractFloat=0.002`: sampling interval in secs.
- `f::Vector{AbstractFloat}=[2.0, 10.0, 40.0, 60.0]`: corner frequencies in Hz.
      ^
    1 |     ***************
      |    *               *
      |   *                 *
      |  *                   *
      | *                     *
      -----------------------------> f
        f1  f2           f3  f4
# Example
```julia
julia> w = Ormsby(); plot(w);
```
"""
function Ormsby(; dt::Tf=0.002, f::Vector{Tf}=[2.0, 10.0, 40.0, 60.0]) where {Tf<:AbstractFloat}

    f1,f2,f3,f4 = f

    fc = (f2+f3)/2.0
    nw = 2.2/(fc*dt)
    nc = floor(Int, nw/2)
    t = dt*collect(-nc:1:nc)
    nw = 2*nc + 1
    a4 = (pi*f4)^2/(pi*(f4-f3))
    a3 = (pi*f3)^2/(pi*(f4-f3))
    a2 = (pi*f2)^2/(pi*(f2-f1))
    a1 = (pi*f1)^2/(pi*(f2-f1))

    u = [ a4 * (sinc(f4 * t[i] ))^2 - a3 * (sinc(f3 * t[i] ))^2 for i in eachindex(t) ]
    v = [ a2 * (sinc(f2 * t[i] ))^2 - a1 * (sinc(f1 * t[i] ))^2 for i in eachindex(t) ]

    w  = u - v
    Hm = Hamming(nw)
    w  = [w[i] * Hm[i] for i in eachindex(w)]
    w  = w/maximum(w)

end


using Plots

function bracket_minimum(f, x=0; s=1e-2, k=2.0)
    a, ya = x, f(x)
    b, yb = a + s, f(a + s)
    if yb > ya
        a, b = b, a
        ya, yb = yb, ya
        s = -s
    end
    while true
        c, yc = b + s, f(b + s)
        if yc > yb
            return a < c ? (a, c) : (c, a)
        end
        a, ya, b, yb = b, yb, c, yc
        s *= k
    end
end

φ = MathConstants.φ
function golden_section_search(f, a, b, n)
    ρ = φ-1
    d = ρ * b + (1 - ρ)*a
    yd = f(d)
    for i = 1 : n-1
        c = ρ*a + (1 - ρ)*b
        yc = f(c)
        if yc < yd
            b, d, yd = d, c, yc
        else
            a, b = b, c
        end
    end
    return a < b ? (a, b) : (b, a)
end

#definicao da funcao
f = x-> -(0.4/((x^2+1))^(1/2))+((x^2+1)^(1/2))*(1-0.4/(x^2+1))-x    #multiplicada por -1 para encontrar o minimo
g = x-> -(-(0.4/((x^2+1))^(1/2))+((x^2+1)^(1/2))*(1-0.4/(x^2+1))-x) #funcao original

#ponto inicial
initial_point = 0
#busca do intervalo
a,c = bracket_minimum(f,initial_point)

#numero de iteracoes
n=(c-a)/(10^-3*log(φ))

am,bm = golden_section_search(f,a,c,n)
#(am,f(am))
#am,bm

println("x* = ", am)

plot(g,a-1.,c+1.,lw=2,draw_arrow="true",label="Function")
scatter!([a,c],[g(a),g(c)],label="interval")
scatter!([am,bm],[g(am),g(bm)],label="x*")


module DRAGO

using LinearAlgebra
using Plots
using Distributions
using BandedMatrices

include("utility.jl")

export drago, simulate_data, find_gaps, get_present

"""
    drago(y, λ1, λ2, S = I; ΔFmin = 0.01, maxiter = 10, verbose = false)

Nonlinear Smoothing of Data with Random Gaps and Outliers (DRAGO). A detailed description of the algorithm can be found in [1]

[1] Parekh, A., et al. "Nonlinear Smoothing of Core Body Temperature Data with Random Gaps and Outliers (DRAGO)." Biomedical Signal Processing. Springer, Cham, 2021. 63-84.
# Arguments

* y:    Signal with gaps and outlier
* λ1:   Influences the sparsity of the estimated outliers `x`. As a reasonable heuristic is λ1 = 3σ, where σ is the standard deviation of the noise.
* λ2:   Influences the smoothness of the estimate `f`
* S:    A matrix encoding gaps defaulting to `I` if none are present. Otherwise it can be created by starting with `I` and deleting all rows corresponding to a gap.

# Keyword Arguments

* ΔFmin:    The minimal change in the objective function F that defines convergence.
* maxiter:  Maximum number of iterations
* verbose:  Print some information

# Returns

* fRes:     The smoothed signal
* xRes:     The estimated outliers
* residual: The residual signal y - (f + x)
"""
function drago(y, λ1, λ2, S = I(length(y)); ΔFmin = 0.01, maxiter = 10, verbose = false)
    # Initialize
    N = length(y)
    xRes = S * y
    present = [1:N;][diag(S' * S) .== 1]
    Dn = Tridiagonal(ones(N-1), -2ones(N), ones(N-1))
    Dn = Dn[2:end-1, 1:end] # make it N-2 x N
    λ2DDn = λ2 .* Dn' * Dn

    # Objective function
    F(f, x) = 0.5norm2(S * y .- S * f .- x)^2 + 0.5λ1 * norm1(x) + .5λ2 *  norm2(Dn * f)^2

    # Iterate
    iter = 1
    fRes = similar(y)
    Fold = ΔF = Inf
    while ΔF > ΔFmin && iter <= maxiter
        A = Diagonal(λ1 ./ (abs.(xRes) .+ λ1))
        # B = Matrix(S' * A * S .+ λ2 .* Dn' * Dn) # faster for low N but more Memory gets allocated
        B = BandedMatrix(S' * A * S .+ λ2DDn) # less Memory but apparently slower
        fRes = inv(B) * S' * A * S * y
        xRes = abs.(xRes) .* (S * (y .- fRes)) ./ (abs.(xRes) .+ λ1)
        Fnow = F(fRes, xRes)
        verbose && @info "Iteration: $iter with F = $Fnow"
        ΔF = Fold .- Fnow
        Fold = Fnow
        iter += 1
    end
    residual = y .- (S' * xRes .+ fRes)

    return fRes, xRes, residual
end
end #module


module BigCombinatorics

export Fibonacci
export Factorial, DoubleFactorial, FallingFactorial, RisingFactorial, HyperFactorial
export Binomial, Catalan
export Derangements, MultiChoose, Multinomial
export Bell, Stirling1, Stirling2
export IntPartitions, IntPartitionsDistinct
export Euler, PowerSum, Menage

"""
This is where we cache values already computed
"""
_master_table = Dict{Function,Dict}()

"""
    _save(f::Function, x, val::BigInt)

Save the computed value of `f` with argument(s) `x` and value `val`.
"""
function _save(f::Function, x, val::BigInt)
    d = _master_table[f]
    d[x] = val
    nothing
end

"""
    _has(f::Function, x)

Check if argument `x` for function `f` is already in the `_master_table`.
"""
function _has(f::Function, x)::Bool
    d = _master_table[f]
    return haskey(d, x)
end

"""
    _max_arg(f::Function)

Find the largest argument known for `f`. Note that `f` must be a function
of just a single value.
"""
function _max_arg(f::Function)
    tab = _master_table[f]
    return maximum(keys(tab))
end

"""
    _get(f::Function, x)

Retrieve the function value `f(x)` from the `_master_table`.
"""
function _get(f::Function, x)::BigInt
    d = _master_table[f]
    return d[x]
end

"""
    _make(f::Function, T::Type)

Create an entry in the `_master_table` for the function `f`.
"""
function _make(f::Function, T::Type)
    _master_table[f] = Dict{T,BigInt}()
    nothing
end

"""
`BigCombinatorics.cache_report()` reports how many
entries are saved for each function in the `BigCombinatorics`
module.
"""
function cache_report()
    total = 0
    for func in keys(_master_table)
        parse_func = split(string(func), ".")
        func_name = last(parse_func)
        sz = length(_master_table[func])
        total += sz
        println("$sz\t$func_name")
    end
    println()
    println("$total\tTotal entries")
    nothing
end

"""
    BigCombinatorics.cache_clear()

Clears all cached values.
"""
function cache_clear()
    for f in keys(_master_table)
        f()
    end
    nothing
end

"""
`Fibonacci(n)` returns the `n`-th Fibonacci number.
We begin with `Fibonacci(0)==0` and `Fibonacci(1)==1`.
See https://oeis.org/A000045
"""
function Fibonacci(n::Integer)::BigInt
    if n < 0
        throw(DomainError(n, "argument must be nonngative"))
    end

    if _has(Fibonacci, n)
        return _get(Fibonacci, n)
    end

    start = _max_arg(Fibonacci) + 1
    for m = start:n
        val = _get(Fibonacci, m - 1) + _get(Fibonacci, m - 2)
        _save(Fibonacci, m, val)
    end

    return _get(Fibonacci, n)
end

function Fibonacci()
    _make(Fibonacci, Integer)
    _save(Fibonacci, 0, big(0))
    _save(Fibonacci, 1, big(1))
end
Fibonacci()

"""
`Factorial(n)` returns `n!` for nonnegative integers `n`.
See https://oeis.org/A000142

`Factorial(n,k)` returns `n!/k!` (to be consistent with Julia's
`factorial`.) Requires `0 <= k <= n`.

See also `FallingFactorial` and `RisingFactorial`.
"""
function Factorial(n::Integer)::BigInt
    if n < 0
        throw(DomainError(n, "argument must be nonngative"))
    end
    return factorial(big(n))
end

function Factorial(n::Integer, k::Integer)::BigInt
    if k > n
        throw(DomainError((n, k), "$k cannot exceed $n"))
    end
    return div(Factorial(n), Factorial(k))
end

"""
    FallingFactorial(n,k)

returns `n*(n-1)*(n-2)*...*(n-k+1)`
(with a total of `k` factors). Requires `n,k >= 0`.
If `k>n` then `0` is returned.
"""
function FallingFactorial(n::Integer, k::Integer)::BigInt
    if n < 0 || k < 0
        throw(DomainError((n, k), " arguments must be nonnegative"))
    end
    if k > n
        return big(0)
    end
    return Factorial(n, n - k)
end

"""
    RisingFactorial(n,k)

returns `n*(n+1)*(n+2)*...*(n+k-1)`
(with a total of `k` factors). Requires `n,k >= 0`.
"""
function RisingFactorial(n::Integer, k::Integer)::BigInt
    if n < 0 || k < 0
        throw(DomainError((n, k), "arguments must be nonnegative"))
    end
    if k == 0
        return big(1)
    end
    if n == 0
        return big(0)
    end

    return FallingFactorial(n + k - 1, k)
end

"""
    DoubleFactorial(n)

returns `n!!`, i.e., `n*(n-2)*...` with `(-1)!! == 0!! == 1!! == 1`.
See https://oeis.org/A006882
"""
function DoubleFactorial(n::Integer)::BigInt
    if n < -1
        throw(DomainError(n, "argument must be at least -1"))
    end
    if _has(DoubleFactorial, n)
        return _get(DoubleFactorial, n)
    end

    start = _max_arg(DoubleFactorial) + 1
    for m = start:n
        val = m * _get(DoubleFactorial, m - 2)
        _save(DoubleFactorial, m, val)
    end

    return _get(DoubleFactorial, n)
end

function DoubleFactorial()
    _make(DoubleFactorial, Integer)
    _save(DoubleFactorial, -1, big(1))
    _save(DoubleFactorial, 0, big(1))
    _save(DoubleFactorial, 1, big(1))
end
DoubleFactorial()

"""
    HyperFactorial(n)

returns the hyperfactorial of `n`, that is
`1^1 * 2^2 * 3^3 * ... * n^n`.
See https://oeis.org/A002109
"""
function HyperFactorial(n::Integer)::BigInt
    if n < 0
        throw(DomainError(n, "arument must be nonnegative"))
    end

    if _has(HyperFactorial, n)
        return _get(HyperFactorial, n)
    end
    start = _max_arg(HyperFactorial) + 1
    for m = start:n
        val = (big(m))^m * _get(HyperFactorial, m - 1)
        _save(HyperFactorial, m, val)
    end
    return _get(HyperFactorial, n)
end

function HyperFactorial()
    _make(HyperFactorial, Integer)
    _save(HyperFactorial, 0, big(1))
    _save(HyperFactorial, 1, big(1))
end
HyperFactorial()

"""
    Binomial(n,k)

returns the binomial coefficient `n`-choose-`k`.
This is the number of `k`-element subsets of an `n`-element set.
See https://oeis.org/A007318
"""
Binomial(n::Integer, k::Integer) = binomial(big(n), big(k))::BigInt

"""
    Multinomial(vec)

returns the multinomial coefficient whose
top index is the sum of `vec` (an array of `Int`s) and whose
bottom indices are given by `vec`.

This may also be called with a common-separated list of arguments,
that is, either of `Multinomial([1,2,3])` or `Multinomial(1,2,3)`.
The result is `60` in both cases as these equal `6!/(1! 2! 3!)`.

**Warning**: This is not the same as `MultiChoose`.
"""
function Multinomial(v...)::BigInt
    nv = length(v)
    for i = 1:nv
        typeof(v[i]) <: Integer || throw(DomainError(v, "arguments must be integers"))
        v[i] >= 0 || throw(DomainError(v, "arguments must be nonngative"))
    end
    vals = [t for t in v]
    return Multinomial(vals)
end

Multinomial() = big(1)::BigInt

function Multinomial(vals::Vector{T})::BigInt where {T<:Integer}
    if any([t < 0 for t in vals])
        throw(DomainError(vals, "arguments must be nonnegative"))
    end

    nv = length(vals)
    n = sum(vals)
    # base cases
    if nv <= 1 || n == 0
        return big(1)
    end
    # reduce
    return Binomial(n, vals[end]) * Multinomial(vals[1:nv-1])
end

"""
    MultiChoose(n,k)

returns the number of `k`-element
*multisets* that can be formed using the elements of an
`n`-element set.

**Warning**: This is not the same as `Multinomial`.
"""
function MultiChoose(n::Integer, k::Integer)::BigInt
    return Binomial(n + k - 1, k)
end

"""
    Catalan(n)

returns the `n`-th Catalan number.
See https://oeis.org/A000108
"""
function Catalan(n::Integer)::BigInt
    n >= 0 || throw(DomainError(n, "argument must be nonnegative"))
    return div(Binomial(2n, n), n + 1)
end

"""
    Derangements(n)

returns the number of permutations of
an `n`-set that have no fixed point.
See https://oeis.org/A000166
"""
function Derangements(n::Integer)::BigInt
    if n < 0
        throw(DomainError(n, "argument must be nonnegative"))
    end
    if _has(Derangements, n)
        return _get(Derangements, n)
    end

    start = _max_arg(Derangements) + 1
    for m = start:n
        s = (m % 2 == 0) ? 1 : -1
        val = m * _get(Derangements, m - 1) + s
        _save(Derangements, m, val)
    end
    return _get(Derangements, n)
end

function Derangements()
    _make(Derangements, Integer)
    _save(Derangements, 0, big(1))
    _save(Derangements, 1, big(0))
end
Derangements()

"""
    Bell(n)

gives the `n`-th Bell number, that is,
the number of partitions of an `n`-element set.
See https://oeis.org/A000110
"""
function Bell(n::Integer)::BigInt
    if n < 0
        throw(DomainError(n, "argument must be nonnegative"))
    end

    if _has(Bell, n)
        return _get(Bell, n)
    end

    start = _max_arg(Bell) + 1
    for m = start:n
        val = big(0)
        for k = 0:m-1
            val += Binomial(m - 1, k) * Bell(k)
        end
        _save(Bell, m, val)
    end

    return _get(Bell, n)
end

function Bell()
    _make(Bell, Integer)
    _save(Bell, 0, big(1))
    _save(Bell, 1, big(1))
end
Bell()

"""
    Stirling2(n,k)

gives the Stirling number of the second kind,
that is, the number of paritions of an `n`-set into `k`-parts."
See https://oeis.org/A008277
"""
function Stirling2(n::Integer, k::Integer)::BigInt
    # special cases
    if k < 0 || n < 0
        throw(DomainError((n, k), "arguments must be nonnegative"))
    end

    if k > n
        return big(0)
    end

    if n == 0  # and by logic, k==0
        return big(1)
    end

    if k == 0
        return big(0)
    end

    if n == k
        return big(1)
    end
    # END OF SPECIAL CASES, invoke recursion
    if _has(Stirling2, (n, k))
        return _get(Stirling2, (n, k))
    end

    val = Stirling2(n - 1, k - 1) + Stirling2(n - 1, k) * k
    _save(Stirling2, (n, k), val)
    return val
end
function Stirling2()
    _make(Stirling2, Tuple{Integer,Integer})
end
Stirling2()

"""
    Stirling1(n,k)

gives the (signed) Stirling number
of the first kind, that is, the coefficient of `x^k`
in the poynomial `x(x-1)(x-2)...(x-n+1)`.
See https://oeis.org/A008275
"""
function Stirling1(n::Integer, k::Integer)::BigInt
    # special cases
    if k < 0 || n < 0
        throw(DomainError((n, k), "arguments must be nonnegative"))
    end

    if k > n
        return big(0)
    end

    if n == 0  # and, by logic, k==0
        return big(1)
    end

    if k == 0  # and, by logic, n>0
        return big(0)
    end

    if _has(Stirling1, (n, k))
        return _get(Stirling1, (n, k))
    end

    # end of special cases, invoke recursion

    val = Stirling1(n - 1, k - 1) - (n - 1) * Stirling1(n - 1, k)
    _save(Stirling1, (n, k), val)
    return val
end
function Stirling1()
    _make(Stirling1, Tuple{Integer,Integer})
end
Stirling1()

"""
`IntPartitions(n)` is the number of partitions of the integer `n`.
See https://oeis.org/A000041

`IntPartitions(n,k)` is the number of partitions of the integer
`n` with exactly `k` (nonzero) parts.
"""
function IntPartitions(n::Integer, k::Integer)::BigInt
    if n < 0 || k < 0
        throw(DomainError((n, k), "arguments must be nonnegative"))
    end
    # lots of special cases
    if k > n
        return big(0)
    end
    if n == 0
        return big(1)
    end
    if k == 0
        return big(0)
    end
    if k == n || k == 1
        return big(1)
    end
    if _has(IntPartitions, (n, k))
        return _get(IntPartitions, (n, k))
    end

    val = sum([IntPartitions(n - k, i) for i = 0:k])
    _save(IntPartitions, (n, k), val)
    return val
end

function IntPartitions(n::Integer)::BigInt
    if n < 0
        throw(DomainError(n, "argument must be nonnegative"))
    end
    if _has(IntPartitions, n)
        return _get(IntPartitions, n)
    end
    val = sum([IntPartitions(n, k) for k = 0:n])
    _save(IntPartitions, n, val)
    return val
end
function IntPartitions()
    _make(IntPartitions, Union{Tuple{Integer,Integer},Integer})
end
IntPartitions()

"""
`IntPartitionsDistinct(n,k)` is the number of partitions of
the integer `n` into exactly `k` *distinct* parts.

`IntPartitionsDistinct(n)` is the number of partitions of `n`
into *distinct* parts.
"""
function IntPartitionsDistinct(n::Integer, k::Integer)::BigInt
    if n < 0 || k < 0
        throw(DomainError((n, k), "arguments must be nonnegative"))
    end
    Ck2 = div(k * (k - 1), 2)
    if n < Ck2
        return big(0)
    end
    return IntPartitions(n - Ck2, k)
end

function IntPartitionsDistinct(n::Integer)::BigInt
    if n < 0
        throw(DomainError(n, "argument must be nonngative"))
    end
    result = big(0)
    for k = 1:n
        s = IntPartitionsDistinct(n, k)
        if s == 0
            break
        end
        result += s
    end
    return result
end

"""
    Euler(n)

returns the `n`-th Euler number. Starting with `n=0`
this is the sequence
1, 0, -1, 0, 5, 0, -61, 0, 1385 and so on.
See https://oeis.org/A122045

Not to be confused with `Eulerian`.
"""
function Euler(n::Integer)::BigInt
    n >= 0 || throw(DomainError(n, "argument must be nonngative"))
    if n % 2 == 1
        return big(0)
    end

    if _has(Euler, n)
        return _get(Euler, n)
    end

    start = _max_arg(Euler) + 2

    for m = start:2:n
        last = div(m, 2) - 1
        val = -sum([Binomial(m, 2k) * Euler(2k) for k = 0:last])
        _save(Euler, m, val)
    end

    return _get(Euler, n)
end

function Euler()
    _make(Euler, Integer)
    _save(Euler, 0, big(1))
end
Euler()

"""
    PowerSum(n,k)

returns the sum of the `k`-th powers of the
integers `1` through `n`, i.e.,
`1^k + 2^k + 3^k + ... + n^k`.
"""
function PowerSum(n::Integer, k::Integer)::BigInt
    (n >= 0 && k >= 0) || throw(DomainError((n, k), "arguments must be nonngative"))
    # Base and special cases
    if n == 0
        return big(0)
    end
    if k == 0
        return big(n)
    end
    if k == 1
        return Binomial(n + 1, 2)
    end
    if _has(PowerSum, (n, k))
        return _get(PowerSum, (n, k))
    end

    val = big(n)^k + PowerSum(n - 1, k)
    _save(PowerSum, (n, k), val)
    return val
end
function PowerSum()
    _make(PowerSum, Tuple{Integer,Integer})
end
PowerSum()

export Eulerian
"""
    Eulerian(n,k)

returns the number of permutations of `{1,2,...,n}`
with `k` ascents. See https://oeis.org/A008292

Not to be confused with `Euler`.
"""
function Eulerian(n::Integer, k::Integer)::BigInt
    @assert (n >= 0 && k >= 0) "$n,$k must both be nonnegative"

    if n == 0
        return big(0)
    end

    if k > n || k == 0
        return big(0)
    end

    if n < 2
        return big(1)
    end

    if k == n      # includes the case n=k=0
        return big(1)
    end

    if k == 1
        return big(1)
    end

    if _has(Eulerian, (n, k))
        return _get(Eulerian, (n, k))
    end

    val = (n - k + 1) * Eulerian(n - 1, k - 1) + k * Eulerian(n - 1, k)
    _save(Eulerian, (n, k), val)
    return val
end
function Eulerian()
    _make(Eulerian, Tuple{Integer,Integer})
end
Eulerian()

"""
    Menage(n)

Number of solutions to the Menage problem with `n` male/female couples.
See https://oeis.org/A059375
"""
function Menage(n::Int)::BigInt
    if n < 0
        throw(DomainError(n, "Argument must be nonnegative"))
    end
    if _has(Menage, n)
        return _get(Menage, n)
    end
    start = _max_arg(Menage) + 1
    for m = start:n
        s = (m % 2 == 0) ? -1 : 1
        a1 = _get(Menage, m - 1) ÷ (2 * Factorial(m - 1))
        a2 = _get(Menage, m - 2) ÷ (2 * Factorial(m - 2))

        am = m * a1 + (m * a2 + 4s) ÷ (m - 2)
        val = 2 * Factorial(m) * am

        _save(Menage, m, val)
    end
    return _get(Menage, n)
end
function Menage()
    _make(Menage, Int)
    _save(Menage, 0, big(1))
    _save(Menage, 1, big(0))
    _save(Menage, 2, big(0))
    _save(Menage, 3, big(12))
end
Menage()

end  #end of module


using Plots, Printf, LinearAlgebra

# enable plotting by default
if !@isdefined do_visu; do_visu = true end

@views function diffusion_1D(; do_visu=true)
    # Physics
    lx     = 10.0       # domain size
    D      = 1.0        # diffusion coefficient
    ttot   = 0.6        # total simulation time
    dt     = 0.1        # physical time step
    # Numerics
    nx     = 256        # numerical grid resolution
    tol    = 1e-6       # tolerance
    itMax  = 1e5        # max number of iterations
    damp   = 1-41/nx    # damping (this is a tuning parameter, dependent on e.g. grid resolution)
    # Derived numerics
    dx     = lx/nx      # grid size
    dtau   = (1.0/(dx^2/D/2.1) + 1.0/dt)^-1 # iterative "timestep"
    xc     = LinRange(dx/2, lx-dx/2, nx)
    # Array allocation
    qH     = zeros(nx-1)
    dHdtau = zeros(nx-2)
    ResH   = zeros(nx-2)
    # Initial condition
    H0     = exp.(-(xc.-lx/2).^2)
    Hold   = copy(H0)
    H      = copy(H0)
    t = 0.0; it = 0; ittot = 0
    # Physical time loop
    while t<ttot
        iter = 0; err = 2*tol
        # Pseudo-transient iteration
        while err>tol && iter<itMax
            qH         .= -D*diff(H)/dx              # flux
            ResH       .= -(H[2:end-1] - Hold[2:end-1])/dt - diff(qH)/dx # residual of the PDE
            dHdtau     .= ResH + damp*dHdtau         # damped rate of change
            H[2:end-1] .= H[2:end-1] + dtau*dHdtau   # update rule, sets the BC as H[1]=H[end]=0
            iter += 1; err = norm(ResH)/length(ResH)
        end
        ittot += iter; it += 1; t += dt
        Hold .= H
    end
    # Analytic solution
    Hana = 1/sqrt(4*(ttot+1/4)) * exp.(-(xc.-lx/2).^2 /(4*(ttot+1/4)))
    @printf("Total time = %1.2f, time steps = %d, iterations tot = %d, error vs analytic = %1.2e \n", round(ttot, sigdigits=2), it, ittot, norm(H-Hana))
    # Visualize
    if do_visu
        plot(xc, H0, linewidth=3); display(plot!(xc, H, legend=false, framestyle=:box, linewidth=3, xlabel="lx", ylabel="H", title="damped diffusion (nt=$it, iters=$ittot)"))
    end
    return xc, H0
end

diffusion_1D(; do_visu=do_visu);


using InfiniteOpt, Ipopt, Plots
using KNITRO

# Parmeters
# Time span
t0 = 0
tf = 1
# Initial values
x1 = 1

# Model
model = InfiniteModel(KNITRO.Optimizer)

# infinite_parameter
@infinite_parameter(model, t ∈ [t0, tf], num_supports = 101)

# variable
@variable(model, x ≥ 0, Infinite(t))

@variable(model, u, Infinite(t), start = 0)

# objective
@objective(model, Min, x(1)^2 + ∫(u^2, t))

# constraint
@constraint(model, x(0) == x1)
@constraint(model, x_constr, ∂(x, t) == x + u)

# Optimization

print(model)

optimize!(model)

# Visulization
ts = value(t)
u_value = value(u)
x_value = value(x)

display(plot(ts, u_value))
display(plot(ts, x_value))

using DiffEqFlux, DifferentialEquations
const solveeq = DifferentialEquations.solve

tspan = (t0, tf)
ann = FastChain(FastDense(1, 64, tanh), FastDense(64, 1))
θ = initial_params(ann)
function sir_nn(du, u, p, t)
    du[1] = u[1] + ann([t], p)[1]
end
u0 = [x1]
ts = Float32.(collect(0.0:0.01:tspan[2]))
prob = ODEProblem(sir_nn, u0, tspan, θ)
sol_init = solveeq(prob, Vern9(), abstol = 1e-10, reltol = 1e-10)
plot(sol_init)
function predict_adjoint(θ)
    Array(solveeq(prob, Vern9(), p = θ, saveat = ts, sensealg = InterpolatingAdjoint(autojacvec = ReverseDiffVJP(true))))
end
function loss_adjoint(θ)
    x = predict_adjoint(θ)
    x[1, end]^2 + 0.01 * sum(abs2, [first(ann([t], θ)) for t in ts])
end
l = loss_adjoint(θ)

const losses = []
callback(θ, l, pred) = begin
    push!(losses, l)
    if length(losses) % 50 == 0
        println(losses[end])
        p = plot(solveeq(remake(prob, p = θ), Tsit5(), saveat = ts), lw = 3)
        #plot!(p, ts, [first(ann([t], θ)) for t in ts], label = "u(t)", lw = 3)
        display(p)
    end
    false
end
# cb = function (θ, l)
#     println(l)
#     p = plot(solveeq(remake(prob, p = θ), Tsit5(), saveat = ts), lw = 3)
#     #plot!(p, ts, [first(ann([t], θ)) for t in ts], label = "u(t)", lw = 3)
#     display(p)
#     return false
# end
# Display the ODE with the current parameter values.
cb(θ, l)
loss1 = loss_adjoint(θ)
res1 = DiffEqFlux.sciml_train(loss_adjoint, θ, ADAM(0.05), cb = cb, maxiters = 100)
res2 = DiffEqFlux.sciml_train(loss_adjoint, res1.u,
    BFGS(initial_stepnorm = 0.01), cb = cb, maxiters = 100,
    allow_f_increases = false)

using DataFrames
using CSV
p_value = res2.u
results_saving_opt = DataFrame()
results_saving_opt.t = ts
results_saving_opt.I = x_value
results_saving_opt.u = u_value
results_saving_opt.I_nn = Array(solveeq(remake(prob, p = p_value), Tsit5(), saveat = ts))[1, :]
results_saving_opt.u_nn = [first(ann([t], p_value)) for t in ts]
CSV.write("results_saving_Example3_2.csv", results_saving_opt)

plot(results_saving_opt.t, results_saving_opt.I, lw = 2, label = "I(t) by direct collection")
plot!(results_saving_opt.t, results_saving_opt.I_nn, lw = 2, label = "I(t) by deep learning")
xlabel!("t(day)")
ylabel!("I(t)")
savefig("Example3_2_I.png")

plot(results_saving_opt.t[2:end-1], results_saving_opt.u[2:end-1], lw = 2, label = "u(t) by direct collection")
plot!(results_saving_opt.t[2:end-1], results_saving_opt.u_nn[2:end-1], lw = 2, label = "u(t) by deep learning")
xlabel!("t(day)")
ylabel!("u(t)")
savefig("Example3_2_control.png")

#
opt_loss = objective_value(model)
# opt_loss = 1.75
opt_loss_nn = loss_adjoint(p_value)


#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Functions related to the associated Legendre functions.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
#
#   [1] Holmes, S. A. and W. E. Featherstone, 2002. A unified approach to the
#       Clenshaw summation and the recursive computation of very high degree and
#       order normalised associated Legendre functions. Journal of Geodesy,
#       76(5), pp. 279-299.
#
#       For more info.: http://mitgcm.org/~mlosch/geoidcookbook/node11.html
#
#   [2] Vallado, D. A (2013). Fundamentals of Astrodynamics and Applications.
#       Microcosm Press, Hawthorn, CA, USA.
#
#   [3] Schmidt, A (1917). Erdmagnetismus, Enzykl. Math. Wiss., 6, pp. 265–396.
#
#   [4] Winch, D. E., Ivers, D. J., Turner, J. P. R., Stening R. J (2005).
#       Geomagnetism and Schmidt quasi-normalization. Geophysical Journal
#       International, 160(2), pp. 487-504.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

export denormalize_legendre, legendre!, legendre

export legendre_fully_normalized!, legendre_fully_normalized
export legendre_schmidt_quasi_normalized!, legendre_schmidt_quasi_normalized
export legendre_conventional!, legendre_conventional

"""
    legendre!([N,] P::AbstractMatrix, ϕ::Number, ph_term::Bool = false, n_max::Integer = -1, m_max::Integer = -1)

Compute the associated Legendre function `P_n,m[cos(ϕ)]`. The maximum degree and
order that will be computed are given by the parameters `n_max` and `m_max`. If
they are negative, then the dimensions of matrix `P` will be used.

The result will be stored at matrix `P`.

The optional parameter `N` can be used to select the normalization. The
following values are valid:

* `Val(:full)`: Compute the fully normalized associated Legendre function (see
  `legendre_fully_normalized!`).
* `Val(:schmidt)`: Compute the Schmidt quasi-normalized associated Legendre
  function (see `legendre_schmidt_quasi_normalized!`).
* `Val(:conv)`: Compute the conventional associated Legendre function (see
  `legendre_conventional!`).

If `N` is omitted, then the full normalization will be used.

If `ph_term` is set to `true`, then the Condon-Shortley phase term `(-1)ᵐ` will
be included. If `ph_term` is not present, then it defaults to `false`.

"""
legendre!(P::AbstractMatrix, ϕ::Number, ph_term::Bool = false,
          n_max::Integer = -1, m_max::Integer = -1) =
    legendre_fully_normalized!(P, float(ϕ), ph_term, n_max, m_max)

legendre!(::Val{:full}, P::AbstractMatrix, ϕ::Number, ph_term::Bool = false,
          n_max::Integer = -1, m_max::Integer = -1) =
    legendre_fully_normalized!(P, float(ϕ), ph_term, n_max, m_max)

legendre!(::Val{:schmidt}, P::AbstractMatrix, ϕ::Number, ph_term::Bool = false,
          n_max::Integer = -1, m_max::Integer = -1) =
    legendre_schmidt_quasi_normalized!(P, float(ϕ), ph_term, n_max, m_max)

legendre!(::Val{:conv}, P::AbstractMatrix, ϕ::Number, ph_term::Bool = false,
          n_max::Integer = -1, m_max::Integer = -1) =
    legendre_conventional!(P, float(ϕ), ph_term, n_max, m_max)

"""
    legendre([N,] ϕ::Number, n_max::Integer, m_max::Integer = -1, ph_term::Bool = false)

Compute the associated Legendre function `P_n,m[cos(ϕ)]`. The maximum degree
that will be computed is `n_max` and the maximum order is `m_max`. Notice that
if `m_max` is higher than `n_max` or negative, than it is set to `n_max`.

The optional parameter `N` can be used to select the normalization. The
following values are valid:

* `Val(:full)`: Compute the fully normalized associated Legendre function (see
  `legendre_fully_normalized`).
* `Val(:schmidt)`: Compute the Schmidt quasi-normalized associated Legendre
  function (see `legendre_schmidt_quasi_normalized`).
* `Val(:conv)`: Compute the conventional associated Legendre function (see
  `legendre_conventional`).

If `N` is omitted, then the full normalization will be used (`Val(:full)`).

If `ph_term` is set to `true`, then the Condon-Shortley phase term `(-1)ᵐ` will
be included. If `ph_term` is not present, then it defaults to `false`.

# Returns

A matrix with the Legendre associated functions `P_n,m[cos(ϕ)]`.

"""
legendre(ϕ::Number, n_max::Integer, m_max::Integer = -1, ph_term::Bool = false) =
    legendre_fully_normalized(float(ϕ), n_max, m_max, ph_term)

legendre(::Val{:full}, ϕ::Number, n_max::Integer, m_max::Integer = -1,
         ph_term::Bool = false) =
    legendre_fully_normalized(float(ϕ), n_max, m_max, ph_term)

legendre(::Val{:schmidt}, ϕ::Number, n_max::Integer, m_max::Integer = -1,
         ph_term::Bool = false) =
    legendre_schmidt_quasi_normalized(float(ϕ), n_max, m_max, ph_term)

legendre(::Val{:conv}, ϕ::Number, n_max::Integer, m_max::Integer = -1,
         ph_term::Bool = false) =
    legendre_conventional(float(ϕ), n_max, m_max, ph_term)

################################################################################
#                Fully Normalized Associated Legendre Functions
################################################################################

"""
    legendre_fully_normalized!(P::AbstractMatrix, ϕ::Number, ph_term::Bool = false, n_max::Integer = -1, m_max::Integer = -1)

Compute the fully normalized associated Legendre function `P_n,m[cos(ϕ)]`. The
maximum degree and order that will be computed are given by the parameters
`n_max` and `m_max`. If they are negative, then the dimensions of matrix `P`
will be used:

    maximum degree -> number of rows
    maximum order  -> number of columns

The result will be stored at matrix `P`.

If `ph_term` is set to `true`, then the Condon-Shortley phase term `(-1)ᵐ` will
be included. If `ph_term` is not present, then it defaults to `false`.

# Remarks

This algorithm was based on [1]. Our definition of fully normalized associated
Legendre function can be seen in [2, p. 546]. The conversion is obtained by:

                 _                     -
                |  (n-m)! . k . (2n+1)  |      k = 1 if m  = 0
    K_n,m = sqrt| --------------------- |,     k = 2 if m != 0
                |         (n+m)!        |
                 -                     -
    _
    P_n,m = P_n,m * K_n,m,

          _
    where P_n,m is the fully normalized Legendre associated function.

"""
function legendre_fully_normalized!(P::AbstractMatrix, ϕ::Number,
                                    ph_term::Bool = false, n_max::Integer = -1,
                                    m_max::Integer = -1)

    # Obtain the maximum degree and order that must be computed.
    n_max, m_max = _get_degree_and_order(P, n_max, m_max)

    # Auxiliary variables to improve code performance.
    c = cos(ϕ)
    s = sqrt(1-c^2)

    s_fact = !ph_term ? +s : -s

    @inbounds for n = 0:n_max
        # Starting values.
        if n == 0
            P[0+1,0+1] = 1
            continue
        elseif n == 1
            P[1+1,0+1] = +sqrt(3)*c

            if m_max > 0
                P[1+1,1+1] = +sqrt(3)*s_fact
            end

            continue
        end

        aux_n = (2n-1)*(2n+1)

        for m = 0:n

            if n == m
                P[n+1,n+1] = s_fact*sqrt( (2n+1)/(2n) )*P[n-1+1,n-1+1]
            else
                aux_nm = (n-m)*(n+m)
                a_nm   = sqrt( aux_n / aux_nm )
                b_nm   = sqrt( ( (2n+1)*(n+m-1)*(n-m-1) ) / ( aux_nm*(2n-3) ) )

                # We assume that the matrix is not initialized. Hence, we must
                # not access elements on the upper triangle.
                if m != n-1
                    P[n+1,m+1] = a_nm*c*P[n-1+1,m+1] - b_nm*P[n-2+1,m+1]
                else
                    P[n+1,m+1] = a_nm*c*P[n-1+1,m+1]
                end
            end

            # Check if the maximum desired order has been reached.
            m == m_max && break
        end
    end

    nothing
end

"""
    legendre_fully_normalized(ϕ::T, n_max::Integer, m_max::Integer = -1, ph_term::Bool = false) where T<:AbstractFloat

Compute the fully normalized associated Legendre function `P_n,m[cos(ϕ)]`. The
maximum degree that will be computed is `n_max` and the maximum order is
`m_max`. Notice that if `m_max` is higher than `n_max` or negative, than it is
set to `n_max`.

If `ph_term` is set to `true`, then the Condon-Shortley phase term `(-1)ᵐ` will
be included. If `ph_term` is not present, then it defaults to `false`.

# Returns

A matrix with the Legendre associated functions `P_n,m[cos(ϕ)]`.

# Remarks

This algorithm was based on [1]. Our definition of fully normalized associated
Legendre function can be seen in [2, p. 546]. The conversion is obtained by:

                 _                     -
                |  (n-m)! . k . (2n+1)  |      k = 1 if m  = 0
    K_n,m = sqrt| --------------------- |,     k = 2 if m != 0
                |         (n+m)!        |
                 -                     -
    _
    P_n,m = P_n,m * K_n,m,

          _
    where P_n,m is the fully normalized Legendre associated function.

"""
function legendre_fully_normalized(ϕ::T, n_max::Integer, m_max::Integer = -1,
                                   ph_term::Bool = false) where T<:AbstractFloat

    (n_max < 0) && throw(ArgumentError("n_max must be positive."))

    ( (m_max < 0) || (m_max > n_max) ) && (m_max = n_max)

    P = zeros(T, n_max+1, m_max+1)
    legendre_fully_normalized!(P, ϕ, ph_term)
    return P
end

################################################################################
#            Schmidt Quasi-Normalized Associated Legendre Functions
################################################################################

"""
    legendre_schmidt_quasi_normalized!(P::AbstractMatrix, ϕ::Number, ph_term::Bool = false, n_max::Integer = -1, m_max::Integer = -1)

Compute the Schmidt quasi-normalized associated Legendre function
`P_n,m[cos(ϕ)]` [3,4]. The maximum degree and order that will be computed are
given by the parameters `n_max` and `m_max`. If they are negative, then the
dimensions of matrix `P` will be used:

    maximum degree -> number of rows
    maximum order  -> number of columns

The result will be stored at matrix `P`.

If `ph_term` is set to `true`, then the Condon-Shortley phase term `(-1)ᵐ` will
be included. If `ph_term` is not present, then it defaults to `false`.

# Remarks

This algorithm was based on [3,4]. The conversion is obtained by:

                 _           -
                |     (n-m)!  |    k = 1 if m  = 0
    K_n,m = sqrt| k. -------- |,   k = 2 if m != 0
                |     (n+m)!  |
                 -           -

    =
    P_n,m = P_n,m * K_n,m,

          =
    where P_n,m is the quasi-normalized normalized Legendre associated function.

"""
function legendre_schmidt_quasi_normalized!(P::AbstractMatrix, ϕ::Number,
                                            ph_term::Bool = false,
                                            n_max::Integer = -1,
                                            m_max::Integer = -1)

    # Obtain the maximum degree and order that must be computed.
    n_max, m_max = _get_degree_and_order(P, n_max, m_max)

    # Auxiliary variables to improve code performance.
    c = cos(ϕ)
    s = sqrt(1-c^2)

    s_fact = !ph_term ? +s : -s

    @inbounds for n = 0:n_max
        # Starting values.
        if n == 0
            P[0+1,0+1] = 1
            continue

        elseif n == 1
            P[1+1,0+1] = +c

            if m_max > 0
                P[1+1,1+1] = +s_fact
            end

            continue
        end

        aux_n = 2n-1 # -> sqrt( (2n-1)*(2n-1) )

        for m = 0:n

            if m == n
                P[n+1,n+1] = s_fact*sqrt( aux_n/(2n) )*P[n-1+1,n-1+1]
            else
                aux_nm = sqrt( (n-m)*(n+m) )
                a_nm   = aux_n / aux_nm
                b_nm   = sqrt( (n+m-1)*(n-m-1) ) / aux_nm

                # We assume that the matrix is not initialized. Hence, we must not
                # access elements on the upper triangle.
                if m != n-1
                    P[n+1,m+1] = a_nm*c*P[n-1+1,m+1] - b_nm*P[n-2+1,m+1]
                else
                    P[n+1,m+1] = a_nm*c*P[n-1+1,m+1]
                end
            end

            # Check if the maximum desired order has been reached.
            m == m_max && break
        end
    end

    nothing
end

"""
    legendre_schmidt_quasi_normalized(ϕ::T, n_max::Integer, m_max::Integer = -1, ph_term::Bool = false) where T<:AbstractFloat

Compute the Schmidt quasi-normalized associated Legendre function
`P_n,m[cos(ϕ)]`. The maximum degree that will be computed is `n_max` and the
maximum order is `m_max`. Notice that if `m_max` is higher than `n_max` or
negative, than it is set to `n_max`.

If `ph_term` is set to `true`, then the Condon-Shortley phase term `(-1)ᵐ` will
be included. If `ph_term` is not present, then it defaults to `false`.

# Returns

A matrix with the Legendre associated functions `P_n,m[cos(ϕ)]`.

# Remarks

This algorithm was based on [3,4]. The conversion is obtained by:

                 _           -
                |     (n-m)!  |    k = 1 if m  = 0
    K_n,m = sqrt| k. -------- |,   k = 2 if m != 0
                |     (n+m)!  |
                 -           -

    =
    P_n,m = P_n,m * K_n,m,

          =
    where P_n,m is the quasi-normalized normalized Legendre associated function.

"""
function legendre_schmidt_quasi_normalized(ϕ::T, n_max::Integer,
                                           m_max::Integer = -1,
                                           ph_term::Bool = false) where T<:AbstractFloat

    (n_max < 0) && throw(ArgumentError("n_max must be positive."))

    ( (m_max < 0) || (m_max > n_max) ) && (m_max = n_max)

    P = zeros(T, n_max+1, m_max+1)
    legendre_schmidt_quasi_normalized!(P, ϕ, ph_term)
    return P
end

################################################################################
#                  Conventional Associated Legendre Function
################################################################################

"""
    legendre_conventional!(P::AbstractMatrix, ϕ::Number, ph_term::Bool = false, n_max::Integer = -1, m_max::Integer = -1)

Compute the conventional associated Legendre function `P_n,m[cos(ϕ)]`. The
maximum degree and order that will be computed are given by the parameters
`n_max` and `m_max`. If they are negative, then the dimensions of matrix `P`
will be used:

    maximum degree -> number of rows
    maximum order  -> number of columns

The result will be stored at matrix `P`.

If `ph_term` is set to `true`, then the Condon-Shortley phase term `(-1)ᵐ` will
be included. If `ph_term` is not present, then it defaults to `false`.

"""
function legendre_conventional!(P::AbstractMatrix, ϕ::Number,
                                ph_term::Bool = false, n_max::Integer = -1,
                                m_max::Integer = -1)

    # Obtain the maximum degree and order that must be computed.
    n_max, m_max = _get_degree_and_order(P, n_max, m_max)

    # Auxiliary variables to improve code performance.
    c = cos(ϕ)
    s = sqrt(1-c^2)

    s_fact = !ph_term ? +s : -s

    @inbounds for n = 0:n_max
        # Starting values.
        if n == 0
            P[0+1,0+1] = 1
            continue
        elseif n == 1
            P[1+1,0+1] = +c

            if m_max > 0
                P[1+1,1+1] = +s_fact
            end

            continue
        end

        aux_n = 2n-1 # -> sqrt( (2n-1)*(2n-1) )

        for m = 0:n

            if n == m
                P[n+1,n+1] = s_fact*aux_n*P[n-1+1,n-1+1]
            else
                aux_nm = n-m # -> sqrt( (n-m)*(n-m) )
                a_nm   = aux_n / aux_nm
                b_nm   = (n+m-1) / aux_nm # -> sqrt( (n+m-1)*(n+m-1) ) / aux_nm

                # We assume that the matrix is not initialized. Hence, we must
                # not access elements on the upper triangle.
                if m != n-1
                    P[n+1,m+1] = a_nm*c*P[n-1+1,m+1] - b_nm*P[n-2+1,m+1]
                else
                    P[n+1,m+1] = a_nm*c*P[n-1+1,m+1]
                end
            end

            # Check if the maximum desired order has been reached.
            m == m_max && break
        end
    end

    nothing
end

"""
    legendre_conventional(ϕ::T, n_max::Integer, m_max::Integer = -1, ph_term::Bool = false) where T<:AbstractFloat

Compute the conventional associated Legendre function `P_n,m[cos(ϕ)]`. The
maximum degree that will be computed is `n_max` and the maximum order is
`m_max`. Notice that if `m_max` is higher than `n_max` or negative, than it is
set to `n_max`.

If `ph_term` is set to `true`, then the Condon-Shortley phase term `(-1)ᵐ` will
be included. If `ph_term` is not present, then it defaults to `false`.

# Returns

A matrix with the Legendre associated functions `P_n,m[cos(ϕ)]`.

"""
function legendre_conventional(ϕ::T, n_max::Integer, m_max::Integer = -1,
                               ph_term::Bool = false) where T<:AbstractFloat

    (n_max < 0) && throw(ArgumentError("n_max must be positive."))

    ( (m_max < 0) || (m_max > n_max) ) && (m_max = n_max)

    P = zeros(T, n_max+1, m_max+1)
    legendre_conventional!(P, ϕ, ph_term)
    return P
end

################################################################################
#                                   Private
################################################################################

"""
    _get_degree_and_order(P, n_max, m_max)

Return the maximum degree and order to compute the Legendre associated functions
given the matrix `P` and the configuration values `n_max` and `m_max`.

"""
@inline function _get_degree_and_order(P, n_max, m_max)
    # Get the size of the matrix.
    (rows, cols) = size(P)

    # If the order or degree is less than 0, then the user wants to use all the
    # available memory.
    n_max < 0 && (n_max = rows - 1)
    m_max < 0 && (m_max = cols <= rows ? cols - 1 : n_max)

    # Make sure that the degree and order fits the matrix.
    n_max > rows - 1 && (n_max = rows - 1)
    ( (m_max > cols - 1) || (m_max > n_max) ) && (m_max = min(cols-1, n_max))

    return n_max, m_max
end


"""
    y = bquantize(x, nsd=3, abstol=eps, reltol=10*eps)

Bidirectionally quantize a n by 1 vector x to nsd signed digits, terminate early
if the error is less than the specified tolerances.

`y` is a 2-tuple of arrays with the following fields:
- `y`[1][i] is the quantized value in floating-point form
- `y`[2][i] is a 2-by-nsd (or less) matrix containing the powers of two
    (first row) and their signs (second row).

See also bunquantize.
"""
function bquantize(x, nsd=3, abstol=eps(Float64), reltol=10*eps(Float64))
    n = length(x)
    offset = -log2(0.75)

    y_val = zeros(n)
    y_csd = Array{Matrix{Int}}(undef, n)

    for i = 1:n
        xp = x[i]
        y_val[i] = 0.0
        for j = 1:nsd
            error = abs(y_val[i] - x[i])
            if error <= abstol || error <= abs(x[i])*reltol
                break
            end
            p = floor(Int, log2(abs(xp)) + offset)
            p2 = 2.0 .^ p
            sx = cmp(xp, 0)
            xp = xp - sx*p2
            y_val[i] = y_val[i] + sx*p2
            if isassigned(y_csd, i)
                y_csd[i] = hcat(y_csd[i], [p; sx])
            else
                y_csd[i] = reshape([p; sx], (2,1))
            end
        end
    end

    return y_val, y_csd
end


#### ODE Steppers for y'(t) = f(t, y(t)) #####

##### Secondary Methods ######

function Derivative(x, f, d=1, h=10.0^-8)
    """
    Estimate the derivative of f at x using the center
    finite difference technique with step size h.

    x and f can be vector valued, in which case
    d must be provided. d should be a list of only
    zeros and ones.

    For example, if x and f(x) are vectors of length four
    and we want the derivative with respect to the second
    component in x, then we call Derivative(x, f, [0, 1, 0, 0]).
    """
    (f(x+h*d) - f(x-h*d)) / (2*h)
end

function NewtonsMethod(x, f, error_tol=10.0^-10)
    """
    Estimate the root of f starting from x.

    x and f(x) are vector valued. Newton's method becomes

    \vec{x}_{n+1} = \vec{x}_{n} - inv(Jacobian) * \vec{f(\vec{x}_n)}

    I create the Jacobian by estimating the derivatives
    using a center finite difference method.
    """
    h, n = 10.0^-5, length(x)
    J = zeros(n, n)
    for j=1:n
        d = zeros(n)
        d[j] = 1
        J[:, j] = Derivative(x, f, d)
    end
    count = 0
    while norm(f(x)) > error_tol
        count += 1
        x -= inv(J) * f(x)
        if count > 50
            println("Newton's Method took unusually long so it was terminated prematurely.")
            return x
        end
    end
    x
end

##### End Secondary Methods #####

###### Single Steppers ######

## Step functions that do not include an error estimate ##
# Call these with Stepper(f, method, t0, y0, tf, h)

function EulerStep(f, t, y, h)
    h * f(t, y)
end

function ImprovedEulerStep(f, t, y, h)
    f0 = f(t, y)
    (h / 2.0) * (f0 + f(t + h, y + h * f0))
end

function BackwardEulerStep(f, t, y, h)
    """ Implicit Backward Euler Step """
    #Use Euler step as starting point for NewtonsMethod.
    NewtonsMethod(f(t, y), f1 -> h*f(t+h, y+f1) - f1)
end

function GL2Step(f, t, y, h)
    """ Implicit Gauss-Legendre Order 2 Method """
    #Use Euler step as starting point for NewtonsMethod.
    NewtonsMethod(f(t, y), f1 -> h*f(t+h/2.0, y+f1/2.0) - f1)
end

function MidpointStep(f, t, y, h)
    h * f(t + h / 2, y + (h / 2) * f(t, y))
end

function CrankNicholsonStep(f, t, y, h)
    """ Second order implicit """
    f1 = f(t, y)
    NewtonsMethod(f1, f2 -> (h/2.0) * (f1 + f(t+h, y+f2)) - f2)
end

function RK4Step(f, t, y, h)
    f1 = f(t, y)
    f2 = f(t + h / 2, y + (h / 2) * f1)
    f3 = f(t + h / 2, y + (h / 2) * f2)
    f4 = f(t + h, y + h * f3)
    (h / 6) * (f1 + f4 + 2 * (f2 + f3))
end

function Stepper(f, method, t0, y0, tf, h)
    """
    The step function `method` returns just the step,
    not an error estimate.
    """
    ts, ys = Float64[], Vector{Float64}[]
    while t0 <= tf
        push!(ts, t0)
        push!(ys, y0)
        y0 += method(f, t0, y0, h)
        t0 += h
    end
    ts, ys
end

## End step functions that do not include an error estimate ##

## Step functions that include an error estimate ##
# Call these with AdaptiveStepper(f, method, t0, y0, tf)

function SimpleErrorStep(f, t, y, h)
    """ Compare implicit Crank Nicholson to Midpoint to get error """
    step = CrankNicholsonStep(f, t, y, h)
    step, norm(step - MidpointStep(f, t, y, h))
end

function RKTrapStep(f, t, y, h)
    """ Implicit Runge-Kutta Trapazoidal Method """
    f1 = h * f(t, y)
    f2 = NewtonsMethod(f1, f2 -> h*f(t+h, y+f1/2.0+f2/2.0) - f2)

    soln = f1/2.0 + f2/2.0
    soln, norm(soln - f1)
end

function DopriStep(f, t, y, h)
    """ Dormand-Prince Method """
    f1 = h * f(t, y)
    f2 = h * f(t + h/5.0, y + f1/5.0)
    f3 = h * f(t + 3*h/10.0, y + 3*f1/40.0 + 9*f2/40.0)
    f4 = h * f(t + 4*h/5.0, y + 44*f1/45.0 - 56*f2/15.0 + 32*f3/9.0)
    f5 = h * f(t + 8*h/9.0, y + 19372*f1/6561.0 - 25360*f2/2187.0 + 64448*f3/6561.0 - 212*f4/729.0)
    f6 = h * f(t + h, y + 9017*f1/3168.0 - 355*f2/33.0 + 46732*f3/5247.0 + 49*f4/176.0 - 5103*f5/18656.0)
    soln = 35*f1/384.0 + 500*f3/1113.0 + 125*f4/192.0 - 2187*f5/6784.0 + 11*f6/84.0
    f7 = h * f(t + h, y + soln)
    alt = 5179*f1/57600.0 + 7571*f3/16695.0 + 393*f4/640.0 - 92097*f5/339200.0 + 187*f6/2100.0 + f7/40.0

    soln, norm(soln - alt)
end

function CarpStep(f, t, y, h)
    """ Cash-Karp Method """
    f1 = h * f(t, y)
    f2 = h * f(t + h/5.0, y + f1/5.0)
    f3 = h * f(t + 3*h/10, y + 3*f1/40.0 + 9*f2/40.0)
    f4 = h * f(t + 3*h/5.0, y + 3*f1/10.0 - 9*f2/10.0 + 6*f3/5.0)
    f5 = h * f(t + h, y - 11*f1/54.0 + 5*f2/2.0 - 70*f3/27.0 + 35*f4/27.0)
    f6 = h * f(t + 7*h/8.0, y + 1631*f1/55296 + 175*f2/512 + 575*f3/13824 + 44275*f4/110592.0 + 253*f5/4096.0)
    soln = 37*f1/378.0 + 250*f3/621.0 + 125*f4/594.0 + 512*f6/1771.0
    alt = 2825*f1/27648.0 + 18575*f3/48384.0 + 13525*f4/55296.0 + 277*f5/14336.0 + f6/4.0

    soln, norm(soln - alt)
end

function RKFStep(f, t, y, h)
    """ Runge-Kutta-Fehlberg Method """
    f1 = h * f(t, y)
    f2 = h * f(t + h/4.0, y + f1/4.0)
    f3 = h * f(t + 3*h/8.0, y + 3*f1/32.0 + 9*f2/32.0)
    f4 = h * f(t + 12*h/13.0, y + 1932*f1/2197.0 - 7200*f2/2197.0 + 7296*f3/2197.0)
    f5 = h * f(t + h, y + 439*f1/216.0 - 8*f2 + 3680*f3/513.0 - 845*f4/4104.0)
    f6 = h * f(t + h/2.0, y - 8*f1/27.0 + 2*f2 - 3544*f3/2565.0 + 1859*f4/4104.0 - 11*f5/40.0)
    soln = 16*f1/135.0 + 6656*f3/12825.0 + 28561*f4/56430.0 - 9*f5/50.0 + 2*f6/55.0
    alt = 25*f1/216.0 + 1408*f3/2565.0 + 2197*f4/4104.0 - f5/5.0

    soln, norm(soln - alt)
end

function AdaptiveStepper(f, method, t0, y0, tf, error_tol=10.0^-7)
    """
    The step function `method` must return the tuple,
    (step, error estimate).
    """
    ts, ys = Float64[t0], Vector{Float64}[y0]
    h, totalerror = 0.001, 0
    while t0 < tf
        if h < 10.0^-8 && h < tf - t0
            println("Step size effectively zero at t = ", t0)
            h = 0.001
        else
            if h > tf - t0
                h = tf - t0
            end
            m, error_est = method(f, t0, y0, h)
            if error_est > error_tol
                h *= .75
            else
                t0 += h
                y0 += m
                push!(ts, t0)
                push!(ys, y0)
                if error_est < error_tol / 10.0
                    h *= 1.2
                end
                totalerror += error_est
            end
        end
    end
    println("Estimated upper bound on total error of ", method, ": ", totalerror)
    ts, ys
end

## End step functions that include an error estimate ##

##### End Single Steppers #####

##### Multi Steppers ######
#Call these with MultiStepper(f, method, t0, y0, tf, h)

function AdamsBash2(f, t, y, h)
    """ Adams–Bashforth Order 2 explicit method """
    y0n = Array[y, y + RK4Step(f, t, y, h)]
    function g(t, yn)
        #yn = Array[y0, y1]
        y0, y1 = yn
        y2 = y1 + h * (1.5 * f(t + h, y1) - 0.5 * f(t, y0))
        Array[y1, y2]
    end
    g, y0n
end

function AdamsBash3(f, t, y, h)
    """ Adams–Bashforth Order 3 explicit method """
    y1 = y + RK4Step(f, t, y, h)
    y2 = y1 + RK4Step(f, t+h, y1, h)
    y0n = Array[y, y1, y2]
    function g(t, yn)
        #yn = Array[y0, y1, y2]
        y0, y1, y2 = yn
        y3 = y2 + h * (23.0 / 12.0 * f(t+2*h, y2) - 4.0 / 3.0 * f(t+h, y1) + 5.0 / 12.0 * f(t, y0))
        Array[y1, y2, y3]
    end
    g, y0n
end

function AdamsBash4(f, t, y, h)
    """ Adams–Bashforth Order 4 explicit method """
    y1 = y + RK4Step(f, t, y, h)
    y2 = y1 + RK4Step(f, t+h, y1, h)
    y3 = y2 + RK4Step(f, t+2*h, y2, h)
    y0n = Array[y, y1, y2, y3]
    function g(t, yn)
        #yn = Array[y0, y1, y2, y3]
        y0, y1, y2, y3 = yn
        y4 = y3+h * (55.0 / 24.0 * f(t+3*h, y3) - 59.0 / 24.0 * f(t+2*h, y2) + 37.0 / 24.0 * f(t+h, y1) - 3.0 / 8.0 * f(t, y0))
        Array[y1, y2, y3, y4]
    end
    g, y0n
end

function AdamsMoult2(f, t, y, h)
    """ Adams-Moulton Order 2 implicit method """
    y1 = y + RK4Step(f, t, y, h)
    y0n = Array[y, y1]
    function g(t, yn)
        y0, y1 = yn
        f0, f1 = f(t, y0), f(t+h, y1)
        y2 = NewtonsMethod(y1, y2 -> y1+h*(5*f(t+2*h, y2)/12.0 + 2*f1/3.0 - f0/12.0) - y2)
        Array[y1, y2]
    end
    g, y0n
end

function AdamsMoult3(f, t, y, h)
    """ Adams-Moulton Order 3 implicit method """
    y1 = y + RK4Step(f, t, y, h)
    y2 = y1 + RK4Step(f, t+h, y1, h)
    y0n = Array[y, y1, y2]
    function g(t, yn)
        y0, y1, y2 = yn
        f0, f1, f2 = f(t, y0), f(t+h, y1), f(t+2*h, y2)
        y3 = NewtonsMethod(y2, y3 -> y2+h*(3*f(t+3*h, y3)/8.0 + 19*f2/24.0 - 5*f1/24.0 + f0/24.0)-y3)
        Array[y1, y2, y3]
    end
    g, y0n
end

function AdamsMoult4(f, t, y, h)
    """ Adams-Moulton Order 4 implicit method """
    y1 = y + RK4Step(f, t, y, h)
    y2 = y1 + RK4Step(f, t+h, y1, h)
    y3 = y2 + RK4Step(f, t+2*h, y2, h)
    y0n = Array[y, y1, y2, y3]
    function g(t, yn)
        y0, y1, y2, y3 = yn
        f0, f1, f2, f3 = f(t, y0), f(t+h, y1), f(t+2*h, y2), f(t+3*h, y3)
        y4 = NewtonsMethod(y3, y4 -> y3+h*(251*f(t+4*h, y4)/720.0+646*f3/720.0 - 264*f2/720.0 + 106*f1/720.0 - 19*f0/720.0)-y4)
        Array[y1, y2, y3, y4]
    end
    g, y0n
end

function MultiStepper(f, method, t0, y0, tf, h)
    """
    method returns g(t, yn), y0n with y0n is
    an array  of previous values of y.
    """
    g, y0n = method(f, t0, y0, h)
    #y0n = Array[y0, y1, ..., yn]
    ts, ys = Float64[], Vector{Float64}[]
    while t0 <= tf + h
        push!(ts, t0)
        push!(ys, y0n[1])
        y0n = g(t0, y0n)
        t0 += h
    end
    ts, ys
end

##### End Multi Steppers #####

#### Test ####
function func(t, y)
    y1, ydot = y
    [ydot, -t*y1]
end

t, tmax, h = 0.0, 20, 0.01
y0 = [-1.0, 0.0]

#xs, ys = MultiStepper(func, AdamsMoult4, t, copy(y0), tmax, h)
xs, ys = Stepper(func, GL2Step, t, copy(y0), tmax, h)
#xs, ys = AdaptiveStepper(func, RKTrapStep, t, copy(y0), tmax, h)
figure(0)
plot(xs, [ys[i][1] for i=1:length(ys)], "o-", label="Position")
plot(xs, [ys[i][2] for i=1:length(ys)], "x--", label="Velocity")
legend()

xs, ys = AdaptiveStepper(func, RKFStep, t, copy(y0), tmax)
figure(1)
plot(xs, [ys[i][1] for i=1:length(ys)], "o-", label="Position")
plot(xs, [ys[i][2] for i=1:length(ys)], "x--", label="Velocity")
legend()


using DataFrames, Plots
pyplot()

ProjDir = dirname(@__FILE__)
cd(ProjDir) do

  function euler_ex71(f::Function, y0::Float64, x::FloatRange{Float64})
    y = Vector{Real}(length(x))
    y[1] = y0
    h = x.step/x.divisor
    for i in 2:length(x)
      k0 = h * f(x[i-1], y[i-1])
      k1 = h * f(x[i-1] + h, y[i-1] + k0)
      y[i] = y[i-1] + 1/2*(k0 + k1)
    end
    columns=[]
    push!(columns, collect(x))
    push!(columns, y)
    df = DataFrame(columns, [:x, :y])
  end

  f(x,y) = (x+y)/x
  fy(x) = x*(1+log(x/2))

  x1 = 2.0:0.25:2.5
  x2 = 2.0:0.1:2.5
  x3 = 1.8:0.1:2.6
  df1 = euler_ex71(f, 2.0, x1)
  df2 = euler_ex71(f, 2.0, x2)

  df1 |> display
  println()
  df2 |> display
  println()

  global p2

  p1 = plot(x3, fy.(x3), ylims=(1.8, 3.5))
  plot!(p1, df1[:x], df1[:y], line=(:dash, 1), marker=(:plus), color=:red)
  plot!(p1, df2[:x], df2[:y], line=(:dash, 1), marker=(:circle), color=:red)
  savefig("euler_ex7.2.png")
  plot(p1, layout=(1,1))
  gui()
end


## PART 4

println("\nPart 4");

bvals = [b-3:b+3];
println("\n\tEstimating σ for f1..f3 with bin around b=$b");

σf1jack = Float64[];
σf2jack = Float64[];
σf3jack = Float64[];

for bb in bvals
    jestb = jacknife(v, N2, bb);
    push!(σf1jack, jacknifeσ(f1, jestb));
    push!(σf2jack, jacknifeσ(f2, jestb));
    push!(σf3jack, jacknifeσ(f3, jestb));
end

σfjack = cat(2, σf1jack, σf2jack, σf3jack);

println("\n\tGenerating plots in σfvsb to show σf dependence on b.");
using Gadfly
σfvsb = Plot[];
for i in [1:3]
    push!(σfvsb, Gadfly.plot(
        x = bvals,
        y = σfjack[:,i],
        Geom.line,
        Geom.point,
        Guide.xlabel("Bin size b"),
        Guide.ylabel("Estimated Variance"),
        Guide.title("Estimated Variance of function f$i vs. Bin Size")
    ));
end

println("\n\tCompare σfjack to naive σ̂ from part 1:\n")
println("\t\tσ̂f1N2/σfjack[4,1] = $(σ̂f1N2/σfjack[4,1])");
println("\t\tσ̂f2N2/σfjack[4,2] = $(σ̂f2N2/σfjack[4,2])");
println("\t\tσ̂f3N2/σfjack[4,3] = $(σ̂f3N2/σfjack[4,3])");


# # [Open Circular Port Graph Examples](@id cpg_examples)
#
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/examples/CPG_examples.ipynb)

using AlgebraicDynamics.DWDDynam
using AlgebraicDynamics.CPortGraphDynam
using AlgebraicDynamics.CPortGraphDynam: draw, barbell, gridpath, grid, meshpath

using Catlab
using Catlab.WiringDiagrams
using Catlab.WiringDiagrams.CPortGraphs
using Catlab.Theories
using Catlab.CategoricalAlgebra

using OrdinaryDiffEq
using DynamicalSystems
using Plots, Plots.PlotMeasures

using PrettyTables

# ## SIR Epidemiology Model

# An SIR epidemiology model has three types of people: susceptible, infected, and recovered. When a susceptible person interacts with infected person, the susceptible person also becomes infected. Over time infected people  recover. Transition rates determine the how frequently susceptible people come into contact with infected people and how fast infected people recover. The system evolves according to the law of mass action.

# In a multi-city SIR model, each city has susceptible, infected, and recovered populations. To see the spread of the disease we will consider both susceptible and infected people moving between cities. To define a multi-city SIR model, we can compose multiple single-city SIR models using the composition syntax of open CPGs. The composition pattern will consist of three boxes each of which will be filled by a single-city SIR model. Ports expose the susceptible and infected populations of each city. One set of wires connect the susceptible and infected populations of cities 1 and 2. A second set of wires connect the susceptible and infected popuation of cities 2 and 3.

## Define the composition pattern
d₀ = OpenCPortGraph()
add_parts!(d₀, :Box, 1)
d₁ = barbell(2)
F = ACSetTransformation((Box=[2],), d₀, d₁)
G = ACSetTransformation((Box=[1],), d₀, d₁)
d₂ = apex(pushout(F,G))

## Define the primitive systems
β, μ, α₁, α₂ = 0.4, 0.4, 0.01, 0.01

sirfuncb = (u,x,p,t)->[-β*u[1]*u[2] - α₁*(u[1]-x[1]), # Ṡ
                        β*u[1]*u[2] - μ*u[2] - α₂*(u[2]-x[2]), #İ
                        μ*u[2] # Ṙ
                        ]
sirfuncm = (u,x,p,t)->[-β*u[1]*u[2] - α₁*(u[1]-(x[1]+x[3])/2),
                        β*u[1]*u[2] - μ*u[2] - α₂*(u[2]-(x[2]+x[4])/2),
                        μ*u[2]
                       ]

boundary  = ContinuousMachine{Float64}(2,3,sirfuncb, u->u[1:2])
middle    = ContinuousMachine{Float64}(4,3, sirfuncm, u->u[[1,2,1,2]])

## Compose
threecity = oapply(d₂, [boundary,middle,boundary])

# First, we will approximate the solution to the three city SIR model using Euler's method. The initial condition has 100 susceptible people in each city a single infected person in the first city. We show the infected populations in each city over time.

u0 = [100,1,0,100,0,0,100,0,0.0]

h = 0.01
nsteps = 100

threecity_approx = euler_approx(threecity, h)
traj = trajectory(threecity_approx, u0, [], nothing, nsteps)

map(traj) do u
    return (i1=u[2], i2=u[5], i3=u[8])
end |> pretty_table

# Next, we will solve the continuous system and plot the results. Over time the infected populations increase and the susceptible populations decrease. The delays in the plots illustrate how the disease spreads from city 1 to city 2 and then from city 2 to city 3.

## Solve and plot
tspan = (0.0, 1.0)

prob = ODEProblem(threecity, u0, tspan)
sol = solve(prob, Tsit5(); dtmax = 0.01)

plot(sol, lw=2, title = "SIR Epidemiology Model", bottom_margin=10mm, left_margin=10mm,
    label=["S" "I" "R"])

# ## Cellular automata

#  Circular port graphs are particularly useful for modeling systems where the composition pattern is given by a grid and where the dynamics are repetative. In the case of cellular automata the composition pattern is a row of $n$ cells each of which is connected to its two neighbors. The primitive systems are identical machines whose discrete dynamics are a specified rule. See [here](https://mathworld.wolfram.com/ElementaryCellularAutomaton.html) for a complete set of rules and the patterns they generate.

function Rule(k::Int)
    (left_neighbor, x, right_neighbor) ->
    Bool(digits(k, base=2, pad=8)[1 + right_neighbor + 2*x + 4*left_neighbor])
end

## Define the composition pattern
n = 100
row = apex(gridpath(n, 1))

## Define the primitive system which will be repeated for each cell
rule = DiscreteMachine{Bool}(2, 1, 2, (u, x, p, t)->Rule(p)(x[2], u[1], x[1]),
            u->[u[1], u[1]])

## Compose
automaton = oapply(row, rule)

## Solve and plot
u0 = zeros(Int, n); u0[Int(n/2)] = 1

rule_number = 126
traj = trajectory(automaton, u0, [0,0], rule_number, 100)
spy(Matrix(traj))


using Statistics, Random, Distributions

# returns the variable (or matrix), lagged p times,
# with the first p rows filled with ones (to avoid divide errors)
# remember to drop those rows before doing analysis
function lag(x,p::Int64)
	n = size(x,1)
	lagged_x = zeros(eltype(x),n,p)
	lagged_x = [ones(p); x[1:n-p]]
end

# lags of a vector from 1 to p in a p column array
function  lags(x,p)
	n = size(x,1)
	lagged_x = zeros(eltype(x),n,p)
	for i = 1:p
		lagged_x[:,i] = lag(x,i)
	end
    return lagged_x
end

# compute moving average using p most recent values, including current value
function ma(x, p)
    m = similar(x)
    for i = p:size(x,1)
        m[i] = mean(x[i-p+1:i])
    end
    return m
end

# auxiliary model: HAR-RV
# Corsi, Fulvio. "A simple approximate long-memory model
# of realized volatility." Journal of Financial Econometrics 7,
# no. 2 (2009): 174-196.
function HAR(y)
    ylags = lags(y,10)
    X = [ones(size(y,1)) ylags[:,1]  mean(ylags[:,1:4],dims=2) mean(ylags[:,1:10],dims=2)]
    # drop missings
    y = y[11:end]
    X = X[11:end,:]
    βhat = X\y
    σhat = std(y-X*βhat)
    vcat(βhat,σhat)
end

function aux_stat(y)
    y, m, s  = stnorm(abs.(y))
    # look for evidence of volatility clusters
    mm = ma(y,5)
    mm = mm[5:end]
    clusters = quantile(mm,0.75) -quantile(mm, 0.25)
    ϕ = HAR(y)
    vcat(m, s, clusters, ϕ)
end

# the dgp: simple discrete time stochastic volatility (SV) model
function SVmodel(σe, ρ, σu, n, shocks_u, shocks_e)
    burnin = size(shocks_u,1) - n
    hlag = 0.0
    h = ρ.*hlag .+ σu.*shocks_u[1] # figure out type
    y = σe.*exp(h./2.0).*shocks_e[1]
    ys = zeros(eltype(y),n)
    for t = 1:burnin+n
        h = ρ.*hlag .+ σu.*shocks_u[t]
        y = σe.*exp(h./2.0).*shocks_e[t]
        if t > burnin
            ys[t-burnin] = y
        end
        hlag = h
    end
    #plot(ys)
    sqrt(n)*aux_stat(ys)
end


#=
  From Joost-Pieter Katoen
  "Probabilistic Programming Quantitative Modeling for the Masses?"
  (MMB 2018 Conference, Erlangen)

  Slide 4:
  """
  How likely does a student end up with a bad mood after getting
  a bad grade for an easy exam, given that she is well prepared?
  """

Distributions of variable difficulty
easy       =>    6022  (0.602200)
hard       =>    3978  (0.397800)

Distributions of variable preparation
good       =>   10000  (1.000000)

Distributions of variable grade
good       =>    6821  (0.682100)
bad        =>    3179  (0.317900)

Distributions of variable mood
bad        =>    5004  (0.500400)
good       =>    4996  (0.499600)

  Cf ~/webppl/student_mood_after_exam.wppl

=#
using Turing, StatsPlots, Distributions
include("jl_utils.jl")

@model function student_mood_after_exam()
    bad = 1
    good = 2
    badGood = [bad,good]

    easy = 1
    hard = 2

    difficulty ~ Categorical([0.6,0.4]) # [easy,hard]

    preparation ~ Categorical([0.7,0.3]) # badGood

    grade ~
        (difficulty==easy && preparation==bad)  ? Categorical([0.95,0.05]) : # badGood
        (difficulty==easy && preparation==good) ? Categorical([0.5,0.5])   :
        (difficulty==hard && preparation==bad)  ? Categorical([0.6,0.4])   :
        (difficulty==hard && preparation==good) ? Categorical([0.05,0.95]) : Dirac(easy)

    mood ~ grade == bad ? # badGood
        Categorical([0.9,0.1]) :
        Categorical([0.3,0.7])

    # true ~ Dirac(difficulty == easy)
    true ~ Dirac(preparation == good)
    # true ~ Dirac(grade == bad)
    # true ~ Dirac(mood == bad)

end

model = student_mood_after_exam()

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 10_000)
chns = sample(model, PG(5), 10_000)
# chns = sample(model, SMC(), 10_000)
# chns = sample(model, IS(), 10_000)

display(chns)
# display(plot(chns))

bad_good = ["bad","good"]
show_var_dist_pct(chns, :difficulty, ["easy","hard"])

println()
show_var_dist_pct(chns, :preparation, bad_good)

println()
show_var_dist_pct(chns, :grade, bad_good)

println()
show_var_dist_pct(chns, :mood, bad_good)


# indicator of the L2 norm ball with given radius

export IndBallL2

"""
**Indicator of a Euclidean ball**

    IndBallL2(r=1.0)

Returns the indicator function of the set
```math
S = \\{ x : \\|x\\| \\leq r \\},
```
where ``\\|\\cdot\\|`` is the ``L_2`` (Euclidean) norm. Parameter `r` must be positive.
"""
struct IndBallL2{R <: Real} <: ProximableFunction
    r::R
    function IndBallL2{R}(r::R) where {R <: Real}
        if r <= 0
            error("parameter r must be positive")
        else
            new(r)
        end
    end
end

is_convex(f::IndBallL2) = true
is_set(f::IndBallL2) = true

IndBallL2(r::R=1.0) where {R <: Real} = IndBallL2{R}(r)

function (f::IndBallL2)(x::AbstractArray{T}) where {R <: Real, T <: RealOrComplex{R}}
    if isapprox_le(norm(x), f.r, atol=eps(R), rtol=sqrt(eps(R)))
        return R(0)
    end
    return R(Inf)
end

function prox!(y::AbstractArray{T}, f::IndBallL2, x::AbstractArray{T}, gamma::R=R(1)) where {R <: Real, T <: RealOrComplex{R}}
    scal = f.r/norm(x)
    if scal > 1
        y .= x
        return R(0)
    end
    for k in eachindex(x)
        y[k] = scal*x[k]
    end
    return R(0)
end

fun_name(f::IndBallL2) = "indicator of an L2 norm ball"
fun_dom(f::IndBallL2) = "AbstractArray{Real}, AbstractArray{Complex}"
fun_expr(f::IndBallL2) = "x ↦ 0 if ||x|| ⩽ r, +∞ otherwise"
fun_params(f::IndBallL2) = "r = $(f.r)"

function prox_naive(f::IndBallL2, x::AbstractArray{T}, gamma::R=R(1)) where {R <: Real, T <: RealOrComplex{R}}
    normx = norm(x)
    if normx > f.r
        y = (f.r/normx)*x
    else
        y = x
    end
    return y, R(0)
end


# indicator of the ball of matrices with (at most) a given rank

using LinearAlgebra
using TSVD

export IndBallRank

"""
**Indicator of rank ball**

    IndBallRank(r=1)

Returns the indicator function of the set of matrices of rank at most `r`:
```math
S = \\{ X : \\mathrm{rank}(X) \\leq r \\},
```
Parameter `r` must be a positive integer.
"""
struct IndBallRank{I <: Integer} <: ProximableFunction
    r::I
    function IndBallRank{I}(r::I) where {I <: Integer}
        if r <= 0
            error("parameter r must be a positive integer")
        else
            new(r)
        end
    end
end

is_set(f::IndBallRank) = true
is_prox_accurate(f::IndBallRank) = false

IndBallRank(r::I=1) where {I <: Integer} = IndBallRank{I}(r)

function (f::IndBallRank)(x::AbstractArray{T, 2}) where {R <: Real, T <: RealOrComplex{R}}
    maxr = minimum(size(x))
    if maxr <= f.r return R(0) end
    U, S, V = tsvd(x, f.r+1)
    # the tolerance in the following line should be customizable
    if S[end]/S[1] <= 1e-7
        return R(0)
    end
    return R(Inf)
end

function prox!(y::AbstractMatrix{T}, f::IndBallRank, x::AbstractMatrix{T}, gamma::R=R(1)) where {R <: Real, T <: RealOrComplex{R}}
    maxr = minimum(size(x))
    if maxr <= f.r
        y .= x
        return R(0)
    end
    U, S, V = tsvd(x, f.r)
    # TODO: the order of the following matrix products should depend on the shape of x
    M = S .* V'
    mul!(y, U, M)
    return R(0)
end

fun_name(f::IndBallRank) = "indicator of the set of rank-r matrices"
fun_dom(f::IndBallRank) = "AbstractArray{Real,2}, AbstractArray{Complex,2}"
fun_expr(f::IndBallRank) = "x ↦ 0 if rank(x) ⩽ r, +∞ otherwise"
fun_params(f::IndBallRank) = "r = $(f.r)"

function prox_naive(f::IndBallRank, x::AbstractMatrix{T}, gamma::R=R(1)) where {R <: Real, T <: RealOrComplex{R}}
    maxr = minimum(size(x))
    if maxr <= f.r
        y = x
        return y, R(0)
    end
    F = svd(x)
    y = F.U[:,1:f.r]*(Diagonal(F.S[1:f.r])*F.V[:,1:f.r]')
    return y, R(0)
end


"""
    Ridge(alpha = 0.1)
Ridge Regression. `alpha` is the value multiplied by regularization term.

# Example
```jldoctest regression
julia> model = Ridge()
Ridge(Float64[], 0.1)

julia> fit!(model, x, t)
3-element Vector{Float64}:
 -0.5635468573581848
  2.1185952951687614
 40.334109796666425

julia> model(x)
20-element Vector{Float64}:
 175.12510514593038
 170.28763505842625
 149.54478779081344
 159.7466476176333
 165.4525001910219
 129.6935485937018
 164.79709438985097
 161.3621431448216
 170.17418141858434
 176.95192713546982
 174.8078461898064
 168.76930346791391
 171.09202561187362
 170.58861763111338
 155.04089855305028
 168.05151465675456
 149.76311329450505
 169.5183634524783
 141.7695072903308
 163.94744858842117
```
"""
mutable struct Ridge
    w::Array
    α::Float64
    Ridge(alpha = 0.1) = new(Array{Float64}(undef, 0), alpha)
end

function fit!(model::Ridge, x, t)
    check_size(x, t)
    x = expand(x)
    n = size(x, 1)
    _I = Matrix{Float64}(I, n, n)
    model.w = inv(x * x' .+ model.α * _I) * x * t
end

(model::Ridge)(x) = expand(x)' * model.w


#=
The number 3797 has an interesting property. Being prime itself, it is possible to continuously remove digits from left to right, and remain prime at each stage: 3797, 797, 97, and 7. Similarly we can work from right to left: 3797, 379, 37, and 3.

Find the sum of the only eleven primes that are both truncatable from left to right and right to left.

NOTE: 2, 3, 5, and 7 are not considered to be truncatable primes.
=#
include("projecteulerutils.jl")
using Primes

function istruncatableprime(a)
  da = reverse(digits(a))
  f1,f2 = copy(da), copy(da)
  for i in 1:length(da)-1
    deleteat!(f1,1)
    pop!(f2)
    if !isprime(nd(f1)) || !isprime(nd(f2))
      return false
    end
  end
  true
end

function calc()
  i,count,total = 10,0,0
  while count < 11
    if isprime(i) && istruncatableprime(i)
      count += 1
      total += i
    end
    i += 1
  end
  total
end
@time println(calc())


module solver

function euler(y0::AbstractArray{Float64}, f, a::Float64, b::Float64, h::Float64)::AbstractArray{Float64}
    k = ceil(Integer, (b - a) / h)
    t_range = range(a, stop=b, length=k)
    y = y0
    for t_n = t_range
        if t_n == a
            continue
        end
        y_n = y[:,end]
        y_next = y_n + h * f(t_n, y_n)
        y = [y y_next]
    end
    return y
end

function midpoint(y0::AbstractArray{Float64}, f, a::Float64, b::Float64, h::Float64)::AbstractArray{Float64}
    k = ceil(Integer, (b - a) / h)
    t_range = range(a, stop=b, length=k)
    y = y0
    for t_n = t_range
        if t_n == a
            continue
        end
        y_n = y[:,end]
        y_next = y_n + h * f(t_n + h / 2.0, y_n + h / 2.0 * f(t_n, y_n))
        y = [y y_next]
    end
    return y
end

end # module