module CubicEquation

export Solver

type Solver
end

function (solver::Solver)(a::Real, b::Real, c::Real, d::Real)
    if a == zero(typeof(a))
        return Base.error("Can't solve. a=0 in a*x³ + b*x² + c*x + d =0")
    end
    f = (3c/a - (b/a)^2) / 3
    g = (2(b/a)^3 - (9*b*c/a^2) + 27d/a) / 27
    h = g^2 / 4 + f^3 / 27
    if f == zero(typeof(f)) && g == zero(typeof(g)) && h == zero(typeof(h))
        x1 = x2 = x3 = -cbrt(d/a)
        return [x1, x2, x3]
    elseif  h <= zero(typeof(h))
        return three_real_root(a, b, g, h)
    elseif h > zero(typeof(h))
        return one_real_root(a, b, g, h)
    end

    return Base.error("Can't solve when a=$(a) b=$(b) c=$(c) d=$(d)")
end

function three_real_root(a::Real, b::Real, g::Float64, h::Float64)
    i = (g^2 / 4 - h)^(1/2)
    j = cbrt(i)
    K = acos(-(g/2i))
    L = -j
    M = cos(K/3)
    N = sqrt(3) * sin(K/3)
    P = -(b/3a)
    x1 = 2j * cos(K/3) - (b/3a)
    x2 = L * (M + N) + P
    x3 = L * (M - N) + P
    return sort([x1, x2, x3])
end

function one_real_root(a::Real, b::Real, g::Float64, h::Float64)
    R = -g/2 + sqrt(h)
    S = cbrt(R)
    T = -g/2 - sqrt(h)
    U = cbrt(T)
    x1 = (S + U) - (b/3a)
    x2 = -(S + U)/2 - (b/3a) - im*(S-U)*sqrt(3) / 2
    x3 = -(S + U)/2 - (b/3a) + im*(S-U)*sqrt(3) / 2
    return Union{Real,Complex}[x1, x2, x3]
end

function (solver::Solver)(a::Real, b::Real, c::Real)
    if a == zero(typeof(a))
        return Base.error("Can't solve. a=0 in a*x² + b*x + c = 0")
    end

    # Discriminant for quadratic equation
    D = b^2 - 4*a*c

    if D >= zero(typeof(D))
        return [(-b - √(D)) / 2a, (-b + √(D)) / 2a]
    elseif D < zero(typeof(D))
        return [(-b - √(-D)*im) / 2a, (-b + √(-D)*im) / 2a]
    else
        return Base.error("Can't solve when a=$(a) b=$(b) c=$(c)")
    end
end

end #end module


# If an integer is passed down to the function, it means that the user is requesting an exact number of iterations.
function bisection_it(f,ansatz::Tuple{Float64,Float64}; N::Int64=100)
    (lower,upper) = ansatz
    mid = 0
    for _ in 1:N

    mid = (lower + upper)/2

        if f(lower)*f(mid) < 0
            upper = mid
        elseif f(lower)*f(mid) > 0
            lower = mid
        else
            return mid
        end

    end
    return mid
end

# f(x) = sin(x)
# bisection(f,(2.,4.))

#If then a Float is passed to the function, then it means it must mean an objective relative error.
function bisection_err(f,ansatz::Tuple{Float64,Float64}; error::Float64=0.01)
    mid = (lower + upper)/2;
    ea = 1; #Initialize
    #We do the first iteration, otherwise, there is no way to have a relative error to measure
    if f(lower)*f(mid) < 0
        upper = mid
    elseif f(lower)*f(mid) > 0
        lower = mid
    else
        return mid
    end
    #Now we start doing the iterations until it is done
    while ea > error
        last = mid #We store the last approximation
        mid = (lower + upper)/2
        ea = abs((mid-last)/mid);

        if f(lower)*f(mid) < 0
            upper = mid
        elseif f(lower)*f(mid) > 0
            lower = mid
        else
            return mid
        end

    end
    return mid
end


# ---
# title: 688. Knight Probability in Chessboard
# id: problem688
# author: Indigo
# date: 2021-06-29
# difficulty: Medium
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/knight-probability-in-chessboard/description/>
# hidden: true
# ---
#
# On an `N`x`N` chessboard, a knight starts at the `r`-th row and `c`-th column
# and attempts to make exactly `K` moves. The rows and columns are 0 indexed, so
# the top-left square is `(0, 0)`, and the bottom-right square is `(N-1, N-1)`.
#
# A chess knight has 8 possible moves it can make, as illustrated below. Each
# move is two squares in a cardinal direction, then one square in an orthogonal
# direction.
#
#
#
# ![](https://assets.leetcode.com/uploads/2018/10/12/knight.png)
#
#
#
# Each time the knight is to move, it chooses one of eight possible moves
# uniformly at random (even if the piece would go off the chessboard) and moves
# there.
#
# The knight continues moving until it has made exactly `K` moves or has moved
# off the chessboard. Return the probability that the knight remains on the
# board after it has stopped moving.
#
#
#
# **Example:**
#
#
#
#     Input: 3, 2, 0, 0
#     Output: 0.0625
#     Explanation: There are two moves (to (1,2), (2,1)) that will keep the knight on the board.
#     From each of those positions, there are also two moves that will keep the knight on the board.
#     The total probability the knight stays on the board is 0.0625.
#
#
#
#
# **Note:**
#
#   * `N` will be between 1 and 25.
#   * `K` will be between 0 and 100.
#   * The knight always initially starts on the board.
#
#
## @lc code=start
using LeetCode

function knight_probability(n::Int, k::Int, row::Int, col::Int)
    dp = fill(0, n, n, 2)
    dp[row + 1, col + 1, 1] = 1
    for i in 1:k
        dp1 = @view(dp[:, :, mod1(i, 2)])
        dp2 = @view(dp[:, :, mod1(i + 1, 2)])
        idcs = CartesianIndices(dp2)
        for I in idcs, hop in ((1, 2), (2, 1), (-1, 2), (2, -1), (1, -2), (-2, 1), (-1, -2), (-2, -1))
            new_I = I + CartesianIndex(hop)
            new_I ∈ idcs && (dp2[new_I] += dp1[I])
        end
        fill!(dp1, 0)
    end
    return sum(dp) / 8 ^ k
end
## @lc code=end


import Voronoi

using Random
using Printf
include("Draw.jl")

function randf(start, finish, n)
   v = rand(n)
   return map(x -> start + x*(finish-start), v)
end
Random.seed!(100)

WIDTH = 100.0
HEIGHT = 100.0

function benchmarkLloyd(Z::Array{Tuple{Real, Real}, 1}, ϵ::Number)
   global WIDTH, HEIGHT

   points = deepcopy(Z)

   results = Array{Tuple{Real, Real, Real, Real}}([])

   times = @elapsed V, f, ∇f, ξ = Voronoi.Optimization.init(points, WIDTH, HEIGHT)
   evaluations = 1
   push!(results, (f, ξ, evaluations, times))

   while ξ > ϵ
      time = @elapsed V, f, ξ = Voronoi.Optimization.lloydIteration(points, V)

      evaluations += 1
      times += time

      push!(results, (f, ξ, evaluations, times))
   end

   return results
end

function benchmarkGradient(Z::Array{Tuple{Real, Real}, 1}, ϵ::Number)
   global WIDTH, HEIGHT

   points = deepcopy(Z)

   results = Array{Tuple{Real, Real, Real, Real}}([])

   times = @elapsed V, f, ∇f, ξ = Voronoi.Optimization.init(points, WIDTH, HEIGHT)
   evaluations = 1
   push!(results, (f, ξ, evaluations, times))
   μ = 0.5
   α = 0.01
   λ₀ = 0.01

   while ξ > ϵ
      d = ∇f

      time = @elapsed V, f, ∇f, ξ, evals = Voronoi.Optimization.lineSearch(points, d, α, λ₀, μ, ϵ, V, f, ∇f, ξ)

      evaluations += evals
      times += time

      push!(results, (f, ξ, evaluations, times))
   end

   return results
end

function benchmark(n, ϵ)
   global WIDTH, HEIGHT

   points = convert(Array{Tuple{Real, Real}}, collect(zip(randf(1, WIDTH-1, n), randf(1, HEIGHT-1, n))))

   lloyd_results = benchmarkLloyd(points, ϵ)
   gradient_results = benchmarkGradient(points, ϵ)

   lloyd_f = Array{Number}([])
   lloyd_ξ = Array{Number}([])
   lloyd_evals = Array{Number}([])
   lloyd_time = Array{Number}([])
   gradient_f = Array{Number}([])
   gradient_ξ = Array{Number}([])
   gradient_evals = Array{Number}([])
   gradient_time = Array{Number}([])

   i = 1
   t₀ = lloyd_results[1][4]
   for (fᵢ, ξᵢ, evalsᵢ, timeᵢ) in lloyd_results
      push!(lloyd_f, fᵢ)
      push!(lloyd_ξ, ξᵢ)
      push!(lloyd_evals, evalsᵢ)
      push!(lloyd_time, timeᵢ - t₀)
      i += 1
   end

   i = 1
   t₀ = gradient_results[1][4]
   for (fᵢ, ξᵢ, evalsᵢ, timeᵢ) in gradient_results
      push!(gradient_f, fᵢ)
      push!(gradient_ξ, ξᵢ)
      push!(gradient_evals, evalsᵢ)
      push!(gradient_time, timeᵢ - t₀)
      i += 1
   end

   #@printf("f = %.10g\t\t|∇f| = %.10g\t\ttime: %s seconds\n", f, ξ, time)
   #@printf("f = %.10g\t\t|∇f| = %.10g\t\ttime: %s seconds\n", f, ξ, time)

   Draw.init(0, 0)
   Draw.clear(string("n = ", n))
   Draw.plot(lloyd_time, lloyd_f, "xkcd:azure", "Algoritmo de Lloyd", "-")
   Draw.plot(gradient_time, gradient_f, "xkcd:tomato", "Método do Gradiente", ":")
   Draw.legend("Tempo (s)", "Valor da Função Objetivo")
   Draw.commit()
   println("Press Return to finish.")
   command = readline(stdin)

   Draw.init(0, 0)
   Draw.clear(string("n = ", n))
   Draw.plot(lloyd_time, lloyd_ξ, "xkcd:azure", "Algoritmo de Lloyd", "-")
   Draw.plot(gradient_time, gradient_ξ, "xkcd:tomato", "Método do Gradiente", ":")
   Draw.legend("Tempo (s)", "Norma do Gradiente")
   Draw.commit()
   println("Press Return to finish.")
   command = readline(stdin)
end

ϵ = 10^-2
benchmark(3, ϵ)
benchmark(30, ϵ)
benchmark(100, ϵ)
benchmark(300, ϵ)
benchmark(1000, ϵ)


# Numbers and symbols can't be simplified further
simplify(n::Number) = n
simplify(s::Symbol) = s

# Handles all lengths for ex.args
# Removes any 0's in a sum
function simplify_sum(ex::Expr)
    new_args = map(x -> simplify(x), filter(x -> x != 0, ex.args[2:end]))
    if length(new_args) == 0
        return 0
    # Special Case: simplify(:(+x)) == x
    elseif length(new_args) == 1
        return new_args[1]
    else
        unshift(new_args, :+)
        return Expr(:call, new_args, Any)
    end
end

# Assumes length(ex.args) == 3
# Removes any 0's in a subtraction
function simplify_subtraction(ex::Expr)
    new_args = map(x -> simplify(x), filter(x -> x != 0, ex.args[2:end]))
    if length(new_args) == 0
        return 0
    # Special Case: simplify(:(x - x)) == 0
    elseif length(new_args) == 2 && new_args[1] == new_args[2]
        return 0
    else
        unshift(new_args, :-)
        return Expr(:call, new_args, Any)
    end
end

# Handles all lengths for ex.args
# Removes any 1's in a product
function simplify_product(ex::Expr)
    new_args = map(x -> simplify(x), filter(x -> x != 1, ex.args[2:end]))
    if length(new_args) == 0
        return 1
    # Special Case: simplify(:(*x)) == x
    elseif length(new_args) == 1
        return new_args[1]
    # Special Case: simplify(:(x * y * z * 0)) == 0
    elseif any(new_args .== 0)
        return 0
    else
        unshift(new_args, :*)
        return Expr(:call, new_args, Any)
    end
end

# Assumes length(ex.args) == 3
function simplify_quotient(ex::Expr)
    new_args = map(x -> simplify(x), ex.args[2:end])
    # Special Case: simplify(:(x / 1)) == x
    if new_args[2] == 1
        return new_args[1]
    # Special Case: simplify(:(0 / x)) == 0
    elseif new_args[1] == 0
        return 0
    else
        unshift(new_args, :/)
        return Expr(:call, new_args, Any)
    end
end

# Assumes length(ex.args) == 3
function simplify_power(ex::Expr)
    new_args = map(x -> simplify(x), ex.args[2:end])
    # Special Case: simplify(:(x ^ 0)) == 1
    if new_args[2] == 0
        return 1
    # Special Case: simplify(:(x ^ 1)) == x
    elseif new_args[2] == 1
        return new_args[1]
    # Special Case: simplify(:(0 ^ x)) == 0
    elseif new_args[1] == 0
        return 0
    # Special Case: simplify(:(1 ^ x)) == 1
    elseif new_args[1] == 1
        return 1
    else
        unshift(new_args, :^)
        return Expr(:call, new_args, Any)
    end
end

# Lookup table of simplification rules
simplify_lookup = {
                    :+ => simplify_sum,
                    :- => simplify_subtraction,
                    :* => simplify_product,
                    :/ => simplify_quotient,
                    :^ => simplify_power
                  }

# The simplest form of an expression is a fixed point of simplify()
function simplify(ex::Expr)
    if ex.head == :call
        if all(map(a -> isa(a, Number), ex.args[2:end]))
            return eval(ex)
        end
        if has(simplify_lookup, ex.args[1])
            new_ex = simplify_lookup[ex.args[1]](ex)
            while new_ex != ex
                new_ex, ex = simplify(new_ex), new_ex
            end
            return new_ex
        else
          return ex
        end
    else
        return ex
    end
end

# The Constant Rule
# d/dx c = 0
differentiate(x::Number, target::Symbol) = 0

# The Symbol Rule
# d/dx x = 1
# d/dx y = 0
function differentiate(s::Symbol, target::Symbol)
    if s == target
        return 1
    else
        return 0
    end
end

# The Sum Rule for Unary and Binary +
# d/dx +(f) = +(d/dx f)
# d/dx (f + g) = d/dx f + d/dx g
function differentiate_sum(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :+
        error("Not a valid sum call: $(ex)")
    end
    n = length(ex.args)
    new_args = Array(Any, n)
    new_args[1] = :+
    for i in 2:n
        new_args[i] = differentiate(ex.args[i], target)
    end
    return Expr(:call, new_args, Any)
end

# The Subtraction Rule for Unary and Binary -
# d/dx -(f) = -(d/dx f)
# d/dx (f - g) = d/dx f - d/dx g
function differentiate_subtraction(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :-
        error("Not a valid subtraction call: $(ex)")
    end
    n = length(ex.args)
    new_args = Array(Any, n)
    new_args[1] = :-
    for i in 2:n
        new_args[i] = differentiate(ex.args[i], target)
    end
    return Expr(:call, new_args, Any)
end

# The Product Rule
# d/dx (f * g) = (d/dx f) * g + f * (d/dx g)
# d/dx (f * g * h) = (d/dx f) * g * h + f * (d/dx g) * h + ...
function differentiate_product(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :*
        error("Not a valid product call: $(ex)")
    end
    n = length(ex.args)
    res_args = Array(Any, n)
    res_args[1] = :+
    for i in 2:n
       new_args = Array(Any, n)
       new_args[1] = :*
       for j in 2:n
           if j == i
               new_args[j] = differentiate(ex.args[j], target)
           else
               new_args[j] = ex.args[j]
           end
       end
       res_args[i] = Expr(:call, new_args, Any)
    end
    return Expr(:call, res_args, Any)
end

# The Quotient Rule
# d/dx (f / g) = ((d/dx f) * g - f * (d/dx g)) / g^2
function differentiate_quotient(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :/
        error("Not a valid quotient call: $(ex)")
    end
    return Expr(:call,
                {
                    :/,
                    Expr(:call,
                         {
                            :-,
                            Expr(:call,
                                 {
                                    :*,
                                    differentiate(ex.args[2], target),
                                    ex.args[3]
                                 },
                                 Any),
                            Expr(:call,
                                 {
                                    :*,
                                    ex.args[2],
                                    differentiate(ex.args[3], target)
                                 },
                                 Any)
                         },
                         Any),
                    Expr(:call,
                         {
                            :^,
                            ex.args[3],
                            2
                         },
                         Any)
                },
                Any)
end

# The Power Rule:
# Case 1: x^n <=> /dx x^n = n * x^(n - 1)
# Case 2: x^x <=> d/dx x^x = x^x (log(x) + 1)
# Case 3: n^n <=> d/dx n^n = 0
# Case 4: n^x <=> n^x * log(n)
# TODO: Handle general case for things like sin(x)^2
function differentiate_power(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :^
        error("Not a valid power call: $(ex)")
    end
    if ex.args[2] == target && ex.args[3] != target
        return Expr(:call,
                    {
                        :*,
                        ex.args[3],
                        Expr(:call,
                             {
                                :^,
                                ex.args[2],
                                Expr(:call,
                                     {
                                         :-,
                                         ex.args[3],
                                         1
                                     },
                                     Any)
                             },
                             Any)
                    },
                    Any)
    elseif ex.args[2] == target && ex.args[3] == target
        return Expr(:call,
                    {
                        :*,
                        Expr(:call,
                             {
                                :^,
                                target,
                                target
                             },
                             Any),
                        Expr(:call,
                             {
                                :+,
                                Expr(:call,
                                     {
                                        :log,
                                        target
                                     },
                                     Any),
                                1
                             },
                             Any)
                    },
                    Any)
    elseif ex.args[2] != target && ex.args[3] != target
        return ex
    else
        return Expr(:call,
                    {
                        :*,
                        Expr(:call,
                             {
                                :^,
                                ex.args[2],
                                ex.args[3]
                             },
                             Any),
                        Expr(:call,
                             {
                                :log,
                                ex.args[2]
                             },
                             Any)
                    },
                    Any)
    end
end

# The Sin Rule:
# d/dx sin(x) = cos(x)
function differentiate_sin(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :sin
        error("Not a valid sin call: $(ex)")
    end
    return Expr(:call,
                {
                    :*,
                    Expr(:call,
                         {
                            :cos,
                            ex.args[2]
                         },
                         Any),
                    differentiate(ex.args[2], target)
                },
                Any)
end

# The Cos Rule:
# d/dx cos(x) = -sin(x)
function differentiate_cos(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :cos
        error("Not a valid cos call: $(ex)")
    end
    return Expr(:call,
                {
                    :*,
                    Expr(:call,
                         {
                            :-,
                            Expr(:call,
                                 {
                                    :sin,
                                    ex.args[2]
                                 },
                                 Any)
                         },
                         Any),
                    differentiate(ex.args[2], target)
                },
                Any)
end

# The Tan Rule:
# d/dx tan(x) = 1 + tan(x)^2
function differentiate_tan(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :tan
        error("Not a valid tan call: $(ex)")
    end
    return Expr(:call,
                {
                    :*,
                    Expr(:call,
                         {
                            :+,
                            1,
                            Expr(:call,
                                 {
                                    :^,
                                    Expr(:call,
                                         {
                                            :tan,
                                            ex.args[2]
                                         },
                                         Any),
                                    2
                                 },
                                 Any)
                         },
                         Any),
                    differentiate(ex.args[2], target)
                },
                Any)
end

# The Exp Rule:
# d/dx exp(x) = exp(x)
function differentiate_exp(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :exp
        error("Not a valid exp call: $(ex)")
    end
    return Expr(:call,
                {
                    :*,
                    Expr(:call,
                         {
                            :exp,
                            ex.args[2]
                         },
                         Any),
                    differentiate(ex.args[2], target)
                },
                Any)
end

# The Log Rule:
# d/dx log(x) = 1 / x
function differentiate_log(ex::Expr, target::Symbol)
    if ex.head != :call || ex.args[1] != :log
        error("Not a valid log call: $(ex)")
    end
    return Expr(:call,
                {
                    :*,
                    Expr(:call,
                         {
                            :/,
                            1,
                            ex.args[2]
                         },
                         Any),
                    differentiate(ex.args[2], target)
                },
                Any)
end

# Lookup table of differentation rules
differentiate_lookup = {
                          :+ => differentiate_sum,
                          :- => differentiate_subtraction,
                          :* => differentiate_product,
                          :/ => differentiate_quotient,
                          :^ => differentiate_power,
                          :sin => differentiate_sin,
                          :cos => differentiate_cos,
                          :tan => differentiate_tan,
                          :exp => differentiate_exp,
                          :log => differentiate_log
                       }

function differentiate(ex::Expr, target::Symbol)
    if ex.head == :call
        if has(differentiate_lookup, ex.args[1])
            return simplify(differentiate_lookup[ex.args[1]](ex, target))
        else
            error("Don't know how to differentiate $(ex.args[1])")
        end
    else
        return simplify(differentiate(ex.head))
    end
end
function differentiate(ex::Expr, targets::Vector{Symbol})
    n = length(targets)
    exprs = Array(Expr, n)
    for i in 1:n
        exprs[i] = differentiate(ex, targets[i])
    end
    return exprs
end

differentiate(ex::Expr) = differentiate(ex, :x)

function differentiate(s::String, target::Symbol)
    differentiate(parse(s)[1], target)
end
function differentiate(s::String, targets::Vector{Symbol})
    differentiate(parse(s)[1], targets)
end
function differentiate(s::String, target::String)
    differentiate(parse(s)[1], symbol(target))
end
function differentiate{T <: String}(s::String, targets::Vector{T})
    differentiate(parse(s)[1], map(target -> symbol(target), targets))
end
function differentiate(s::String)
    differentiate(parse(s)[1], :x)
end

# begin x = 1; eval(differentiate(:(sin(x)), :x)) end

# Full out differentation returns an immediately evaluable Julia function

# function derivative(ex::Expr, target::Symbol)
#     function f(x)
#         d_ex = differentiate(ex, target)
#         return eval(d_ex)
#     end
#     return f
# end

# function derivative(ex::Expr, target::Symbol, x::Any)
#     d_ex = differentiate(ex, target)
#     return eval(d_ex)
# end


# Helper functions for spherical geometry

using Rotations

"""
    c2s(x, y, z) -> [θ, ϕ]
    c2s(xyz::AbstractVector) -> [θ, ϕ]
    c2s(xyz::AbstractMatrix) -> [θs; ϕs]

Convert Cartesian vector `[x, y, z]` (or `xyz`) to spherical unit vector `[θ,
ϕ]`.  For AbstractMatrix `xyz`, each column is converted and the results are
reassmebled into a Matrix using `hcat`.
"""
function c2s(x,y,z)
    θ=atan(y,x)
    ϕ=atan(z, hypot(x,y))
    [θ, ϕ]
end

function c2s(xyz::AbstractVector{<:Real})
    c2s(xyz[1], xyz[2], xyz[3])
end

function c2s(xyz::AbstractMatrix{<:Real})
    @views reduce(hcat, c2s.(xyz[1,:], xyz[2,:], xyz[3,:]))
end

"""
    s2c(θ, ϕ) -> [x, y, z)
    s2c(θϕ::AbstractVector) -> [x, y, z]
    s2c(θϕ::AbstractMaxtrix) -> [xs; ys; zs]

Convert spherical unit vector `[θ, ϕ]` (or `θϕ`) to Cartesian unit vector `[x,
y, z]`.  For AbstractMatrix `θϕ`, each column is converted and the results are
reassmebled into a Matrix using `hcat`.
"""
function s2c(θ,ϕ)
    z, r = sincos(ϕ)
    y, x = sincos(θ) .* r
    [x, y, z]
end

function s2c(θϕ::AbstractVector{<:Real})
    s2c(θϕ[1], θϕ[2])
end

function s2c(θϕ::AbstractMatrix{<:Real})
    @views reduce(hcat, s2c.(θϕ[1,:], θϕ[2,:]))
end

"""
    beamrings(θ, ϕ; nrings=4, dϕ=deg2rad(10/3600)) -> Matrix

Compute the centers of beams that are arranged in concentric rings around the
spherical coordinates `(θ, ϕ)`, both in radians with `θ` being the longitudinal
coordinate and `ϕ` being the latitudinal coordinate.  The number of rings for
which beam centers are returned is given by `nrings`, which defaults to 4.  The
rings are radially separated by `dϕ` radians, which defaults to the equivalent
of 10 arcseconds.  The beams returned do not form a packed hexagon because that
is not possible on a sperical sky, but the number of beams per ring does follow
the hexagonal packing convention of `6n` beams for ring `n` (when `n>0`).  The
so-called boresight beam center at `(θ, ϕ)` is included as well, so 61 beam
centers are returned for `nrings==4`.  More generally,
`nbeams=6*sum(1:nrings)+1` beam centers are returned in a `2xnbeams` Matrix,
where the first row contains the `θ_beam` coordinates and the second row
contains the `ϕ_beam` coordinates.
"""
function beamrings(θ, ϕ; nrings=4, dϕ=deg2rad(10/3600))
    nbeams = 6 * sum(1:nrings) + 1
    θs = Vector{Float64}(undef, nbeams)
    ϕs = Vector{Float64}(undef, nbeams)
    θs[1] = 0.0
    ϕs[1] = π/2
    b=2
    for r in 1:nrings
        nθ = 6r
        dθ = 2π/nθ
        ϕr = π/2 - r * dϕ
        for i in 0:nθ-1
            θs[b] = i*dθ
            ϕs[b] = ϕr
            b += 1
        end
    end
    reduce(hcat, c2s.(Ref(RotZY(θ, π/2-ϕ)) .* s2c.(θs, ϕs)))
end


using LinearAlgebra
using Plots

datapoints=[2.4 420;
            2.0 350;
            2.1 310;
            1.8 280;
            1.3 75]
A = hcat(ones(size(datapoints,1)),datapoints[:,1])
b = datapoints[:,2]

x = A\b

line = t -> x[1] + x[2]*t

scatter(datapoints[:,1], datapoints[:,2], legend=:none)
plot!(line, legend=:none)
ylabel!("weight, in pounds")
xlabel!("length, in meters")


euler(f::Function, T::Number, t0::Int, t1::Int, h::Int) = collect(begin T += h * f(T); T end for t in t0:h:t1)

# Prints a series of arbitrary values in a tabular form, left aligned in cells with a given width
tabular(width, cells...) = println(join(map(s -> rpad(s, width), cells)))

# prints the table according to the task description for h=5 and 10 sec
for h in (5, 10)
    print("Step $h:\n\n")
    tabular(15, "Time", "Euler", "Analytic")
    t = 0
    for T in euler(y -> -0.07 * (y - 20.0), 100.0, 0, 100, h)
        tabular(15, t, round(T,6), round(20.0 + 80.0 * exp(-0.07t), 6))
        t += h
    end
    println()
end


export correlationsum_fixedmass

using SpecialFunctions, Random

"""
    correlationsum_fixedmass(data, max_j; metric = Euclidean(), M = length(data)) → rs, ys
A fixed mass algorithm for the calculation of the fractal dimension ``\\Delta`` according
to [^Grassberger1988] with `max_j` the maximum number of neighbours that
should be considered for the calculation, `M` defines the number of points
considered for the calculation, default is the whole data set.

Implements
```math
\\Delta \\overline{\\log r^{(j)}} \\sim Ψ(j) - \\log N
```
where `` \\Psi(j) = \\frac{\\text{d} \\log Γ(j)}{\\text{d} j}
``, `rs` = ``\\overline{\\log r^{(j)}}`` and `ys` = ``\\Psi(j) - \\log N``.

``\\Delta`` can be computed by using `linear_region(rs, ys)`.

[^Grassberger1988]: Peter Grassberger (1988) [Finite sample Corrections to Entropy and Dimension Estimates, Physics Letters A 128(6-7)](https://doi.org/10.1016/0375-9601(88)90193-4)
"""
function correlationsum_fixedmass(data, max_j; metric = Euclidean(), M = length(data), w = 0)
    N = length(data)
    M > N && throw(ArgumentError("The number of points used for the calculation `M` should not exceed the number of points provided."))
    # Transform the data into a tree.
    tree = searchstructure(KDTree, data, metric)
    # Calculate all nearest neighbours up to max_j.
    _, distances =
        bulksearch(
            tree,
            N == M ? data.data : data[view(randperm(N), 1:M)],
            NeighborNumber(max_j),
            Theiler(w),
        )
    # The epsilons define the left side of the equation
    ys = [digamma(j) - log(N) for j in 1:max_j]
    # Holds the mean value of the logarithms of the distances.
    rs = zeros(max_j)
    for dists in distances
        for j in 1:max_j
            rs[j] += log(dists[j])
        end
    end
    return rs ./ M, ys
end


using OrdinaryDiffEq
using DiffEqCallbacks
using Surrogates
using DataFrames
using RCall
using Random
using Plots;

R"library(hmer)";

Random.seed!(123)
R"set.seed(123)";

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

tmax = 10000.0
tspan = (0.0,tmax)
δt = 1.0;

n_train = 50 # Number of training samples
n_test = 1000; # Number of test samples

# Parameters are β, γ
lb = [0.00005, 0.1]
ub = [0.001, 1.0];

N = 1000.0
u0 = [990.0,10.0,0.0]
p = [0.0005,0.25]
prob_ode = ODEProblem(sir_ode!,u0,tspan,p);

sampler = LatinHypercubeSample();

θ = Surrogates.sample(n_train,lb,ub,sampler);

logit = (x) -> log(x/(1-x))
invlogit = (x) -> exp(x)/(exp(x)+1.0)
cb_ss = TerminateSteadyState()
logit_final_size = function(z)
  prob = remake(prob_ode;p=z)
  sol = solve(prob, ROS34PW3(),callback=cb_ss)
  fsp = sol[end][3]/N
  logit(fsp)
end;

lfs = logit_final_size.(θ);

training_df = DataFrame(θ)
rename!(training_df,["b","g"])
training_df[!,:lfs] = lfs;

input_ranges = Dict(:b => [lb[1], ub[1]], :g => [lb[2], ub[2]])
output_names = ["lfs"];

@rput training_df
@rput output_names
@rput input_ranges;

R"emulator <- emulator_from_data(training_df, output_names, input_ranges)"

θ_test = sample(n_test,lb,ub,sampler)
lfs_test = logit_final_size.(θ_test);

test_df = DataFrame(θ_test)
rename!(test_df,["b","g"])
test_df[!,:lfs] = lfs_test
@rput test_df; # copy to R

R"lfs_test_pred <- list()"
R"lfs_test_pred$mean <- emulator$lfs$get_exp(test_df)"
R"lfs_test_pred$unc <- emulator$lfs$get_cov(test_df)"
@rget lfs_test_pred;

scatter(invlogit.(lfs_test),
        invlogit.(lfs_test_pred[:mean]),
        xlabel = "Model final size",
        ylabel = "Surrogate final size",
        legend = false,
        title = "Test set")

β_grid = collect(lb[1]:0.00001:ub[1])
θ_eval = [(βᵢ,0.25) for βᵢ in β_grid]
eval_df = DataFrame(θ_eval)
rename!(eval_df,["b","g"])
@rput eval_df
R"lfs_eval <- list()"
R"lfs_eval$mean <- emulator$lfs$get_exp(eval_df)"
R"lfs_eval$unc <- emulator$lfs$get_cov(eval_df)"
@rget lfs_eval
fs_eval = invlogit.(lfs_eval[:mean])
fs_eval_uc = invlogit.(lfs_eval[:mean] .+ 1.96 .* sqrt.(lfs_eval[:unc]))
fs_eval_lc = invlogit.(lfs_eval[:mean] .- 1.96 .* sqrt.(lfs_eval[:unc]))
plot(β_grid,
     fs_eval,
     xlabel = "Infectivity parameter, β",
     ylabel = "Final size",
     label = "Model")
plot!(β_grid,
      invlogit.(logit_final_size.(θ_eval)),
      ribbon = (fs_eval .- fs_eval_lc, fs_eval_uc - fs_eval),
      label = "Surrogate",
      legend = :right)

γ_grid = collect(lb[2]:0.001:ub[2])
θ_eval = [(0.001,γᵢ) for γᵢ in γ_grid]
eval_df = DataFrame(θ_eval)
rename!(eval_df,["b","g"])
@rput eval_df
R"lfs_eval <- list()"
R"lfs_eval$mean <- emulator$lfs$get_exp(eval_df)"
R"lfs_eval$unc <- emulator$lfs$get_cov(eval_df)"
@rget lfs_eval
fs_eval = invlogit.(lfs_eval[:mean])
fs_eval_uc = invlogit.(lfs_eval[:mean] .+ 1.96 .* sqrt.(lfs_eval[:unc]))
fs_eval_lc = invlogit.(lfs_eval[:mean] .- 1.96 .* sqrt.(lfs_eval[:unc]))
plot(γ_grid,
     fs_eval,
     xlabel = "Recovery rate, γ",
     ylabel = "Final size",
     label = "Model")
plot!(γ_grid,
      invlogit.(logit_final_size.(θ_eval)),
      ribbon = (fs_eval .- fs_eval_lc, fs_eval_uc - fs_eval),
      label = "Surrogate")

obs = logit_final_size(p)
obs_lc = logit(0.99*invlogit(obs))
obs_uc = logit(1.01*invlogit(obs))
target = Dict(:lfs => [obs_lc, obs_uc])
@rput target

R"new_points <- generate_new_runs(emulator, $n_test, target, method = 'lhs', cutoff = 3)"
@rget new_points;

l = @layout [a b]
pl1 = histogram(new_points[!,:b],legend=false,xlim=(lb[1],ub[1]),bins=lb[1]:0.00005:ub[1],title="NROY values for β")
vline!(pl1,[p[1]])
pl2 = histogram(new_points[!,:g],legend=false,xlim=(lb[2],ub[2]),bins=lb[2]:0.05:ub[2],title="NROY values for γ")
vline!(pl2,[p[2]])
plot(pl1, pl2, layout = l)


"""
    GaussLik <: Likelihood

Gaussian, a.k.a. Normal, likelihood
```math
p(y | f, σ) = 1 / √(2πσ²) \\exp(-(x - f)²/(2σ²)),
```
where standard deviation ``σ`` is a non-fixed hyperparameter and ``f`` is the latent
Gaussian process.
"""
mutable struct GaussLik <: Likelihood
    "Standard deviation"
    σ::Float64
    "Priors for likelihood parameters"
    priors::Array

    """
        GaussLik(lσ::Float64)

    Create `GaussLik` with standard deviation `exp(lσ)`.
    """
    GaussLik(lσ::Float64) = new(exp(lσ), [])
end

# log of probability density
function log_dens(gauss::GaussLik, f::VecF64, y::VecF64)
    return (-0.5 * log(2 * pi) - log(gauss.σ)) .- 0.5 * ((y - f) / gauss.σ).^2
end

# derivative of log pdf wrt latent function
function dlog_dens_df(gauss::GaussLik, f::VecF64, y::VecF64)
    return [(yi-fi)/gauss.σ^2 for (fi,yi) in zip(f,y)]
end

# derivative of log pdf wrt to parameters
function dlog_dens_dθ(gauss::GaussLik, f::VecF64, y::VecF64)
    return gauss.σ*[-1/gauss.σ + 1/gauss.σ^3*(yi-fi).^2 for (fi,yi) in zip(f,y)]
end

#mean and variance under likelihood
mean_lik(gauss::GaussLik, f::VecF64) = f
var_lik(gauss::GaussLik, f::VecF64) = ones(length(f))*gauss.σ^2

function set_params!(gauss::GaussLik, hyp::VecF64)
    length(hyp) == 1 || throw(ArgumentError("Gaussian/Normal likelihood has only one free parameter"))
    gauss.σ = exp(hyp[])
end

get_params(gauss::GaussLik) = Float64[log(gauss.σ)]
num_params(gauss::GaussLik) = 1

#Computes the predictive mean and variance
function predict_obs(gauss::GaussLik, fmean::VecF64, fvar::VecF64)
    return fmean, fvar + gauss.σ^2
end


export
    Line,
    get_polar_angle,
    get_distance,
    get_side

immutable Line
    C::VecE2
    θ::Float64
end
Line(A::VecE2, B::VecE2) = Line((A+B)/2, atan2(B - A))

Base.:-(line::Line, V::VecE2) = Line(line.C - V, line.θ)
Base.:+(line::Line, V::VecE2) = Line(line.C + V, line.θ)

get_polar_angle(line::Line) = line.θ
rot(line::Line, Δθ::Float64) = Line(line.C, line.θ+Δθ)
Base.rot180(line::Line) = rot(line, 1π)
Base.rotl90(line::Line) = rot(line,  π/2)
Base.rotr90(line::Line) = rot(line, -π/2)

"""
The distance between the line and the point P
"""
function get_distance(line::Line, P::VecE2)

    ab = polar(1.0, line.θ)
    pb = P - line.C

    denom = abs2(ab)
    if denom ≈ 0.0
        return 0.0
    end

    r = dot(ab, pb)/denom
    return abs(P - (line.C + r*ab))
end

"""
What side of the line you are on
Is -1 if on the left, 1 if on the right, and 0 if on the line
"""
function get_side(line::Line, p::VecE2, ε::Float64=1e-10)
    ab = polar(1.0, line.θ)
    signed_dist = ab.x*(p.y-line.C.y) - ab.y*(p.x-line.C.x)
    if abs(signed_dist) < ε
        return 0
    else
        return sign(signed_dist)
    end
end


"""
    frst(signal, α, p)

Computing the α order fractional sine transform of the input **signal**.

# Example

```julia-repl
julia> frst([1,2,3], 0.5, 2)
3-element Vector{ComplexF64}:
1.707106781186548 + 1.207106781186547im
1.9999999999999998 - 1.7071067811865481im
-1.1213203435596437 - 1.2071067811865468im
```

### References

```tex
@article{article,
author = {Pei, Soo-Chang and Yeh, Min-Hung},
year = {2001},
month = {07},
pages = {1198 - 1207},
title = {The discrete fractional cosine and sine transforms},
volume = {49},
journal = {Signal Processing, IEEE Transactions on},
doi = {10.1109/78.923302}
}
```
"""
function frst(signal, α, p)
    N = length(signal)
    @views signal = signal[:]
    p = min(max(2, p), N-1)
    E = dFRST(N,p)
    result = E *(exp.(-im*pi*α*collect(0:N-1)) .*(E' *signal))
    return result
end

function dFRST(N, p)
    N1 = 2*N+2
    d2 = [1, -2, 1]

    d_p = 1
    s = 0
    st = zeros(1, N1)

    for k = 1:floor(Int, p/2)
        if isa(d_p, Number)
            d_p = @. d2*d_p
        else
            d_p = conv(d2, d_p)
        end
        st[vcat(collect(N1-k+1:N1), collect(1:k+1))] = d_p
        st[1]=0
        temp=vcat(union(1, collect(1:k-1)), union(2,collect(1:k-1)))
        temp = temp[:] ./collect(1:2*k)
        s=s.+(-1)^(k-1)*prod(temp)*2*st
    end

    H = Toeplitz(s[:], s[:]) +diagm(real.(fft(s[:])))

    V = hcat(zeros(N), reverse(zeros(N, N)+I, dims=1), zeros(N), -(zeros(N, N)+I)) ./sqrt(2)

    Od = V*H*V'

    _, vo = eigen(Od)

    return reverse(reverse(vo, dims=2), dims=1)
end


using EngEconomics, Plots, ColorSchemes, Colors
plotly()

uwEngColorScheme = ColorScheme([parse(Colorant, "#D0B4E7"), parse(Colorant, "#FFFFFF"), parse(Colorant, "#57058B"), parse(Colorant, "#8100B4")])

F = 50
A_base = 7
i_base = 0.10
sensitivityVector = [.90, 0.95, 1.0, 1.05, 1.10]

baseN = log(F / A_base * i_base + 1) / log(1 + i_base)

N_annualSavings = log.(F ./ (A_base .* sensitivityVector) .* i_base .+ 1) ./ log(1 + i_base)
N_interestRate  = log.(F / A_base .* (sensitivityVector * i_base) .+ 1) ./ log.(1 .+ (i_base .* sensitivityVector))

plot(sensitivityVector, N_annualSavings, label="Annual Savings Sensitivity",
	xlabel="Sensitivity (+/- 5%, 10%)", ylabel="Number of Years",
	palette=cgrad(uwEngColorScheme))
plot!(sensitivityVector, N_interestRate, label="Interest Rate Sensitivity")


# span.jl

@doc """
### findSpan

Determines the knot span index, i

**Inputs**

*  `u` : coordinate value (u,v,w is the coordinate space in 3D)
*  `U` : Knot vector
*  `k` : B-spline order
*  `nctl` : Number of control points

**Outputs**

*  `span` : Knot span index

SOURCE: The NURBS book 2nd Edition, Algorithm A2.1

"""->

function findSpan(u::Tffd, U::AbstractArray{Tffd,1}, k::Int, nctl::Int) where Tffd

  if u >= U[nctl+1]
    return nctl  # Special case when u = last term of knot vector
  elseif u < U[k]
    return k  # When u lies at the starting point of the curve
  end

  low = k-1
  high = nctl+1
  span = div(low + high, 2)
  # Do a binary search
  while u < U[span] || u >= U[span+1]
    if u < U[span]
      high = span
    else
      low = span
    end  # End if
    span = div(low+high, 2)
  end  # End while

  return span
end  # End function findSpan


## Linear algebra API for adjacency matrix
using LinearAlgebra

function adjacency_matrix(adj::AbstractMatrix, T::DataType=eltype(adj))
    m, n = size(adj)
    (m == n) || throw(DimensionMismatch("adjacency matrix is not a square matrix: ($m, $n)"))
    T.(adj)
end

"""
    degrees(g[, T; dir=:out])

Degree of each vertex. Return a vector which contains the degree of each vertex in graph `g`.

# Arguments
- `g`: should be a adjacency matrix, `SimpleGraph`, `SimpleDiGraph` (from LightGraphs) or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).

# Examples
```jldoctest
julia> using GeometricFlux

julia> m = [0 1 1; 1 0 0; 1 0 0];

julia> GeometricFlux.degrees(m)
3-element Array{Int64,1}:
 2
 1
 1

```
"""
function degrees(adj::AbstractMatrix, T::DataType=eltype(adj); dir::Symbol=:out)
    if issymmetric(adj)
        d = vec(sum(adj, dims=1))
    else
        if dir == :out
            d = vec(sum(adj, dims=1))
        elseif dir == :in
            d = vec(sum(adj, dims=2))
        elseif dir == :both
            d = vec(sum(adj, dims=1)) + vec(sum(adj, dims=2))
        else
            throw(DomainError(dir, "invalid argument, only accept :in, :out and :both"))
        end
    end
    d
end

"""
    degree_matrix(g[, T; dir=:out])

Degree matrix of graph `g`. Return a matrix which contains degrees of each vertex in its diagonal.
The values other than diagonal are zeros.

# Arguments
- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from LightGraphs) or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).

# Examples
```jldoctest
julia> using GeometricFlux

julia> m = [0 1 1; 1 0 0; 1 0 0];

julia> GeometricFlux.degree_matrix(m)
3×3 SparseArrays.SparseMatrixCSC{Int64,Int64} with 3 stored entries:
  [1, 1]  =  2
  [2, 2]  =  1
  [3, 3]  =  1
```
"""
function degree_matrix(adj::AbstractMatrix, T::DataType=eltype(adj); dir::Symbol=:out)
    d = degrees(adj, T, dir=dir)
    return SparseMatrixCSC(T.(diagm(0=>d)))
end

"""
    inv_sqrt_degree_matrix(g[, T; dir=:out])

Inverse squared degree matrix of graph `g`. Return a matrix which contains inverse squared degrees of each vertex in its diagonal.
The values other than diagonal are zeros.

# Arguments
- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from LightGraphs) or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).
"""
function inv_sqrt_degree_matrix(adj::AbstractMatrix, T::DataType=eltype(adj); dir::Symbol=:out)
    d  = inv.(sqrt.(degrees(adj, T, dir=dir)))
    return Diagonal(d)
end

"""
    laplacian_matrix(g[, T; dir=:out])

Laplacian matrix of graph `g`.

# Arguments
- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from LightGraphs) or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `dir`: direction of degree; should be `:in`, `:out`, or `:both` (optional).
"""
function laplacian_matrix(adj::AbstractMatrix, T::DataType=eltype(adj); dir::Symbol=:out)
    degree_matrix(adj, T, dir=dir) - SparseMatrixCSC(T.(adj))
end

"""
    normalized_laplacian(g[, T; selfloop=false])

Normalized Laplacian matrix of graph `g`.

# Arguments
- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from LightGraphs) or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
- `selfloop`: adding self loop while calculating the matrix (optional).
"""
function normalized_laplacian(adj::AbstractMatrix, T::DataType=eltype(adj); selfloop::Bool=false)
    selfloop && (adj += I)
    inv_sqrtD = inv_sqrt_degree_matrix(adj, T, dir=:both)
    T.(I - inv_sqrtD * adj * inv_sqrtD)
end

@doc raw"""
    scaled_laplacian(adj::AbstractMatrix[, T::DataType])

Scaled Laplacien matrix of graph `g`,
defined as ``\hat{L} = \frac{2}{\lambda_{max}} L - I`` where ``L`` is the normalized Laplacian matrix.

# Arguments
- `g`: should be a adjacency matrix, `FeaturedGraph`, `SimpleGraph`, `SimpleDiGraph` (from LightGraphs) or `SimpleWeightedGraph`, `SimpleWeightedDiGraph` (from SimpleWeightedGraphs).
- `T`: result element type of degree vector; default is the element type of `g` (optional).
"""
function scaled_laplacian(adj::AbstractMatrix, T::DataType=eltype(adj))
    T(2. / eigmax(adj)) * normalized_laplacian(adj, T) - I
end

function neighbors(adj::AbstractMatrix, T::DataType=eltype(adj))
    n = size(adj,1)
    @assert n == size(adj,2) "adjacency matrix is not a square matrix."
    A = (adj .!= zero(T))
    if !issymmetric(adj)
        A = A .| A'
    end
    indecies = collect(1:n)
    ne = Vector{Int}[indecies[view(A, :, i)] for i = 1:n]
    return ne
end


# This example solution is partially derived from the Julia base implementation of rational numbers (MIT license)
# https://github.com/JuliaLang/julia/blob/52bafeb981bac548afd2264edb518d8d86944dca/base/rational.jl
# https://github.com/JuliaLang/julia/blob/52bafeb981bac548afd2264edb518d8d86944dca/LICENSE.md

import Base: +, *, ^, -, /, <, <=, inv, show, promote_rule

struct RationalNumber{T<:Integer} <: Real
    num::T
    den::T

    # reduce to lowest terms
    function RationalNumber{T}(num::Integer, den::Integer) where T<:Integer
        num == den == zero(T) && throw(ArgumentError("zero($T)//zero($T) is not a valid rational number"))
        num2, den2 = sign(den) < 0 ? Base.divgcd(-num, -den) : Base.divgcd(num, den)
        new(num2, den2)
    end
end

RationalNumber(num::T, den::T) where {T<:Integer} = RationalNumber{T}(num, den)

numerator(x::RationalNumber) = x.num
denominator(x::RationalNumber) = x.den

RationalNumber{T}(x::Integer) where {T<:Integer} = RationalNumber{T}(convert(T, x), convert(T, 1))

promote_rule(::Type{RationalNumber{T}}, ::Type{S}) where {T<:Integer,S<:Integer} = RationalNumber{promote_type(T,S)}

show(io::IO, x::RationalNumber) = (show(io, numerator(x)); print(io, "//"); show(io, denominator(x)))

# Helper method
numden(x::RationalNumber) = numerator(x), denominator(x)

# Arithmetics
function +(x::RationalNumber, y::RationalNumber)
    a, b = numden(x)
    c, d = numden(y)

    RationalNumber(a * d + b * c, b * d)
end

-(x::RationalNumber, y::RationalNumber) = x + (-1) * y
-(x::RationalNumber) = -1 * x

function *(x::RationalNumber, y::RationalNumber)
    a, b = numden(x)
    c, d = numden(y)

    RationalNumber(a * c, b * d)
end

inv(x::RationalNumber) = RationalNumber(denominator(x), numerator(x))
/(x::RationalNumber, y::RationalNumber) = x * inv(y)

^(x::Real, y::RationalNumber) = x^(numerator(y) / denominator(y))

# Ordering
<(x::RationalNumber, y::RationalNumber) = x.den == y.den ? x.num < y.num : x.num * y.den < y.num * x.den
<=(x::RationalNumber, y::RationalNumber) = x.den == y.den ? x.num <= y.num : x.num * y.den <= y.num * x.den


# indicator of the Cartesian product of real binary sets

export IndBinary

"""
**Indicator of the product of binary sets**

    IndBinary(low, up)

Returns the indicator function of the set
```math
S = \\{ x : x_i = low_i\\ \\text{or}\\ x_i = up_i \\},
```
Parameters `low` and `up` can be either scalars or arrays of the same dimension as the space.
"""
struct IndBinary{T, S} <: ProximableFunction
    low::T
    high::S
end

is_set(f::IndBinary) = true

IndBinary() = IndBinary(0.0, 1.0)

IndBinary_low(f::IndBinary{<: Number, S}, i) where S = f.low
IndBinary_low(f::IndBinary{T, S}, i) where {T, S} = f.low[i]
IndBinary_high(f::IndBinary{T, <: Number}, i) where T = f.high
IndBinary_high(f::IndBinary{T, S}, i) where {T, S} = f.high[i]

function (f::IndBinary)(x::AbstractArray{C}) where {R <: Real, C <: RealOrComplex{R}}
    for k in eachindex(x)
        if x[k] != IndBinary_low(f, k) && x[k] != IndBinary_high(f, k)
            return R(Inf)
        end
    end
    return R(0)
end

function prox!(y::AbstractArray{T}, f::IndBinary, x::AbstractArray{T}, gamma::R=R(1)) where {R <: Real, T <: Union{R, Complex{R}}}
    for k in eachindex(x)
        low = IndBinary_low(f, k)
        high = IndBinary_high(f, k)
        if abs(x[k] - low) < abs(x[k] - high)
            y[k] = low
        else
            y[k] = high
        end
    end
    return R(0)
end

fun_name(f::IndBinary) = "indicator of binary array"
fun_dom(f::IndBinary) = "AbstractArray{Real}"

function prox_naive(f::IndBinary, x::AbstractArray{T}, gamma::R=R(1)) where {R <: Real, T <: Union{R, Complex{R}}}
    distlow = abs.(x .- f.low)
    disthigh = abs.(x .- f.high)
    indlow = distlow .< disthigh
    indhigh = distlow .>= disthigh
    y = f.low.*indlow + f.high.*indhigh
    return y, R(0)
end


import Distributions: Normal, pdf, cdf

###############################
# Type Definition/Constructors
###############################
mutable struct Grid
    points::Vector{Float64}
    weights::Vector{Float64}
    scale::Float64
    Grid(points, weights, scale) = sum(weights) ≈ scale ? new(points, weights, scale) : error("scaled weights do not sum up properly")
end

# Constructor utilizing a custom weight calculation function
function Grid(quadrature::Function,
              lower_bound::T, upper_bound::T,
              n_points::Int; scale::Float64 = 1.) where {T<:Real}
    grid, weights = quadrature(lower_bound, upper_bound, n_points)
    Grid(reshape(grid, size(grid)[1]), weights, scale)
end

####################
# Quadrature rules
####################
# Enforce that quadrature rules with additional keyword arguments get mapped to
# A pre-populated version of that rule that only accepts 3 arguments:
# Lower bound, upper bound, and number of points
function uniform_quadrature(lower_bound::T, upper_bound::T, n_points::Int;
                            scale::T = 1) where {T<:Real}
    grid = collect(range(lower_bound, stop = upper_bound, length = n_points))
    weights = fill(scale/n_points, n_points)
    return grid, weights
end

function uniform_quadrature(scale::T = 1) where {T<:Real}
    return (lb, ub, n) -> uniform_quadrature(lb, ub, n, scale = scale)
end

# NOTE: Should modify chebpts function to return cleaner types...
# Kind indicates the "kind" of the chebyshev polynomial grid
# The terminology is generally, "Chebyshev polynomials of the Nth kind"
function curtis_clenshaw_quadrature(lower_bound::T, upper_bound::T,
                                    n_points::Int; kind::Int = 2) where {T<:Real}
    grid, weights = chebpts(n_points, lower_bound, upper_bound, kind)
    return grid', dropdims(weights', dims=2)
end

function curtis_clenshaw_quadrature(kind::Int = 2)
    return (lb, ub, n) -> curtis_clenshaw_quadrature(lb, ub, n, kind = kind)
end

# Tauchen86 developed a method for "finding a discrete-valued Markov chain
# whose sample paths approximate well those of a vector autoregression"
# Hence the method below is the Tauchen86 method applied to a general AR(1).
function tauchen86(μ::AbstractFloat,ρ::AbstractFloat,σ::AbstractFloat,n::Int64,λ::AbstractFloat)
    #output is xgrid, xprob
    # x_t+1 = μ + ρ x_t + σ e_{t+1}, e_t+1 ∼ N(0,1)
    xhi = μ/(1-ρ) + λ*sqrt(σ^2/(1-ρ)^2)
    xlo = μ/(1-ρ) - λ*sqrt(σ^2/(1-ρ)^2)
    xgrid = zeros(n);
    xscale =(xhi-xlo)/(n-1)

    for i=1:n
        xgrid[i] = xlo + xscale*(i-1)
    end
    m=zeros(n-1);
    for i=1:n-1
        m[i] = (xgrid[i]+xgrid[i+1])/2
    end
    xprob = zeros(n,n) # xprob[i,j] = Pr(x_t+1=xgrid[j]|x_t=xgrid[i])
    for i=1:n # this is the state today
        normie = Normal(μ+ρ*xgrid[i],σ)
        normpdf(x) = pdf.(normie,x)
        normcdf(x) = cdf.(normie,x)
        for j=2:n-1
            xprob[i,j] = normcdf(m[j]) - normcdf(m[j-1])
        end
        xprob[i,1] = normcdf(m[1])
        xprob[i,n] = 1 - normcdf(m[n-1])
    end
    xprob = xprob./sum(xprob, dims = 2) # make sure the rows sum to 1
    return ( xgrid,xprob, xscale )
end

# However, Tauchen86 can also be applied to "degenerate" AR(1)s where ρ = 0.
# i.e. i.i.d noise with mean μ, std. σ, but the way the returned
# Markov transition matrix, xprob, is used in the code, assumes it to be
# 1-dimensional, since the second-dimension of the Markov transition matrix is trivial
# (since the process is i.i.d).
function tauchen86(μ::AbstractFloat, σ::AbstractFloat, n::Int64, λ::AbstractFloat)
    xgrid, xprob, xscale = tauchen86(μ, 0., σ, n, λ)
    return xgrid, vec(xprob[:, 1]), xscale
end

####################
# Grid-based utils
####################
function get_grid(m::AbstractDSGEModel, grid_name::Symbol)
    return m.grids[grid_name]
end

function quadrature_sum(x::Vector{T}, grid::Grid) where {T<:Real}
    return sum(grid.weights .* x .* grid.points)
end

function quadrature_sum(grid::Grid, x::Vector{T}) where {T<:Real}
    return sum(x, grid)
end

# # Defining arithmetic on/standard function evaluation of grids
# for op in (:(Base.:+),
           # :(Base.:-),
           # :(Base.:*),
           # :(Base.:/))
           # # Exponentiation also omitted because of broadcasting issues

    # @eval ($op)(g::Grid, x::Integer)           = ($op)(g.points, x)
    # @eval ($op)(g::Grid, x::Number)            = ($op)(g.points, x)
    # if op == :(Base.:/)
        # op = :(Base.:./) # Will not work because the syntax for broadcasting has been
                         # # updated to broadcast(/, ...)?
        # @eval ($op)(x::Integer, g::Grid)       = ($op)(x, g.points)
        # @eval ($op)(x::Number, g::Grid)        = ($op)(x, g.points)
    # end
# end

# for f in (:(Base.:-),
          # :(Base.:<),
          # :(Base.:>),
          # :(Base.:<=),
          # :(Base.:>=))

    # @eval ($f)(g::Grid) = ($f)(g.points)

    # # Similar issues with broadcasting...
    # if f != :(Base.:-)
        # @eval ($f)(g::Grid, x::Number) = ($f)(g.points, x)
    # end
# end


using Plots, LaTeXStrings, Statistics

function Deposition(;len, tot_time)
    surf = [0 for i=1:len]
    AvgList = [0.0 for i=1:tot_time]
    for n in 2:tot_time+1
        randsurf = rand(1:len)
        for i in randsurf
            index = FindLeast(surf, i, len)
            surf[index] += 1
        end
        AvgList[n-1] = mean(surf)
    end
    return AvgList
end

function sides(n, L)
    if n == L
        return n-1 , 1
    elseif n == 1
        return L , n+1
    else
        return n-1, n+1
    end
end

function FindLeast(surface, index_,L_surf)
    i1 , i2 = sides(index_, L_surf)
    lens = Dict(surface[i1]=>i1,
        surface[index_]=>index_,
        surface[i2]=>i2)
    minlen = min(surface[i1],surface[index_],surface[i2])
    return lens[minlen]
end

function Linear_fit(;Time, AvgList, tot_time)
    A = [hcat(Time[1:tot_time]) reshape(ones(tot_time), tot_time, 1)]
    b = reshape(AvgList[1:tot_time], tot_time, 1)
    line = (A \ b)
    return line
end

iternum = 1000
Parameters = Dict(:len => 300,
                    :tot_time => 50,)
allAvg = [ [0.0 for i in 1:Parameters[:tot_time]] for j = 1:iternum]
meanAvg = [0.0 for i in 1:Parameters[:tot_time]]
vars = [0.0 for i in 1:Parameters[:tot_time]]
for i in 1:iternum
    AvgList = Deposition(;Parameters...)
    allAvg[i] = AvgList
    meanAvg += AvgList
    print("\r$i")
end
meanAvg /= iternum
for i in 1:Parameters[:tot_time]
    vars[i] = std(hcat(allAvg...)[i,:])
end

Time = 0:Parameters[:tot_time]-1

Paraline = Dict(
                :Time => Time[1:end],
                :AvgList => meanAvg[1:end],
                :tot_time => 30)
Line = Linear_fit(;Paraline...)
X = Time[1]:Time[50]
Y = X .* Line[1] .+ Line[2]
Line[2]
plot(X,Y,c=:black,label = L"y = %$(round(Line[1],digits= 3))x + %$(round(Line[2],digits= 3))")
scatter!(Time[1:end], meanAvg[1:end],
    # xlims = (1, Parameters[:tot_time]),
    c = :steelblue,
    xlabel= L"Time",
    ylabel= L"H_{(t)}",
    title= L"~H_{(t)}-Time~\ (L = %$(Parameters[:len]))",
    label = L"Data\ point",
    yerror = vars)
    # legend = nothing)

savefig("../../Figs/Q1/H-t(L=$(Parameters[:len])).pdf")


# example from https://github.com/malmaud/TensorFlow.jl
using Distributions
using Printf

# Generate some synthetic data
x = randn(100, 50)
w = randn(50, 10)
y_prob = exp.(x*w)
y_prob ./= sum(y_prob,dims=2)

function draw(probs)
    y = zeros(size(probs))
    for i in 1:size(probs, 1)
        idx = rand(Categorical(probs[i, :]))
        y[i, idx] = 1
    end
    return y
end

y = draw(y_prob)

# Build the model
reset_default_graph()
sess = Session()

X = placeholder(Float64, shape=[nothing, 50])
Y_obs = placeholder(Float64, shape=[nothing, 10])

variable_scope("test_logisitic_model"; initializer=random_normal_initializer(0, .001), reuse=AUTO_REUSE) do
    global W = get_variable("W", shape=[50, 10], dtype=Float64)
    global B = get_variable("B", shape=[10], dtype=Float64)
end

Y=softmax(X*W + B)

Loss = -sum(log(Y).*Y_obs)
# adam = AdamOptimizer()
# minimize_op = minimize(adam, Loss)

# # Run training
# init(sess)
# for epoch in 1:1000
#     cur_loss, _ = run(sess, [Loss, minimize_op], feed_dict=Dict(X=>x, Y_obs=>y))
#     println(@sprintf("Current loss is %.2f.", cur_loss))
# end

function print_loss(loss_evaled)
    println(loss_evaled)
end

opt = ScipyOptimizerInterface(Loss, method="L-BFGS-B",options=Dict("maxiter"=> 100))
init(sess)
ScipyOptimizerMinimize(sess, opt, feed_dict=Dict(X=>x, Y_obs=>y), loss_callback=print_loss, fetches=[Loss])


using DifferentialEquations
using OrdinaryDiffEq
using Distributions
using MonteCarloMeasurements
using StatsBase
using Plots

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

δt = 1.0
tmax = 40.0
tspan = (0.0,tmax);

N = 1000.0;

n_samples = 1000; # Number of samples

p = [Particles(n_samples,Uniform(0.01,0.1)),
      Particles(n_samples,Uniform(5,20.0)),
      Particles(n_samples,Uniform(0.1,1.0))]

I₀=Particles(n_samples,Uniform(1.0,50.0))
u0 = [N-I₀,I₀,0.0]

prob_ode = ODEProblem(sir_ode!,u0,tspan,p);

sol_ode = solve(prob_ode, Tsit5(), dt=δt);

s20 = sol_ode(20.0)

l = @layout [a b c]
binwidth = 50
pl1 = histogram(s20[1],bins=0:binwidth:N, title="S(20)", xlabel="S", ylabel="Frequency", color=:blue)
pl2 = histogram(s20[2],bins=0:binwidth:N, title="I(20)", xlabel="I", ylabel="Frequency", color=:red)
pl3 = histogram(s20[3],bins=0:binwidth:N, title="R(20)", xlabel="R", ylabel="Frequency", color=:green)
plot(pl1,pl2,pl3,layout=l,legend=false)

corkendall(hcat(Array(p),Array(I₀)),Array(s20))


export Cos

"""
`Cos([domainType=Float64::Type,] dim_in::Tuple)`

Creates a cosine non-linear operator with input dimensions `dim_in`:
```math
\\cos (\\mathbf{x} ).
```

"""
struct Cos{T,N} <: NonLinearOperator
	dim::NTuple{N,Int}
end

function Cos(DomainType::Type, DomainDim::NTuple{N,Int}) where {N}
	Cos{DomainType,N}(DomainDim)
end

Cos(DomainDim::NTuple{N,Int}) where {N} = Cos{Float64,N}(DomainDim)
Cos(DomainDim::Vararg{Int}) = Cos{Float64,length(DomainDim)}(DomainDim)

function mul!(y::AbstractArray{T,N}, L::Cos{T,N}, x::AbstractArray{T,N}) where {T,N}
	y .= cos.(x)
end

function mul!(y::AbstractArray,
              J::AdjointOperator{Jacobian{A,TT}},
              b::AbstractArray) where {T,N, A<: Cos{T,N}, TT <: AbstractArray{T,N}}
    L = J.A
    y .= -conj.(sin.(L.x)).*b
end

fun_name(L::Cos) = "cos"

size(L::Cos) = (L.dim, L.dim)

domainType(L::Cos{T,N}) where {T,N} = T
codomainType(L::Cos{T,N}) where {T,N} = T


using Polynomials

function mypolyval(p::Poly,x::Number)
    s=p[0]
    t=one(x)
    for i=1:length(p)-1
        t*=x
        s+=p[i]*t
    end
    s
end

function myhorner(p::Poly,x::Number)
    s=p[end]
    for i=length(p)-2:-1:0
        s=s*x+p[i]
    end
    s
end

n=10001
pbig=Poly(rand(n))
x=0.12345;

src/Module A - Short Julia Course/myfunctions.jl


export AbLoss, MSE, MAD, LOGIT, altloss

abstract type AbLoss end
# Notation
#       y = Observed target
#       z = fit Σ_j ϕ(X, ω_j)
# Methods
#       Loss evaluation
#       Generalized Residual
valnderv(l::AbLoss, y, z) = l(y, z), derv(l, y, z)
altloss(l::AbLoss) =  (_...) -> NaN

################################################################################
type MSELoss <: AbLoss
end

const MSE = MSELoss()

(l::MSELoss)(y, z) = sum(abs2, z-y)/2
derv(::MSELoss, y, z) = z-y
derv2(::MSELoss, y, z) = 1.
function valnderv(::MSELoss, y, z)
  diff = z-y
  sum(abs2, diff)/2, diff
end

################################################################################
type MADLoss <: AbLoss
end

const MAD = MADLoss()

(l::MADLoss)(y, z) =  sum(abs, z-y)
derv(::MADLoss, y, z) = sign.(z-y)
derv2(::MADLoss, y, z) = 0.
function valnderv(::MADLoss, y, z)
    diff = z-y
    sum(abs, diff), sign.(diff)
end

################################################################################
type LogitLoss <: AbLoss
end

const LOGIT = LogitLoss()

log1plusexp(x) = (x<0.) ? log1p(exp(x)) : x+log1p(exp(-x))
sigmoid(x) = (x<0.) ? exp(x)/(1.+exp(x)) : 1./(1.+exp(-x))
(l::LogitLoss)(y, z) =  sum(log1plusexp.(z)) - y'*z
derv(::LogitLoss, y, z) = sigmoid.(z) - y
derv2(::LogitLoss, y, z) = (s=sigmoid.(z); (1-s).*s)
"""
bits(sigmoid(-64)) = "0011101000101001011010011101010001110011001000011110010011001100"
bits(1-(1-sigmoid(-64))) = "0000000000000000000000000000000000000000000000000000000000000000"
"""

misclassification(y, z, threshold=.5) = sum(y .!= (sigmoid.(z) .≥ threshold))
altloss(::LogitLoss) =  misclassification
################################################################################


# some kernels
epanechnikov(x::Tp) where {Tp<:AbstractFloat} = Tp( (abs(x) ≤  1.0) ? 0.75 * (1.0-x*x) : 0.0 )
gaussian(x::Tp) where {Tp<:AbstractFloat} = Tp( exp( -0.5 * x * x) * 0.3989422804014327 )
triweight(x::Tp) where {Tp<:AbstractFloat} = Tp( (abs(x) ≤ 1.0) ? 1.09375 * (1.0-x*x)^3  : 0.0 )
uniform(x::Tp) where {Tp<:AbstractFloat} = Tp( (abs(x) ≤ 1.0) ? 0.5 : 0.0 )
cosinus(x::Tp) where {Tp<:AbstractFloat} = Tp( (abs(x) ≤ 1.0) ?   0.7853981633974483    * cos( 1.5707963267948966 * x ) : 0.0 )
quartic(x::Tp) where {Tp<:AbstractFloat} = Tp( (abs(x) ≤ 1.0) ? 0.9375 * (1.0 - x*x)^2 : 0.0 )


"""
# `SphericalGeom`

Calculate properties on a sphere.

By default, calculations are made in degrees, and all routines have a final
argument `degrees` which may be set to false for radians.  This is advantageous
when making repeated calculations or when using large arrays in terms of speed.
"""
module SphericalGeom

import StaticArrays: SVector

import Base.step

export
    azimuth,
    cart2geog,
    delta,
    geog2cart,
    great_circle,
    great_circle_azimuth,
    nearest_to_gcp,
    project_to_gcp,
    sample,
    step

"""
    cart2geog(x, y, z, degrees::Bool=true) -> lon, lat, r

Compute the longitude, latitude and radius given the cartesian coordinates `x`,
`y` and `z`, where `x` is at (lon,lat) = (0,0), `y` is at (90°,0) and `z` is
through lat = 90°.
"""
function cart2geog(x, y, z, degrees::Bool=true)
    r = sqrt(x^2 + y^2 + z^2)
    r == 0. && return zero(x), zero(x), zero(x)
    lon = atan(y, x)
    lat = asin(z/r)
    degrees ? (rad2deg(lon), rad2deg(lat), r) : (lon, lat, r)
end

function cart2geog(x::AbstractArray, y::AbstractArray, z::AbstractArray, degrees::Bool=true)
    dims = size(x)
    dims == size(y) == size(z) || throw(ArgumentError("All arrays must have same length"))
    T = promote_type(float.(eltype.((x, y, z)))...)
    lon, lat, r = Array{T}(dims), Array{T}(dims), Array{T}(dims)
    for i in eachindex(lon)
        lon[i], lat[i], r[i] = cart2geog(x[i], y[i], z[i], degrees)
    end
    lon, lat, r
end

"""
    delta(lon1, lat1, lon2, lat2, degrees::Bool=true) -> d

Compute the angular distance `d` on the sphere between two points, (`lon1`, `lat1`)
and (`lon2`, `lat2`).  Points and distance are read and returned in degrees
by default; use `degrees=false` for radians.
"""
function delta(lon1, lat1, lon2, lat2, degrees::Bool=true)
    if degrees
        lon1, lat1, lon2, lat2 = deg2rad(lon1), deg2rad(lat1), deg2rad(lon2), deg2rad(lat2)
    end
    d = atan(sqrt(
               (cos(lat2)*sin(lon2-lon1))^2 + (cos(lat1)*sin(lat2) -
                sin(lat1)*cos(lat2)*cos(lon2-lon1))^2),
               sin(lat1)*sin(lat2) + cos(lat1)*cos(lat2)*cos(lon2-lon1)
              )
    degrees ? rad2deg(d) : d
end

"""
    geog2cart(lon, lat, r, degrees::Bool=true) -> x, y, z
    geog2cart(lon, lat, degrees::Bool=true) -> x, y, z

Return the cartesian coordinates given the geographic longitude, latitude and
radius `lon`, `lat` and `r`.

If `r` is not given, points are returned on the unit sphere.
"""
function geog2cart(lon, lat, r, degrees::Bool=true)
    points_valid(lon, lat, degrees) || error("geog2cart: Points are not on the sphere")
    degrees && begin lon, lat = deg2rad(lon), deg2rad(lat) end
    x = r*cos(lon)*cos(lat)
    y = r*sin(lon)*cos(lat)
    z = r*sin(lat)
    x, y, z
end
function geog2cart(lon::AbstractArray, lat::AbstractArray, r::AbstractArray, degrees::Bool=true)
    size(lon) == size(lat) == size(r) ||
        throw(ArgumentError("Sizes of lon, lat and r must be the same"))
    T = promote_type(float.(eltype.((lon, lat, r)))...)
    x = Array{T}(undef, size(lon))
    y, z = similar(x), similar(x)
    for i in eachindex(lon)
        x[i], y[i], z[i] = geog2cart(lon[i], lat[i], r[i], degrees)
    end
    x, y, z
end
geog2cart(lon::AbstractArray, lat::AbstractArray, degrees::Bool=true) =
    geog2cart(lon, lat, fill(one(eltype(lon)), size(lon)), degrees)

"""
    step(lon, lat, az, delta, degrees::Bool=true) -> lon1, lat1

Compute the end point (`lon1`, `lat1`) reached by travelling on the sphere along
azimuth `az` for `delta` angular distance.  Points, angles and distance are read
and returned in degrees by default; use `degrees=false` for radians.
"""
function step(lon, lat, az, delta, degrees::Bool=true)
    if degrees
        delta <= 180 || error("step: delta cannot be more than 180°")
        lon, lat, az, delta = deg2rad(lon), deg2rad(lat), deg2rad(az), deg2rad(delta)
    else
        delta <= pi || error("step: delta cannot be more than π radians")
    end
    lat2 = asin(sin(lat)*cos(delta) + cos(lat)*sin(delta)*cos(az))
    lon2 = lon + atan(sin(az)*sin(delta)*cos(lat),
                      cos(delta)-sin(lat)*sin(lat2))
    if degrees
        lon2, lat2 = rad2deg(lon2), rad2deg(lat2)
    end
    lon2, lat2
end

"""
    azimuth(lon1, lat1, lon2, lat2, degrees::Bool=true) -> az

Compute the azimuth `az` from point (`lon1`, `lat1`) to (`lon2`, `lat2`) on the sphere.
Points and azimuth are read and returned in degrees by default; use `degrees=false`
for radians.
"""
function azimuth(lon1, lat1, lon2, lat2, degrees::Bool=true)
    if degrees
        lon1, lat1, lon2, lat2 = deg2rad(lon1), deg2rad(lat1), deg2rad(lon2), deg2rad(lat2)
    end
    azimuth = atan(sin(lon2-lon1)*cos(lat2),
                   cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(lon2-lon1))
    degrees ? rad2deg(azimuth) : azimuth
end

"""
    sample(d=5, degrees::Bool=true) -> lon[], lat[]

Create a set of points which approximately evenly sample the sphere, spaced about `d`
degrees apart (default 5°).  Returns arrays `lon` and `lat` containing the longitude
and latitude (degrees).  Spacing and points are read and returned in degrees by default;
use `false` as last argument to use radians.
"""
function sample(d=5, degrees::Bool=true)
    nmax = 50000
    lon = Array(Float64, nmax)
    lat = Array(Float64, nmax)
    n = 1
    dlat = d
    dlon = dlat
    lat[n] = 90.0
    lon[n] = 0.0
    lon_i = 0.0
    lat_i = lat[1] - dlat
    while lat_i > -90.0
        dlon_i = dlon/sind(90.0 - lat_i)
        n_i = round(Int, 360.0/dlon_i)
        for i = 1:n_i
            n += 1
            n <= nmax || error("Number of points greater than predetermined limits ($nmax)")
            lat[n] = lat_i
            lon[n] = lon_i
            lon_i = mod(lon_i + dlon_i, 360.0)
        end
        lon_i = mod(lon_i + dlon_i, 360.0)
        lat_i = lat_i - dlat
    end
    n += 1
    lat[n] = -90.0
    lon[n] = 0.0
    lon[1:n] = mod(lon[1:n] + 180.0, 360.0) - 180.0
    degrees ? (lon[1:n], lat[1:n]) : (rad2deg(lon[1:n], rad2deg(lat[1:n])))
end

"""
    project_to_gcp(lon_gc, lat_gc, lon_p, lat_p, degrees::Bool=true) -> lon, lat

Project the point at (`lon_p`, `lat_p`) onto the great circle defined by the
pole to the circle (`lon_gc`, `lat_gc`), returning the point (`lon`, `lat`).
"""
function project_to_gcp(long, latg, lonp, latp, degrees::Bool=true)
    # Convert to vectors
    g = SVector(geog2cart(long, latg, degrees)...)
    p = SVector(geog2cart(lonp, latp, degrees)...)
    # Pole to the gcp containing g and p
    gp = g × p
    # Check for point and pole being the same
    norm(gp) ≈ 0. && error("Point and pole to plane the same")
    gp = normalize(gp)
    pp = gp × g
    acos(p⋅pp) > π/2 && (pp *= -1)
    lon, lat, r = cart2geog(pp[1], pp[2], pp[3], degrees)
    lon, lat
end

"""
    nearest_to_gcp(lon_gc, lat_gc, lons, lats, degrees::Bool=trye) -> i, lon, lat

Find the nearest point to the great circle defined by the pole (`lon_gc`, `lat_gc`)
which occurs in the vectors `lons` and `lats`.  The index `i` and values at that index
`lon` and `lat` are returned.
"""
function nearest_to_gcp(long, latg, lons::AbstractArray, lats::AbstractArray, degrees::Bool=true)
    length(lons) == length(lats) ||
        throw(ArgumentError("lons and lats must have same number of points"))
    mindist = Inf
    imin = 0
    for (i, (lon, lat)) in enumerate(zip(lons, lats))
        lonp, latp = project_to_gcp(long, latg, lon, lat, degrees)
        dist = delta(lon, lat, lonp, latp, degrees)
        if dist < mindist
            mindist = dist
            imin = i
        end
    end
    imin, lons[imin], lats[imin]
end

"""
    great_circle(lon, lat, azimuth, degrees::Bool=true) -> lon_gc, lat_gc
    great_circle(lon1, lat1, lon2, lat2, degrees::Bool=true) -> lon_gc, lat_gc

Determine the great circle, defined by the pole at (`lon_gc`, `lat_gc`), given by
either (the first form), a point and an azimuth, or (the second) two points on the
sphere.
"""
function great_circle(lon, lat, azimuth, degrees::Bool=true)
    dist = degrees ? 45 : π/4
    lonp, latp = step(lon, lat, azimuth, dist, degrees)
    great_circle(lon, lat, lonp, latp, degrees)
end
function great_circle(lon1, lat1, lon2, lat2, degrees::Bool=true)
    p1 = SVector(geog2cart(lon1, lat1, degrees)...)
    p2 = SVector(geog2cart(lon2, lat2, degrees)...)
    p1 ≈ p2 && error("Two points overlap (are: $p1 and $p2)")
    delta(lon1, lat1, lon2, lat2, degrees) ≈ (degrees ? 180 : π) &&
        error("Points are antipodal (are: $p1 and $p2)")
    gp = p1 × p2
    lon, lat, r = cart2geog(gp[1], gp[2], gp[3], degrees)
    lon, lat
end

"""
    great_circle_azimuth(long, latg, lonp, latp, degrees::Bool=true) -> azimuth

Return the local `azimuth` of the great circle with pole (`long`, `latg`) where
the point (`lonp`, `latp`) projects onto the great circle.
"""
function great_circle_azimuth(long, latg, lonp, latp, degrees::Bool=true)
    lon, lat = project_to_gcp(long, latg, lonp, latp, degrees)
    mod(azimuth(lon, lat, long, latg, degrees) + (degrees ? 90 : π/2), (degrees ? 360 : 2π))
end

"""
    points_valid(lon, lat, degrees::Bool=true) -> ::Bool

Return `true` if all points in arrays `lon` and `lat` are on the sphere.
`lon` is ignored, but `lat` is checked to see if points are in the range
-90°–90° (-π–π).  Points are read and returned in degrees by default; use
`degrees=false` for radians.
"""
points_valid(lon, lat, degrees::Bool=true) =
    degrees ? !any(abs.(lat) .> 90.) : !any(abs.(lat) .> pi/2.)

end # module


"""
    tracking_error(asset_returns, benchmark_returns; multiplier=1.0)

Calculates the ex-post Tracking Error based on the standard deviation of the active returns.

# Formula

    TE = std(asset_returns - benchmark_returns) * sqrt(multiplier)

# Arguments
- `asset_returns`:      Vector of asset returns.
- `benchmark_returns`:  Vector of benchmark returns.
- `multiplier`:         Optional scalar multiplier, i.e. use `12` to annualize monthly returns, and use `252` to annualize daily returns.
"""
function tracking_error(asset_returns, benchmark_returns; multiplier=1.0)
    std(asset_returns .- benchmark_returns) * sqrt(multiplier)
end


# this is the funtion that appears in the
# FoggySurface and FoggyContour graphics,
# It is the Octave sombrero, tilted a bit
function FoggyMountainObj(θ₁, θ₂)
   	r = sqrt(θ₁^2.0 + θ₂^2.0) + 1e-10
	z = sin(r) / (2*r)
	z = z + θ₁/40.0 - 2*(θ₁/40.0)^2.0 + θ₂/40.0 - 2*(θ₂/40.0)^2.0
	z = -z/10.0 # switch to minimization
end

function FoggyMountainObj(θ)
	θ₁ = θ[1]
	θ₂ = θ[2]
    FoggyMountainObj(θ₁, θ₂)
end


# # Specifying and fine-tuning prior distributions
#
# This example shows how to specify priors in `ParameterEstimocean`
# and illustrates some of their properties.
#
# ## Install dependencies
#
# First let's make sure we have all required packages installed.

# ```julia
# using Pkg
# pkg"add ParameterEstimocean, Oceananigans, Distributions, CairoMakie"
# ```
#
# Then we import packages

using ParameterEstimocean
using CairoMakie
using Distributions

# # What's a prior?
#
# A "prior" is a probability distribution for an known parameter that
# represents "prior knowledge" of the problem at hand, before we generate
# new knowledge with a new model-data comparison via ensemble Kalman inversion (EKI).
#
# In the (EKI) context, a prior distribution determines (i) the initial distribution
# of parameter values in the ensemble and (ii) the mapping between physical space
# where constraints on parameter values are applied, and the "unconstrained" space
# in which the EKI dynamics evolve (and where parameters are assumed normally-distributed).
#
# # Prior flavors
#
# `ParameterEstimocean` supports three types of prior
# distributions. The normal prior distribution is provided by `Distributions.jl`,

normal_prior = Normal(0.1, 0.1)

# For `LogNormal` priors, we provide a special constructor that takes the `mean` and
# standard deviation `std` of the prior distribution in _constrained_ space:

lognormal_prior = lognormal(mean=0.1, std=0.1)

# `lognormal` calculates `μ` and `σ` for `Distributions.Lognormal(μ, σ)`.
# Parameters with a `Lognormal` prior distribution are _strictly_ positive.
# Finally, the "scaled" logit-normal prior,

logitnormal_prior = ScaledLogitNormal(bounds=(0, 0.2))

# can be used to provide strict upper and lower bounds for parameter values.
#
# ## Samples
#
# Sampling the distributions reveals their properties. We define a function to use
# later, too.

function visualize_priors(priors; samples = 10^6)
    fig = Figure()
    ax = Axis(fig[1, 1:9], xlabel="Random samples of priors", ylabel="Density")

    for (prior, name) in zip(values(priors), keys(priors))
        density!(ax, rand(prior, samples), label=replace(string(name), "_" => " "))
    end

    axislegend(ax)

    return fig, ax
end

fig, ax = visualize_priors(Dict("Normal" => normal_prior,
                                "Log normal" => lognormal_prior,
                                "Scaled logit-normal" => logitnormal_prior))

xlims!(ax, -0.5, 1.5)
save("prior_flavors.svg", fig); nothing # hide

# ![](prior_flavors.svg)

# We note three important features:
#
# 1. `Normal` samples can be negative.
# 2. `Lognormal` samples cannot be negative, but can have large positive values.
# 3. `ScaledLogitNormal` samples have compact support, and thus strict upper and lower bounds.
#
# Boundedness is a very useful property of `ScaledLogitNormal`, so we
# explore specifying `ScaledLogitNormal` priors in more detail.
#
# # Fine-tuning a `ScaledLogitNormal` prior
#
# We can tune the "width" of a `ScaledLogitNormal` by supplying the
# standard-deviation `σ`:

logit_priors = Dict("Narrow (σ = 0.1)"                 => ScaledLogitNormal(σ = 0.1),
                    "Default (σ = 1)"                  => ScaledLogitNormal(σ = 1),
                    "Weird (σ = 4 with bounds=(0, 1))" => ScaledLogitNormal(σ = 4))

fig, ax = visualize_priors(logit_priors)
xlims!(ax, -0.1, 1.1)
save("logit_normal_widths.svg", fig); nothing #hide

# ![](logit_normal_widths.svg)

# Smaller standard deviations produce narrower prior distributions.
# However, weird shapes can result when a standard deviation is specified
# that's larger than the bounds interval (eg `σ > diff(bounds)`).
# In the above example we're using the default `bounds = (0, 1)`.
#
# Another way to build `ScaledLogitNormal` prior is to specify
# a central `interval` and `mass`. This allows us to shift the center of
# mass relative to the bounds (here we use the default `bounds=(0, 1)`),
# and provides a slightly safer way to tune a distribution.

shifted_priors = Dict("Shifted left"  => ScaledLogitNormal(interval=(0.2, 0.3), mass=0.9),
                      "Shifted right" => ScaledLogitNormal(interval=(0.7, 0.8), mass=0.9),
                      "Centered"      => ScaledLogitNormal(interval=(0.2, 0.8), mass=0.6))

fig, ax = visualize_priors(shifted_priors)
xlims!(ax, -0.1, 1.1)
save("logit_normal_shifting.svg", fig); nothing #hide

# ![](logit_normal_shifting.svg)
#
# The "Centered" case uses a `mass` that's proportional to the relative width spanned
# by `interval` (eg `mass = diff(interval) / diff(bounds)`) to produce a nearly
# uniform distribution within the bounds.


## Basic weight functions

export LebesgueMeasure,
    Lebesgue,
    LebesgueDomain,
    lebesguemeasure,
    dx,
    LegendreWeight,
    JacobiWeight,
    ChebyshevWeight,
    ChebyshevTWeight,
    ChebyshevUWeight,
    LaguerreWeight,
    HermiteWeight,
    GaussianWeight,
    DiracWeight,
    point,
    jacobi_α,
    jacobi_β,
    laguerre_α

####################
# Lebesgue measures
####################

"Supertype of Lebesgue measures"
abstract type LebesgueMeasure{T} <: Weight{T} end

unsafe_weightfun(μ::LebesgueMeasure, x) = one(codomaintype(μ))

islebesguemeasure(m::Measure) = false
islebesguemeasure(m::LebesgueMeasure) = true

==(μ1::LebesgueMeasure, μ2::LebesgueMeasure) = support(μ1) == support(μ2)

"The Lebesgue measure on the space `FullSpace{T}`."
struct Lebesgue{T} <: LebesgueMeasure{T}
end

Lebesgue() = Lebesgue{Float64}()
similar(μ::Lebesgue, ::Type{T}) where {T} = Lebesgue{T}()

Base.show(io::IO, μ::Lebesgue) = print(io, "dx")

"The Lebesgue measure on the unit interval `[0,1]`."
struct LebesgueUnit{T} <: LebesgueMeasure{T}
end

LebesgueUnit() = LebesgueUnit{Float64}()
similar(μ::LebesgueUnit, ::Type{T}) where {T <: Real} = Lebesgue{T}()
support(μ::LebesgueUnit{T}) where {T} = UnitInterval{T}()

isnormalized(μ::LebesgueUnit) = true

Base.show(io::IO, μ::LebesgueUnit) = print(io, "dx(0..1)")

"Lebesgue measure supported on a general domain."
struct LebesgueDomain{T} <: LebesgueMeasure{T}
    domain  ::  Domain{T}
end

similar(μ::LebesgueDomain, ::Type{T}) where {T} = LebesgueDomain{T}(μ.domain)
support(m::LebesgueDomain) = m.domain

Base.show(io::IO, mime::MIME"text/plain", μ::LebesgueDomain) = composite_show(io, mime, μ)
Display.displaystencil(μ::LebesgueDomain) = ["dx(", support(μ), ")"]

###############
# Dirac weight
###############

"A continuous Dirac measure at a point"
struct DiracWeight{T} <: Weight{T}
    point   ::  T
end

similar(μ::DiracWeight, ::Type{T}) where {T} = DiracWeight{T}(μ.point)

point(μ::DiracWeight) = μ.point
support(μ::DiracWeight) = Point(μ.point)
isnormalized(μ::DiracWeight) = true
unsafe_weightfun(μ::DiracWeight, x) = convert(codomaintype(μ), Inf)

Base.show(io::IO, μ::DiracWeight) = iszero(point(μ)) ? print(io, "δ(x)dx") : print(io, "δ(x-$(repr(point(μ))))dx")

###################################
# Classical orthogonal polynomials
###################################

"The Legendre weight is a Lebesgue measure on the interval `[-1,1]`."
struct LegendreWeight{T} <: LebesgueMeasure{T}
end
LegendreWeight() = LegendreWeight{Float64}()

similar(μ::LegendreWeight, ::Type{T}) where {T <: Real} = LegendreWeight{T}()
support(μ::LegendreWeight{T}) where {T} = ChebyshevInterval{T}()

Base.show(io::IO, μ::LegendreWeight) = print(io, "dx(-1..1)  (Legendre)")
Display.object_parentheses(μ::LegendreWeight) = true

"Supertype of Jacobi weights."
abstract type AbstractJacobiWeight{T} <: Weight{T} end

==(μ1::AbstractJacobiWeight, μ2::AbstractJacobiWeight) =
    jacobi_α(μ1) == jacobi_α(μ2) && jacobi_β(μ1) == jacobi_β(μ2)

"The Jacobi weight on the interval `[-1,1]`."
struct JacobiWeight{T} <: AbstractJacobiWeight{T}
    α   ::  T
    β   ::  T

    JacobiWeight{T}(α = zero(T), β = zero(T)) where {T} = new(α, β)
end
JacobiWeight() = JacobiWeight{Float64}()
JacobiWeight(α, β) = JacobiWeight(promote(α, β)...)
JacobiWeight(α::T, β::T) where {T<:AbstractFloat} = JacobiWeight{T}(α, β)
JacobiWeight(α::N, β::N) where {N<:Number} = JacobiWeight(float(α), float(β))

jacobi_weightfun(x, α, β) = (1+x)^α * (1-x)^β

similar(μ::JacobiWeight, ::Type{T}) where {T <: Real} = JacobiWeight{T}(μ.α, μ.β)
support(μ::JacobiWeight{T}) where {T} = ChebyshevInterval{T}()
unsafe_weightfun(μ::JacobiWeight, x) = jacobi_weightfun(x, μ.α, μ.β)

jacobi_α(μ::JacobiWeight) = μ.α
jacobi_β(μ::JacobiWeight) = μ.β

Base.show(io::IO, μ::JacobiWeight) = print(io, "(1+x)^$(jacobi_α(μ))(1-x)^$(jacobi_β(μ)) dx  (Jacobi)")
Display.object_parentheses(μ::JacobiWeight) = true

"""
The `Chebyshev` or `ChebyshevT` weight is the measure on `[-1,1]` with the
Chebyshev weight function `w(x) = 1/√(1-x^2)`.
"""
struct ChebyshevTWeight{T} <: AbstractJacobiWeight{T}
end
ChebyshevTWeight() = ChebyshevTWeight{Float64}()

const ChebyshevWeight = ChebyshevTWeight

chebyshev_weight_firstkind(x) = 1/sqrt(1-x^2)

jacobi_α(μ::ChebyshevTWeight{T}) where {T} = -one(T)/2
jacobi_β(μ::ChebyshevTWeight{T}) where {T} = -one(T)/2

similar(μ::ChebyshevTWeight, ::Type{T}) where {T <: Real} = ChebyshevTWeight{T}()
support(μ::ChebyshevTWeight{T}) where {T} = ChebyshevInterval{T}()
unsafe_weightfun(μ::ChebyshevTWeight, x) = chebyshev_weight_firstkind(x)

weightfunction(μ::ChebyshevTWeight) = chebyshev_weight_firstkind

Base.show(io::IO, μ::ChebyshevTWeight) = print(io, "1/√(1+x)^2 dx  (ChebyshevT)")
Display.object_parentheses(μ::ChebyshevTWeight) = true

"""
The ChebyshevU weight is the measure on `[-1,1]` with the Chebyshev weight
function of the second kind `w(x) = √(1-x^2).`
"""
struct ChebyshevUWeight{T} <: AbstractJacobiWeight{T}
end
ChebyshevUWeight() = ChebyshevUWeight{Float64}()

chebyshev_weight_secondkind(x) = sqrt(1-x^2)

jacobi_α(μ::ChebyshevUWeight{T}) where {T} = one(T)/2
jacobi_β(μ::ChebyshevUWeight{T}) where {T} = one(T)/2

similar(μ::ChebyshevUWeight, ::Type{T}) where {T <: Real} = ChebyshevUWeight{T}()
support(μ::ChebyshevUWeight{T}) where {T} = ChebyshevInterval{T}()
unsafe_weightfun(μ::ChebyshevUWeight, x) = chebyshev_weight_secondkind(x)

weightfunction(μ::ChebyshevUWeight) = chebyshev_weight_secondkind

Base.show(io::IO, μ::ChebyshevUWeight) = print(io, "√(1+x)^2 dx  (ChebyshevU)")
Display.object_parentheses(μ::ChebyshevUWeight) = true

convert(::Type{JacobiWeight}, μ::LegendreWeight{T}) where {T} =
    JacobiWeight{T}(0, 0)
convert(::Type{JacobiWeight}, μ::ChebyshevTWeight{T}) where {T} =
    JacobiWeight{T}(-one(T)/2, -one(T)/2)
convert(::Type{JacobiWeight}, μ::ChebyshevUWeight{T}) where {T} =
    JacobiWeight{T}(one(T)/2, one(T)/2)

function convert(::Type{ChebyshevTWeight}, μ::JacobiWeight{T}) where {T}
    (jacobi_α(μ) ≈ -one(T)/2 && jacobi_β(μ) ≈ -one(T)/2) || throw(InexactError(:convert, ChebyshevTWeight, μ))
    ChebyshevTWeight{T}()
end

function convert(::Type{ChebyshevUWeight}, μ::JacobiWeight{T}) where {T}
    (jacobi_α(μ) ≈ one(T)/2 && jacobi_β(μ) ≈ one(T)/2) || throw(InexactError(:convert, ChebyshevUWeight, μ))
    ChebyshevUWeight{T}()
end

function convert(::Type{LegendreWeight}, μ::JacobiWeight{T}) where {T}
    (μ.α ≈ 0 && μ.β ≈ 0) || throw(InexactError(:convert, LegendreWeight, μ))
    LegendreWeight{T}()
end

jacobi_α(μ::LegendreWeight{T}) where {T} = zero(T)
jacobi_β(μ::LegendreWeight{T}) where {T} = zero(T)

==(μ1::AbstractJacobiWeight, μ2::LegendreWeight) =
    jacobi_α(μ1) == jacobi_α(μ2) && jacobi_β(μ1) == jacobi_β(μ2)
==(μ1::LegendreWeight, μ2::AbstractJacobiWeight) =
    jacobi_α(μ1) == jacobi_α(μ2) && jacobi_β(μ1) == jacobi_β(μ2)

"The generalised Laguerre measure on the halfline `[0,∞)`."
struct LaguerreWeight{T} <: Weight{T}
    α   ::  T

    LaguerreWeight{T}(α = zero(T)) where {T} = new(α)
end
LaguerreWeight() = LaguerreWeight{Float64}()
LaguerreWeight(α::T) where {T<:AbstractFloat} = LaguerreWeight{T}(α)
LaguerreWeight(α) = LaguerreWeight(float(α))

laguerre_weightfun(x, α) = exp(-x) * x^α

similar(μ::LaguerreWeight, ::Type{T}) where {T <: Real} = LaguerreWeight{T}(μ.α)
support(μ::LaguerreWeight{T}) where {T} = HalfLine{T}()
isnormalized(m::LaguerreWeight) = m.α == 0

unsafe_weightfun(μ::LaguerreWeight, x) = laguerre_weightfun(x, μ.α)

laguerre_α(μ::LaguerreWeight) = μ.α

Base.show(io::IO, μ::LaguerreWeight) =
    laguerre_α(μ) == 0 ? print(io, "exp(-x)dx  (Laguerre)") : print(io, "x^$(laguerre_α(μ))exp(-x)dx  (Laguerre)")
Display.object_parentheses(μ::LaguerreWeight) = true

"The Hermite measure with weight exp(-x^2) on the real line."
struct HermiteWeight{T} <: Weight{T}
end
HermiteWeight() = HermiteWeight{Float64}()

hermite_weightfun(x) = exp(-x^2)

similar(μ::HermiteWeight, ::Type{T}) where {T <: Real} = HermiteWeight{T}()
unsafe_weightfun(μ::HermiteWeight, x) = hermite_weightfun(x)

weightfunction(μ::HermiteWeight) = hermite_weightfun

Base.show(io::IO, μ::HermiteWeight) = print(io, "exp(-x^2)dx  (Hermite)")
Display.object_parentheses(μ::HermiteWeight) = true

"The Gaussian measure with weight exp(-|x|^2/2)."
struct GaussianWeight{T} <: Weight{T}
end
GaussianWeight() = GaussianWeight{Float64}()

gaussian_weightfun(x, ::Type{T} = prectype(x)) where {T} =
    1/(2*convert(T, pi))^(length(x)/2) * exp(-norm(x)^2)

similar(μ::GaussianWeight, ::Type{T}) where {T} = GaussianWeight{T}()
isnormalized(μ::GaussianWeight) = true
unsafe_weightfun(μ::GaussianWeight, x) = gaussian_weightfun(x, prectype(μ))

Base.show(io::IO, μ::GaussianWeight{T}) where {T<:StaticTypes} =
    print(io, "1/(2π)^($(euclideandimension(T)/2)) exp(-|x|^2)dx  (Gauss)")
Base.show(io::IO, μ::GaussianWeight) = print(io, "C(d) exp(-|x|^2)dx  (Gauss)")
Display.object_parentheses(μ::GaussianWeight) = true

"The Lebesgue measure associated with the given domain"
lebesguemeasure(domain::UnitInterval{T}) where {T} = LebesgueUnit{T}()
lebesguemeasure(domain::ChebyshevInterval{T}) where {T} = LegendreWeight{T}()
lebesguemeasure(domain::FullSpace{T}) where {T} = Lebesgue{T}()
lebesguemeasure(domain::Domain{T}) where {T} = LebesgueDomain{T}(domain)

dx(domain::Domain) = lebesguemeasure(domain)


# # [Sparse Identification with noisy data](@id noisy_sindy)
#
# Many real world data sources are corrupted with measurment noise, which can have
# a big impact on the recovery of the underlying equations of motion. This example show how we can
# use [`collocation`](@ref collocation) and [`batching`](@ref DataSampler) to perform SINDy in the presence of
# noise.

using DataDrivenDiffEq
using LinearAlgebra
using ModelingToolkit
using OrdinaryDiffEq
#md using Plots
#md gr()

function pendulum(u, p, t)
    x = u[2]
    y = -9.81sin(u[1]) - 0.3u[2]^3 -3.0*cos(u[1]) - 10.0*exp(-((t-5.0)/5.0)^2)
    return [x;y]
end

u0 = [0.99π; -1.0]
tspan = (0.0, 15.0)
prob = ODEProblem(pendulum, u0, tspan)
sol = solve(prob, Tsit5(), saveat = 0.01);

# We add random noise to our measurements.

X = sol[:,:] + 0.2 .* randn(size(sol));
ts = sol.t;

#md plot(ts, X', color = :red)
#md plot!(sol, color = :black)

# To estimate the system, we first create a [`DataDrivenProblem`](@ref) via feeding in the measurement data.
# Using a [Collocation](@ref) method, it automatically provides the derivative and smoothes the trajectory. Control signals can be passed
# in as a function `(u,p,t)->control` or an array of measurements.

prob = ContinuousDataDrivenProblem(X, ts, GaussianKernel() ,
    U = (u,p,t)->[exp(-((t-5.0)/5.0)^2)], p = ones(2))

#md plot(prob, size = (600,600))

# Now we infer the system structure. First we define a [`Basis`](@ref) which collects all possible candidate terms.
# Since we want to use SINDy, we call `solve` with an [`Optimizer`](@ref sparse_optimization), in this case [`STLSQ`](@ref) which iterates different sparsity thresholds
# and returns a pareto optimal solution of the underlying [`sparse_regression!`](@ref). Note that we include the control signal in the basis as an additional variable `c`.

@variables u[1:2] c[1:1]
@parameters w[1:2]
u = collect(u)
c = collect(c)
w = collect(w)

h = Num[sin.(w[1].*u[1]);cos.(w[2].*u[1]); polynomial_basis(u, 5); c]

basis = Basis(h, u, parameters = w, controls = c)

# To solve the problem, we also define a [`DataSampler`](@ref) which defines randomly shuffled minibatches of our data and selects the
# best fit.

sampler = DataSampler(Batcher(n = 5, shuffle = true, repeated = true))
λs = exp10.(-10:0.1:-1)
opt = STLSQ(λs)
res = solve(prob, basis, opt, progress = false, sampler = sampler, denoise = false, normalize = false, maxiter = 5000)
println(res) #hide

# !!! info
#     A more detailed description of the result can be printed via `print(res, Val{true})`, which also includes the discovered equations and parameter values.
#
# Where the resulting [`DataDrivenSolution`](@ref) stores information about the inferred model and the parameters:

system = result(res)
params = parameters(res)
println(system) #hide
println(params) #hide

# And a visual check of the result can be perfomed via plotting the result

#md plot(
#md     plot(prob), plot(res), layout = (1,2)
#md )

#md # ## [Copy-Pasteable Code](@id autoregulation_copy_paste)
#md #
#md # ```julia
#md # @__CODE__
#md # ```

## Test #src
for r_ in [res] #src
    @test all(aic(r_) .> 1e3) #src
    @test all(determination(r_) .>= 0.9) #src
end #src


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

The radical of n, rad(n), is the product of distinct prime factors of n. For
example, 504 = 2^3 × 3^2 × 7, so rad(504) = 2 × 3 × 7 = 42.

We shall define the triplet of positive integers (a, b, c) to be an abc-hit if:

    gcd(a, b) = gcd(a, c) = gcd(b, c) = 1
    a < b
    a + b = c
    rad(abc) < c

For example, (5, 27, 32) is an abc-hit, because:

    gcd(5, 27) = gcd(5, 32) = gcd(27, 32) = 1
    5 < 27
    5 + 27 = 32
    rad(4320) = 30 < 32

It turns out that abc-hits are quite rare and there are only thirty-one abc-hits
for c < 1000, with ∑c = 12523.

Find ∑c for c < 120000.

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

Analyse
-------

1) a < b; a + b = c < bound
    a)  ⇒ 2a < a + b = c < bound - 1 ⇒ 1 ≤ a < bound ÷ 2
    b)  ⇒ b < bound - a

2) if a and b are coprime, then c = a+b is coprime with a and with b
    => gcd(a, b) = 1 ⇒ gcd(a, c) = gcd(b, c) = 1

3) gcd(a, b) == 1 ⇔ gcd(rad(a), rad(b)) == 1

4) if a and b are coprime, the rad(ab) = rad(a) rad(b)

5) rad(n) = 1 if n = 1
          ≥ 2 if n > 1

6) So the conditions can be simplifed to
        gcd(a, b) = 1
        1 ≤ a < bound ÷ 2
        a < b < bound - a
        c = a + b
        rad(a) rad(b) rad(c) < c

7) Precomputing rad(n) for all n in 2..bound-2

case a=1
--------
    gcd(1,b) = 1 is granted
    2 ≤ b ≤ bound-2
    c = 1+b
    rad(b) rad(c) < c ⇒ rad(b) < (1+b) ÷ 2
                      ⇒ rad(b) rad(1+b) < 1+b

    Therefore the constraints are:
        1 < b < bound-1
        c = b+1
        rad(b) rad(b+1) < (b+1)

case a>1
--------
    a < b => a+1 ≤ b,
    2a+1 ≤ a + b = c < bound => a < (bound-1)÷2
                             => a ≤ (bound-1)÷2 -1
    rad(a) ≥ 2, rad(b) ≥ 2, rad(c) ≥ 2
    rad(a) rad(b) rad(a+b) < a+b ⇒ rad(a) < c ÷ 4
                                 ⇒ rad(b) < c ÷ (2 rad(a))

    Therefore the constraints are:
        2   ≤ a ≤ (bound-1)÷2 -1
        a+1 ≤ b ≤ bound - 1 - a
        gcd(a,b) = 1
        c = a + b
        rad(a) < c ÷ 4                  < (bound-1) ÷ 4
        rad(b) < c ÷ (2 rad(a))         < (bound-1) ÷ 4
        rad(c) < c ÷ (rad(a) rad(b))    < (bound-1) ÷ 4

Conclusion
==========

The principle behind the algorithm is to precompute rad(n) for 2 ≤ n ≤ bound-, and to proceed to all possible tests before computing gcd(rad(a), rad(b)).

Note that precomputing gcd(rad(a), rad(b)) for all possible a and b is not efficient.

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

import Primes

function euler(bound::Int)
    #precimpute the radicals
    rad = Array{Int}(bound-1)
    for n in 1:bound-1
        rad[n]=prod(keys(Primes.factor(n)))
    end

    res::Int = 0

    # case a=1
    for b in 2:bound-2
        c=b+1
        if (rad[b]*rad[c] < c) res+=c end
    end

    # case a>1
    for a in 2:(bound-1) ÷ 2 -1
        rad_a = rad[a]
        if (rad_a ≥ (bound-1) ÷ 4) continue end

        for b in a+1:bound-1-a
            rad_b = rad[b]
            c=a+b
            if (rad_a * rad_b * rad[c] ≥ c) continue end
            if (gcd(rad_a, rad_b) == 1) res += c end
        end
    end

    res
end

println("  1_000: $(euler(  1_000))")
@time println("120_000: $(euler(120_000))")


if VERSION >= v"0.6.0-"
    using SpecialFunctions: besseli
end

# This file contains different window functions.
# The function getWindow returns a pair of window functions based on a string

function getWindow(window::Symbol)
    if window == :gauss
        return window_gauss, window_gauss_hat
    elseif window == :spline
        return window_spline, window_spline_hat
    elseif window == :kaiser_bessel_rev
        return window_kaiser_bessel_rev, window_kaiser_bessel_rev_hat
    else # default to kaiser_bessel
        return window_kaiser_bessel, window_kaiser_bessel_hat
    end
end

function window_kaiser_bessel(x,n,m,sigma)
    b = pi*(2-1/sigma)
    arg = m^2-n^2*x^2
    if abs(x) < m/n
        y = sinh(b*sqrt(arg))/sqrt(arg)/pi
    elseif abs(x) > m/n
        y = zero(x)
    else
        y = b/pi
    end
    return y
end

function window_kaiser_bessel_hat(k,n,m,sigma)
    b = pi*(2-1/sigma)
    return besseli(0,m*sqrt(b^2-(2*pi*k/n)^2))
end

function window_kaiser_bessel_rev(x,n,m,sigma)
    b = pi*(2-1/sigma)
    if abs(x) < m/n
        arg = m*b*sqrt(1-(n*x/m)^2)
        y = 0.5/m*besseli(0,arg)
    else
        y = zero(x)
    end
    return y
end

function window_kaiser_bessel_rev_hat(k,n,m,sigma)
    b = pi*(2-1/sigma)

    arg = sqrt(complex((2*pi*m*k/n)^2-(m*b)^2))
    return sinc(arg/pi)
end

function window_gauss(x,n,m,sigma)
    b = m / pi
    if abs(x) < m/n
        y = 1 / sqrt(pi*b) * exp(-(n*x)^2 / b)
    else
        y =  zero(x)
    end
    return y
end

function window_gauss_hat(k,n,m,sigma)
    b = m / pi
    return exp(-(pi*k/(n))^2 * b)
end

function cbspline(m,x)
    if m == 1
        if x>=0 && x<1
            y = one(x)
        else
            y = zero(x)
        end
    else
        y = x/(m-1)*cbspline(m-1,x) + (m-x)/(m-1)*cbspline(m-1,x-1)
    end
    return y
end

function window_spline(x,n,m,sigma)
    if abs(x) < m/n
        y = cbspline(2*m, n*x+m)
    else
        y = zero(x)
    end
    return y
end

function window_spline_hat(k,n,m,sigma)
    return (sinc(k/n))^(2*m)
end


d=2;
A=Array{Int64,3}(d,d,d); B=Array{Int64,3}(d,d,d); C=zeros(Int64,d,d)
for i in 1:d,j in 1:d,k in 1:d
    A[i,j,k] = 2i+j^2-k^3
    B[i,j,k] = i+2j+4k
end

tic()
for i in 1:size(A,1)
    for j in 1:size(A,3)
        for k in 1:size(A,1)
            for n in 1:size(B,3)
                C[j,n] += A[i,j,k]*B[k,i,n]
            end
        end
    end
end
toc()
print("Matrix C is ", C)

A_permuted = permutedims(A,[2,1,3]); B_permuted = permutedims(B,[2,1,3]);
A_reshaped = reshape(A_permuted,2,4); B_reshaped = reshape(B_permuted,4,2);
tic()
C_reshaped = A_reshaped*B_reshaped;
toc()
print("Matrix C is ", C_reshaped)

A=rand(Int64,100,100,100); B=rand(Int64,100,100,100);
C=zeros(Int64,100,100);

tic()
for i in 1:size(A,1)
    for j in 1:size(A,3)
        for k in 1:size(A,1)
            for n in 1:size(B,3)
                C[j,n] += A[i,j,k]*B[k,i,n]
            end
        end
    end
end
toc()

A_reshaped=reshape(permutedims(A,[2,1,3]),100,10000);
B_reshaped=reshape(permutedims(B,[2,1,3]),10000,100);
tic()
C_reshaped = A_reshaped*B_reshaped;
toc()

X=Complex{Float64}[0.0 1.0;1.0 0.0];
Y=Complex{Float64}[0.0im -1.0im;1.0im 0.0im];
Z=Complex{Float64}[1.0 0.0;0.0 -1.0];
h=kron(X,X)+kron(Y,Y)+kron(Z,Z);
N=4; C=2; d=2;
H=zeros(Complex{Float64},d^N,d^N);

for l in 0:N-C
    H+=kron(kron(eye(d^l),h),eye(d^(N-C-l)))
end
E,V = eig(H)
print("The first eigenvalues are: ", E[1:11])

function diagonalization_model(h::Matrix{T},N::Int64;C::Int64=2,
        d::Int64=2,method_sparse::Bool=true) where {T}
    H=zeros(T,d^N,d^N)
    for l in 0:N-C
        H+=kron(kron(eye(d^l),h),eye(d^(N-C-l),d^(N-C-l)))
    end
    if method_sparse
        E,V = eigs(H;nev=10,which=:SR)
    else
        E,V = eig(H)
    end
    return E
end

@time diagonalization_model(h,12)

@time diagonalization_model(h,12,method_sparse=false)

function permR2(i::Int64,P::Int64)
    return mod.(collect(1:P)+i-2,P)+1
end

function permL2(i::Int64,P::Int64)
    return mod.(collect(1:P)-i,P)+1
end

function H_wavefunction(h_tilde::Matrix{T},phi::Array{T,P},N::Int64;
        OBC::Int64=1,d::Int64=2,C::Int64=2) where {T,P}
    H_phi = zeros(T,fill(d,C+1)...,d^(N-C-1))
    for i in 1:N-OBC
        if N == 1
            phi = permutedims(phi,permR2(1,P))
            H_phi = permutedims(H_phi,permR2(1,P))
            phi_tmp = h_tilde*reshape(phi,d^C,d^(N-C))
            phi_tmp = reshape(phi_tmp,fill(d,C+1)...,d^(N-C-1))
            H_phi = reshape(H_phi,fill(d,C+1)...,d^(N-C-1))
            H_phi += phi_tmp
        else
            phi = permutedims(phi,permR2(2,P))
            H_phi = permutedims(H_phi,permR2(2,P))
            phi_tmp = h_tilde*reshape(phi,d^C,d^(N-C))
            phi_tmp = reshape(phi_tmp,fill(d,C+1)...,d^(N-C-1))
            H_phi = reshape(H_phi,fill(d,C+1)...,d^(N-C-1))
            phi = reshape(phi,fill(d,C+1)...,d^(N-C-1))
            H_phi += phi_tmp
        end
    end
    if OBC == 1
        return(reshape(permutedims(H_phi,permR2(2,P)),d^N))
    elseif OBC == 0
        return(reshape(H_phi,d^N))
    end
end

function power_method(h::Matrix{T},N::Int64;OBC::Int64=1,d::Int64=2,
        C::Int64=2,tol::Float64=1e-4) where {T}
    #phi = rand(T,fill(d,C+1)...,d^(N-C-1))
    #phi = collect(Complex{Float64},1:2^N)
    phi = rand(T,d^N)
    phi = phi/sqrt(dot(conj(phi),phi))
    phi = reshape(phi,fill(d,C+1)...,d^(N-C-1))
    list_E_tmp = []
    E,V = eig(h)
    alpha=max(E...)
    h_tilde = h - alpha*kron(eye(d),eye(d))
    i = 1
    bool = true
    while bool
        H_phi = H_wavefunction(h_tilde,phi,N,OBC=OBC)
        #H_phi = H_phi/dot(conj(H_phi),H_phi)
        phi = reshape(phi,d^N)
        #E = dot(conj(phi),H_phi)/dot(conj(phi),phi) + α*(N-OBC)
        E = dot(conj(phi),H_phi) + alpha*(N-OBC)
        #println("E = ", E)
        push!(list_E_tmp,E)
        if i>2 && abs(list_E_tmp[i]-list_E_tmp[i-1])<tol
           bool = false
           println("Desired precision reached!")
        end
        i+=1
        phi = reshape(phi,fill(d,C+1)...,d^(N-C-1))
        phi = reshape(H_phi/sqrt(dot(conj(H_phi),H_phi)),fill(d,C+1)...,d^(N-C-1))
    end
    println("E = ",E)
    println("Number of iterations = ", i)
end

@time power_method(h,4,tol=1e-9)

@time power_method(h,12,tol=1e-9)

@time power_method(h,16,OBC=0,tol=1e-9)

@time power_method(h,16,tol=1e-9)

@time power_method(h,25)

@time power_method(h,25,OBC=0)