"""
    reconstruction_matrix_meshwidth(f, domain, N)

Assemble inverse discrete filtering matrix from a continuous filter `f` width constant width
\$h(x) = \\Delta x / 2\$.
"""
function reconstruction_matrix_meshwidth(f::TopHatFilter, domain::PeriodicIntervalDomain, N)
    x = discretize(domain, N)
    Δx = 1 // N * (domain.right - domain.left)
    h = f.width
    h₀ = h(x[1])

    all(h.(x) .≈ h(x[1])) || error("Filter width must be constant")

    if Δx .≈ 2h₀
        # Three point stencil
        inds = [-1, 0, 1]
        stencil = [-1 // 24, 13 // 12, -1 // 24]
    elseif Δx .≈ h₀
        # Three point stencil
        inds = [-1, 0, 1]
        stencil = [-1 // 6, 8 // 6, -1 // 6]
    else
        error("Filter width must be equal to mesh width")
    end

    # Five point stencil
    # inds = [-2, -1, 0, 1, 2]
    # stencil = [3//640, -29//480, 1067//960, -29//480, 3//640]

    # Construct banded matrix
    diags = [i => fill(s, N - abs(i)) for (i, s) ∈ zip(inds, stencil)]
    R = spdiagm(diags...)

    # Periodic extension of three point stencil
    R[1, end] = stencil[1]
    R[end, 1] = stencil[end]

    # Periodic extension of five point stencil
    # R[1, [end - 1, end]] = stencil[[1, 2]]
    # R[2, end] = stencil[1]
    # R[end - 1, 1] = stencil[end]
    # R[end, [1, 2]] = stencil[[end-1, end]]

    R
end

function reconstruction_matrix_meshwidth(f::TopHatFilter, domain::ClosedIntervalDomain, N)
    x = discretize(domain, N)
    Δx = 1 // N * (domain.right - domain.left)
    h = f.width
    h₀ = h(x[1])

    all(h.(x) .≈ h(x[1])) || error("Filter width must be constant")
    Δx .≈ 2h₀ || error("Filter width must be equal to mesh width")

    # Three point stencil
    inds = [-1, 0, 1]
    stencil = [-1 // 24, 13 // 12, -1 // 24]

    # Five point stencil
    # inds = [-2, -1, 0, 1, 2]
    # stencil = [3 // 640, -29 // 480, 1067 // 960, -29 // 480, 3 // 640]

    # Construct banded matrix
    diags = [i => fill(s, N + 1 - abs(i)) for (i, s) ∈ zip(inds, stencil)]
    R = spdiagm(diags...)

    # Boundary weights for three point stencil
    R[1, [1, 2]] = [4 // 3, -1 // 3]
    R[end, [end, end - 1]] = [4 // 3, -1 // 3]

    # Boundary weights for five point stencil
    # R[1, [1, 2, 3]] = [23 // 15, -41 // 60, 3 // 20]
    # R[2, [1, 2, 3, 4]] = [-8//105, 319//280, -29//420, 1//168]
    # R[end - 1, [end, end - 1, end - 2, end - 3]] = [-8//105, 319//280, -29//420, 1//168]
    # R[end, [end, end - 1, end - 2]] = [23 // 15, -41 // 60, 3 // 20]

    R
end


# Triangle in 2D defined by its 3 vertices.
struct Triangle_2D <: Face_2D
    points::SVector{3, Point_2D}
end

# Constructors
# -------------------------------------------------------------------------------------------------
Triangle_2D(p₁::Point_2D, p₂::Point_2D, p₃::Point_2D) = Triangle_2D(SVector(p₁, p₂, p₃))

# Methods
# -------------------------------------------------------------------------------------------------
# Interpolation
function (tri::Triangle_2D)(r::Real, s::Real)
    # See The Visualization Toolkit: An Object-Oriented Approach to 3D Graphics, 4th Edition
    # Chapter 8, Advanced Data Representation, in the interpolation functions section
    rₜ = Float64(r); sₜ = Float64(s)
    return (1 - rₜ - sₜ)*tri[1] + rₜ*tri[2] + sₜ*tri[3]
end

function area(tri::Triangle_2D)
    # A = bh/2
    # Let u⃗ = (v₂ - v₁), v⃗ = (v₃ - v₁)
    # b = |u⃗|
    # h = |sin(θ) v⃗|, where θ is the angle between u⃗ and v⃗
    # u⃗ × v⃗ = |u⃗||v⃗| sin(θ), hence
    # A = |u⃗ × v⃗|/2 = bh/2
    u⃗ = tri[2] - tri[1]
    v⃗ = tri[3] - tri[1]
    # 2D cross product returns a scalar
    return abs(u⃗ × v⃗)/2
end

centroid(tri::Triangle_2D) = tri(0.3333333333333333, 0.3333333333333333)

function in(p::Point_2D, tri::Triangle_2D)
    # If the point is to the left of every edge
    #  3<-----2
    #  |     ^
    #  | p  /
    #  |   /
    #  |  /
    #  v /
    #  1
    return isleft(p, LineSegment_2D(tri[1], tri[2])) &&
           isleft(p, LineSegment_2D(tri[2], tri[3])) &&
           isleft(p, LineSegment_2D(tri[3], tri[1]))
end

function intersect(l::LineSegment_2D, tri::Triangle_2D)
    # Create the 3 line segments that make up the triangle and intersect each one
    edges = SVector(LineSegment_2D(tri[1], tri[2]),
                    LineSegment_2D(tri[2], tri[3]),
                    LineSegment_2D(tri[3], tri[1]))
    ipoints = MVector(Point_2D(), Point_2D(), Point_2D())
    n_ipoints = 0x00000000
    # We need to account for 3 points returned due to vertex intersection
    for k ∈ 1:3
        npoints, point = l ∩ edges[k]
        if npoints === 0x00000001
            n_ipoints += 0x00000001
            ipoints[n_ipoints] = point
        end
    end
    return n_ipoints, SVector(ipoints)
end

# Plot
# -------------------------------------------------------------------------------------------------
if enable_visualization
    function convert_arguments(LS::Type{<:LineSegments}, tri::Triangle_2D)
        l₁ = LineSegment_2D(tri[1], tri[2])
        l₂ = LineSegment_2D(tri[2], tri[3])
        l₃ = LineSegment_2D(tri[3], tri[1])
        lines = [l₁, l₂, l₃]
        return convert_arguments(LS, lines)
    end

    function convert_arguments(LS::Type{<:LineSegments}, T::Vector{Triangle_2D})
        point_sets = [convert_arguments(LS, tri) for tri ∈  T]
        return convert_arguments(LS, reduce(vcat, [pset[1] for pset ∈ point_sets]))
    end

    function convert_arguments(M::Type{<:Mesh}, tri::Triangle_2D)
        points = [tri[i] for i = 1:3]
        face = [1 2 3]
        return convert_arguments(M, points, face)
    end

    function convert_arguments(M::Type{<:Mesh}, T::Vector{Triangle_2D})
        points = reduce(vcat, [[tri[i] for i = 1:3] for tri ∈  T])
        faces = zeros(Int64, length(T), 3)
        k = 1
        for i in 1:length(T), j = 1:3
            faces[i, j] = k
            k += 1
        end
        return convert_arguments(M, points, faces)
    end
end


module ErrorFree

import Base:fma

export eftRecip, eftSquare, eftCube,
       eftSqrt,  eftRecipSqrt, # see comment for eftSqrt
       eftSum2,  eftSum2inOrder, eftDiff2, eftDiff2inOrder,
       eftProd2, eftDiv2,
       eftSum3,  eftSum3as2, eftSum3inOrder, eftSum3inOrderAs2,
       eftProd3, eftProd3as2,
       eftFMA,   eftFMS

#>   one operand error free transformations

@inline function eftSquare(a::Float64)
    x = a * a
    y = fma(a, a, -x)
    x,y
end

function eftCube(a::Float64)
    p = a*a; e = fma(a, a, -p)
    x = p*a; p = fma(p, a, -x)
    y = e*a
    x,y
end

@inline function eftRecip(a::Float64)
     x = one(Float64)/a
     y = -(fma(x,a,-1.0)/a)
     x,y
end

#=
   While not strictly an error-free transformation it is quite reliable and recommended for use.
   Augmented precision square roots, 2-D norms and discussion on correctly reounding sqrt(x^2 + y^2)
   by Nicolas Brisebarre, Mioara Joldes, Erik Martin-Dorel, Hean-Michel Muller, Peter Kornerup
=#
@inline function eftSqrt(a::Float64)
     x = sqrt(a)
     t = fma(x,-x,a)
     y = t / (x*2.0)
     x,y
end

@inline function eftRecipSqrt(a::Float64)
     r = 1.0/a
     x = sqrt(r)
     t = fma(x,-x,r)
     y = t / (x*2.0)
     x,y
end

#>   two operand error free transformations

"""
 (a,b) ↦ (x,y)\\
x⊕y ≖ a⊕b and x⊕y≖x
"""
@inline function eftSum2{T<:Float64}(a::T, b::T)
  x = a + b
  t = x - a
  y = (a - (x - t)) + (b - t)
  x,y
end

"""
 (a,b) ↦ (x,y)\\
x⊕y ≖ a⊕b and x⊕y≖x\\
 *presumes* |a| ≥ |b|
"""
@inline function eftSum2inOrder{T<:Float64}(a::T, b::T)
  x = a + b
  y = b - (x - a)
  x,y
end

"""
 (a,b) ↦ (x,y)\\
x⊕y ≖ a⊝b and x⊕y≖x
"""
@inline function eftDiff2{T<:Float64}(a::T, b::T)
  x = a - b
  t = x - a
  y = (a - (x - t)) - (b + t)
  x,y
end

"""
 (a,b) ↦ (x,y)\\
x⊕y ≖ a⊝b and x⊕y≖x\\
*presumes* |a| ≥ |b|
"""
@inline function eftDiff2inOrder{T<:Float64}(a::T, b::T)
  x = a - b
  y = (a - x) - b
  x,y
end

@inline function eftProd2{T<:Float64}(a::T, b::T)
    x = a * b
    y = fma(a, b, -x)
    x,y
end

@inline function eftDiv2{T<:Float64}(a::T,b::T)
     x = a/b
     y = -(fma(x,b,-a)/b)
     x,y
end

#>   three operand error free transformations

function eftSum3{T<:Float64}(a::T,b::T,c::T)
    s,t = eftSum2(b, c)
    x,u = eftSum2(a, s)
    y,z = eftSum2(u, t)
    x,y = eftSum2inOrder(x, y)
    x,y,z
end

function eftSum3inOrder{T<:Float64}(a::T,b::T,c::T)
    s,t = eftSum2inOrder(b, c)
    x,u = eftSum2inOrder(a, s)
    y,z = eftSum2inOrder(u, t)
    x,y = eftSum2inOrder(x, y)
    x,y,z
end

@inline function eftSum3as2{T<:Float64}(a::T,b::T,c::T)
    s,t = eftSum2(b, c)
    x,u = eftSum2(a, s)
    y = u+t
    x,y = eftSum2inOrder(x, y)
    x,y
end

@inline function eftSum3inOrderAs2{T<:Float64}(a::T,b::T,c::T)
    s,t = eftSum2inOrder(b, c)
    x,u = eftSum2inOrder(a, s)
    y = u+t
    x,y = eftSum2inOrder(x, y)
    x,y
end

function eftProd3{T<:Float64}(a::T, b::T, c::T)
    p,e = eftProd2(a,b)
    x,p = eftProd2(p,c)
    y,z = eftProd2(e,c)
    x,y,z
end

@inline function eftProd3as2{T<:Float64}(a::T, b::T, c::T)
    p,e = eftProd2(a,b)
    x,p = eftProd2(p,c)
    y = e*c
    x,y
end

end # module ErrorFree


export SVD

"""
    SVD(
        data::DataAccessor,
        k::Int
    )

Recommendation based on SVD of a user-item matrix ``R \\in \\mathbb{R}^{|\\mathcal{U}| \\times |\\mathcal{I}|}``, which was originally studied by [Sarwar et al.](http://files.grouplens.org/papers/webKDD00.pdf) Rank ``k`` is configured by `k`.

In a context of recommendation, ``U_k \\in \\mathbb{R}^{|\\mathcal{U}| \\times k}``, ``V \\in \\mathbb{R}^{|\\mathcal{I}| \\times k}`` and ``\\Sigma \\in \\mathbb{R}^{k \\times k}`` are respectively seen as ``k`` user/item feature vectors and corresponding weights. The idea of low-rank approximation that discards lower singular values intuitively works as *compression* or *denoising* of the original matrix; that is, each element in a rank-``k`` matrix ``A_k`` holds the best *compressed* (or *denoised*) value of the original element in ``A``. Thus, ``R_k = \\mathrm{SVD}_k(R)``, the best rank-``k`` approximation of ``R``, captures as much as possible of underlying users' preferences. Once ``R`` is decomposed into ``U, \\Sigma`` and ``V``, a ``(u, i)`` element of ``R_k`` calculated by ``\\sum^k_{j=1} \\sigma_j u_{u, j} v_{i, j}`` could be a prediction for the user-item pair.
"""
struct SVD <: Recommender
    data::DataAccessor
    k::Int
    U::AbstractMatrix
    S::AbstractVector
    Vt::AbstractMatrix

    function SVD(data::DataAccessor, k::Int)
        n_user, n_item = size(data.R)
        U = matrix(n_user, k)
        S = vector(k)
        Vt = matrix(k, n_item)
        new(data, k, U, S, Vt)
    end
end

SVD(data::DataAccessor) = SVD(data, 20)

isbuilt(recommender::SVD) = isfilled(recommender.U)

function build!(recommender::SVD)
    # NaNs are filled by zeros for now
    R = copy(recommender.data.R)
    R[isnan.(R)] .= 0

    res = svd(R)
    recommender.U[:] = res.U[:, 1:recommender.k]
    recommender.S[:] = res.S[1:recommender.k]
    recommender.Vt[:] = res.Vt[1:recommender.k, :]
end

function predict(recommender::SVD, u::Int, i::Int)
    check_build_status(recommender)
    dot(recommender.U[u, :] .* recommender.S, recommender.Vt[:, i])
end


#NOTE: LogNormal_Models reuse Normal_Records

struct LogNormal_Model <: GMC_NS_Model
    trajectory::Integer #id integer assigned at construction
    i::Integer #id of parent model, if any

    θ::Vector{Float64} #model's parameter Vector
    log_Li::Float64 #model's likelihood, calculated by constructor

    pos::Vector{Float64}
    v::Vector{Float64} #model's velocity in parameter space
end

function construct_lognormal_model(trajectory::Integer, i::Integer, θ::Vector{Float64}, pos::Vector{Float64}, v::Vector{Float64}, obs::Vector{Float64}; v_init=false)
    μ,λ=θ
    lμ=log(μ^2/sqrt(μ^2 + inv(λ)))
    lσ=sqrt(log(1+(inv(λ)/μ^2)))
    mod_lnormal=LogNormal(lμ,lσ)
    log_lh=lps(logpdf.(mod_lnormal, obs))
    v_init && (v=rand(MvNormal(length(θ),1.)))

    LogNormal_Model(trajectory, i, θ, log_lh, pos, v)
end

function Base.show(io::IO, m::LogNormal_Model; xsteps=100)
    μ,λ=m.θ
    lμ=log(μ^2/sqrt(μ^2 + inv(λ)))
    lσ=sqrt(log(1+(inv(λ)/μ^2)))
    n=LogNormal(lμ,lσ)
    X=[quantile(n,.025):(quantile(n,.975)-quantile(n,.025))/xsteps:quantile(n,.975)...]
    y=pdf.(n,X)

    show(io, lineplot(X,y,xlabel="X",ylabel="p",title=title="LogNormal Model $(m.trajectory).$(m.i), log_Li $(m.log_Li)"))
    println()
    println("θ: $(m.θ)")
    println("v: $(m.v)")
end


import Base: ∘

abstract type AbstractMobiusTransformation{T <: Number} <: Function end

"""
    MobiusTransformation(a,b,c,d)

Möbius transformation, a conformal automorphism in the Riemann sphere
\$z \\mapsto \\frac{az+b}{cz+d}\$
with \$ad-bc\\neq 0\$.
"""
struct MobiusTransformation{T} <: AbstractMobiusTransformation{T}
  a::T
  b::T
  c::T
  d::T

  function MobiusTransformation{T}(a0::T, b0::T, c0::T, d0::T) where T <: Number
    #@assert a0 * d0 - b0 * c0 != 0 "Invalid parameters for Möbius transformation."
    new(a0,b0,c0,d0)
  end

end

MobiusTransformation(a0::T, b0::T, c0::T, d0::T) where {T <: Number} =
MobiusTransformation{T}(a0,b0,c0,d0)

MobiusTransformation(a0::Number, b0::Number, c0::Number, d0::Number) =
MobiusTransformation(promote(a0,b0,c0,d0)...)

"""
    LinearTransformation(a)

Linear transformation (homotecy and rotation) in the Riemann sphere
\$z \\mapsto a z\$.
"""
struct LinearTransformation{T} <: AbstractMobiusTransformation{T}
  a::T

  function LinearTransformation{T}(a0::T) where T <: Number
    #@assert a0 == 0 "Invalid parameters for Möbius transformation."
    new(a0)
  end
end

LinearTransformation(a0::T) where {T <: Number} = LinearTransformation{T}(a0)

MobiusTransformation(a0::Number) = LinearTransformation(a0)

"""
    Translation(b)

Translation in the Riemann sphere
\$z \\mapsto z + b\$.
"""
struct Translation{T} <: AbstractMobiusTransformation{T}
  b::T

  function Translation{T}(b0::T) where T <:Number
    new(b0)
  end
end

Translation(b0::T) where {T <: Number} = Translation{T}(b0)

"""
    AffineTransformation(a,b)

Affine transformation in the Riemann sphere
\$z \\mapsto a z + b\$.
"""
struct AffineTransformation{T} <: AbstractMobiusTransformation{T}
  a::T
  b::T

  function AffineTransformation{T}(a0::T, b0::T) where T <:Number
    new(a0,b0)
  end
end

AffineTransformation(a0::T, b0::T) where {T <: Number} = AffineTransformation{T}(a0,b0)

AffineTransformation(a0::Number, b0::Number) = AffineTransformation(promote(a0,b0)...)

MobiusTransformation(a0::Number, b0::Number) = AffineTransformation(promote(a0,b0)...)

"""
    InversionReflection(b)

Inversion reflection in the Riemann sphere
\$z \\mapsto \\frac{b}{z}\$.
"""
struct InversionReflection{T} <: AbstractMobiusTransformation{T}
  b::T

  function InversionReflection{T}(b0::T) where T <:Number
    new(b0)
  end
end

InversionReflection(b0::T) where {T <: Number} = InversionReflection{T}(b0)

Base.show(io::IO, f::MobiusTransformation{T}) where T =
  print(io, "MöbiusTransformation{$T}: z -> ((", f.a, ")z + (", f.b, ")) / ((", f.c, ")z + (", f.d, "))")

Base.show(io::IO, f::LinearTransformation{T}) where T  =
  print(io, "LinearTransformation{$T}: z -> (", f.a, ")z")

Base.show(io::IO, f::Translation{T}) where T  =
  print(io, "Translation{$T}: z -> z + (", f.b, ")")

Base.show(io::IO, f::AffineTransformation{T}) where T  =
  print(io, "AffineTransformation{$T}: z -> (", f.a, ")z + (", f.b, ")")

Base.show(io::IO, f::InversionReflection{T}) where T  =
  print(io, "InversionReflection{$T}: z -> (", f.b, ") / z")

function (f::MobiusTransformation)(z::Number)
  # ToDo! Create a method to eval infinity case
  if isinf(z)
    return f.a / f.c
  end

  (f.a * z + f.b) / (f.c * z + f.d)
end

function (f::LinearTransformation)(z::Number)
  f.a * z
end

function (f::Translation)(z::Number)
  z + f.b
end

function (f::AffineTransformation)(z::Number)
  f.a * z + f.b
end

function (f::InversionReflection)(z::Number)
  # ToDo! Create a method to eval infinity case
  if isinf(z)
    return 0
  end

  f.b / z
end

"""
Möbius transformation parameters accesors.
"""
a(f::MobiusTransformation) = f.a
b(f::MobiusTransformation) = f.b
c(f::MobiusTransformation) = f.c
d(f::MobiusTransformation) = f.d

a(f::LinearTransformation) = f.a
b(f::LinearTransformation) = 0
c(f::LinearTransformation) = 0
d(f::LinearTransformation) = 1

a(f::Translation) = 1
b(f::Translation) = f.b
c(f::Translation) = 0
d(f::Translation) = 1

a(f::AffineTransformation) = f.a
b(f::AffineTransformation) = f.b
c(f::AffineTransformation) = 0
d(f::AffineTransformation) = 1

a(f::InversionReflection) = 0
b(f::InversionReflection) = f.b
c(f::InversionReflection) = 1
d(f::InversionReflection) = 0

"""
Trace

\$tr(T)=a+d\$
"""
tr(f::AbstractMobiusTransformation) = a(f) + d(f)

tr(f::MobiusTransformation) = f.a + f.d

tr(f::LinearTransformation) = f.a + 1

tr(f::Translation) = 1

tr(f::AffineTransformation) = f.a + 1

tr(f::InversionReflection) = 0

"""
Determinant.

\$det(T)=ad-bc\$
"""
det(f::AbstractMobiusTransformation) = a(f)*d(f) - b(f)*c(f)

det(f::MobiusTransformation) = f.a * f.d - f.b * f.c

det(f::LinearTransformation) = f.a

det(f::Translation) = 1

det(f::AffineTransformation) = f.a

det(f::InversionReflection) = -f.b

#function normalize(f::MobiusTransformation)
#  dt = det(f)
#  MobiusTransformation(f.a/dt, f.b/det, f.c/det, f.d/det)
#end

#function normalize(f::LinearTransformation)
#  f
#end

"""
Inverse.

\$T^{-1}(z)=\\frac{d z - b}{-c z + a}\$
"""
inverse(f::MobiusTransformation) = MobiusTransformation(f.d, -f.b, -f.c, f.a)

inverse(f::LinearTransformation) = LinearTransformation(1.0 / f.a)

inverse(f::Translation) = Translation(-f.b)

inverse(f::AffineTransformation) = AffineTransformation(1.0 / f.a, -f.b / f.a)

inverse(f::InversionReflection) = InversionReflection(f.b)

"""
Derivative.

\$T'(z)=\\frac{ad-bc}{(c z + d)^2}\$
"""
function derivative(f::MobiusTransformation)
  function der(z::Number)
    (f.a * f.d - f.b * f.c) / ((f.c * z + f.d)^2)
  end
end

function derivative(f::LinearTransformation)
  function der(z::Number)
    f.a
  end
end

function derivative(f::Translation)
  function der(z::Number)
    1
  end
end

function derivative(f::AffineTransformation)
  function der(z::Number)
    f.a
  end
end

function derivative(f::InversionReflection)
  function der(z::Number)
    -f.b / (z^2)
  end
end

"""
    fixedpoints(f)

Returns the fixed points of a Möbius transformation. Always returns two points,
even in the parabolic case returns the one fixed point duplicated.
"""
function fixedpoints(f::MobiusTransformation{T}) where T <: Real
  if f.c == 0
    return f.b / (f.d - f.a), Inf
  end
  discr = (f.a + f.d)^2 - 4 * (f.a * f.d - f.b * f.c)
  rd = discr < 0 ? sqrt(complex(discr)) : sqrt(discr)
  (f.a - f.d + rd)/(2*f.c), (f.a - f.d - rd)/(2*f.c)
end

function fixedpoints(f::MobiusTransformation)
  if f.c == 0
    return f.b / (f.d - f.a), Inf
  end
  rd = sqrt((f.a + f.d)^2 - 4 * (f.a * f.d - f.b * f.c))
  (f.a - f.d + rd)/(2*f.c), (f.a - f.d - rd)/(2*f.c)
end

function fixedpoints(f::LinearTransformation)
  0, Inf
end

function fixedpoints(f::Translation)
  Inf, Inf
end

function fixedpoints(f::AffineTransformation)
  if f.a == 1
    return Inf, Inf
  end
  f.b / (1 - f.a), Inf
end

function fixedpoints(f::InversionReflection{T}) where T <: Real
  rd = f.b < 0 ? sqrt(complex(f.b)) : sqrt(f.b)
  rd, -rd
end

function fixedpoints(f::InversionReflection)
  rd = sqrt(f.b)
  rd, -rd
end

"""
Kind of Möbius transformation:
- `:parabolic` if \$t^2=4\$.
- `:elliptic` if \$t^2\\in [0,4)\$.
- `:hyperbolic` if \$t^2\\in (-\\infty,0)\\cup (4,\\infty)\$.
- `:loxodromic` otherwise.
where \$t=tr(T)/det(T)\$.
"""
function kind(f::AbstractMobiusTransformation)
  t = tr(f) / det(f)
  t2 = t^2
  if imag(t2) ≈ 0
    t2 = real(t2)
    if t2 == 4
      return :parabolic
    end

    if 0 <= t2 < 4
      return :elliptic
    end

    return :hyperbolic
  end

  :loxodromic
end

kind(f::Translation) = :parabolic

"""
Compose two Möbius transformations.
"""
compose(f::AbstractMobiusTransformation, g::AbstractMobiusTransformation) =
  MobiusTransformation(a(f)*a(g)+b(f)*c(g), a(f)*b(g)+b(f)*d(g),
    c(f)*a(g)+d(f)*c(g), c(f)*b(g)+c(f)*d(g))

compose(f::MobiusTransformation, g::MobiusTransformation) =
  MobiusTransformation(f.a*g.a+f.b*g.c, f.a*g.b+f.b*g.d,
    f.c*g.a+f.d*g.c, f.c*g.b+f.d*g.d)

compose(f::LinearTransformation, g::LinearTransformation) =
  LinearTransformation(f.a * g.a)

compose(f::Translation, g::Translation) =
  Translation(f.b + g.b)

compose(f::AffineTransformation, g::AffineTransformation) =
  AffineTransformation(f.a * g.a, f.a * g.b + f.b)

compose(f::InversionReflection, g::InversionReflection) =
  InversionReflection(f.b / g.b)

# LinearTransformation vs ...
compose(f::LinearTransformation, g::MobiusTransformation) =
  MobiusTransformation(f.a*g.a, f.a*g.b, g.c, g.d)

compose(f::MobiusTransformation, g::LinearTransformation) =
  MobiusTransformation(f.a*g.a, f.b, f.c*g.a, f.d)

compose(f::LinearTransformation, g::Translation) =
  AffineTransformation(f.a, f.a * g.b)

compose(f::Translation, g::LinearTransformation) =
  AffineTransformation(g.a, f.b)

compose(f::LinearTransformation, g::AffineTransformation) =
  AffineTransformation(f.a * g.a, f.a * g.b)

compose(f::AffineTransformation, g::LinearTransformation) =
  AffineTransformation(f.a * g.a, f.b)

# Translation vs ...
compose(f::Translation, g::MobiusTransformation) =
  MobiusTransformation(g.a+f.b*g.c, g.b+f.b*g.d, g.c, g.d)

compose(f::MobiusTransformation, g::Translation) =
  MobiusTransformation(f.a, f.a*g.b+f.b, f.c, f.c*g.b+f.d)

compose(f::Translation, g::AffineTransformation) =
  AffineTransformation(g.a, g.b+f.b)

compose(f::AffineTransformation, g::Translation) =
  AffineTransformation(f.a, f.a*g.b+f.b)

# AffineTransformation vs ...
compose(f::AffineTransformation, g::MobiusTransformation) =
  MobiusTransformation(f.a*g.a+f.b*g.c, f.a*g.b+f.b*g.d, g.c, g.d)

compose(f::MobiusTransformation, g::AffineTransformation) =
  MobiusTransformation(f.a*g.a, f.a*g.b+f.b, f.c*g.a, f.c*g.b+f.d)

# InversionReflection vs ...
# result in a MobiusTransformation, fall back to AbstractMobiusTransformation implementation

"""
Binary operator to compose two Möbius transformations.
"""
∘(f::AbstractMobiusTransformation, g::AbstractMobiusTransformation) = compose(f,g)

"""
    maptozerooneinf(z1,z2,z3)

Create the Möbius transformation such that \$z_1 \\mapsto 0\$,
\$z_2 \\mapsto 1\$ and \$z_3 \\mapsto \\infty\$.
"""
maptozerooneinf(z1::Number, z2::Number, z3::Number) =
  MobiusTransformation(z2-z3, z1*(z3-z2), z2-z1, z3*(z1-z2))

"""
    mapfromzerooneinf(z1,z2,z3)

Create the Möbius transformation such that \$0 \\mapsto z_1\$,
  \$1 \\mapsto z_2\$ and \$\\infty \\mapsto z_3\$.
"""
mapfromzerooneinf(z1::Number, z2::Number, z3::Number) =
  MobiusTransformation(z3*(z1-z2), z1*(z2-z3), z1-z2, z2-z3)

"""
Create the Möbius transformation such that \$z_1 \\mapsto w_1\$,
  \$z_2 \\mapsto w_2\$ and \$z_3 \\mapsto w_3\$.
"""
function MobiusTransformation(z1::Number, z2::Number, z3::Number,
  w1::Number, w2::Number, w3::Number)
  f = maptozerooneinf(z1,z2,z3)
  g = mapfromzerooneinf(w1,w2,w3)
  g∘f
end

#=
function (f::MobiusTransformation)(c::CLine)
  CLine(
   abs2(f.d) * c.A - 2 * real( conj(f.c) * f.d * c.B ) + abs2(f.c) * c.C,
   conj(f.a) * (f.d * c.B - f.c * c.C) - conj(f.b) * (f.c * conj(c.B) - f.d * c.A),
   abs2(f.b) * c.A - 2 * real( conj(f.a) * f.b * c.B ) + abs2(f.a) * c.C
  )
end

# ToDO: Methods!!!
=#

"""
Given a Möbius transformation \$f(z)=\\frac{az+b}{cz+d}\$ and a circle
\$C: |center-z|=radius\$, the circle  \$f(C)\$ has center

\$ w = f(center - \\frac{radius^2}{\\overline{\\frac{d}{c}+center}})

and radius

\$|w - f(center+radius)|\$.
"""
function (f::MobiusTransformation)(c::Circle)
  if f.c ≈ 0.0
    return Circle( f(c.center), abs(f.a/f.d)*c.radius )
  elseif abs(c.center + f.d/f.c) ≈ c.radius
    z = c.center + f.d/f.c
    w1 = f(c.center + z)
    w2 = f(c.center + im*z)
    return Line(w1, angle(w1-w2))
  end

  # "Grandma's recipe" from "Indra's Pearls" to calculate image of a circle
  z = c.center - c.radius*c.radius/conj(f.d/f.c + c.center)
  Circle(f(z), abs(z - f(c.center + c.radius)))
end

function (f::LinearTransformation)(c::Circle)
  Circle( f(c.center), abs(f.a)*c.radius )
end

function (f::Translation)(c::Circle)
  Circle( f(c.center), c.radius )
end

function (f::AffineTransformation)(c::Circle)
  Circle( f(c.center), abs(f.a)*c.radius )
end

function (f::InversionReflection)(c::Circle)
  if abs(c.center) ≈ c.radius
    w1 = f(2*c.center)
    w2 = f(c.center + im*c.center)
    return Line(w1, angle(w1-w2))
  end

  # "Grandma's recipe" from "Indra's Pearls" to calculate image of a circle
  z = c.center - c.radius*c.radius/conj(c.center)
  Circle(f(z), abs(z - f(c.center + c.radius)))
end

function (f::MobiusTransformation)(c::Arc)
  if f.c ≈ 0.0
    return Arc( f(c.center), f(c.p1), f(c.p2) )
  elseif abs(c.center + f.d/f.c) ≈ radius(c)
    #ToDo!: Case 2 rays!
    return LineSegment(f(c.p1), f(c.p2))
  end

  # "Grandma's recipe" from "Indra's Pearls" to calculate image of a circle
  z = c.center - radius2(c)/conj(f.d/f.c + c.center)
  Arc(f(z), f(c.p1), f(c.p2))
end

function (f::LinearTransformation)(c::Arc)
  Arc(f(c.center), f(c.p1), f(c.p2))
end

function (f::Translation)(c::Arc)
  Arc(f(c.center), f(c.p1), f(c.p2))
end

function (f::AffineTransformation)(c::Arc)
  Arc(f(c.center), f(c.p1), f(c.p2))
end

function (f::InversionReflection)(c::Arc)
  if abs(c.center) ≈ c.radius
    #ToDo!: Case 2 rays!
    return LineSegment(f(c.p1), f(c.p2))
  end

  # "Grandma's recipe" from "Indra's Pearls" to calculate image of a circle
  z = c.center - radius2(c)/conj(c.center)
  Arc(f(z), f(c.p1), f(c.p2))
end

function (f::MobiusTransformation)(l::Line)
  if f.c ≈ 0.0
    w1 = f(l.base)
    return Line( w1, angle(w1-f(l.base+exp(l.angle*im))) )
  elseif l.base ≈ -f.d/f.c
    w = f(Inf)
    return Line(w, angle(w-f(Inf)))
  else
    a = angle(l.base + f.d/f.c)
    if a ≈ l.angle || a ≈ (l.angle + pi)
      w = f(l.base)
      return Line(w, angle(w-f(Inf)))
    end
  end

  v = exp(im*l.angle)
  w1 = f(l.base)
  w2 = f(l.base + v)
  w3 = f(l.base - v)
  Circle(circumcenter(w1,w2,w3), circumradius(w1,w2,w3))
end

function (f::LinearTransformation)(l::Line)
  Line(f(l.base), l.angle + angle(f.a))
end

function (f::Translation)(l::Line)
  Line(f(l.base), l.angle)
end

function (f::AffineTransformation)(l::Line)
  Line(f(l.base), l.angle + angle(f.a))
end

function (f::InversionReflection)(l::Line)
  if l.base ≈ 0
    return Line(l.base, l.angle + angle(f.b))
  else
    a = angle(l.base)
    if a ≈ l.angle || a ≈ (l.angle + pi)
      return Line(l.base, l.angle + angle(f.b))
    end
  end

  v = exp(im*l.angle)
  w1 = f(l.base)
  w2 = f(l.base + v)
  w3 = f(l.base - v)
  Circle(circumcenter(w1,w2,w3), circumradius(w1,w2,w3))
end

function (f::MobiusTransformation)(l::LineSegment)
  if f.c ≈ 0.0
    return LineSegment( f(l.p1), f(l.p2) )
  elseif l.p1 ≈ -f.d/f.c
    # ToDo! Ray
  elseif l.p2 ≈ -f.d/f.c
    # ToDo! Ray
  else
    # ToDo! Two Rays!
    # ToDo! Line Segment!
  end

  w1 = f(l.z1)
  w2 = f(l.z2)
  w3 = f((l.z1+l.z2)/2)
  Arc(circumcenter(w1,w2,w3), w1, w2)
end

function (f::LinearTransformation)(l::LineSegment)
  LineSegment(f(l.p1), f(l.p2))
end

function (f::Translation)(l::LineSegment)
  LineSegment(f(l.p1), f(l.p2))
end

function (f::AffineTransformation)(l::LineSegment)
  LineSegment(f(l.p1), f(l.p2))
end

function (f::InversionReflection)(l::LineSegment)
  if l.p1 ≈ 0
    # ToDo! Ray
  elseif l.p2 ≈ 0
    # ToDo! Ray
  else
    # ToDo! Two Rays!
    # ToDo! Line Segment!
  end

  w1 = f(l.z1)
  w2 = f(l.z2)
  w3 = f((l.z1+l.z2)/2)
  Arc(circumcenter(w1,w2,w3), w1, w2)
end


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    TaubinSmoothing(n; λ=0.5, μ=-0.5)

Perform `n` iterations of Taubin smoothing with parameters `λ` and `μ`.

## References

* Taubin, G. 1995. [Curve and Surface Smoothing without Shrinkage]
  (https://ieeexplore.ieee.org/document/466848)
"""
struct TaubinSmoothing <: SmoothingMethod
  n::Int
  λ::Float64
  μ::Float64
end

function TaubinSmoothing(n; λ=0.5, μ=-0.5)
  @assert n > 0 "invalid number of iterations"
  @assert 0 < λ ≤ -μ < 1 "invalid parameters λ and μ"
  TaubinSmoothing(n, λ, μ)
end

function smooth(mesh, method::TaubinSmoothing)
  n = method.n
  λ = method.λ
  μ = method.μ

  # Laplacian matrix with uniform weights
  L = laplacematrix(mesh, weights=:uniform)

  # matrix with vertex coordinates (nvertices x ndims)
  V = reduce(hcat, coordinates.(vertices(mesh))) |> transpose

  # Taubin updates
  for _ in 1:n
    V = V + λ*L*V
    V = V + μ*L*V
  end

  # new points of the smooth mesh
  points = Point.(eachrow(V))

  SimpleMesh(points, topology(mesh))
end


#==============================================================================

          Solving the HJB with labor supply using the implicit method

               Translated from matlab code on Ben Moll's website:
                             https://benjaminmoll.com/codes/

==============================================================================#

using LinearAlgebra, SparseArrays, Plots, Roots, LaTeXStrings

γ = 2 # parameter from CRRA utility
φ = .5 # frisch elasticity
ρ = 0.05 # the discount rate
r = 0.03
w = 1.0

z1=.1
z2 =.2
z= [z1 z2]

λ1 = 1.5
λ2 = 1.0

λ = [λ1 λ2]

H = 500 # number of points in the grid space
amin= -0.15
amax= 3.0

a = LinRange(amin,amax,H)
da = (amax-amin)/(H-1)

aa = [a a]
zz = ones(H,1)*z

maxit = 20 # the maximum number of iterations we allow the finite differencing algorithm
crit = 10^(-6) # our critical value
Δ = 1000

dVf=zeros(H,2)
dVb = zeros(H,2)
c= zeros(H,2)

# Create the matrix that captures the systems dynamic from λ process
Aswitch = [-1*sparse(I,H,H)*λ[1] sparse(I,H,H)*λ[1]; sparse(I,H,H)*λ[2] -1*sparse(I,H,H)*λ[2]]

x0 = (w*z1)^(φ*(1-γ)/(1+γ*φ)) #inital value for labor

# create function for labor supply
labor_solve(l, a, z, w, r, γ, φ) = (l .- ((w*z*l .+ r*a).^(-γ*φ))*(w*z)^φ)

# set empty matrix and find all possible values for l
l0 = zeros(500,2)

for i in 1:H
    l_solve1(l) = labor_solve(l, a[i], z1, w, r, γ, φ)
    l01 = find_zero(l_solve1,x0)

    l_solve2(l) = labor_solve(l, a[i], z2, w, r, γ, φ)
    l02 = find_zero(l_solve2,x0)

    l0[i,:] = [l01 l02]
end

v0=zeros(H,2)
v0[:,1] = (w*z[1].*l0[1,1] .+ r.*a).^(1-γ)/(1-γ)/ρ
v0[:,2] = (w*z[2].*l0[1,2] .+ r.*a).^(1-γ)/(1-γ)/ρ
plot(a,v0[:,2])

lmin = l0[1,:]
lmax = l0[H,:]

global v=v0

# set up spaces to save variables of interest
dist=[]; V_n=[]; g_r=[]; dV_r=[]; V_r=[]; c_r=[]; adot=[]

# Loop that finds the equilibrium interest rate
for n in 1:maxit
    global V=v
    push!(V_n,V)
    # Forward differencing
    dVf[1:H-1,:]=(V[2:H,:]-V[1:H-1,:])/da
    dVf[H,:] = (w*z'.*lmax .+ r.*amax).^(-γ) # impose state constraint at the max, just in case
    # Backward differencing
    dVb[2:H,:]=(V[2:H,:]-V[1:H-1,:])/da
    dVb[1,:]= (w*z'.*lmin .+ r.*amin).^(-γ)

    # Find consumption and savings with the forward difference
    cf = dVf.^(-1/γ)
    lf = (dVf.*w.*zz).^φ
    sf = w*zz.*lf +r.*aa -cf

    # Find consumption and savings with the backward difference
    cb= dVb.^(-1/γ)
    lb = (dVb.*w.*zz).^φ
    sb = w*zz.*lb +r.*aa -cb

    # Find consumption and savings at steady state
    c0 = w*zz.*l0 + r.*aa
    dV0 = c0.^(-γ)

    # Now implement the upwind scheme in order to select the best differencing method
    Ib = (sb .< 0)
    If = (sf .> 0)#.*(1 .-Ib) this term was included in Matlab code, messes up julia code for some reason??? Probably rounding differences?
    I0 = (1 .- If - Ib)

    # State constraint at amin is automatically implemented
    global c = cf.*If + cb.*Ib + c0.*I0
    global l = lf.*If + lb.*Ib + l0.*I0
    u = c.^(1-γ)/(1-γ) - l.^(1+1/φ)/(1+1/φ)

    # Construct matrix for the evolution of the system
    global X = -Ib.*sb/da
    global Y = -If.*sf/da + Ib.*sb/da
    global Z = If.*sf/da

    A1 = spdiagm(0=>Y[:,1], -1 => X[2:H,1], 1=> Z[1:H-1,1])
    A2 = spdiagm(0=>Y[:,2], -1 => X[2:H,2], 1=> Z[1:H-1,2])
    AA = [A1 spzeros(H,H); spzeros(H,H) A2]

    global A = AA + Aswitch

    B = (ρ +1/Δ)*sparse(I,2*H,2*H) - A

    u_stacked = [u[:,1]; u[:,2]]
    V_stacked = [V[:,1]; V[:,2]]

    b = u_stacked + V_stacked/Δ
    V_stacked = B\b # Solves the system of equations

    V = [V_stacked[1:H] V_stacked[H+1:2*H]]

    V_change = V - v

    global v = V

    push!(dist, findmax(abs.(V_change))[1])

    if dist[n] < crit
        println("Value Function converged, Iteration=")
        println(n)
        break
    end
end

adot = w.*zz.*l + r.*aa - c

#plot savings
plot(a, adot[:,1], label = L"s_1(a)", xlabel="Wealth, a", ylabel=L"\mathrm{Savings,\hspace{1ex} } s_i(a)")
plot!(a, adot[:,2], label = L"s_2(a)")
plot!(a, zeros(H,1), line=:dash, color=:black, label="")

#plot value function for j=1,2
plot(a, v[:,1], label = L"v_1(a)", xlabel="Wealth, a", ylabel=L"\mathrm{Value Function,\hspace{1ex} } v_i(a)")
plot!(a, v[:,2], label = L"v_2(a)", legend=:bottomright)

#plot labor for j=1,2
plot(a, l[:,1], label = L"l_1(a)", xlabel="Wealth, a", ylabel=L"\mathrm{Labor Supply,\hspace{1ex} } l_i(a)")
plot!(a, l[:,2], label = L"l_2(a)")

#plot consumption for j=1,2
plot(a, c[:,1], label = L"c_1(a)", xlabel="Wealth, a", ylabel=L"\mathrm{Consumption,\hspace{1ex} } c_i(a)")
plot!(a, c[:,2], label = L"c_2(a)", legend=:bottomright)


const errmax = 1e-15

#Compute tail of noncentral Beta distribution
#Russell Lenth, Algorithm AS 226: Computing Noncentral Beta Probabilities,
#Applied Statistics,Volume 36, Number 2, 1987, pages 241-244

"""
	ncbeta_tail(x,a,b,lambda)

Compute tail of the noncentral beta distribution.
Uses the recursive relation
```math
I_{x}(a,b+1;0) = I_{x}(a,b;0) - \\Gamma(a+b)/\\Gamma(a+1)\\Gamma(b)x^{a}(1-x)^{b}
```
and ``\\Gamma(a+1) = a\\Gamma(a)`` given in https://dlmf.nist.gov/8.17.21.
"""
function ncbeta_tail(a::Float64, b::Float64, lambda::Float64, x::Float64)
    if x <= 0.0
        return 0.0
    elseif x >= 1.0
        return 1.0
    end

    c = 0.5*lambda
    #Init series

    beta = logabsbeta(a,b)[1]
    temp = beta_inc(a,b,x)[1]
    gx = (beta_integrand(a,b,x,1.0-x))/a
    q = exp(-c)
    xj = 0.0
    ax = q*temp
    sumq = 1.0 - q
    ans = ax

    while true
        xj += 1.0
        temp -= gx
        gx *= x*(a+b+xj-1.0)/(a+xj)
        q *= c/xj
        sumq -= q
        ax = temp*q
        ans += ax

        #Check convergence
        errbd = abs((temp-gx)*sumq)
        if xj > 1000 || errbd < 1e-10
            break
        end
    end
    return ans
end

"""
    ncbeta_poisson(a,b,lambda,x)

Compute CDF of noncentral beta if lambda >= 54 using:
First ``\\lambda/2`` is calculated and the Poisson term is calculated using ``P(j-1)=j/\\lambda P(j)`` and ``P(j+1) = \\lambda/(j+1) P(j)``.
Then backward recurrences are used until either the Poisson weights fall below `errmax` or `iterlo` is reached.
```math
I_{x}(a+j-1,b) = I_{x}(a+j,b) + \\Gamma(a+b+j-1)/\\Gamma(a+j)\\Gamma(b)x^{a+j-1}(1-x)^{b}
```
Then forward recurrences are used until error bound falls below `errmax`.
```math
I_{x}(a+j+1,b) = I_{x}(a+j,b) - \\Gamma(a+b+j)/\\Gamma(a+j)\\Gamma(b)x^{a+j}(1-x)^{b}
```
"""
function ncbeta_poisson(a::Float64, b::Float64, lambda::Float64, x::Float64)
    c = 0.5*lambda
    xj = 0.0
    m = round(Int, c)
    mr = float(m)
    iterlo = m - trunc(Int, 5.0*sqrt(mr))
    iterhi = m + trunc(Int, 5.0*sqrt(mr))
    t = -c + mr*log(c) - logabsgamma(mr + 1.0)[1]
    q = exp(t)
    r = q
    psum = q

    beta = logabsbeta(a+mr,b)[1]
    gx = beta_integrand(a+mr,b,x,1.0-x)/(a + mr)
    fx = gx
    temp = beta_inc(a+mr,b,x)[1]
    ftemp = temp
    xj += 1.0

    sm = q*temp
    iter1 = m

    #Iterations start from M and goes downwards

    for iter1 = m:-1:iterlo
        if q < errmax
            break
        end

        q *= iter1/c
        xj += 1.0
        gx *= (a + iter1)/(x*(a+b+iter1-1.0))
        iter1 -= 1
        temp += gx
        psum += q
        sm += q*temp
    end

    t0 = logabsgamma(a+b)[1] - logabsgamma(a+1.0)[1] - logabsgamma(b)[1]
    s0 = a*log(x) + b*log1p(-x)

    s = 0.0
    for j = 0:iter1-1
        s += exp(t0+s0+j*log(x))
        t1 = log(a+b+j) - log(a+j+1.0) + t0
        t0 = t1
    end
    #Compute first part of error bound

    errbd = (gamma_inc(float(iter1),c,0)[2])*(temp+s)
    q = r
    temp = ftemp
    gx = fx
    iter2 = m
    #Iterations for the higher part

    for iter2 = m:iterhi-1
        ebd = errbd + (1.0 - psum)*temp
        if ebd < errmax
            return sm
        end
        iter2 += 1
        xj += 1.0
        q *= c/iter2
        psum += q
        temp -= gx
        gx *= x*(a+b+iter2-1.0)/(a+iter2)
        sm += q*temp
    end
    return sm
end

#R Chattamvelli, R Shanmugam, Algorithm AS 310: Computing the Non-central Beta Distribution Function,
#Applied Statistics, Volume 46, Number 1, 1997, pages 146-156

"""
	ncbeta(a,b,lambda,x)

Compute the CDF of the noncentral beta distribution given by
```math
I_{x}(a,b;\\lambda ) = \\sum_{j=0}^{\\infty}q(\\lambda/2,j)I_{x}(a+j,b;0)
```
For ``\\lambda < 54`` : algorithm suggested by Lenth(1987) in `ncbeta_tail(a,b,lambda,x)`.
Else for ``\\lambda >= 54`` : modification in Chattamvelli(1997) in `ncbeta_poisson(a,b,lambda,x)` by using both forward and backward recurrences.
"""
function ncbeta(a::Float64, b::Float64, lambda::Float64, x::Float64)
    ans = x
    if x <= 0.0
        return 0.0
    elseif x >= 1.0
        return 1.0
    end

    if lambda < 54.0
        return ncbeta_tail(a,b,lambda,x)
    else
        return ncbeta_poisson(a,b,lambda,x)
    end
end

"""
    ncF(x,v1,v2,lambda)

Compute CDF of noncentral F distribution given by:
```math
F(x, v1, v2; lambda) = I_{v1*x/(v1*x + v2)}(v1/2, v2/2; \\lambda)
```
where ``I_{x}(a,b; lambda)`` is the noncentral beta function computed above.

Wikipedia: https://en.wikipedia.org/wiki/Noncentral_F-distribution
"""
function ncF(x::Float64, v1::Float64, v2::Float64, lambda::Float64)
    return ncbeta(v1/2, v2/2, lambda, (v1*x)/(v1*x + v2))
end

function ncbeta(a::T,b::T,lambda::T,x::T) where {T<:Union{Float16,Float32}}
	T.(ncbeta(Float64(a),Float64(b),Float64(lambda),Float64(x)))
end

function ncF(x::T,v1::T,v2::T,lambda::T) where {T<:Union{Float16,Float32}}
	T.(ncF(Float64(x),Float64(v1),Float64(v2),Float64(lambda)))
end

ncbeta(a::Real,b::Real,lambda::Real,x::Real) = ncbeta(promote(float(a),float(b),float(lambda),float(x))...)
ncbeta(a::T,b::T,lambda::T,x::T) where {T<:AbstractFloat} = throw(MethodError(ncbeta,(a,b,lambda,x,"")))
ncF(x::Real,v1::Real,v2::Real,lambda::Real) = ncF(promote(float(x),float(v1),float(v2),float(lambda))...)
ncF(x::T,v1::T,v2::T,lambda::T) where {T<:AbstractFloat} = throw(MethodError(ncF,(x,v1,v2,lambda,"")))


using MacroTools, SymEngine
import MacroTools.postwalk

"""
    abs_to_relu!(ex::Expr)
    abs_to_relu(ex::Expr)
function to convert an `abs(x)` expression to `relu(x) + relu(-x)`. Mutating and nonmutating versions.
"""
function abs_to_relu(ex::Expr)
    check_for_abs(ex)
    x = ex.args[2] # the thing getting abs'ed
    return :(relu($x) + relu(-$x))
end
function abs_to_relu!(ex::Expr)
    check_for_abs(ex)
    x = ex.args[2] # the thing getting abs'ed
    ex.args = [:+, :(relu($x)), :(relu(-$x))]
end

function check_for_abs(ex::Expr)
    ex.args[1] == :abs || throw(ArgumentError("Not an absolute value expression"))
    @assert length(ex.args) == 2 "Malformed expression. `abs` can take only one argument. Got $ex"
    return nothing
end

function check_for_maxmin(ex::Expr)
    ex.args[1] ∈ (:max, :min) || throw(ArgumentError("function is neither max nor min. Got $(ex.args[1])"))
    @assert length(ex.args) == 3 "max/min_to_abs can't handle more than two inputs at the moment."
    return nothing
end

function maxmin_to_abs(ex::Expr)
    check_for_maxmin(ex)
    a, b = ex.args[2:3]
    if ex.args[1] == :max
        return :(0.5*($a + $b + abs($a-$b)))
    elseif ex.args[1] == :min
        return :(0.5*($a + $b - abs($a-$b)))
    end
end

function max0torelu(ex::Expr)
    @assert(length(ex.args)==3)
    a, b = ex.args[2:3]
    if (a == 0)
        return :(relu($b))
    elseif (b == 0)
        return :(relu($a))
    end
end

to_relu_expression(not_ex) = not_ex
function to_relu_expression(ex::Expr)
    ex.head != :call && return ex
    if ex.args[1] ∈ (:max, :min)
        if (ex.args[1] == :max) & ((ex.args[2] == 0) | (ex.args[3] == 0))
            ex = max0torelu(ex)
        else
            ex = maxmin_to_abs(ex)
        end
    elseif ex.args[1] == :abs
        ex = abs_to_relu(ex) end
    for i in 1:length(ex.args)
        ex.args[i] = to_relu_expression(ex.args[i])
    end
    return ex
end

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

"""
Construct a symbolic expression for a line between the points (x₀, 1.0) on the left and (x₁, 0) on the right.
`pos_unit` has positive slope while `neg_unit` has negative slope. Note that due to the left-to-right assumption

    neg_unit(x₀, x₁) == pos_unit(x₁, x₀)
"""
neg_unit(x0, x1) = :($(1/(x0-x1)) * (x - $x1))
"""
Construct a symbolic expression for a line between the points (x₀, 0.0) on the left and (x₁, 1.0) on the right.
`pos_unit` has positive slope while `neg_unit` has negative slope. Note that due to the left-to-right assumption

    neg_unit(x₀, x₁) == pos_unit(x₁, x₀)
"""
pos_unit(x0, x1) = :($(1/(x1-x0)) * (x - $x0))

"""
    closed_form_piecewise_linear(pts)::Expr

Constructs a closed-form piecewise linear expression from an ordered (left to right) sequence of points.
The method is inspired by the paper by Lum and Chua (cite) that considers a piecewise linear function of the form:
`f(x) = Σᵢ(yᵢ⋅gᵢ(xᵢ))` where `gᵢ(xⱼ) = δᵢⱼ`
Here in the 1D case, `gᵢ = max(0, yᵢ*min(L1, L2))`, where `L1` and `L2` are the lines "ramping up" towards xᵢ
and "ramping down" away from xᵢ. The function returns an `Expr` based on a variable `x`.
This can be turned into a callable function `f` by running something like `eval(:(f(x) = \$expression_of_x))`.

# Example
    julia> pts = [(0,0), (1,1), (2, 0)]
    3-element Array{Tuple{Int64,Int64},1}:
     (0, 0)
     (1, 1)
     (2, 0)

    julia> closed_form_piecewise_linear(pts)
    :(max(0, 0 * (-1.0 * (x - 1))) + max(0, 1 * min(1.0 * (x - 0), -1.0 * (x - 2))) + max(0, 0 * (1.0 * (x - 1))))
"""
function closed_form_piecewise_linear(pts)
    n = length(pts)
    x, y = first.(pts), last.(pts) # split the x and y coordinates
    G = []
    for i in 2:n-1
        x0, x1, x2 = x[i-1:i+1] # consider the "triangulation" of points x0,x1,x2
        L1 = pos_unit(x0, x1) # x0-x1 is an increasing linear unit
        L2 = neg_unit(x1, x2) # x1-x2 is a decreasing linear unit
        gᵢ = :($(y[i]) * max(0, min($L1, $L2)))
        push!(G, gᵢ)
    end
    # first and last points are special cases that ignore the min
    g₀ = :($(y[1]) * max(0, $(neg_unit(x[1], x[2]))))
    gᵣ = :($(y[end]) * max(0, $(pos_unit(x[end-1], x[end]))))
    # Order doesn't matter now but for our debugging purposes earlier we enforce sequential ordering.
    pushfirst!(G, g₀)
    push!(G, gᵣ)
    return :(+$(G...))
end

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

function make_expr_dict(ex)
    D = Dict()
    ex = postwalk(ex) do e
        if is_relu_expr(e)
            return get!(D, e, Symbol("z$(length(D)+1)"))
        end
        return e
    end
    D[ex] = Symbol("z$(length(D)+1)")
    return D
end

is_relu_expr(ex) = ex isa Expr && ex.head == :call && ex.args[1] == :relu

simplify(ex::Expr) = postwalk(e -> _simplify(e), ex)
_simplify(s) = s
_simplify(e::Expr) = Meta.parse(string(expand(Basic(e))))

# thinking/scripting:
Base.occursin(x, y) = x == y
function Base.occursin(needle::Union{Symbol, Expr}, haystack::Expr)
    needle == haystack && return true
    for arg in haystack.args
        occursin(needle, arg) && return true
    end
    return false
end

# Type piracy again:
Base.Expr(B::Basic) = Meta.parse(string(B))

function get_symbols(ex::Union{Expr, Symbol})
    syms = Symbol[]
    ops = (:*, :+, :-, :relu)
    postwalk(e -> e isa Symbol && e ∉ ops ? push!(syms, e) : nothing, ex)
    unique(syms)
end

function count_min_max(expr)
    """ count number of min and max in expr"""
    c = [0, 0]
    if expr == :min
        c[1] = 1
    elseif expr == :max
        c[2] = 1
    elseif expr isa Expr
        for arg in expr.args
            c += count_min_max(arg)
        end
    end
    return c
end


# -----------------------------------------------------------
#
# An example of contructing the initial data for a traveling
# wave in D-dimensions using the tensor_construct method
# and the wave_evolve in PDEs.jl
#
# -----------------------------------------------------------

using GalerkinSparseGrids
using LinearAlgebra

# -----------------------------------------------------
# Generates the coefficients for a waveform
# A cos(\vec{k} \cdot \vec{x} + \phi), \vec{k} = 2 \pi \vec{m}
# using an interpolation of type (k,n)
# and periodic boundary conditions in D-dimensionss
# -----------------------------------------------------
function cos_coeffs(k::Int, n::Int, m::AbstractArray{T,1};
                    scheme="sparse", phase = 0.0, A = 1.0) where T
    D = length(m)
    wavenumber = 2*pi*m

    # Begin with writing cos(\sum_i k_i x_i) as a sum of products
    # of sines/cosines of individual k_j x_j
    sines   = [i==1 ? x->sin(wavenumber[i]*x[1]+phase) : x->sin(wavenumber[i]*x[1]) for i in 1:D]
    cosines = [i==1 ? x->cos(wavenumber[i]*x[1]+phase) : x->cos(wavenumber[i]*x[1]) for i in 1:D]

    sine_dicts   = [coeffs_DG(1, k, n, sines[i]) for i in 1:D]
    cosine_dicts = [coeffs_DG(1, k, n, cosines[i]) for i in 1:D]

    ansVect = zeros(get_size(Val(D), k, n, Val(Symbol(scheme))))

    for SCs in CartesianIndices(ntuple(q->2, D))
        num_sines = sum([SCs[i]-1 for i in 1:D])
        if num_sines % 2 == 1
            continue
        end
        sign = num_sines%4==0 ? 1 : -1

        coeff_array = [SCs[i]==1 ? cosine_dicts[i] : sine_dicts[i] for i in 1:D]
        productDict = tensor_construct(D, k, n, coeff_array; scheme=scheme)
        productVect = D2V(D, k, n, productDict; scheme=scheme)

        ansVect += sign * productVect
    end

    return A * ansVect
end

# -----------------------------------------------------
# The same as above, but using sin
# -----------------------------------------------------
function sin_coeffs(k::Int, n::Int, m::Array{Int,1};
                    scheme="sparse", phase=0.0, A=1.0)
    return cos_coeffs(k, n, m; scheme=scheme, phase=phase-pi/2, A=A)
end

# -----------------------------------------------------
# Returns the data for a traveling wave
# using the above methods
# ----------------------------------------------------
function traveling_wave(k::Int, n::Int, m::Array{Int,1};
                        scheme="sparse", phase=0.0, A=1.0)
    wavenumber = 2*pi*m
    frequency = sqrt(dot(wavenumber,wavenumber))

    # u(x) = A * cos(dot(wavenumber,x) + phase)
    # v(v) = A * frequency * sin(dot(k,x) + phase)
    u0_coeffs = cos_coeffs(k, n, m; scheme=scheme, phase=phase, A=A)
    v0_coeffs = sin_coeffs(k, n, m; scheme=scheme, phase=phase, A=A*frequency)
    return (u0_coeffs, v0_coeffs)
end

# -----------------------------------------------------
# Main routine:
# -----------------------------------------------------

# Modify m to change the wavenumber.
# We use integer entries here because of periodic boundary conditions
m = [1,2,-1]
truesoln = x -> cos(2*pi*(dot(m,x) - sqrt(dot(m,m))*0.54))
k_max = 5
n_max = 6
D = length(m)

t0 = 0;
t1 = 0.54;

println("Wave Evolution in ", D, "D.")
println("Going to k_max = ", k_max, ", n_max = ", n_max, ":")

for k_used in 1:k_max
    for n_used in 1:n_max
        f0coeffs, v0coeffs = traveling_wave(k_used, n_used, m)
        soln = wave_evolve(D, k_used, n_used, f0coeffs, v0coeffs, t0, t1)
        dict = V2D(D, k_used, n_used, soln[2][end])

        err = mcerr(x->reconstruct_DG(dict, [x...]), truesoln, D)
        println("(k = ", k_used, ", n = ", n_used, ") : err = ", err)
    end
end


"""
idwi(args...)  -->Plot

iwm is a Plot Recipe in which makes a spatial interpolation of data provided applying Inverse Weight Mapping.

General equation for spatial interpolation is:
``{ Z }^{ * }=\\sum _{ i=1 }^{ n }{ { \\lambda  }_{ i }{ z }_{ i } } ``

where ``\\lambda`` is the weight Parameter

Applying the Inverse‐Distance Weighted Interpolation

``{ Z }\\left( x \\right) =\\frac { \\sum _{ i=1 }^{ n }{ { w }_{ i }{ z }_{ i } }  }{ \\sum _{ i=1 }^{ n }{ { z }_{ i } }  } \\\\ \\\\ { w }_{ i }=\\frac { 1 }{ { d }_{ i }^{ n } } ``

<br> The next table show the list of variables allowed:

|PropertyName|Args|Default|Input|Description
|---|---|---|---|---|
|x|Mandatory|--|Array{Number,1}| X values|
|y|Mandatory|--|Array{Number,1}| Y values|
|z|Mandatory|--|Array{Number,1}| Z values|
|m|Optional|m=100|m=number| Number of grids in y axis|
|n|Optional|n=100|n=number| Number of grids in x axis|
|p|Optional|p=2|p=number| IDWI exponent. The greater more influenced by near points|
"""
@userplot idwi

@recipe function f(h::idwi; m=100, n=100, p=2, surf=false)

    ylabel --> "North [m]"
    xlabel --> "East [m]"
    seriescolor --> :Spectral

x, y, z = h.args

# Build the grid of prediction points

    ## X & Y range
  Xrange=range(minimum(x),stop=maximum(x),length=n)
  Yrange=range(minimum(y),stop=maximum(y),length=m)

    ## X & Y grids
  X=repeat(Xrange',length(Yrange),1)
  Y=repeat(Yrange,1,length(Xrange))

    # X & Y grids converted to vectors
    Xvec=vec(X)
    Yvec=vec(Y)

    #Number of Sample Points and Prediction Points
    Nsp=length(x)                           #Numbers of Sample Points
    Npp=length(Xrange)*length(Yrange)       #Number of Prediction Points

    #DISTANCE BETWEEN PREDICTION AND SAMPLE POINTS---------------------------
    #Matrix of distance   I x J    Sample Points x Prediction Points

    d=zeros(Nsp,Npp)
    for i=1:Nsp
        for j=1:Npp
            d[i,j]=sqrt((x[i]-Xvec[j]).^2+(y[i]-Yvec[j]).^2)
        end
    end

    #Estimate the matrix of weights

    W=map(dis->(1 ./(dis.^p)),d)

    #Calculate value in prediction point in a vector

    Zvec=zeros(Npp)
    for i=1:Npp
        Zvec[i]=W[:,i]'*z ./ sum(W[:,i])
    end

    Z=reshape(Zvec,m,n)

#Coutour series
if surf==true
    @series begin
        seriestype := :surface
        zflip := true
        Xrange, Yrange, Z
        end
else
@series begin
    seriestype := :contour
    clabels := true
    fill --> true

    Xrange, Yrange, Z
end
end
end


### A Pluto.jl notebook ###
# v0.16.0

using Markdown
using InteractiveUtils

# ╔═╡ 0bdd0fc6-9203-11eb-1ea3-9fe58aca7da0
using SymPy, LinearAlgebra

# ╔═╡ aa073e00-323c-11eb-0297-83e3db12248d
md"
# Gradijent, divergencija i rotacija

__Nabla__ ili __Hamiltonov operator__  je

$$\nabla = \frac{\partial}{\partial x} \vec{\imath}+\frac{\partial}{\partial y} \vec{\jmath} +\frac{\partial}{\partial z} \vec{k}.$$

Nabla ima svojstva derivacije i vektora.

__Gradijent__ skalarnog polja $f(x,y,z)$ je

$\mathop{\mathrm{grad}} f = \nabla \cdot f=\frac{\partial f}{\partial x} \vec{\imath}+\frac{\partial f}{\partial y} \vec{\jmath} +\frac{\partial f}{\partial z} \vec{k}.$

__Divergencija__ vektorskog polja

$\vec{w}(x,y,z)=w_x(x,y,z)\vec{\imath} + w_y(x,y,z) \vec{\jmath} + w_z(x,y,z)\vec{k}$

je

$\mathop{\mathrm{div}} \vec{w} = \nabla \cdot \vec{w}= \frac{\partial w_x}{\partial x} +\frac{\partial w_y}{\partial y} +\frac{\partial w_z}{\partial z}.$

__Rotacija__ vektorskog polja $\vec{w}(x,y,z)$ je

$\mathop{\mathrm{rot}} \vec{w} = \nabla \times \vec{w}= \begin{vmatrix} \vec{\imath} & \vec{\jmath} & \vec{k} \\ \frac{\partial}{\partial x} & \frac{\partial}{\partial y} & \frac{\partial}{\partial z} \\ w_x & w_y &w_z \end{vmatrix}.$

__Usmjerena derivacija__ skalarnog polja $f(x,y,z)$ u smjeru vektora $\vec a$ je

$$\frac{\partial f}{\partial \vec a}(x,y,z)=\vec a_0 \cdot \mathop{\mathrm{grad}} f(x,y,z).$$

__Usmjerena derivacija__ vektorskog polja $\vec w(x,y,z)$ u smjeru vektora $\vec a$ je

$$\frac{\partial \vec w}{\partial \vec a}(x,y,z)=(\vec a_0 ∇) \vec w(x,y,z).$$
"

# ╔═╡ c75d36c0-923c-11eb-0477-c3101e21f880
x,y,z=symbols(:x),symbols(:y),symbols(:z)

# ╔═╡ 1b261092-923a-11eb-01d6-7f705b30d46e
begin
	∂x(f::Sym) = diff(f,x)
	∂y(f::Sym) = diff(f,y)
	∂z(f::Sym) = diff(f,z)
	∇=[∂x,∂y,∂z]
end

# ╔═╡ 1ca28980-925d-11eb-2be1-815a5df595ff
begin
	import Base.*
	*(∂x::typeof(∂x),f::Sym)=∂x(f)
	*(∂y::typeof(∂y),f::Sym)=∂y(f)
	*(∂z::typeof(∂z),f::Sym)=∂z(f)
	*(∇::Function,f::Sym)=∇(f)
	grad(f)=∇*f
	rot(u)=∇×u
	import LinearAlgebra.⋅
	⋅(∇::Array{Function,1},w::Array{Sym,1})=∇[1]*w[1]+∇[2]*w[2]+∇[3]*w[3]
	div(u)=∇⋅u
	⋅(a::Vector,∇::Array{Function,1})=a[1]*∇[1]+a[2]*∇[2]+a[3]*∇[3]
end

# ╔═╡ c2a476e6-e948-485f-9b2e-b6f821504263
# Izvedeni operator a∇
begin
	a∂x(a::Vector,f::Sym) = a[1]*diff(f,x)
	a∂y(a::Vector,f::Sym) = a[2]*diff(f,y)
	a∂z(a::Vector,f::Sym) = a[3]*diff(f,z)
	a∇(a::Vector,f::Sym)=a∂x(a,f)+a∂y(a,f)+a∂z(a,f)
	a∇(a::Vector,w::Array{Sym,1})=[a∇(a,w[1]),a∇(a,w[2]),a∇(a,w[3])]
end

# ╔═╡ bd70ec2e-ad07-4526-aa70-5f25c86b444e
md"
## Primjeri
"

# ╔═╡ fd3305b2-923a-11eb-15c4-5b79db40bf46
begin
	# Skalarna polja
	f=x^2+3*y*z+5
	g=x*y*z
	# Vektorska polja
	w=[y*z,z*x,x*y]
	u=[x^2*y,x*y,z/x]
	# Vektor
	a=[1,1,-2]
	# Točka
	T=(1,-1/2,2)
end

# ╔═╡ 2b00c286-0807-4fa0-a9c2-6b1d56ae3716
f

# ╔═╡ 66b9df0a-06c4-409c-9ae4-79b3cc3833b5
∂x(f)

# ╔═╡ 9b816a31-675a-4dea-83e7-67430eeb741e
∂x*f

# ╔═╡ ec770eca-54d2-4394-b359-ad5e47218e92
a∇(a,f)

# ╔═╡ 3577e5e0-8fa8-4938-9119-b7c14f31e5be
a∇(a,u)

# ╔═╡ 2a47ed85-0691-4cb2-84b5-52facb4eda0b
# Gradijent
∇*f

# ╔═╡ 26292a50-9627-40a3-bacc-034de0882ac2
grad(f)

# ╔═╡ 8d4a98d1-414e-4714-9c94-6b6426388bc0
# Gradijent u zadanoj točki
subs.(∇*f,x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ c62f2abe-b330-4e5b-8a73-c2ac5fe32284
# Divergencija
∇⋅u

# ╔═╡ f141e418-574d-4a0f-903c-35ad8d3e6883
div(u)

# ╔═╡ 0e012c70-afc3-410f-a73c-978783948cc3
# Divergencija u zadanoj točki
subs(∇⋅u,x=>T[1],y=>T[2],z=>T[2])

# ╔═╡ faea848e-f4d7-4476-993f-d9db5c6ca2ee
# Rotor
∇×u

# ╔═╡ e3c9e5d1-8542-4940-8dd2-c60849b152c3
rot(u)

# ╔═╡ 0496ef19-9e5c-4f5b-aa4b-22d98cfcf9a0
# Rotor u zadanoj točki
subs.(∇×u,x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ ad3b7955-cd19-4aaf-8a98-b4727bafe572
# Primjer svojstva
rot(f*grad(g))

# ╔═╡ 93446240-2427-4964-9e3a-75f2de6b67e6
grad(f)×grad(g)

# ╔═╡ 44c1a521-657b-43c4-ac59-7a8e24bf25cc
# Drugi primjer svojstva
rot(w×u)

# ╔═╡ 54692583-1661-4b04-8f2c-94689630cff8
expand.(w*div(u)-u*div(w)+a∇(u,w)-a∇(w,u))

# ╔═╡ fadb40e2-6c5e-48b2-a45f-88ea4f8cc4e5
# Usmjerena derivacija skalarnog polja
a/norm(a)⋅grad(f)

# ╔═╡ 180512f7-c8e8-4aee-9872-0057d6fe9c5d
# Usmjerena derivacija skalarnog polja u točki
subs(a/norm(a)⋅grad(f),x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ 258fbdc7-7c24-4ecf-bc5a-468069f5c482
# Skalarno polje najbrže raste u smjeru gradijenta
subs(grad(f)/norm(grad(f))⋅grad(f),x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ eeb8e757-668e-48c1-b560-076b3f47b5bf
# Usmjerena derivacija vektorskog polja
a∇(a/norm(a),u)

# ╔═╡ 694962cc-cca2-4a4d-939d-7eb329de9917
# Usmjerena derivacija vektorskog polja u točki
subs.(a∇(a/norm(a),u),x=>T[1],y=>T[2],z=>T[3])

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"

[compat]
SymPy = "~1.0.52"
"""

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

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

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

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

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

[[CommonEq]]
git-tree-sha1 = "d1beba82ceee6dc0fce8cb6b80bf600bbde66381"
uuid = "3709ef60-1bee-4518-9f2f-acd86f176c50"
version = "0.2.0"

[[CommonSolve]]
git-tree-sha1 = "68a0743f578349ada8bc911a5cbd5a2ef6ed6d1f"
uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
version = "0.2.0"

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

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

[[Conda]]
deps = ["JSON", "VersionParsing"]
git-tree-sha1 = "299304989a5e6473d985212c28928899c74e9421"
uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d"
version = "1.5.2"

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

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

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

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

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

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

[[IrrationalConstants]]
git-tree-sha1 = "f76424439413893a832026ca355fe273e93bce94"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.0"

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

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

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

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

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

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

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

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

[[LogExpFunctions]]
deps = ["ChainRulesCore", "DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "34dc30f868e368f8a17b728a1238f3fcda43931a"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.3"

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

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

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

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

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

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

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

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

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

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

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

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

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

[[PyCall]]
deps = ["Conda", "Dates", "Libdl", "LinearAlgebra", "MacroTools", "Serialization", "VersionParsing"]
git-tree-sha1 = "169bb8ea6b1b143c5cf57df6d34d022a7b60c6db"
uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
version = "1.92.3"

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

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

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

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

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

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

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

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

[[SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "793793f1df98e3d7d554b65a107e9c9a6399a6ed"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.7.0"

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

[[SymPy]]
deps = ["CommonEq", "CommonSolve", "LinearAlgebra", "Markdown", "PyCall", "RecipesBase", "SpecialFunctions"]
git-tree-sha1 = "1ef257ecbcab8058595a68ca36a6844b41babcbd"
uuid = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"
version = "1.0.52"

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

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

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

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

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

[[VersionParsing]]
git-tree-sha1 = "80229be1f670524750d905f8fc8148e5a8c4537f"
uuid = "81def892-9a0e-5fdd-b105-ffc91e053289"
version = "1.2.0"

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

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

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

# ╔═╡ Cell order:
# ╟─aa073e00-323c-11eb-0297-83e3db12248d
# ╠═0bdd0fc6-9203-11eb-1ea3-9fe58aca7da0
# ╠═c75d36c0-923c-11eb-0477-c3101e21f880
# ╠═1b261092-923a-11eb-01d6-7f705b30d46e
# ╠═1ca28980-925d-11eb-2be1-815a5df595ff
# ╠═c2a476e6-e948-485f-9b2e-b6f821504263
# ╟─bd70ec2e-ad07-4526-aa70-5f25c86b444e
# ╠═fd3305b2-923a-11eb-15c4-5b79db40bf46
# ╠═2b00c286-0807-4fa0-a9c2-6b1d56ae3716
# ╠═66b9df0a-06c4-409c-9ae4-79b3cc3833b5
# ╠═9b816a31-675a-4dea-83e7-67430eeb741e
# ╠═ec770eca-54d2-4394-b359-ad5e47218e92
# ╠═3577e5e0-8fa8-4938-9119-b7c14f31e5be
# ╠═2a47ed85-0691-4cb2-84b5-52facb4eda0b
# ╠═26292a50-9627-40a3-bacc-034de0882ac2
# ╠═8d4a98d1-414e-4714-9c94-6b6426388bc0
# ╠═c62f2abe-b330-4e5b-8a73-c2ac5fe32284
# ╠═f141e418-574d-4a0f-903c-35ad8d3e6883
# ╠═0e012c70-afc3-410f-a73c-978783948cc3
# ╠═faea848e-f4d7-4476-993f-d9db5c6ca2ee
# ╠═e3c9e5d1-8542-4940-8dd2-c60849b152c3
# ╠═0496ef19-9e5c-4f5b-aa4b-22d98cfcf9a0
# ╠═ad3b7955-cd19-4aaf-8a98-b4727bafe572
# ╠═93446240-2427-4964-9e3a-75f2de6b67e6
# ╠═44c1a521-657b-43c4-ac59-7a8e24bf25cc
# ╠═54692583-1661-4b04-8f2c-94689630cff8
# ╠═fadb40e2-6c5e-48b2-a45f-88ea4f8cc4e5
# ╠═180512f7-c8e8-4aee-9872-0057d6fe9c5d
# ╠═258fbdc7-7c24-4ecf-bc5a-468069f5c482
# ╠═eeb8e757-668e-48c1-b560-076b3f47b5bf
# ╠═694962cc-cca2-4a4d-939d-7eb329de9917
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:AbstractArray, TBCL<:PeriodicBC, TBCR<:PeriodicBC}

    # build eigenvalues
    @. problem.λ = ((2*π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs
    @. problem.rhs = problem.f;

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:Function, TBCL<:PeriodicBC, TBCR<:PeriodicBC}

    # build eigenvalues
    @. problem.λ = ((2*π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs
    @. problem.rhs = problem.f(problem.x);

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:AbstractArray, TBCL<:DirichletBC, TBCR<:DirichletBC}

    # build eigenvalues
    @. problem.λ = ((π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs
    @. problem.rhs = problem.f;

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:Function, TBCL<:DirichletBC, TBCR<:DirichletBC}

    # build eigenvalues
    @. problem.λ = ((π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs with odd extension
    @. problem.rhs[problem.N÷2+1:end] = problem.f([problem.a; problem.x]);
    problem.rhs[1:problem.N÷2] .= -reverse(problem.f.([problem.x; problem.b]));

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:AbstractArray, TBCL<:NeumannBC, TBCR<:NeumannBC}

    # build eigenvalues
    @. problem.λ = ((π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs
    @. problem.rhs = problem.f;

    problem
end

function assemble_system!(problem::SpectralBVPProblem{TF, TBCL, TBCR}) where {TF<:Function, TBCL<:NeumannBC, TBCR<:NeumannBC}

    # build eigenvalues
    @. problem.λ = ((π/(problem.b-problem.a)) * [0:problem.N÷2; -(problem.N÷2)+1:-1])^2;

    # build rhs with even extension
    @. problem.rhs[problem.N÷2+1:end] = problem.f(problem.x[1:end-1]);
    problem.rhs[1:problem.N÷2] .= reverse(problem.f.(problem.x[2:end]));

    problem
end


""" package  -------------------------------------------------------

   Performs several verification calculations given a file of grid spacings
   and some observed quantity corresponding to each grid spacing.

   Computes:
   - order of convergence
   - Richardson extrapolation to zero grid spacing
   - grid convergence indices (GCI)

 --------------------------------------------------------------------------

Adapted from:

    NPARC Alliance CFD Verification and Validation Web Site
    Examining Spatial (Grid) Convergence
    verify.f90
    URL: http://www.grc.nasa.gov/WWW/wind/valid/tutorial/spatconv.html

    Nov '11: Updated to reflect Celik et al 2008.
    Sept '18: Translated to Julia

    Credit to Matthew Topper.
"""

""" Calculate the order of convergence values generated with three
grids of reducing resolution (ie grid_1 is finest). The values of the grids
are needed along with the ratios between them.

An iterative method with under-relaxation is used to calculate the order
of convergence as the refinement ratio is not necessarily constant.

This has been modified to the method of Celik (2008).
"""
function order_of_convergence(value_1, value_2, value_3, ratio_21, ratio_32, omega=0.5, tol=1.E-6)
    # Set a maximum residual and number of iterations
    max_res = 1.E6
    # calculate the epsilons.
    epsilon32 = float(value_3 - value_2)
    epsilon21 = float(value_2 - value_1)
    # Calculate the fraction
    epfrac = epsilon32 / epsilon21
    # Get the signed unit, s
    s = epfrac / abs(epfrac)
    # Initial guess at order of convergence, p
    p1 = (1. / log(ratio_21)) * abs(log(abs(epfrac))) # start_p
    # Initialise the residual and number of iterations
    residual = 1.0
    iterations = 0
    while abs(residual) > tol
        # Break if it's all gone bad
        if float(iterations) > max_res && residual > max_res
            println("Residual out of range or too many iterations")
        end
        # Get the last value
        p0 = p1
        # Calculate q
        q = log((ratio_21^p0 - s) / (ratio_32^p0 - s))
        # Calculate the p iteration
        pnew = (1. / log(ratio_21)) * abs(log(abs(epfrac)) + q)
        # Calculate the relaxation step.
        p1 = (1. - omega) * p0 + omega * pnew
        residual = p1 - p0
        iterations += 1
    end
    # if abs(p1) > 1.0
    #     return 1.0
    # else
    return abs(p1)
    # end
end

""" Estimate the zero grid spacing value using richardsons extrapolation and
two grids of reducing resolution (ie grid_1 is finest). The refinement ratio
is needed. The order of convergence, p, is also required.
"""
function richardson_extrapolate(value_1, value_2, ratio_21, p)
    f_exact = ( ratio_21^p * value_1 - value_2 ) / ( ratio_21^p - 1.0 )
    return f_exact
end

""" This routine returns the relative error and extrapolated
relative error. The values of the grids and needed along
with the extrapolated value.
"""
function error_estimates(value_1, value_2, f_exact)
    # Get the approximate relative error
    e21a =  abs( (value_1 - value_2) / value_1 )
    # Get the extrapolated relative error
    e21ext = abs( ( f_exact - value_1 ) / f_exact )
    return e21a, e21ext
end

""" Calculate the fine and coarse grid convergence index for two grids of
reducing resolution (ie grid_1 is finest). The refinement ration between the
grids is required along with the approximate relative error (e21_approx) and
the order of convergence, p.
"""
function gci(ratio_21, e21_approx, p)
    # Using a fixed safety factor as per Celik (2008)
    safety_factor = 1.25
    # Calculate the gci
    gci_fine = safety_factor * e21_approx / (ratio_21^p - 1.0)
    gci_coarse = ratio_21^p * gci_fine
    return gci_fine, gci_coarse
end

""" Calculate the ratio in succesive Eps as defined at the bottom of page
129 in Roache. If the ration is close to one then the asymptotic range has
been reached.
"""
function asymptotic_ratio(gci_fine_21, gci_fine_32, ratio_21, p)
    ratio = ratio_21^p * ( gci_fine_21 / gci_fine_32)
    return ratio
end

"""Return the fine GCI value"""
function GCI_calc(value_1, value_2, value_3, h1, h2, h3)
    ratio_21 = h2/h1
    ratio_32 = h3/h2
    ooc = order_of_convergence(value_1, value_2, value_3, ratio_21, ratio_32)
    f_exact_21 = richardson_extrapolate(value_1, value_2, ratio_21, ooc)
    f_exact_32 = richardson_extrapolate(value_2, value_3, ratio_32, ooc)
    e21a, e21ext = error_estimates(value_1, value_2, f_exact_21)
    e32a, e32ext = error_estimates(value_2, value_3, f_exact_32)
    gci_fine_21, gci_coarse_21 = gci(ratio_21, e21a, ooc)
    gci_fine_32, gci_coarse_32 = gci(ratio_32, e32a, ooc)
    ratio = asymptotic_ratio(gci_fine_21, gci_fine_32, ratio_21, ooc)
    return abs(gci_fine_21)
end

"""Return the fine GCI value"""
function extrap_value(value_1, value_2, value_3, h1, h2, h3)
    ratio_21 = h2/h1
    ratio_32 = h3/h2
    ooc = order_of_convergence(value_1, value_2, value_3, ratio_21, ratio_32)
    f_exact_21 = richardson_extrapolate(value_1, value_2, ratio_21, ooc)
    return abs(f_exact_21)
end

"""Return the order of convergence."""
function ooc_value(value_1, value_2, value_3, h1, h2, h3)
    ratio_21 = h2/h1
    ratio_32 = h3/h2
    ooc = order_of_convergence(value_1, value_2, value_3, ratio_21, ratio_32)
    return ooc
end


### A Pluto.jl notebook ###
# v0.12.10

using Markdown
using InteractiveUtils

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

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

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

# ╔═╡ fa68607e-22e5-11eb-0558-c9a4d9f77426
begin
	mu = 20
	d1, d2 = Normal(mu,mu), Exponential(mu)

	n = 100
	data1 = rand(d1,n)
	data2 = rand(d2,n)
end

# ╔═╡ bdabe54e-250a-11eb-1714-25afa772347e
begin
	qqnorm(data1, c=:blue, ms=3, msw=0, label="Normal Data")
	qqnorm!(data2, c=:red, ms=3, msw=0, label="Exponential Data",
			xlabel="Normal Theoretical Quantiles",
			ylabel="Data Quantiles", legend=true)
end

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

# ╔═╡ Cell order:
# ╟─ca5fbdc8-22e5-11eb-0a60-cb0a127a3ca5
# ╠═e077ab16-22e5-11eb-15ed-5f0654bf7328
# ╠═e75a9a06-22e5-11eb-2e36-8d4f62b830ed
# ╠═fa68607e-22e5-11eb-0558-c9a4d9f77426
# ╠═bdabe54e-250a-11eb-1714-25afa772347e
# ╟─475ff888-22e6-11eb-2354-09f8f40a8e12


# Reset the per-thread random seeds to make results reproducible
reseed!() = for i in 1:Threads.nthreads() Random.seed!(TRNG[i], i) end

"Per-thread rand()"
@inline function trand()
	@inbounds rng = TRNG[Threads.threadid()]
	rand(rng)
end

@inline function trand(::Type{T}) where T
	@inbounds rng = TRNG[Threads.threadid()]
	rand(rng, T)
end

@inline function random_vec3_in_sphere(::Type{T}) where T # equiv to random_in_unit_sphere()
	while true
		p = random_vec3(T(-1), T(1))
		if p⋅p <= 1
			return p
		end
	end
end

@inline random_between(min::T=0, max::T=1) where T = trand(T)*(max-min) + min # equiv to random_double()
@inline random_vec3(min::T, max::T) where T = @inbounds @SVector[random_between(min, max) for i ∈ 1:3]
@inline random_vec2(min::T, max::T) where T = @inbounds @SVector[random_between(min, max) for i ∈ 1:2]

"Random unit vector. Equivalent to C++'s `unit_vector(random_in_unit_sphere())`"
@inline random_vec3_on_sphere(::Type{T}) where T = normalize(random_vec3_in_sphere(T))

@inline function random_vec2_in_disk(::Type{T}) where T # equiv to random_in_unit_disk()
	while true
		p = random_vec2(T(-1), T(1))
		if p⋅p <= 1
			return p
		end
	end
end


# used when thinking about the `alpha` exponent for xQ
using PyPlot
using LaTeXStrings

x = collect(linspace(0.,1.,1000))
y(r) = x.^r
rs = [1/4,1/3,1/2,2/3,3/4,1.]

fig = plt[:figure](figsize=(5.0,4.0))
ax = fig[:subplots](1,1)
fs = 10

for r in rs
    lw = 1
    if r == 1/2
        lw = 3
    end
    ax[:plot](x,y(r),label="$(@sprintf("r=%.2f",r))",lw = lw)
end

ax[:set_title](L"$y=x^r$ for different $r$",fontsize=fs)
ax[:set_xlabel](L"x",fontsize=fs)
ax[:set_ylabel](L"y=x^r",fontsize=fs)
ax[:legend]()

savefig("figs/power_comparison.pdf",dpi=300,transparent=true)


# Verify discretization technique by simulating income generating process
# and markov chain simulation for T_ret periods and then comparing distributions

using Plots, LinearAlgebra, Statistics, Random, Roots, QuantEcon, Interpolations
using BenchmarkTools

# Simulate income generating process
# Parameters
## Demographics
N = 1000000
T_ret = 10
κ = zeros(T_ret)

# Directly from KV2010
σ_η = 0.01
σ_z0 = 0.15
σ_ε = 0.05

function net_labor_income(N, T_ret, σ_ε, σ_η, σ_z0, κ)
    Y = zeros(Float64, N, T_ret-1)
    z_t1 = sqrt(σ_z0) .* randn(N)

    for t in 1:(T_ret-1)
        z_t = z_t1 + sqrt(σ_η) .* randn(N)
        Y[:,t] = exp.( κ[t] .+ z_t + sqrt(σ_ε) .* randn(N) )
        z_t1 = copy(z_t)
    end
    return Y
end

Y_igp = net_labor_income(N, T_ret, σ_ε, σ_η, σ_z0, κ)
histogram(Y_igp[:,1], alpha = 0.3, bins = 100)
histogram!(Y_igp[:,end], alpha = 0.3, bins = 100)
means = mean(Y_igp,dims=1)'
vars = var(Y_igp,dims=1)'
plot(1:(T_ret-1), means, label="means")
plot!(1:(T_ret-1), vars, label="vars", xlabel="t")

# Tauchen discretization
tauc_ε = tauchen(19,0.,sqrt(σ_ε))
tauc_η = tauchen(39,0.99999,sqrt(σ_η))

tauc_ε.p


```
Central difference

```
function central_diff(
    y::AbstractArray{<:AbstractFloat,1},
    x::AbstractArray{<:AbstractFloat,1},
)

    dy = zeros(eltype(y), axes(y))

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    dy[i0] = (y[i0+1] - y[i0]) / (x[i0+1] - x[i0])
    dy[i1] = (y[i1] - y[i1-1]) / (x[i1] - x[i1-1])
    for i = i0+1:i1-1
        dy[i] = (y[i+1] - y[i-1]) / (x[i+1] - x[i-1])
    end

    return dy

end

function central_diff(y::AbstractArray{<:AbstractFloat,1}, dx::Real)
    x = ones(eltype(y), axes(y)) .* dx
    dy = central_diff(y, x)

    return dy
end

function central_diff!(
    dy::AbstractArray{<:AbstractFloat,1},
    y::AbstractArray{<:AbstractFloat,1},
    x::AbstractArray{<:AbstractFloat,1},
)

    @assert axes(dy) == axes(y) == axes(x)

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    dy[i0] = (y[i0+1] - y[i0]) / (x[i0+1] - x[i0])
    dy[i1] = (y[i1] - y[i1-1]) / (x[i1] - x[i1-1])
    for i = i0+1:i1-1
        dy[i] = (y[i+1] - y[i-1]) / (x[i+1] - x[i-1])
    end

end

function central_diff!(
    dy::AbstractArray{<:AbstractFloat,1},
    y::AbstractArray{<:AbstractFloat,1},
    dx::Real,
)
    x = ones(eltype(y), axes(y)) .* dx
    central_diff!(dy, y, x)
end

```
Upwind difference

```
function upwind_diff(
    y::AbstractArray{<:Real,1},
    x::AbstractArray{<:Real,1};
    stream = :right::Symbol,
)

    dy = zeros(eltype(y), axes(y))

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    if stream == :right
        dy[i0] = 0.0
        for i = i0+1:i1
            dy[i] = (y[i] - y[i-1]) / (x[i] - x[i-1])
        end
    elseif stream == :left
        dy[i1] = 0.0
        for i = i0:i1-1
            dy[i] = (y[i+1] - y[i]) / (x[i+1] - x[i])
        end
    else
        throw("streaming direction should be :left or :right")
    end

    return dy

end

function upwind_diff(y::AbstractArray{<:AbstractFloat,1}, dx::Real; stream = :right::Symbol)
    x = ones(eltype(y), axes(y)) .* dx
    dy = upwind_diff(y, x, stream = stream)

    return dy
end

function upwind_diff!(
    dy::AbstractArray{<:AbstractFloat,1},
    y::AbstractArray{<:AbstractFloat,1},
    x::AbstractArray{<:AbstractFloat,1};
    stream = :right::Symbol,
)

    @assert axes(dy) == axes(y) == axes(x)

    idx = eachindex(y) |> collect
    i0 = idx[1]
    i1 = idx[end]

    if stream == :right
        dy[i0] = 0.0
        for i = i0+1:i1
            dy[i] = (y[i] - y[i-1]) / (x[i] - x[i-1])
        end
    elseif stream == :left
        dy[i1] = 0.0
        for i = i0:i1-1
            dy[i] = (y[i+1] - y[i]) / (x[i+1] - x[i])
        end
    else
        throw("streaming direction should be :left or :right")
    end

    return dy

end

function upwind_diff!(
    dy::AbstractArray{<:AbstractFloat,1},
    y::AbstractArray{<:AbstractFloat,1},
    dx::Real;
    stream = :right::Symbol,
)
    x = ones(eltype(y), axes(y)) .* dx
    upwind_diff!(dy, y, x, stream = stream)
end


"""
    laplace_to_z(rho, n, N, dt, A, b)

Returns the complex matrix valued Laplace variable s that correspond to the
variable z = rho*exp(2*im*pi*n/N) for a given Butcher tableau (A,b,c) and a time step dt.
"""
function laplace_to_z(rho, n, N, dt, A, b)
	z = rho * exp(2*im*pi*n/N);
	s = inv(dt * (A + ones(b) * b' / (z-1)));
	return s;
end

"""
    inverse_z_transform(k, rho, N, X)

Returns the k-th term of the inverse z-transform. X is an array of the z-transform
evaluated in the points z=rho*exp(2*im*pi*n/N) for n in 0:(N-1).
"""
function inverse_z_transform(k, rho, N, X::AbstractArray{T,1}) where T
	return ((rho^k) / N) * sum(n -> X[n+1] * exp(2*im*pi*k*n/N), 0:(N-1));
end

"""
    real_inverse_z_transform(k, rho, N, X)

Returns the k-th term of the inverse z-transform.
It is assumed that X[n+1] = conj(X[N-n]) for each n in 1:(N-1)
so that Nmax = N/2+1 or (N+1)/2 (resp. if N%2==0 or N%2==1) terms are used in X
X is an array of the z-transform
evaluated in the points z=rho*exp(2*im*pi*n/N) for n in 0:(Nmax-1).
"""
function real_inverse_z_transform(k, rho, N, X::AbstractArray{T,1}) where T
	Nmax = (N+1)>>1;
	realTerms = (N%2==0) ? real(X[1]) + (-1)^k * real(X[Nmax+1]) : real(X[1]);
	return ((rho^k) / N) * (realTerms + 2*sum(n -> real(X[n+1] * exp(2*im*pi*k*n/N)), 1:(Nmax-1)));
end


@doc raw"""
PathIntegralTrigonometric is a path integral along a cos^2/sin^2 path

```math
\phi (\tau; q^-, q^+) = \cos^2 (\pi \tau / 2) q^- + \sin^2 (\pi \tau / 2) q^+ .
```
"""
struct PathIntegralTrigonometric <: PathIntegral end

evaluate_l(path::PathIntegralTrigonometric, τ::T) where {T} = cos(π/2*τ)^2
evaluate_r(path::PathIntegralTrigonometric, τ::T) where {T} = sin(π/2*τ)^2

derivative_l(path::PathIntegralTrigonometric, τ::T) where {T} = -π*sin(π/2*τ)*cos(π/2*τ)
derivative_r(path::PathIntegralTrigonometric, τ::T) where {T} = +π*sin(π/2*τ)*cos(π/2*τ)


export awa

"""
    awa(twa, v_s, v_t)

Calculate the apparent wind angle given true wind angle, boat speed and wind speed.

# Example

```jldoctest
using sail_route
c_awa = sail_route.awa(60, 3.086, 5.144)
isapprox(0.6669807044553968, c_awa, rtol=3)

# output

true
```
"""
function awa(twa::Float64, v_s::Float64, v_t::Float64)
    return atan(sind(twa)/(cosd(twa) + v_s/v_t))
end


# Distance metrics for kernel functions

δ(Φ, z) = diff(Φ) ./ diff(z)

"""
    sq_mag(a, b)

||a - b||^2 = norm(a - b)^2 but more efficient.
"""
function sq_mag(a, b)
    ll = 0.0
    @inbounds for k in 1:length(a)
        ll += (a[k] - b[k])^2
    end
    return ll
end

"""
    euclidean_distance(a, b, z)

Computes the Euclidean distance (l²-norm) between two vectors:

    d(x, x') = || x - x' ||
"""
@inline euclidean_distance(a, b, z) = sqrt(sq_mag(a, b))

@inline euclidean_distance(a, b) = sqrt(sq_mag(a, b))

"""
    derivative_distance(a, b, z)

Computes the H¹-norm with respect to z of two vectors:

    d(x, x') = || diff(x) / diff(z) - diff(x') / diff(z) ||
"""
@inline derivative_distance(a, b, z) = sqrt(sq_mag( δ(a, z), δ(b, z) ))

"""
    antiderivative_distance(a, b, z)

Computes the H⁻¹-norm with respect to z of two vectors:

    d(x, x′) = || diff(x) * diff(z) - diff(x') * diff(z) ||
"""
@inline antiderivative_distance(a,b,z) = sqrt(sq_mag(diff(a) .* diff(z), diff(b) .* diff(z)))


module Wynn
    using SymPy

    # data structure to store the epsilon table
    # ϵ_ij indexed by ints i & j, stored within dict.
    struct EpsilonTable{T}
        series::T
        terms::Vector{T}
        etable::Dict{Tuple{Int,Int},T}
    end

    function EpsilonTable(terms::Vector{T}; simplified::Bool = true) where T<:Union{Float64,Sym}
        # maximum occuring i & j index
        max_ind = length(terms)

        # setting base case j = -1
        etable = Dict((i, -1) => eltype(terms)(0) for i in 0:max_ind)

        # setting base case j = 0, i = 0:max_ind-1
        merge!(etable, Dict((i-1, 0) => sum(terms[1:i]) for i in 1:max_ind))

        # setting base case i = -j-1, j even
        merge!(etable, Dict((-j-1, 2j) => eltype(terms)(0) for j in 0:max_ind))

        # setting base case for odd j
        merge!(etable, Dict((-j-1, 2j-1) => eltype(terms)(0) for j in 0:max_ind))

        # recursive calculations, j>=1, i = floor(Int,-j/2):(max_ind-j-1)
        for j in 1:2*(max_ind-1), i in floor(Int,-j/2):(max_ind-j-1)#j-1
            ϵ_ij = etable[i+1, j-2] + 1 / (etable[i+1, j-1] - etable[i, j-1])
            eltype(terms) <: Sym && simplified ? push!(etable, (i, j) =>
                simplify(ϵ_ij)) : push!(etable, (i, j) => ϵ_ij)
        end
        series = sum(terms)
        EpsilonTable(series, terms, etable)
    end

    EpsilonTable(terms::Vector{Int}; simplified::Bool = true) = EpsilonTable(convert(Vector{Float64}, terms); simplified = simplified)

    # recursive function to generate just one term of the epsilon table, much more efficient.
    function epsilon(terms::Vector{T}, order::Tuple{Int,Int}; simplified::Bool = true) where T<:Union{Real,Sym}
        max_ind = length(terms)
        if order[2] == -1
            return 0
        elseif (0 <= order[1] < max_ind) && (order[2] == 0)
            return sum(terms[1:order[1]+1])
        elseif iseven(order[2]) && (order[1] == -order[2]/2-1)
            return 0
        elseif isodd(order[2]) && (order[1] == -(order[2]+1)/2-1)
            return 0
        end
        max_ind = length(terms)
        ϵ_order = epsilon(terms, (order[1]+1, order[2]-2)) + 1 / (epsilon(terms, (order[1]+1, order[2]-1)) - epsilon(terms, (order[1], order[2]-1)))
        if (eltype(terms) <: Sym) && simplified
            return simplify(ϵ_order)
        else
            return ϵ_order
        end
    end

    export EpsilonTable, epsilon
end


"""
ap_test_fm(re, factors)

CROSS-SECTIONAL TESTS USING THE FAMA-MACBETH REGRESSION

Fama-MacBeth standard errors do not include corrections for the fact that the betas are also estimated.

INPUTS
`re': T x N matrix of excess returns, where T is the number of periods for each test assets and N is the number of test assets
`factors': T x K matrix of factors. Factors must be excess returns for time-series tests

OUTPUTS
`lambda': T x K vector of risk premiums for the factors
`alpha': T x N vector of alphas for each test assets

`test_statistics': Wald-statistics of the asset pricing test using Fama-MacBeth procedure
`pvalue': pvalue of the test statistics. The test statistics has a Chi-square distribution.

"""

function ap_test_fm(re, factors)
    (T, K) = size(factors);
    N = size(re, 2);
    cov_f = cov(factors); # coviance matrix for the factors K x K

    X = [ones(T,1) factors];
    (a, beta, resid, cov_resid) = OLSFn(re, X) # First-stage time-series regressions to obatin betas N x K

    lambda = fill(NaN, (T, K))
    alpha = fill(NaN, (T, N))

    for t = 1:T # run cross-sectional regressions for each period
        lambda[t, :] = ((beta' * beta) \  (beta' * vec(re[t, :])))'; # 1 x K
        alpha[t, :] = re[t, :]' - lambda[t, :]' * beta';
    end

    alpha_mean = mean(alpha, dims = 1)'; # time-series average of alphas N x 1
    alpha_mean_cov = zeros(N, N);

    for t = 1:T
        alpha_mean_cov = alpha_mean_cov + (alpha[t, :] - alpha_mean) * (alpha[t, :] - alpha_mean)';
    end
    alpha_mean_cov = alpha_mean_cov ./ T^2;

    test_statistics = alpha_mean' * (alpha_mean_cov \ alpha_mean);
    pvalue = 1.0 .- cdf.(Chisq(N - K), test_statistics);

    return (alpha_mean, test_statistics, pvalue)
end


# This script solves a parabolic partial differential equation

using LinearAlgebra

# Define the paths
CURRENT_DIR = @__DIR__
ROOT_DIR = basename(CURRENT_DIR) == "scripts" ? dirname(CURRENT_DIR) : CURRENT_DIR
TABLES = joinpath(ROOT_DIR, "tables")

# Prepare the output directories
mkpath(TABLES)

# Define the maximum time
T = 0.1

# Define the differential equation's functions
p(x) = x + 3
b(_, _) = 0
c(x, _) = -x
α₁(t) = 0
α₂(t) = -1
β₁(t) = 1
β₂(t) = 0

# Define the first problem's functions
u(x, t) = x + 3t
f(x, t) = x^2 + 3 * x * t + 2
φ(x) = x
α(t) = 1
β(t) = 1 + 3t

"Check if a solution will be stable on the specified grid"
function is_stable(N, M)::Bool
    A = maximum(p, 0:0.0001:1)
    h = 1 / N
    τ = T / M
    ν = τ / h^2
    return A * ν ≤ 0.5
end

"Using provided N, find M, which gives a stable solution"
function find_stable(N)::Int
    M = 5
    while !is_stable(N, M)
        M *= 2
    end
    return M
end

"Calculate the value of the differential operator L"
function L(um, x, t, h, i, k)::Float64
    return p(x[i] + h / 2) * (um[k, i+1] - um[k, i]) / h^2 -
           p(x[i] - h / 2) * (um[k, i] - um[k, i-1]) / h^2 +
           b(x[i], t[k]) * (um[k, i+1] - um[k, i-1]) / (2h) +
           c(x[i], t[k]) * um[k, i]
end

"Solve the problem for the specified grid, return the solution matrix"
function solve(N, M, h, τ, x, t; σ = 0)::Matrix{Float64}
    # Prepare a matrix for the solution
    # (number of columns is the number of nodes for x's)
    um = Matrix{Float64}(undef, M + 1, N + 1)
    # Compute the first layer
    um[1, :] .= φ.(x)
    if σ == 0
        # Use the explicit scheme for other layers
        for k = 2:M+1
            # Compute for i in 2:N
            for i = 2:N
                um[k, i] = um[k-1, i] +
                           τ * (L(um, x, t, h, i, k - 1) +
                                f(x[i], t[k-1]))
            end
            # Compute the rest
            um[k, 1] = (α(t[k]) + α₂(t[k]) * (4 * um[k, 2] - um[k, 3]) / (2h)) /
                       (α₁(t[k]) + 3 * α₂(t[k]) / (2h))
            um[k, N+1] = (β(t[k]) + β₂(t[k]) * (4 * um[k, N] - um[k, N-1]) / (2h)) /
                         (β₁(t[k]) + 3 * β₂(t[k]) / (2h))
        end
    else
        # Use the implicit scheme for other layers
        for k = 2:M+1
            # Compute the linear system's matrix
            tm = Tridiagonal(
                # A's
                [
                    [σ * (p(x[i] - h / 2) / h^2 - b(x[i], t[k]) / (2h)) for i = 2:N]
                    -β₂(t[k]) / h
                ],
                # B's
                [
                    α₁(t[k]) + α₂(t[k]) / h
                    -[1 / τ + σ * ((p(x[i] + h / 2) + p(x[i] - h / 2)) / h^2 - c(x[i], t[i])) for i = 2:N]
                    β₁(t[k]) + β₂(t[k]) / h
                ],
                # C's
                [
                    -α₂(t[k]) / h
                    [σ * (p(x[i] + h / 2) / h^2 + b(x[i], t[k]) / (2h)) for i = 2:N]
                ],
            )
            # Compute the linear system's right-hand side vector
            g = [
                α(t[k])
                [-um[k-1, i] / τ - (1 - σ) * L(um, x, t, h, i, k - 1) - f(x[i], t[k] - (1 - σ) * τ) for i = 2:N]
                β(t[k])
            ]
            # Compute the solution of the linear system
            um[k, :] = tm \ g
        end
    end
    return um
end

"""
Compare the approximate solution to the exact one,
find the biggest absolute difference between the nodes
"""
function max_difference(um, x, t)::Float64
    M1, N1 = size(um)
    # Find the maximum
    Δu = 0
    for i = 1:N1, k = 1:M1
        _Δu = abs(u(x[i], t[k]) - um[k, i])
        if _Δu > Δu
            Δu = _Δu
        end
    end
    return Δu
end

"Create the TeX tables and write them to disk"
function tables(dir, digits, um, N, M)
    # Prepare the output directories
    OUTPUT_DIR = joinpath(TABLES, dir)
    mkpath(OUTPUT_DIR)
    # Create and write the table with the solution grid
    open(joinpath(OUTPUT_DIR, "$N, $M.tex"), "w") do io
        for k in Int.([1:M/5:M+1]...)
            s = ""
            for i in Int.([1:N/5:N+1]...)
                s = "$(s)& \$ $(sprint(show, round(um[k, i]; digits))) \$ "
            end
            s = "$(s)\\\\"
            println(io, s)
        end
    end
end

"""
Solve the specified problem for different grids,
create the TeX tables and write them to disk
"""
function solve_all(dir, digits)
    # Print the output directory
    println('\n', " "^5, "> Output for u(x, t) = ", dir)
    # Define grids
    N = [5, 10, 20]
    Mₑ = find_stable.(N)
    Mᵢ = repeat([Int(1 / (0.1 / 100))], length(N))
    # For each grid
    for l in eachindex(N)
        # Compute the steps
        h = 1 / N[l]
        τₑ = T / Mₑ[l]
        τᵢ = T / Mᵢ[l]
        # Compute the nodes
        x = [j * h for j = 0:N[l]]
        tₑ = [j * τₑ for j = 0:Mₑ[l]]
        tᵢ = [j * τᵢ for j = 0:Mᵢ[l]]
        # Solve the problem with the explicit scheme
        um = solve(N[l], Mₑ[l], h, τₑ, x, tₑ)
        # Print info about the solution
        println(
            '\n',
            " "^5, "σ: 0", '\n',
            " "^5, "N: ", N[l], '\n',
            " "^5, "M: ", Mₑ[l], '\n',
            " "^5, "Δu: ", max_difference(um, x, tₑ)
        )
        # Create and write the tables
        tables(joinpath(dir, "σ = 0"), digits, um, N[l], Mₑ[l])
        # Solve the problem with the implicit scheme (σ = 0.5)
        um = solve(N[l], Mᵢ[l], h, τᵢ, x, tᵢ; σ = 0.5)
        # Print info about the solution
        println(
            '\n',
            " "^5, "σ: 0.5", '\n',
            " "^5, "N: ", N[l], '\n',
            " "^5, "M: ", Mᵢ[l], '\n',
            " "^5, "Δu: ", max_difference(um, x, tᵢ)
        )
        # Create and write the tables
        tables(joinpath(dir, "σ = 0.5"), digits, um, N[l], Mᵢ[l])
        # Solve the problem with the implicit scheme (σ = 1)
        um = solve(N[l], Mᵢ[l], h, τᵢ, x, tᵢ; σ = 1.0)
        # Print info about the solution
        println(
            '\n',
            " "^5, "σ: 1.0", '\n',
            " "^5, "N: ", N[l], '\n',
            " "^5, "M: ", Mᵢ[l], '\n',
            " "^5, "Δu: ", max_difference(um, x, tᵢ)
        )
        # Create and write the tables
        tables(joinpath(dir, "σ = 1.0"), digits, um, N[l], Mᵢ[l])
    end
end

# Solve the first problem
solve_all("x + 3t", 2)

# Define the second problem's functions
u(x, t) = x^3 + t^3
f(x, t) = x^4 + x * t^3 - 9 * x^2 + 3 * t^2 - 18 * x
φ(x) = x^3
α(t) = 0
β(t) = 1 + t^3

# Solve the second problem
solve_all("x^3 + t^3", 6)

println()


# Julia GARCH package
# Copyright 2013 Andrey Kolev
# Distributed under MIT license (see LICENSE.md)

type GarchFit
  data::Vector
  params::Vector
  llh::Float64
  status::Symbol
  converged::Bool
  sigma::Vector
  hessian::Array{Float64,2}
  cvar::Array{Float64,2}
  secoef::Vector
  tval::Vector
end

function Base.show(io::IO ,fit::GarchFit)
  pnorm(x) = 0.5*(1+erf(x/sqrt(2)))
  prt(x) = 2*(1-pnorm(abs(x)))
  @printf io "Fitted garch model \n"
  @printf io " * Coefficient(s): \tomega \t\talpha \t\tbeta\n"
  @printf io "   \t\t\t%f\t%f\t%f\n" fit.params[1] fit.params[2] fit.params[3]
  @printf io " * Log Likelihood: %f\n" fit.llh
  @printf io " * Converged: %s\n" fit.converged
  @printf io " * Solver status: %s\n\n" fit.status
  println(io," * Standardised Residuals Tests:")
  println(io,"   \t\t\t\tStatistic\tp-Value")
  jbstat,jbp = jbtest(fit.data./fit.sigma);
  @printf io "   Jarque-Bera Test\t\U1D6D8\u00B2\t%.6f\t%.6f\n\n" jbstat jbp
  println(io," * Error Analysis:")
  println(io,"   \t\tEstimate\t\Std.Error\tt value \tPr(>|t|)")
  @printf io "   omega\t%f\t%f\t%f\t%f\n" fit.params[1] fit.secoef[1] fit.tval[1] prt(fit.tval[1])
  @printf io "   alpha\t%f\t%f\t%f\t%f\n" fit.params[2] fit.secoef[2] fit.tval[2] prt(fit.tval[2])
  @printf io "   beta \t%f\t%f\t%f\t%f\n"  fit.params[3] fit.secoef[3] fit.tval[3] prt(fit.tval[3])
end

function cdHessian(par,LLH)
  eps = 1e-4 * par
  n = length(par)
  H = zeros(n,n)
  for(i = 1:n)
    for(j = 1:n)
      x1 = copy(par)
      x1[i] += eps[i]
      x1[j] += eps[j]
      x2 = copy(par)
      x2[i] += eps[i]
      x2[j] -= eps[j]
      x3 = copy(par)
      x3[i] -= eps[i]
      x3[j] += eps[j]
      x4 = copy(par)
      x4[i] -= eps[i]
      x4[j] -= eps[j]
      H[i,j] = (LLH(x1)-LLH(x2)-LLH(x3)+LLH(x4)) / (4.*eps[i]*eps[j])
    end
  end
  H
end

function garchLLH(rets::Vector,x::Vector)
  rets2   = rets.^2;
  T = length(rets);
  ht = zeros(T);
  omega,alpha,beta = x;
  ht[1] = sum(rets2)/T;
  for i=2:T
    ht[i] = omega + alpha*rets2[i-1] + beta * ht[i-1];
  end
  -0.5*(T-1)*log(2*pi)-0.5*sum( log(ht) + (rets./sqrt(ht)).^2 );
end

function predict(fit::GarchFit)
 omega, alpha, beta = fit.params;
 rets = fit.data
 rets2   = rets.^2;
 T = length(rets);
 ht    = zeros(T);
 ht[1] = sum(rets2)/T;
 for i=2:T
    ht[i] = omega + alpha*rets2[i-1] + beta * ht[i-1];
 end
 sqrt(omega + alpha*rets2[end] + beta*ht[end]);
end

function garchFit(data::Vector)
  rets = data
  rets2   = rets.^2;
  T = length(rets);
  ht = zeros(T);
  function garchLike(x::Vector, grad::Vector)
    omega,alpha,beta = x;
    ht[1] = sum(rets2)/T;
    for i=2:T
      ht[i] = omega + alpha*rets2[i-1] + beta * ht[i-1];
    end
    sum( log(ht) + (rets./sqrt(ht)).^2 );
  end
  opt = Opt(:LN_SBPLX,3)
  lower_bounds!(opt,[1e-10, 0.0, 0.0])
  upper_bounds!(opt,[1; 0.3; 0.99])
  min_objective!(opt, garchLike)
  (minf,minx,ret) = NLopt.optimize(opt, [1e-5, 0.09, 0.89])
  converged = minx[1]>0 && all(minx[2:3].>=0) && sum(minx[2:3])<1.0
  H = cdHessian(minx,x->garchLLH(rets,x))
  cvar = -inv(H)
  secoef = sqrt(diag(cvar))
  tval = minx./secoef
  out = GarchFit(data, minx, -0.5*(T-1)*log(2*pi)-0.5*minf, ret, converged, sqrt(ht),H,cvar,secoef,tval)
end

# function garchPkgTest()
#   println("Running GARCH package test...")
#   try
#     include(Pkg.dir("GARCH", "test","GARCHtest.jl"))
#     println("All tests passed!")
#   catch err
#     throw(err)
#   end
# end


using SimplePolynomials, BigCombinatorics

function sine_coeffs(n::Int)
    T = Rational{BigInt}
    result = zeros(T,2n)
    for j=1:n
        result[2j] = (-1)^(j-1)//Factorial(2j-1)
    end

    return result
end

"""
`sine_poly(n)` create exact sine power series of degree `2n`
(actually `2n-1` because `sin` is odd).
"""
function sine_poly(n::Int)
    return SimplePolynomial(sine_coeffs(n))
end


using AccretionFormulae
using Plots

function r_hor(a, M)
    (M +  √(M^2 - (a*M)^2))/M
end

M = 10*1.99e30
spin_vals_pos = LinRange(0, 1, 1000)
spin_vals_neg = LinRange(-1, 0 , 1000)
ISCO_pos_vals = AccretionFormulae.r_isco.(spin_vals_pos, M) ./ M
ISCO_neg_vals = reverse(AccretionFormulae.r_isco.(spin_vals_neg, M)) ./ M
EH_pos_vals = r_hor.(spin_vals_pos, M)

plt = plot(
            spin_vals_pos,
            ISCO_pos_vals,
            legend=:topleft,
            label="\$r_{ISCO} \\; \\textrm{Prograde}\$",
            xlabel="a (M)",
            ylabel="R (M)",
            grid=false,
            framestyle=:box
            )
plot!(spin_vals_pos, ISCO_neg_vals, label="\$r_{ISCO} \\; \\textrm{Retrograde}\$")
plot!(spin_vals_pos, EH_pos_vals, label="\$r_{s}\$")
png(plt, "ISCO_EH_spin_comp.png")