# Helper function for the PCA demo

mutable struct pPCAParams
    W::Matrix{Float64}
    m::Vector{Float64}
    v::Float64
end

function readDataSet(filename)
    f = open(filename);
    data = nothing
    for line in eachline(f)
        row = map((s) -> parse(Int, s), split(replace(line, "  " => " "), " "))
        data = (data == nothing) ? row' : vcat(data, row')
    end
    close(f)

    return data
end

function pPCA(X::Matrix, M::Int64)
    # Implement probabilistic PCA using the EM algorithm
    # X collects the data points on its columns
    # X may contain missing values, indicated by NaN

    # Probabilistic model:
    # x[n] = W*z[n] + m + ε, where W is a matrix, m is a vector and ε is a noise term
    # z[n] ~ N(0,I), where I is the identity matrix
    # ε ~ N(0, v*I), where v is a scalar
    N = size(X, 2); D = size(X, 1)
    missing_values = isnan.(X)
    has_missing_values = sum(missing_values) > 0
    # Find row means of X, ignoring missing values
    if has_missing_values
        X[findall(missing_values)] .= 0
        m = ( sum(X, dims=2) ./ (N*ones(D) - sum(missing_values,dims=2)) )[:,1] # [:,1] makes sure this is a vector
        X[findall(missing_values)] .= NaN
        observed_d = [findall(.!missing_values[:,n]) for n=1:N] # observed dimensions for every data vector
        observed_n = [findall(.!missing_values[d,:]) for d=1:D] # observed value indexes per dimension
        x_observed = [X[observed_d[n],n] for n=1:N]  # data vectors without the missing data
    else
        m = (sum(X, dims=2) ./ N)[:,1] # [:,1] makes sure this is a vector
        _X = X .- m # shift data to be zero-mean
    end
    # Initialize variables
    θ = pPCAParams(Matrix{Float64}(undef,D,M), m, 1.0) # θ holds the parameters
    Mz = randn(M,N)              # Mz[:,n] = MEAN[z[n]]
    Σz = [zeros(M,M) for n=1:N]  # Σz[n] = COV[z[n]]

    function eStep!(Mz, Σz)
        # Update Mz and Σz
        W = θ.W; v = θ.v

        if has_missing_values
            for n=1:N
                _W = W[observed_d[n],:]
                _x = x_observed[n] - θ.m[observed_d[n]]
                M_inv = inv(_W'*_W + v*Matrix{Float64}(I, M, M))
                Σz[n] = v * M_inv
                Mz[:,n] = M_inv * _W' * _x
            end
        else

            M_inv = inv(W'*W + v*Matrix{Float64}(I, M, M))
            Mz[:] = M_inv * W' * _X
            Σz[1] = v * M_inv # COV[z[n]] is equal for all n. We only update the one for n=1
        end
    end

    function mStep!(θ)
        # Update W, m, v
        if has_missing_values
            m = zero(θ.m)
            for n=1:N
                m[observed_d[n]] += x_observed[n] .- (θ.W[observed_d[n],:] * Mz[:,n])[:,1]
            end
            m = m ./ (N .- sum(missing_values, dims=2)[:,1])
            θ.m = m

            for d=1:D
                A = inv(Mz[:,observed_n[d]]*Mz[:,observed_n[d]]' + sum(Σz[observed_n[d]]))
                b = zeros(M)
                for n in observed_n[d]
                    b += Mz[:,n]*(X[d,n] .- m[d])
                end
                θ.W[d,:] = ( A * b )'
            end
            W = θ.W

            θ.v = 0.0
            for n=1:N
                for d in observed_d[n]
                    θ.v +=  (X[d,n] .- (W[d,:]'*Mz[:,n])[1] - m[d])^2 + (W[d,:]' * Σz[n] * W[d,:])[1]
                end
            end
            θ.v = θ.v / (N*D-sum(missing_values))

        else
            # no need to update m
            W = _X * Mz' * inv(Mz*Mz' + N*Σz[1])
            θ.W = W
            θ.v = sum((_X - W*Mz).^2)/(N*D) + tr(W*Σz[1]*W')/D
        end
    end

    for i=1:30 # Here should be a decent convergence check, but hey, this also works...
        mStep!(θ)
        eStep!(Mz, Σz)
    end

    return (θ, Mz) # Mz is actually redundant, but we return it for convenient plotting
end


# function ⋃(Sets...)
#     return reduce(∪, Sets)
# end
const ⋃ = union

function ∂(Simplex)
    # if !(Simplex |> issorted) @warn "Simplex $Simplex is not sorted" end
    faces = [setdiff(Simplex, v) for v in reverse(Simplex)]
    return faces
end

function faces(Simplex::Set)
    return Set([setdiff(Simplex, v) for v in Simplex])
end
faces(Set([3,5,3,2]))
∂([1,2,3])
∂([1,3,2])


using NURBS
using Base.Test

@testset "bsurface Building" begin

	arr = [-100.0 -100.0 -100.0 -100.0 -100.0 -50.0 -50.0 -50.0 -50.0 -50.0 0.0 0.0 0.0 0.0 0.0 50.0 50.0 50.0 50.0 50.0 100.0 100.0 100.0 100.0 100.0;-100.0 -50.0 0.0 50.0 100.0 -100.0 -50.0 0.0 50.0 100.0 -100.0 -50.0 0.0 50.0 100.0 -100.0 -50.0 0.0 50.0 100.0 -100.0 -50.0 0.0 50.0 100.0;0.0 0.0 0.0 0.0 0.0 0.0 25.0 50.0 25.0 0.0 0.0 25.0 50.0 25.0 0.0 0.0 25.0 150.0 25.0 0.0 0.0 0.0 0.0 0.0 0.0]

	res = [-100.0 -100.0 -100.0 -100.0 -25.9259 -25.9259 -25.9259 -25.9259 25.9259 25.9259 25.9259 25.9259 100.0 100.0 100.0 100.0; -100.0 -25.9259 25.9259 100.0 -100.0 -25.9259 25.9259 100.0 -100.0 -25.9259 25.9259 100.0 -100.0 -25.9259 25.9259 100.0; 0.0 0.0 0.0 0.0 0.0 34.8422 34.8422 0.0 0.0 51.3032 51.3032 0.0 0.0 0.0 0.0 0.0]

    resu = [
        -50.0 -50.0 -50.0 -50.0 -16.6667 -16.6667 -16.6667  -16.6667 16.6667 16.6667 16.6667 16.6667 50.0 50.0 50.0 50.0;
        -50.0 -16.6667 16.6667 50.0 -50.0 -16.6667 16.6667 50.0 -50.0 -16.6667 16.6667 50.0 -50.0 -16.6667 16.6667 50.0;
        20.8333 32.6646 32.6646 20.8333 25.6687 41.7905 41.7905 25.6687 31.0185 60.2176 60.2176 31.0185 31.9444 70.9362 70.9362 31.9444
    ]

    @testset "B-spline surface using open uniform knot vectors" begin
        @test typeof(bsplsurf(arr, 4, 4, 5, 5, 100, 100)) == Tuple{Array{Float64, 2}, Array{Array{Int64, 1}, 1}, Array{Array{Int64, 1}, 1}}
        @test isapprox(bsplsurf(arr, 4, 4, 5, 5, 4, 4)[1], res; atol = 1e-3)
    end

    @testset "B-spline surface using periodic uniform knot vectors" begin
        @test typeof(bsplsurfu(arr, 4, 4, 5, 5, 100, 100)) == Tuple{Array{Float64, 2}, Array{Array{Int64, 1}, 1}, Array{Array{Int64, 1}, 1}}
        @test isapprox(bsplsurfu(arr, 4, 4, 5, 5, 4, 4)[1], resu; atol = 1e-3)
    end

    @testset "B-spline surface and its derivatives using open uniform knot vectors" begin
        @test typeof(dbsurf(arr, 4, 4, 5, 5, 100, 100)) == Tuple{Array{Float64,2}, Array{Float64,2}, Array{Float64,2}, Array{Float64,2}, Array{Float64,2}, Array{Float64,2}, Array{Array{Int64, 1}, 1}, Array{Array{Int64, 1}, 1}}
        @test isapprox(dbsurf(arr, 4, 4, 5, 5, 4, 4)[1], res; atol = 1e-3)
    end

    @testset "B-Spline surface over open knot comparison" begin
        @test bsplsurf(arr, 4, 4, 5, 5, 100, 100)[1] == dbsurf(arr, 4, 4, 5, 5, 100, 100)[1]
        @test bsplsurf(arr, 4, 4, 5, 5, 100, 100)[2] == dbsurf(arr, 4, 4, 5, 5, 100, 100)[7]
        @test bsplsurf(arr, 4, 4, 5, 5, 100, 100)[3] == dbsurf(arr, 4, 4, 5, 5, 100, 100)[8]
    end
end


"""
    function discrete_gamma_rates(α::T, β::S,k::Int64, method::Symbol=:mean; sig::Int64=1)::Array{Float64} where {T<:Real, S<:Real}

This function calculates the rates of the discretized gamma distribution.
It returns a vector of either the mean or the median weights in each category,
such that each category is of equal proportion.

See: Yang, 1994, Maximum likelihood phylogenetic estimation from DNA sequences
with variable rates over sites: Approximate methods. (https://doi.org/10.1007/BF00160154)
"""
function discrete_gamma_rates(α::T, β::S,k::Int64, method::Symbol=:mean; sig::Int64=1)::Array{Float64} where {T<:Real, S<:Real}
    meanvals = Array{Float64, 1}(undef, k)
    factor::Float64 = α/β*k

    if method==:median
        meanvals .= median_boundaries(α, β, k)
        t = sum(meanvals)
        meanvals .*= (factor / t)
    else
        meanvals .= mean_boundaries(α, β, k)
        @inbounds @simd for i in 1:k-1
            meanvals[i] = gamma_inc(α+1,meanvals[i]*β, sig)[1]
        end
        meanvals[k] = 1.0
        @inbounds for i in k:-1:2
            meanvals[i] -= meanvals[i-1]
            meanvals[i] *= factor
        end
        meanvals[1] *= factor
    end
    meanvals
end

#### helper functions for boundaries of rate categories ####

function mean_boundaries(α::T, β::S, k::Int64)::Array{Float64} where {T<:Real, S<:Real}
    ch = Chisq((2*α)/(2*β))
    boundaries = Array{Float64, 1}(undef, k)
    for i in 1:k-1
        boundaries[i] = quantile(ch, i/k)/(2*β)
    end
    boundaries
end

function median_boundaries(α::T, β::S, k::Int64)::Array{Float64} where {T<:Real, S<:Real}
    ch = Chisq((2*α)/(2*β))
    boundaries = Array{Float64, 1}(undef, k)
    for i in 1:k
        boundaries[i] = quantile(ch, ((i-1)*2+1)/(2*k))
    end
    return boundaries
end


using FourierTools

f(x) = sin(x * 2π)
x = [0.0, 0.2, 0.4, 0.6, 0.8]

@show(f.(x))
@show(f.(x .- 0.5))
@show(shift(f.(x), -2.5))

plot(x, f.(x))
#plot!(range(0, 1, length=100), resample(f.(x), (100,)))
plot!(x .- 0.5, f.(x .- 0.5))
plot!(x .- 0.5, shift(f.(x), -2.5))


"""
    analytic_histogram(d::Distributions.UnivariateDistribution, G::LinRange; normalize=true)

Compute the probability mass of `d` in equally-sized intervals with midpoints given by `G`.

If `normalize`, then the probabilities are re-scaled such that their sum is 1.
"""
function analytic_histogram(d::Distributions.UnivariateDistribution,
                            G::LinRange;
                            normalize=true)
    cdf(x) = Distributions.cdf(d, x)

    mps = get_midpoints(G)
    hist = zeros(length(G))
    for i in eachindex(hist)
        hist[i] = cdf(mps[i + 1]) - cdf(mps[i])
    end

    if normalize
        s = sum(hist)
        hist .= hist ./ s
    end

    return hist
end

# aux for analytic_histogram
function get_midpoints(G::LinRange)
    half_step = step(G) / 2
    return LinRange(first(G) - half_step,
                    last(G) + half_step,
                    length(G) + 1)
end


# ---
# title: 1109. Corporate Flight Bookings
# id: problem1109
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Array, Math
# link: <https://leetcode.com/problems/corporate-flight-bookings/description/>
# hidden: true
# ---
#
# There are `n` flights, and they are labeled from `1` to `n`.
#
# We have a list of flight bookings.  The `i`-th booking `bookings[i] = [i, j,
# k]` means that we booked `k` seats from flights labeled `i` to `j` inclusive.
#
# Return an array `answer` of length `n`, representing the number of seats
# booked on each flight in order of their label.
#
#
#
# **Example 1:**
#
#
#
#     Input: bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5
#     Output: [10,55,45,25,25]
#
#
#
#
# **Constraints:**
#
#   * `1 <= bookings.length <= 20000`
#   * `1 <= bookings[i][0] <= bookings[i][1] <= n <= 20000`
#   * `1 <= bookings[i][2] <= 10000`
#
#
## @lc code=start
using LeetCode

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


using Base: front, tail

"""
    ⊗(t1::Tensor{T}, t2::Tensor{S}) where {T,S} -> Tensor{promote_type(T, S)}

Computes the outer product of the tensors `t1` and `t2`, also referred to as the tensor
product of `t1` and `t2`.
Arguments can be of arbitrary size and dimension.

# Arguments
- `t1::Tensor{T}`: the first factor
- `t2::Tensor{S}`: the second factor

# Returns
- `Tensor{promote_type(T, S)}`: the outer product of `t1` and `t2`
"""
function ⊗(t1::Tensor{T}, t2::Tensor{S}) where {T,S}
    res = Array{promote_type(T, S)}(undef, size(t1)..., size(t2)...)

    j, k = 1, 1
    for i in eachindex(res)
        @inbounds res[i] = t1.data[j] * t2.data[k]
        j = j % length(t1) + 1
        k += j == 1 ? 1 : 0
    end

    return Tensor(res)
end

"""
    ⊗(ts...) -> Tensor{promote_type(eltype.(ts)...)}

Computes the outer product of an arbitrary number of tensors `ts...`. The empty outer
product is defined as `Tensor(1)` (i.e., the neutral element of the outer product).
Arguments can be of arbitrary size and dimension.

# Arguments
- `ts...`: the (possibly empty) list of tensors to be multiplied

# Returns
- `Tensor{promote_type(eltype.(ts)...)}`: the outer product of the tensors `ts...`
"""
⊗(ts...) = reduce(⊗, ts; init=Tensor(1))

"""
    ⋅(t1::Tensor{T}, t2::Tensor{S}) where {T,S} -> Tensor{promote_type(T, S)}

Computes the inner product of the tensors `t1` and `t2`, also referred to as the scalar
product of `t1` and `t2`.
Arguments need to be of non-zero dimension and the last dimension of `t1` needs to match the
first dimension of `t2`.

# Arguments
- `t1::Tensor{T}`: the first factor
- `t2::Tensor{S}`: the second factor

# Returns
- `Tensor{promote_type(T, S)}`: the inner product of `t1` and `t2`

# Throws
- `ArgumentError`: if either `t1` or `t2` is 0-dimensional
- `DimensionMismatch`: if the last dimension of `t1` does not match the first dimension of
    `t2`
"""
function ⋅(t1::Tensor{T}, t2::Tensor{S}) where {T,S}
    @argcheck ndims(t1) > 0
    @argcheck ndims(t2) > 0
    @argcheck last(size(t1)) == first(size(t2)) DimensionMismatch

    return pushover(conj(t1), t2, ndims(t1), 1)
end

"""
    contract(t::Tensor, d1::Integer, d2::Integer) -> Tensor

Computes the contraction of the tensor `t` along the dimensions `d1` and `d2`.
`d1` and `d2` need to be distinct, valid indices for the dimensions of `t`. Additionally,
the `d1`-th dimension of `t` has to match its `d2`-th dimension.

# Arguments
- `t::Tensor`: the tensor to be contracted
- `d1::Integer`: the first contraction dimension
- `d2::Integer`: the second contraction dimension

# Returns
- `Tensor`: the contracted tensor

# Throws
- `ArgumentError`: if `d1` and `d2` are not distinct
- `BoundsError`: if `d1` and `d2` do not index valid dimensions of `t`
- `DimensionMismatch`: if the `d1`-th dimension does not match the `d2`-th dimension of `t`
"""
function contract(t::Tensor, d1::Integer, d2::Integer)
    @argcheck d1 != d2
    @argcheck 1 <= d1 <= ndims(t) BoundsError(size(t), d1)
    @argcheck 1 <= d2 <= ndims(t) BoundsError(size(t), d2)
    @argcheck size(t, d1) == size(t, d2) DimensionMismatch

    # allow more efficient access of the needed (column-major) array elements
    a = permutedims(t.data, (d1, d2, Tuple(k for k in 1:ndims(t) if k != d1 && k != d2)...))

    res = Array{eltype(t)}(undef, size(a)[3:ndims(a)])
    inds = CartesianIndices(res)

    # NOTE: replacing the cartesian indexing by on-the-fly linear indexing hurts performance
    for i in eachindex(res)
        @inbounds res[i] = sum(a[j, j, inds[i]] for j in axes(a, 1))
    end

    return Tensor(res)
end

"""
    trace(t::Tensor{T,2}) where {T} -> Tensor{T,0}

Computes the contraction of the tensor `t` along its two dimensions, equivalent to the trace
of the underlying matrix of `t`.
The two dimensions `t` need to match, i.e., `t` needs to be a 'square' tensor.

# Arguments
- `t::Tensor{T,2}`: the tensor to be contracted

# Returns
- `Tensor{T,0}`: the contracted scalar tensor

# Throws
- `DimensionMismatch`: if the first and the second dimension of `t` do not match
"""
trace(t::Tensor{T,2}) where {T} = contract(t, 1, 2)

"""
    pushover(t1::Tensor{T}, t2::Tensor{S}, d1::Integer, d2::Integer) where {T,S} -> Tensor{promote_type(T, S)}

Computes the 'Überschiebung' of the tensors `t1` and `t2` along the dimensions `d1` and `d2`
respectively.
`d1` and `d2` need to be valid indices for the dimensions of `t1` and `t2` respectively.
Additionally, the `d1`-th dimension of `t1` has to match the `d2`-th dimension of `t2`.

# Arguments
- `t1::Tensor{T}`: the first tensor to push over
- `t2::Tensor{S}`: the second tensor to push over
- `d1::Integer`: the first push-over dimension
- `d2::Integer`: the second push-over dimension

# Returns
- `Tensor{promote_type(T, S)}`: the push-over tensor

# Throws
- `BoundsError`: if `d1` and `d2` do not index valid dimensions of `t1` and `t2`
    respectively
- `DimensionMismatch`: if the `d1`-th dimension of `t1` does not match the `d2`-th dimension
    of `t2`
"""
function pushover(t1::Tensor{T}, t2::Tensor{S}, d1::Integer, d2::Integer) where {T,S}
    @argcheck 1 <= d1 <= ndims(t1) BoundsError(size(t1), d1)
    @argcheck 1 <= d2 <= ndims(t2) BoundsError(size(t2), d2)
    @argcheck size(t1, d1) == size(t2, d2) DimensionMismatch

    # allow more efficient access of the needed (column-major) array elements
    a1 = permutedims(t1.data, (d1, Tuple(k for k in 1:ndims(t1) if k != d1)...))
    a2 = permutedims(t2.data, (d2, Tuple(k for k in 1:ndims(t2) if k != d2)...))

    res = Array{promote_type(T, S)}(
        undef, size(a1)[2:ndims(a1)]..., size(a2)[2:ndims(a2)]...
    )

    # NOTE: improved indexing, but still not great, more optimization?
    j, k = 1, 1
    for i in eachindex(res)
        @inbounds res[i] = sum(a1[j:(j + size(a1, 1) - 1)] .* a2[k:(k + size(a1, 1) - 1)])
        j = (j + size(a1, 1)) % length(t1)
        k += j == 1 ? size(a1, 1) : 0
    end

    return Tensor(res)
end

# TODO: Implement epsilon tensor
# TODO: Implement delta tensor
# TODO: Think about sparse tensors for efficiency?
# TODO: Implement cross product (how?)
# TODO: Add examples to all relevant docstrings
# TODO: Maybe rename actual implementations of ⊗ and ⋅ to outer and inner and alias symbols


"""
Based on
https://districtdatalabs.silvrback.com/parameter-tuning-with-hyperopt
"""
module TestSimple

using Test
using TreeParzen

# Simple 1
best = fmin(params -> params[:x], Dict(:x => HP.Uniform(:x, 0.0, 1.0)), 100)
@test isapprox(best[:x], 0.00076079610166447, rtol = 1e1)

# Simple 2
objective(params) = (params[:x] - 1)^2
best = fmin(objective, Dict(:x => HP.Uniform(:x, -2.0, 2.0)), 100)
@test isapprox(best[:x], 0.9990226896297454, rtol = 1e1)

# fspace
fspace = Dict(:x => HP.Uniform(:x, -5.0, 5.0))
objective(params) = params[:x]^2
best = fmin(objective, fspace, 50)
@test isapprox(best[:x], 0.035669063364728415, rtol = 1e1)

end
true


"""
    mode(nums)

Finds mode of a vector of numbers

# Example

```julia
mode([2, 3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 2, 2, 2])        # returns [2]
mode([3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 4, 2, 2, 2])        # returns [2]
mode([3, 4, 5, 3, 4, 2, 5, 2, 2, 4, 4, 4, 2, 2, 4, 2])  # returns [2, 4]
mode(["x", "y", "y", "z"])                              # returns ["y"]
mode(["x", "x" , "y", "y", "z"])                       # returns ["x", "y"]
```

Contributed By:- [Ashwani Rathee](https://github.com/ashwani-rathee)
"""
function mode(nums)
    dict = Dict() # nums => Number of repetitions
    result = [] # Array of the modes so far
    max = 0 # Max of repetitions so far

    for i in nums
        # Add one to the dict[i] entry (create one if none)
        if i in keys(dict)
            dict[i] += 1
        else
            dict[i] = 1
        end
        # Result updated if no of repetitions of i >= max
        if dict[i] >= max
            if dict[i] > max
                empty!(result)
                max += 1
            end
            append!(result, [i])
        end
    end

    return result
end
println(mode([1, 3, 6, 6, 6, 6, 7, 7, 12, 12, 17]))
println(mode((1, 1, 2, 4, 4)))


"""Length of Longest Common Subsequence problem in Julia

Given two strings, we have to find the largest common subsequence in Julia

The problem can be solved using Dynamic Programming
"""

function lengthoflongestcommonsubsequence(S1, S2)
    # Build a 2D matrix in Bottom-Up manner. Every element
    # dp[i][j] contains the length of the longest common subsequence
    # of S1[0 .. i] and S2[0 .. j].
    n = length(S1)
    m = length(S2)

    # Fill every element of the 2D matrix with 0 as value
    dp = zeros(Int, n + 1, m + 1)

    for i in 1:(n + 1)
        for j in 1:(m + 1)

            # length of LCS when one of the string is empty is zero
            if i == 1 || j == 1
                dp[i,j] = 0

            # If the elements of both the strings match, then
            # the value of LCS increments by one.
            elseif S1[i - 1] == S2[j - 1]
                dp[i,j] = dp[(i - 1),(j - 1)] + 1
            else
            # Either include the last element from one string and exclude it from the other and
            # compare them and vice versa and pick the one giving the maximum length out of the two
                dp[i,j] = max(dp[(i - 1),j], dp[i,(j - 1)])
            end
        end
    end
    return dp[(n + 1),(m + 1)]
end

print("Enter the first string: ")
s1 = readline()
print("Enter the second string: ")
s2 = readline()
res = lengthoflongestcommonsubsequence(s1, s2)
print("The length of the longest common subsequence is ")
print(res)

# Time Complexity: O(n * m), where n, m are the size of the given strings
# Space Complexity: O(n * m)

# SAMPLE INPUT AND OUTPUT

# Enter the first string: cuckoo
# Enter the second string: cucumber
# The length of the longest common subsequence is 3


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.6.3
#     language: julia
#     name: julia-1.6
# ---

# %%
using Distributions
using HypothesisTests
using StatsPlots
default(size=(500, 300))

function pval_t(null, X)
    μ = mean(null)
    n = length(X)
    T = (mean(X) - μ)/√(var(X)/n)
    2ccdf(TDist(n-1), abs(T))
end

function pval_wilcoxon(null, X)
    m = quantile(null, 0.5) # median of null
    W = HypothesisTests.SignedRankTest(X .- m)
    pvalue(W)
end

function plot_pvals(;
        pval = pval_wilcoxon,
        null = Gamma(4, 1),
        n = 100,
        L = 10^6
    )
    PVal = similar(zeros(), L)
    for i in 1:L
        X = rand(null, n)
        PVal[i] = pval(null, X)
    end
    realalpha = mean(PVal .< 0.05)
    histogram(PVal; norm=true, alpha=0.3, bin=0.001:0.025:1.001, label="")
    plot!(; xtick=0:0.05:1, xrotation=90, bottom_margin=3Plots.mm)
    title!("""
        pval = $pval,  n = $n
        null = $null
        P(p-value < 0.05) = $realalpha,  niters = $L""",
        titlefontsize=8)
end

function plot_both(;
        null = Gamma(4, 1),
        n = 100,
        L = 10^6
    )
    P = plot_pvals(; pval = pval_t, null, n, L)
    Q = plot_pvals(; pval = pval_wilcoxon, null, n, L)
    plot(P, Q; size=(500, 600), layout=(2, 1), tickfontsize=8)
end

# %%
plot(Gamma(4, 1); label="Gamma(4, 1)")

# %%
plot_both(; null = Gamma(4, 1), n = 30)

# %%
plot_both(; null = Gamma(4, 1), n = 100)

# %%
plot_both(null = Gamma(4, 1), n = 300, L=10^5)

# %%
plot_both(; null = Gamma(4, 1), n = 1000, L=10^5)

# %%
plot(Gamma(10, 1); label="Gamma(10, 1)")

# %%
plot_both(; null = Gamma(10, 1))

# %%
plot(Gamma(20, 1); label="Gamma(20, 1)")

# %%
plot_both(; null = Gamma(20, 1))

# %%
plot(Gamma(50, 1), 20, 80; label="Gamma(50, 1)")

# %%
plot_both(; null = Gamma(50, 1), n = 30)

# %%
plot_both(; null = Gamma(50, 1), n = 100)

# %%
plot_both(; null=Gamma(50, 1), n = 300, L=10^5)

# %%
plot_both(; null=Gamma(50, 1), n = 1000, L=10^5)

# %%
plot(Uniform(-1, 1), -3, 3; label="Uniform(-1, 1)")

# %%
plot_both(; null = Uniform(-1, 1))

# %%
null = TDist(3)
plot(null, -8, 8; label="TDist(3)")
plot!(Normal(mean(null), std(null)), -8, 8; label="normal approx.", ls=:dash)

# %%
plot_both(; null)

# %%
null = MixtureModel([Normal(), Normal(10, 1)], [0.95, 0.05])
plot(null, -5, 15; label="", title="$null", titlefontsize=8) |> display
plot_both(; null, n = 30)

# %%
plot_both(; null, n = 100)

# %%
plot_both(; null, n = 300, L = 10^5)

# %%
plot_both(; null, n = 1000, L = 10^5)

# %%
null = Exponential()
plot(null; label="", title="$null", titlefontsize=8) |> display
plot_both(; null, n = 30)

# %%
plot_both(; null, n = 100)

# %%

0022/Wilcoxon test vs. t-test for asymmetric null.jl


using LinearAlgebra
using ProximalOperators
using Test

@testset "HuberLoss" begin

f = HuberLoss(1.5, 0.7)

predicates_test(f)

@test ProximalOperators.is_smooth(f) == true
@test ProximalOperators.is_quadratic(f) == false
@test ProximalOperators.is_set(f) == false

x = randn(10)
x = 1.6*x/norm(x)

call_test(f, x)
prox_test(f, x, 1.3)
grad_fx, fx = gradient_test(f, x)

@test abs(fx - f(x)) <= 1e-12
@test norm(0.7*1.5*x/norm(x) - grad_fx, Inf) <= 1e-12

x = randn(10)
x = 1.4*x/norm(x)

call_test(f, x)
prox_test(f, x, 0.9)
grad_fx, fx = gradient_test(f, x)

@test abs(fx - f(x)) <= 1e-12
@test norm(0.7*x - grad_fx, Inf) <= 1e-12

end


"""
PermTrack(args...)

Return Wyllie and Rose permeability correlation using either Timur or Morris coeficients as well as fluid type, oil or gas
``K=Cperm*\frac { { Phie }^{ Dperm } }{ { Sw }^{ Eperm } } ``

## coeficients
### Timur
Dperm=4.4
Eperm=2.0

For Oil
 Cperm=3400

For Gas
Cperm=340

### Morris
Dperm=6.0
Eperm=2.0

For Oil
 Cperm=65000

For Gas
Cperm=6500

## Example

```julia
k=PermWR(0.18,0.3;Fluid="Oil",Author="Timur")
```
"""

function PermWR(Phie,Swir;Fluid="Oil",Author="Timur")
    #https://www.spec2000.net/15-permwyllie.htm

    if (Author=="Timur")
        Dperm=4.4
        Eperm=2.0

        if (Fluid=="Oil")
         Cperm=3400
        elseif (Fluid=="Gas")
            Cperm=340
        end

    elseif (Author=="Morris")
        Dperm=6.0
        Eperm=2.0
           if (Fluid=="Oil")
         Cperm=65000
         elseif (Fluid=="Gas")
            Cperm=6500
        end

        end

    k=Cperm .* (Phie .^ Dperm) ./ (Swir .^ Eperm)
end


using JuMP
using Cbc
using ChooseOptimizer
using DiscreteFunctions
using LinearAlgebra
import Base.sqrt

"""
`sqrt(g::DiscreteFunction)` returns a function `f` such that
`f*f==g` or throws an error if no such function exists.
This method uses integer linear programming.
"""
function sqrt(g::DiscreteFunction)::DiscreteFunction
    err_msg = "This function does not have a square root."
    n = length(g)
    B = Matrix(g)

    if !quick_sqrt_test(g)
        error(err_msg)
    end

    # options = Dict()
    # options[:logLevel] = 0

    MOD = Model(get_solver())

    @variable(MOD, a[1:n,1:n], Bin)  # entry in A matrix
    @variable(MOD, w[1:n,1:n,1:n], Bin) # w[i,j,k] is a[i,j]*a[j,k]

    # this ensures w[i,j,k] is 0 if either a[i,j] or a[j,k] are
    for i=1:n
        for j=1:n
            for k=1:n
                @constraint(MOD, a[i,j]>=w[i,j,k])
                @constraint(MOD, a[j,k]>=w[i,j,k])
            end
        end
    end

    # this ensures that the A matrix represents a function
    for i=1:n
        @constraint(MOD, sum(a[i,j] for j=1:n) == 1)
    end

    # this ensures that A^2 == B
    for i=1:n
        for k=1:n
            @constraint(MOD, sum(w[i,j,k] for j=1:n) == B[i,k])
        end
    end

    # Turns out, we don't need to maximize the w's to get this to work!
    # @objective(MOD, Max, sum(w[i,j,k] for i=1:n for j=1:n for k=1:n))

    # try to solve
    optimize!(MOD)
    status = Int(termination_status(MOD))
    if status != 1   # if status isn't 1, the IP is not feasible, no sqrt
        error(err_msg)
    end

    # build the sqrt function from the A matrix
    A = Int.(value.(a))
    f = DiscreteFunction(n)
    for i=1:n
        for j=1:n
            if A[i,j]==1
                f[i] = j
            end
        end
    end
    return f
end

"""
`has_sqrt(f::DiscreteFunction)` checks if there is a function `g`
such that `g*g==f`. Returns `true` if so and `false` otherwise.
"""
function has_sqrt(f::DiscreteFunction)::Bool
    try
        g = sqrt(f)
        return true
    catch
        return false
    end
end

"""
`quick_sqrt_test(f::DiscreteFunction)` is a quick check of a necessary
condition for `f` to have a square root. If this returns `false` then `f`
does *not* have a square root. If it returns `true`, it might.
"""
function quick_sqrt_test(f::DiscreteFunction)::Bool
    evals = reduced_eigvals(f)
    d = real(prod(evals))
    return d > 0.5
end

function reduced_eigvals(A::Array{T,2}) where T
    elist = eigvals(A)
    return filter(x -> abs(x)>1e-7, elist)
end

"""
`reduced_eigvals(f::DiscreteFunction)` returns the nonzero eigenvalues of
`f`'s matrix.
"""
function reduced_eigvals(f::DiscreteFunction)
    return reduced_eigvals(Matrix(f))
end


import FastTransforms
import QuadratureRules: shift_nodes

@testset "$(rpad("Lobatto-Chebyshev",80))" begin

    @test_throws ErrorException LobattoChebyshevQuadrature(1)

    for s in 2:10
        @test LobattoChebyshevQuadrature(s) == ChebyshevQuadrature(s, 2)
        @test LobattoChebyshevQuadrature(s) == ChebyshevQuadrature(Float64, s, Val(2))

        @test sum(weights(LobattoChebyshevQuadrature(s))) ≈ 1

        c = shift_nodes(reverse(FastTransforms.chebyshevpoints(Float64, s, Val(2))))

        @test chebyshev_points(Float64, s, Val(2)) ≈ c
        @test nodes(LobattoChebyshevQuadrature(s)) ≈ c

        # TODO test weights

    end

end


@doc raw"""
    PowerKernel([γ=1])

The Power Kernel is a negative definite kernel given by:
```
    κ(x,y) = ‖x-y‖²ᵞ   γ ∈ (0,1]
```
where `γ` is a shape parameter of the Euclidean distance.

# Examples

```jldoctest; setup = :(using MLKernels)
julia> PowerKernel()
PowerKernel{Float64}(1.0)

julia> PowerKernel(0.5f0)
PowerKernel{Float32}(0.5)
```
"""
struct PowerKernel{T<:AbstractFloat} <: NegativeDefiniteKernel{T}
    γ::T
    function PowerKernel{T}(γ::Real=T(1)) where {T<:AbstractFloat}
        @check_args(PowerKernel, γ, one(T) >= γ > zero(T), "γ ∈ (0,1]")
        new{T}(γ)
    end
end
PowerKernel(γ::T=1.0) where {T<:Real} = PowerKernel{promote_float(T)}(γ)

@inline basefunction(::PowerKernel) = SquaredEuclidean()

@inline kappa(κ::PowerKernel{T}, d²::T) where {T} = d²^κ.γ

function convert(::Type{K}, κ::PowerKernel) where {K>:PowerKernel{T}} where T
    return PowerKernel{T}(κ.γ)
end


"""
Compute Kummer's confluent hypergeometric function `M(a, b, z) = ₁F₁(a; b; z)`.
"""
function _₁F₁(a, b, z)
    if real(z) ≥ 0
        return _₁F₁maclaurin(a, b, z)
    else
        return exp(z)*_₁F₁(b-a, b, -z)
    end
end

"""
Compute Kummer's confluent hypergeometric function `M(a, b, z) = ₁F₁(a; b; z)`.
"""
const M = _₁F₁

"""
Compute Tricomi's confluent hypergeometric function `U(a, b, z) ∼ z⁻ᵃ ₂F₀([a, a-b+1]; []; -z⁻¹)`.
"""
function U(a, b, z)
    return z^-a*pFq([a, a-b+1], typeof(z)[], -inv(z))
end


using JuMP
using Cbc
m = Model(Cbc.Optimizer)

PROFIT = [15 12 16 18 9 11]
COSTS = [38 33 39 45 23 27]
LIMIT = 100

@variable(m, projects[1:6] ≥ 0, Int)
@objective(m, Max, sum(PROFIT * projects))
@constraint(m, sum(COSTS * projects) ≤ LIMIT)
@constraint(m, projects[1] + projects[2] ≤ 1) # can only choose one of 1 or 2
@constraint(m, projects[3] + projects[4] ≤ 1) # can only choose one of 3 or 4
@constraint(m, projects[3] + projects[4] ≤ projects[1] + projects[2]) # we can do 3 or 4 only if we do 1 or 2
@constraint(m, projects .≤ ones(size(projects))) # can do every project at most once
print(m)
optimize!(m)
status = termination_status(m)
println("Solution status: ", status)
println("Objective value: ", objective_value(m))
println("Projects chores: ", value.(projects))


using LinearAlgebra
function Newton(f, f_prime; x_0, tolerance=1.0E-10, maxiter=10000)
    x_old = x_0
    normdiff = Inf
    iter = 1
    while normdiff > tolerance && iter <= maxiter
        x_new = x_old - (f(x_old) / f_prime(x_old))
        normdiff = norm(x_new - x_old)
        x_old = x_new
        iter = iter + 1
    end
    return (value=x_old, normdiff=normdiff, iter=iter)
end

f(x) = (x - 1)^3
f_prime(x) = 3 * (x - 1)^2
g(x) = x^2 + 3 * x - 10
g_prime(x) = 2 * x + 3
sol_f = Newton(f, f_prime, x_0 = 0)
sol_g = Newton(g, g_prime, x_0 = 0)
sol_f, sol_g


module DivConformingFESpacesTests

using Test
using Gridap.Geometry
using Gridap.ReferenceFEs
using Gridap.FESpaces
using Gridap.CellData
using Gridap.TensorValues
using Gridap.Fields
using Gridap.Io

domain =(0,1,0,1)
partition = (3,3)
model = CartesianDiscreteModel(domain,partition)

order = 1

u(x) = x

reffe = ReferenceFE(raviart_thomas,order)

V = TestFESpace(model,reffe,dirichlet_tags = [1,6])
test_single_field_fe_space(V)

U = TrialFESpace(V,u)

uh = interpolate(u,U)

e = u - uh

Ω = Triangulation(model)
dΩ = Measure(Ω,2*order)

el2 = sqrt(sum( ∫( e⋅e )*dΩ ))
@test el2 < 1.0e-10

#using Gridap.Visualization
#
#writevtk(Ω,"trian",nsubcells=10,cellfields=["uh"=>uh])

order = 1

reffe = ReferenceFE(TET,raviart_thomas,order)

domain =(0,1,0,1,0,1)
partition = (3,3,3)
model = simplexify(CartesianDiscreteModel(domain,partition))

labels = get_face_labeling(model)
dir_tags = Array{Integer}(undef,0)

V = FESpace(model,reffe,conformity=DivConformity())

v(x) = VectorValue(-0.5*x[1]+1.0,-0.5*x[2],-0.5*x[3])
vh = interpolate(v,V)

e = v - vh

Ω = Triangulation(model)
dΩ = Measure(Ω,2*order)

el2 = sqrt(sum( ∫( e⋅e )*dΩ ))
@test el2 < 1.0e-10

#using Gridap.Visualization
#
#writevtk(trian,"test",order=3,cellfields=["vh"=>vh])

# Tests on manifold

# Create domain
domain = (0,1,0,1,0,1)
cells  = (2,2,2)
model  = CartesianDiscreteModel(domain,cells)

# Restrict model to cube surface (using new BoundaryDiscreteModel)
labels = get_face_labeling(model)
bgface_to_mask = get_face_mask(labels,"boundary",2)
Γface_to_bgface = findall(bgface_to_mask)
Dc2Dp3model = BoundaryDiscreteModel(Polytope{2},model,Γface_to_bgface)

order  = 0
degree = 1

reffe_rt = ReferenceFE(raviart_thomas,Float64,order)
V  = FESpace(Dc2Dp3model, reffe_rt ; conformity=:HDiv)
uh = FEFunction(V,rand(num_free_dofs(V)))
vh = interpolate_everywhere(uh,V)

Tₕ_K = Triangulation(Dc2Dp3model)
Qₕ_K = CellQuadrature(Tₕ_K,degree)
e=sqrt(sum(∫((uh-vh)⋅(uh-vh))Qₕ_K))
@test e < 1.0e-12

end # module


"""
    internal_rate_of_return(cashflows::vector)
    internal_rate_of_return(cashflows::Vector, timepoints::Vector)

Calculate the internal_rate_of_return with given timepoints. If no timepoints given, will assume that a series of equally spaced cashflows, assuming the first cashflow occurring at time zero.

Will try to return a root within the range [-2,2]. If the fast solver does not find one matching this condition, then a more robust search will be performed over the [.99,2] range.

The solution returned will be in the range [-2,2], but may not be the one nearest zero. For a slightly slower, but more robust version, call `ActuaryUtilities.irr_robust(cashflows,timepoints)` directly.

# Example
```julia-repl
julia> internal_rate_of_return([-100,110],[0,1]) # e.g. cashflows at time 0 and 1
0.10000000001652906
julia> internal_rate_of_return([-100,110]) # implied the same as above
0.10000000001652906
```

"""
function internal_rate_of_return(cashflows)

    return internal_rate_of_return(cashflows, 0:length(cashflows)-1)

end

function internal_rate_of_return(cashflows,times)
    # first try to quickly solve with newton's method, otherwise
    # revert to a more robust method
    lower,upper = -2.,2.

    v = try
        irr_newton(cashflows,times)
    catch e
        if isa(e,Roots.ConvergenceFailed) || sprint(showerror, e) =="No convergence"
            return irr_robust(cashflows,times)
        else
            throw(e)
        end
    end

    if v <= upper && v >= lower
        return v
    else
        return irr_robust(cashflows,times)
    end
end

irr_robust(cashflows) = irr_robust(cashflows,0:length(cashflows)-1)

function irr_robust(cashflows, times)
    f(i) =  sum(cf / (1+i)^t for (cf,t) in zip(cashflows,times))
    # lower bound at -.99 because otherwise we can start taking the root of a negative number
    # when a time is fractional.
    roots = Roots.find_zeros(f, -0.99, 2)

    # short circuit and return nothing if no roots found
    isempty(roots) && return nothing
    # find and return the one nearest zero
    min_i = argmin(roots)
    return roots[min_i]

end

irr_newton(cashflows) = irr_newton(cashflows,0:length(cashflows)-1)

function irr_newton(cashflows, times)
    # use newton's method with hand-coded derivative
    f(r) =  sum(cf * exp(-r*t) for (cf,t) in zip(cashflows,times))
    f′(r) = sum(-t*cf * exp(-r*t) for (cf,t) in zip(cashflows,times) if t > 0)
    # r = Roots.solve(Roots.ZeroProblem((f,f′), 0.0), Roots.Newton())
    r = Roots.newton(x->(f(x),f(x)/f′(x)),0.0)
    return exp(r)-1

end

"""
    irr()

    An alias for `internal_rate_of_return`.
"""
irr = internal_rate_of_return

"""
    present_value(interest, cashflows::Vector, timepoints)
    present_value(interest, cashflows::Vector)

Discount the `cashflows` vector at the given `interest_interestrate`,  with the cashflows occurring
at the times specified in `timepoints`. If no `timepoints` given, assumes that cashflows happen at times 1,2,...,n.

The `interest` can be an `InterestCurve`, a single scalar, or a vector wrapped in an `InterestCurve`.

# Examples
```julia-repl
julia> present_value(0.1, [10,20],[0,1])
28.18181818181818
julia> present_value(Yields.Forward([0.1,0.2]), [10,20],[0,1])
28.18181818181818 # same as above, because first cashflow is at time zero
```

Example on how to use real dates using the [DayCounts.jl](https://github.com/JuliaFinance/DayCounts.jl) package
```jldoctest

using DayCounts
dates = Date(2012,12,31):Year(1):Date(2013,12,31)
times = map(d -> yearfrac(dates[1], d, DayCounts.Actual365Fixed()),dates) # [0.0,1.0]
present_value(0.1, [10,20],times)

# output
28.18181818181818

```

"""
function present_value(yc::T, cashflows, timepoints) where {T <: Yields.AbstractYield}
    sum(discount(yc,t) * cf for (t,cf) in zip(timepoints, cashflows))
end

function present_value(yc::T, cashflows) where {T <: Yields.AbstractYield}
    present_value(yc,cashflows,1:length(cashflows))
end

function present_value(i, x)

    v = 1.0
    v_factor = 1/(1+i)
    pv = 0.0

    for (t,cf) in zip(1:length(x),x)
        v *= v_factor
        pv += v * cf
    end
    return pv
end

function present_value(i, v, times)
    return present_value(Yields.Constant(i), v, times)
end

# Interest Given is an array, assume forwards.
function present_value(i::AbstractArray, v)
    yc = Yields.Forward(i)
    return sum(discount(yc, t) * cf for (t,cf) in zip(1:length(v),v))
end

# Interest Given is an array, assume forwards.
function present_value(i::AbstractArray, v, times)
    yc = Yields.Forward(i, times)
    return sum(discount(yc, t) * cf for (cf, t) in zip(v,times))
end

"""
    pv()

    An alias for `present_value`.
"""
pv = present_value

"""
    present_value(interest, cashflows::Vector, timepoints)
    present_value(interest, cashflows::Vector)

Efficiently calculate a vector representing the present value of the given cashflows at each period prior to the given timepoint.

# Examples
```julia-repl
julia> present_values(0.00, [1,1,1])
[3,2,1]

julia> present_values(Yields.Forward([0.1,0.2]), [10,20],[0,1])
2-element Vector{Float64}:
 28.18181818181818
 18.18181818181818
```

"""
function present_values(interest, cashflows)
    pvs = Vector{Float64}(undef,length(cashflows))
    pvs[end] = Yields.discount(interest, lastindex(cashflows) - 1, lastindex(cashflows)) * cashflows[end]
    for (t, cf) in Iterators.reverse(enumerate(cashflows[1:end - 1]))
        pvs[t] = Yields.discount(interest, t - 1, t) * (cf + pvs[t + 1])
    end

    return pvs
end

function present_values(interest,cashflows,times)
    present_values_accumulator(interest,cashflows,times)
end

function present_values_accumulator(interest,cashflows,times,pvs=[0.0])
    from_time = length(times) == 1 ? 0. : times[end-1]
    pv = discount(interest,from_time,last(times)) *(first(pvs) + last(cashflows))
    pvs = pushfirst!(pvs,pv)

    if length(cashflows) > 1

        new_cfs = @view cashflows[1:end-1]
        new_times = @view times[1:end-1]
        return present_values_accumulator(interest,new_cfs,new_times,pvs)
    else
        # last discount and return
        return pvs[1:end-1] # end-1 get rid of trailing 0.0
    end
end

# if given a vector of rates, assume that it should be a forward discount yield
function present_values(y::Vector{T}, cfs, times) where {T <: Real}
    return present_values(Yields.Forward(y), cfs, times)
end

"""
    price(...)

The absolute value of the `present_value(...)`.

# Extended help

Using `price` can be helpful if the directionality of the value doesn't matter. For example, in the common usage, duration is more interested in the change in price than present value, so `price` is used there.
"""
price(x1,x2) = present_value(x1, x2) |> abs
price(x1,x2,x3) = present_value(x1, x2, x3) |> abs

"""
    breakeven(yield, cashflows::Vector)
    breakeven(yield, cashflows::Vector,times::Vector)

Calculate the time when the accumulated cashflows breakeven given the yield.

Assumptions:

- cashflows occur at the end of the period
- cashflows evenly spaced with the first one occuring at time zero if `times` not given

Returns `nothing` if cashflow stream never breaks even.

```jldoctest
julia> breakeven(0.10, [-10,1,2,3,4,8])
5

julia> breakeven(0.10, [-10,15,2,3,4,8])
1

julia> breakeven(0.10, [-10,-15,2,3,4,8]) # returns the `nothing` value

```
"""
function breakeven(y::T, cashflows::Vector, timepoints::Vector) where {T <: Yields.AbstractYield}
    accum = zero(eltype(cashflows))
    last_neg = nothing

    accum += cashflows[1]
    if accum >= 0 && isnothing(last_neg)
        last_neg = timepoints[1]
    end

    for i in 2:length(cashflows)
        # accumulate the flow from each timepoint to the next
        accum *= Yields.accumulation(y, timepoints[i - 1], timepoints[i])
        accum += cashflows[i]

        if accum >= 0 && isnothing(last_neg)
            last_neg = timepoints[i]
        elseif accum < 0
            last_neg = nothing
        end
    end

    return last_neg

end

function breakeven(y::T, cfs, times) where {T <: Real}
    return breakeven(Yields.Constant(y), cfs, times)
end

function breakeven(y::Vector{T}, cfs, times) where {T <: Real}
    return breakeven(Yields.Forward(y), cfs, times)
end

function breakeven(i, cashflows::Vector)
    return breakeven(i, cashflows, [t for t in 0:length(cashflows) - 1])
end

abstract type Duration end

struct Macaulay <: Duration end
struct Modified <: Duration end
struct DV01 <: Duration end

"""
    duration(Macaulay(),interest_rate,cfs,times)
    duration(Modified(),interest_rate,cfs,times)
    duration(::DV01,interest_rate,cfs,times)
    duration(interest_rate,cfs,times)             # Modified Duration
    duration(interest_rate,valuation_function)    # modified Duration

Calculates the Macaulay, Modified, or DV01 duration. `times` may be ommitted and the valuation will assume evenly spaced cashflows starting at the end of the first period.
- `interest_rate` should be a fixed effective yield (e.g. `0.05`).

When not given `Modified()` or `Macaulay()` as an argument, will default to `Modified()`.

# Examples

Using vectors of cashflows and times
```julia-repl
julia> times = 1:5
julia> cfs = [0,0,0,0,100]
julia> duration(0.03,cfs,times)
4.854368932038834
julia> duration(Macaulay(),0.03,cfs,times)
5.0
julia> duration(Modified(),0.03,cfs,times)
4.854368932038835
julia> convexity(0.03,cfs,times)
28.277877274012614

```

Using any given value function:

```julia-repl
julia> lump_sum_value(amount,years,i) = amount / (1 + i ) ^ years
julia> my_lump_sum_value(i) = lump_sum_value(100,5,i)
julia> duration(0.03,my_lump_sum_value)
4.854368932038835
julia> convexity(0.03,my_lump_sum_value)
28.277877274012617

```
"""
function duration(::Macaulay, yield, cfs, times)
    return sum(times .* price.(yield, vec(cfs), times) / price(yield, vec(cfs), times))
end

function duration(::Modified, yield, cfs, times)
    D(i) = price(i, vec(cfs), times)
    return duration(yield, D)
end

function duration(yield, valuation_function)
    D(i) = log(valuation_function(i + yield))
    δV =  - ForwardDiff.derivative(D, 0.0)
end

function duration(yield::Y, valuation_function) where {Y <: Yields.AbstractYield}
    D(i) = log(valuation_function(i + yield))
    δV =  - ForwardDiff.derivative(D, 0.0)
end

function duration(yield, cfs, times)
    return duration(Modified(), yield, vec(cfs), times)
end
function duration(yield::Y, cfs::A) where {Y <: Yields.AbstractYield,A <: AbstractArray}
    times = 1:length(cfs)
    return duration(Modified(), yield, vec(cfs), times)
end

function duration(yield::R, cfs) where {R <: Real}
    return duration(Yields.Constant(yield), cfs)
end

function duration(::DV01, yield, cfs, times)
    return duration(DV01(), yield, i -> price(i, vec(cfs), times))
end
function duration(d::Duration, yield, cfs)
    times = 1:length(cfs)
    return duration(d, yield, vec(cfs), times)
end

function duration(::DV01, yield, valuation_function)
    return duration(yield, valuation_function) * valuation_function(yield) / 100
end

"""
    convexity(yield,cfs,times)
    convexity(yield,valuation_function)

Calculates the convexity.
    - `yield` should be a fixed effective yield (e.g. `0.05`).
    - `times` may be omitted and it will assume `cfs` are evenly spaced beginning at the end of the first period.

# Examples

Using vectors of cashflows and times
```julia-repl
julia> times = 1:5
julia> cfs = [0,0,0,0,100]
julia> duration(0.03,cfs,times)
4.854368932038834
julia> duration(Macaulay(),0.03,cfs,times)
5.0
julia> duration(Modified(),0.03,cfs,times)
4.854368932038835
julia> convexity(0.03,cfs,times)
28.277877274012614

```

Using any given value function:

```julia-repl
julia> lump_sum_value(amount,years,i) = amount / (1 + i ) ^ years
julia> my_lump_sum_value(i) = lump_sum_value(100,5,i)
julia> duration(0.03,my_lump_sum_value)
4.854368932038835
julia> convexity(0.03,my_lump_sum_value)
28.277877274012617

```

"""
function convexity(yield, cfs, times)
    return convexity(yield, i -> price(i, vec(cfs), times))
end

function convexity(yield, cfs::A) where {A <: AbstractArray}
    times = 1:length(cfs)
    return convexity(yield, i -> price(i, vec(cfs), times))
end

function convexity(yield, valuation_function)
    v(x) = abs(valuation_function(yield + x[1]))
    ∂²P = ForwardDiff.hessian(v, [0.0])
    return ∂²P[1] / v([0.0])
end


"""
    ConjugateGradients(d,operators,parameters;<keyword arguments>)
Conjugate Gradients following Algorithm 2 from Scales, 1987.
The user provides an array of linear operators. Verify that linear operator(s) pass the dot product.
See also: [`DotTest`](@ref)

# Arguments
- `Niter=10` : Number of iterations
- `mu=0`
- `tol=1.0e-15`

"""
function ConjugateGradients(d,operators,parameters;Niter=10,mu=0,tol=1.0e-15)

    cost = Float64[]
    r = copy(d)
    g = LinearOperator(r,operators,parameters,adj=true)
    m = zero(g)
    s = copy(g)
    gamma = InnerProduct(g,g)
    gamma00 = gamma
    cost0 = InnerProduct(r,r)
    push!(cost,1.0)
    for iter = 1 : Niter
	t = LinearOperator(s,operators,parameters,adj=false)
	delta = InnerProduct(t,t) + mu*InnerProduct(s,s)
	if delta <= tol
#	    println("delta reached tolerance, ending at iteration ",iter)
	    break;
	end
	alpha = gamma/delta
	m = m + alpha*s
	r = r - alpha*t
	g = LinearOperator(r,operators,parameters,adj=true)
	g = g - mu*m
	gamma0 = copy(gamma)
	gamma = InnerProduct(g,g)
        cost1 = InnerProduct(r,r) + mu*InnerProduct(m,m)
        push!(cost,cost1/cost0)
	beta = gamma/gamma0
	s = beta*s + g
	if (sqrt(gamma) <= sqrt(gamma00) * tol)
	    println("tolerance reached, ending at iteration ",iter)
	    break;
	end
    end

    return m, cost
end


# The decimal number, 585 = 10010010012 (binary), is palindromic in both bases.
#
# Find the sum of all numbers, less than one million, which are palindromic in
# base 10 and base 2.
#
# (Please note that the palindromic number, in either base, may not include
# leading zeros.)

using ProjectEulerSolutions

# Simple using Julia's builtin bitstring and parse functions (with BigInt).
function p036solution(n::Integer=10)::Integer
    sums = 0
    for i in 1:n
        if ispalindrome_integer(i) && ispalindrome(parse(BigInt, bitstring(i)))
            sums += i
        end
    end
    return sums
end

p036 = Problems.Problem(p036solution)

Problems.benchmark(p036, 1_000_000)


@testset "xlogx, xlogy, and xlog1py" begin
    @test iszero(xlogx(0))
    @test xlogx(2) ≈ 2.0 * log(2.0)
    @test_throws DomainError xlogx(-1)
    @test isnan(xlogx(NaN))

    @test iszero(xlogy(0, 1))
    @test xlogy(2, 3) ≈ 2.0 * log(3.0)
    @test_throws DomainError xlogy(1, -1)
    @test isnan(xlogy(NaN, 2))
    @test isnan(xlogy(2, NaN))
    @test isnan(xlogy(0, NaN))

    @test iszero(xlog1py(0, 0))
    @test xlog1py(2, 3) ≈ 2.0 * log1p(3.0)
    @test_throws DomainError xlog1py(1, -2)
    @test isnan(xlog1py(NaN, 2))
    @test isnan(xlog1py(2, NaN))
    @test isnan(xlog1py(0, NaN))

    # Since we allow complex/negative values, test for them. See comments in:
    # https://github.com/JuliaStats/StatsFuns.jl/pull/95

    @test xlogx(1 + im) == (1 + im) * log(1 + im)
    @test isnan(xlogx(NaN + im))
    @test isnan(xlogx(1 + NaN * im))

    @test xlogy(-2, 3) == -xlogy(2, 3)
    @test xlogy(1 + im, 3) == (1 + im) * log(3)
    @test xlogy(1 + im, 2 + im) == (1 + im) * log(2 + im)
    @test isnan(xlogy(1 + NaN * im, -1 + im))
    @test isnan(xlogy(0, -1 + NaN * im))
    @test isnan(xlogy(Inf + im * NaN, 1))
    @test isnan(xlogy(0 + im * 0, NaN))
    @test iszero(xlogy(0 + im * 0, 0 + im * Inf))

    @test xlog1py(-2, 3) == -xlog1py(2, 3)
    @test xlog1py(1 + im, 3) == (1 + im) * log1p(3)
    @test xlog1py(1 + im, 2 + im) == (1 + im) * log1p(2 + im)
    @test isnan(xlog1py(1 + NaN * im, -1 + im))
    @test isnan(xlog1py(0, -1 + NaN * im))
    @test isnan(xlog1py(Inf + im * NaN, 1))
    @test isnan(xlog1py(0 + im * 0, NaN))
    @test iszero(xlog1py(0 + im * 0, -1 + im * Inf))
end

@testset "xexpx" begin
    for x in (false, 0, 0.0, 0f0, -Inf, -Inf32)
        @test (@inferred xexpx(x)) === zero(exp(x))
    end
    for x in (NaN16, NaN32, NaN64, Inf16, Inf32, Inf64)
        @test (@inferred xexpx(x)) === x
    end
    for x in (1, true, 1.0, 1f0)
        @test (@inferred xexpx(x)) === exp(x)
    end
    for a in (2, 2f0, 2.0), x in -a:a
        @test (@inferred xexpx(x)) === x * exp(x)
    end
end

@testset "xexpy" begin
    for x in (0, 1, 1.0, 1f0, Inf, Inf32), y in (-Inf, -Inf32)
        @test (@inferred xexpy(x, y)) === zero(x * exp(y))
    end
    for x in (0, 1, 1.0, 1f0, Inf, Inf32, -Inf, -Inf32, NaN, NaN32), nan in (NaN, NaN32)
        @test (@inferred xexpy(x, nan)) === oftype(x * exp(nan), NaN)
        @test (@inferred xexpy(nan, x)) === oftype(nan * exp(x), NaN)
    end
    for x in (2, -2f0, 2.0), y in (1, -1f0, 1.0)
        @test (@inferred xexpy(x, y)) ≈ x * exp(y)
    end
    for x in (randn(), randn(Float32))
        @test xexpy(x, x) ≈ xexpx(x)
    end
end

@testset "logistic & logit" begin
    @test logistic(2) ≈ 1.0 / (1.0 + exp(-2.0))
    @test logistic(-750.0) === 0.0
    @test logistic(-740.0) > 0.0
    @test logistic(+36.0) < 1.0
    @test logistic(+750.0) === 1.0
    @test iszero(logit(0.5))
    @test logit(logistic(2)) ≈ 2.0
end

@testset "logcosh" begin
    for x in (randn(), randn(Float32))
        @test @inferred(logcosh(x)) isa typeof(x)
        @test logcosh(x) ≈ log(cosh(x))
        @test logcosh(-x) == logcosh(x)
    end

    # special values
    for x in (-Inf, Inf, -Inf32, Inf32)
        @test @inferred(logcosh(x)) === oftype(x, Inf)
    end
    for x in (NaN, NaN32)
        @test @inferred(logcosh(x)) === x
    end
end

@testset "log1psq" begin
    @test iszero(log1psq(0.0))
    @test log1psq(1.0) ≈ log1p(1.0)
    @test log1psq(2.0) ≈ log1p(4.0)
end

# log1pexp, log1mexp, log2mexp & logexpm1

@testset "log1pexp" begin
    for T in (Float16, Float32, Float64, BigFloat), x in 1:40
        @test (@inferred log1pexp(+log(T(x)))) ≈ T(log1p(big(x)))
        @test (@inferred log1pexp(-log(T(x)))) ≈ T(log1p(1/big(x)))
    end

    # special values
    @test (@inferred log1pexp(0)) ≈ log(2)
    @test (@inferred log1pexp(0f0)) ≈ log(2)
    @test (@inferred log1pexp(big(0))) ≈ log(2)
    @test (@inferred log1pexp(+1)) ≈ log1p(ℯ)
    @test (@inferred log1pexp(-1)) ≈ log1p(ℯ) - 1

    # large arguments
    @test (@inferred log1pexp(1e4)) ≈ 1e4
    @test (@inferred log1pexp(1f4)) ≈ 1f4
    @test iszero(@inferred log1pexp(-1e4))
    @test iszero(@inferred log1pexp(-1f4))

    # compare to accurate but slower implementation
    correct_log1pexp(x::Real) = x > 0 ? x + log1p(exp(-x)) : log1p(exp(x))
    # large range needed to cover all branches, for all floats (from Float16 to BigFloat)
    for T in (Int, Float16, Float32, Float64, BigInt, BigFloat), x in -300:300
        @test (@inferred log1pexp(T(x))) ≈ float(T)(correct_log1pexp(big(x)))
    end
    # test BigFloat with multiple precisions
    for prec in (10, 20, 50, 100), x in -300:300
        setprecision(prec) do
            y = big(float(x))
            @test @inferred(log1pexp(y)) ≈ correct_log1pexp(y)
        end
    end
end

@testset "log1mexp" begin
    @test log1mexp(-1.0)  ≈ log1p(- exp(-1.0))
    @test log1mexp(-10.0) ≈ log1p(- exp(-10.0))
end

@testset "log2mexp" begin
    @test log2mexp(0.0)  ≈ 0.0
    @test log2mexp(-1.0) ≈ log(2.0 - exp(-1.0))
end

@testset "logexpm1" begin
    @test logexpm1(2.0)            ≈  log(exp(2.0) - 1.0)
    @test logexpm1(log1pexp(2.0))  ≈  2.0
    @test logexpm1(log1pexp(-2.0)) ≈ -2.0

    @test logexpm1(2f0)            ≈  log(exp(2f0) - 1f0)
    @test logexpm1(log1pexp(2f0))  ≈  2f0
    @test logexpm1(log1pexp(-2f0)) ≈ -2f0
end

@testset "log1pmx" begin
    @test iszero(log1pmx(0.0))
    @test log1pmx(1.0) ≈ log(2.0) - 1.0
    @test log1pmx(2.0) ≈ log(3.0) - 2.0
end

@testset "logmxp1" begin
    @test iszero(logmxp1(1.0))
    @test logmxp1(2.0) ≈ log(2.0) - 1.0
    @test logmxp1(3.0) ≈ log(3.0) - 2.0
end

@testset "logsumexp" begin
    @test logaddexp(2.0, 3.0)     ≈ log(exp(2.0) + exp(3.0))
    @test logaddexp(10002, 10003) ≈ 10000 + logaddexp(2.0, 3.0)

    for x in ([1.0], Complex{Float64}[1.0])
        @test @inferred(logsumexp(x)) == 1.0
        @test @inferred(logsumexp((xi for xi in x))) == 1.0
    end

    for x in ([1.0, 2.0, 3.0], Complex{Float64}[1.0, 2.0, 3.0])
        @test @inferred(logsumexp(x)) ≈ 3.40760596444438
        @test logsumexp(x .+ 1000) ≈ 1003.40760596444438
    end

    for x in ((1.0, 2.0, 3.0), map(complex, (1.0, 2.0, 3.0)))
        @test @inferred(logsumexp(x)) ≈ 3.40760596444438
    end

    _x = [[1.0, 2.0, 3.0] [1.0, 2.0, 3.0] .+ 1000.]
    for x in (_x, complex(_x))
        @test @inferred(logsumexp(x; dims=1)) ≈ [3.40760596444438 1003.40760596444438]
        @test @inferred(logsumexp(x; dims=[1, 2])) ≈ [1003.4076059644444]
        y = copy(x')
        @test @inferred(logsumexp(y; dims=2)) ≈ [3.40760596444438, 1003.40760596444438]
    end

    # check underflow
    @test logsumexp([1e-20, log(1e-20)]) ≈ 2e-20
    @test logsumexp(Complex{Float64}[1e-20, log(1e-20)]) ≈ 2e-20

    let cases = [([-Inf, -Inf], -Inf),   # correct handling of all -Inf
                 ([-Inf, -Inf32], -Inf), # promotion
                 ([-Inf32, -Inf32], -Inf32), # Float32
                 ([-Inf, Inf], Inf),
                 ([-Inf, 9.0], 9.0),
                 ([Inf, 9.0], Inf),
                 ([0, 0], log(2.0))] # non-float arguments
        for (arguments, result) in cases
            @test logaddexp(arguments...) ≡ result
            @test logsumexp(arguments) ≡ result
            @test logsumexp(complex(arguments)) ≡ complex(result)
        end
    end

    @test isnan(logsubexp(Inf, Inf))
    @test logsubexp(-Inf, -Inf) ≡ -Inf
    @test logsubexp(Inf, -Inf) ≡ Inf
    @test logsubexp(-Inf, Inf) ≡ Inf
    @test logsubexp(Inf, 9.0) ≡ Inf
    @test logsubexp(9.0, Inf) ≡ Inf
    @test logsubexp(-Inf, 9.0) ≡ 9.0
    @test logsubexp(9.0, -Inf) ≡ 9.0
    @test logsubexp(1f2, 1f2) ≡ -Inf32
    @test logsubexp(0, 0) ≡ -Inf
    @test logsubexp(3, 2) ≈ 2.541324854612918108978

    # NaN propagation
    for f in (logaddexp, logsubexp)
        @test isnan(f(NaN, 9.0))
        @test isnan(f(NaN, Inf))
        @test isnan(f(NaN, -Inf))
        @test isnan(f(9.0, NaN))
        @test isnan(f(Inf, NaN))
        @test isnan(f(-Inf, NaN))
    end

    @test isnan(logsumexp([NaN, 9.0]))
    @test isnan(logsumexp([NaN, Inf]))
    @test isnan(logsumexp([NaN, -Inf]))
    @test isnan(logsumexp(Complex{Float64}[NaN, 9.0]))
    @test isnan(logsumexp(Complex{Float64}[NaN, Inf]))
    @test isnan(logsumexp(Complex{Float64}[NaN, -Inf]))
    @test isnan(logsumexp(Complex{Float64}[NaN * im, 9.0]))
    @test isnan(logsumexp(Complex{Float64}[NaN * im, Inf]))
    @test isnan(logsumexp(Complex{Float64}[NaN * im, -Inf]))

    # logsumexp with general iterables (issue #63)
    xs = range(-500, stop = 10, length = 1000)
    @test @inferred(logsumexp(x for x in xs)) == logsumexp(xs)
    xs = range(-500 + 0.5im, stop = 10 + 30im, length = 1000)
    @test @inferred(logsumexp(x for x in xs)) == logsumexp(xs)

    # complex numbers
    xs = randn(Complex{Float64}, 10, 5)
    @test @inferred(logsumexp(xs)) ≈ log(sum(exp.(xs)))
    @test @inferred(logsumexp(xs; dims=1)) ≈ log.(sum(exp.(xs); dims=1))
    @test @inferred(logsumexp(xs; dims=2)) ≈ log.(sum(exp.(xs); dims=2))
    @test @inferred(logsumexp(xs; dims=[1, 2])) ≈ log(sum(exp.(xs); dims=[1, 2]))
    @test @inferred(logsumexp(x for x in xs)) == logsumexp(xs)
end

@testset "softmax" begin
    x = [1.0, 2.0, 3.0]
    r = exp.(x) ./ sum(exp.(x))

    # in-place versions
    for T in (Float32, Float64)
        s = Vector{T}(undef, 3)
        softmax!(s, x)
        @test s ≈ r

        fill!(s, zero(T))
        softmax!(s, x; dims=1)
        @test s ≈ r

        s = Matrix{T}(undef, 1, 3)
        softmax!(s, x)
        @test s ≈ permutedims(r)

        @test_throws DimensionMismatch softmax!(s, x; dims=1)

        fill!(s, zero(T))
        softmax!(s, permutedims(x); dims=2)
        @test s ≈ permutedims(r)

        fill!(s, zero(T))
        softmax!(s, permutedims(x); dims=1:2)
        @test s ≈ permutedims(r)
    end
    softmax!(x)
    @test x ≈ r

    for (S, T) in ((Int, Float64), (Float64, Float64), (Float32, Float32))
        x = S[1, 2, 3]
        s = softmax(x)
        @test s ≈ r
        @test eltype(s) === T

        x = repeat(S[1, 2, 3], 1, 3)
        s = softmax(x; dims=1)
        @test s ≈ repeat(r, 1, 3)
        @test eltype(s) === T

        x = repeat(S[1 2 3], 3, 1)
        s = softmax(x; dims=2)
        @test s ≈ repeat(permutedims(r), 3, 1)
        @test eltype(s) === T

        x = S[1 2 3]
        s = softmax(x; dims=1:2)
        @test s ≈ permutedims(r)
        @test eltype(s) === T
    end

    x = [1//2, 2//3, 3//4]
    r = exp.(x) ./ sum(exp.(x))
    s = softmax(x)
    @test s ≈ r
    @test eltype(s) === Float64

    # non-standard indices: #12
    x = OffsetArray(1:3, -2:0)
    s = softmax(x)
    @test s isa OffsetArray{Float64}
    @test axes(s, 1) == OffsetArrays.IdOffsetRange(-2:0)
    @test collect(s) ≈ softmax(1:3)
end


"""
    Haversine(radius=6_371_000)

The haversine distance between two locations on a sphere of given `radius`, whose
default value is 6,371,000, i.e., the Earth's (volumetric) mean radius in meters; cf.
[NASA's Earth Fact Sheet](https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html).

Locations are described with longitude and latitude in degrees.
The computed distance has the unit of the radius.
"""
struct Haversine{T<:Number} <: Metric
    radius::T
end
Haversine{T}() where {T<:Number} = Haversine(T(6_371_000))
Haversine() = Haversine{Int}()

function (dist::Haversine)(x, y)
    length(x) == length(y) == 2 || haversine_error(dist)

    λ₁, φ₁ = x
    λ₂, φ₂ = y

    Δλ = λ₂ - λ₁  # longitudes
    Δφ = φ₂ - φ₁  # latitudes

    # haversine formula
    a = sind(Δφ/2)^2 + cosd(φ₁)*cosd(φ₂)*sind(Δλ/2)^2

    # distance on the sphere
    2 * (dist.radius * asin( min(√a, one(a)) )) # take care of floating point errors
end

haversine(x, y, radius::Number=6_371_000) = Haversine(radius)(x, y)

@noinline haversine_error(dist) = throw(ArgumentError("expected both inputs to have length 2 in $dist distance"))

result_type(::Haversine{T1}, ::Type{T2}, ::Type{T3}) where {T1<:Number,T2<:Number,T3<:Number} =
    float(promote_type(T1, T2, T3))

"""
    SphericalAngle()

The spherical angle distance between two locations on a sphere.

Locations are described with two angles, longitude and latitude, in radians.
The distance is computed with the haversine formula and also has units of radians.
"""
struct SphericalAngle <: Metric end

function (dist::SphericalAngle)(x, y)
    length(x) == length(y) == 2 || haversine_error(dist)

    λ₁, φ₁ = x
    λ₂, φ₂ = y

    Δλ = λ₂ - λ₁  # longitudes
    Δφ = φ₂ - φ₁  # latitudes

    # haversine formula
    a = sin(Δφ/2)^2 + cos(φ₁)*cos(φ₂)*sin(Δλ/2)^2

    # distance on the sphere
    2 * asin( min(√a, one(a)) ) # take care of floating point errors
end

const spherical_angle = SphericalAngle()

result_type(::SphericalAngle, ::Type{T1}, ::Type{T2}) where {T1<:Number,T2<:Number} = float(promote_type(T1, T2))


module EOQ

using DecFP
export @d128_str, @d32_str, @d64_str, @d_str, Dec128, Dec32, Dec64, DecFP, exponent10, ldexp10, sigexp
"""
Simple EOQ model with required arguments:
    - demand (constant per unit time), order (set up) cost, holding (carrying) cost
    - holding cost can be calculated as (unit cost)*(holding rate)
Optional arguments:
    - time units ---> Tuple ---> default is ('m' for months, 12) => 1 year
    - lead time (default is 0)
"""
function eoq_execute(;demand::Signed, c::Union{Signed,AbstractFloat}, ct::Union{Signed,AbstractFloat},
    ce::Union{Signed,AbstractFloat}, time_units::Tuple{String,Signed}, lead_time::Signed)
    # Basic Output
    Q = sqrt(2*ct*demand/ce)
    T_raw = Q/demand
    T = Dec64(T_raw*time_units[2])
    N = 1/T_raw

    # Inventory Position
    # ROP_IP = demand*lead_time/time_units[2]

    # Costs (not accounting for pipeline inventory cost, i.e. for lead_time > 0)
    OrderingCost = ct*N
    HoldingCost = ce*(Q/2)
    TRC() = OrderingCost + HoldingCost
    TC() = TRC() + c*demand

    # Inventory Policy
    policy() = Dict("Q"=>Q, "T"=>T, "N"=>N, "TRC"=>TRC(), "TC"=>TC())

    function policy_for(s::String, value::Union{Signed, AbstractFloat})
        if s == "Q"
            Q_for = value
            T_raw_for = value/demand
            T_for = T_raw_for*time_units[2]
            N_for = 1/T_raw_for
        elseif s == "T"
            T_for = value
            N_for = time_units[2]/value
            Q_for = demand/N_for
        else
            return "Invalid input"
        end
        OrderingCost_for = ct*N_for
        HoldingCost_for = ce*(Q_for/2)
        TRC_for() = OrderingCost_for + HoldingCost_for
        TC_for() = TRC_for() + c*demand
        return Dict("Q*"=>Q_for, "T"=>T_for, "N"=>N_for, "TRC"=>TRC_for(), "TC" => TC_for(), "ΔTC"=> TC_for()/TC())
    end

    # [Required] Returning a function with "public" elements
    ()->(Q, T_raw, T, N,
    OrderingCost, HoldingCost, TRC, TC,
    policy, policy_for)
end

"""
Intermediary function to simplify input for eoq_execute()
"""
function eoq(;demand, c=0, ct, ce, time_units=("m",12), lead_time=0)
    d = Dict(:demand=>demand, :c=>c, :ct=>ct, :ce=>ce, :time_units=>time_units, :lead_time=>lead_time)
    return eoq_execute(;d...)
end

end # Module EOQ


ACTIVATIONS = [
    :line, :σ, :sigmoid, :hardσ, :hardsigmoid, :hardtanh, :relu,
    :leakyrelu, :relu6, :rrelu, :prelu, :elu, :gelu, :swish, :selu,
    :celu, :softplus, :softsign, :logσ, :logcosh,
    :mish, :tanhshrink, :softshrink, :trelu, :lisht,
    :gaussian, :GCU, :SQU, :NCU, :SSU, :DSU
]
for f in ACTIVATIONS
    @eval export $(f)
end

"""
    line(x)
Linear function. This is the expression:
```math
line(x) = x
```
"""
line(x) = x

@doc raw"""
    σ(x)
Standard sigmoid activation function. Also, this function can be called with `σ`. This is the expression:
```math
\sigma(x) = \frac{1}{1+e^{-x}}
```
"""
function sigmoid(x)
    t = exp(-abs(x))
    return (x>=0) ? inv(1 + t) : t / (t + 1)
end
const σ = sigmoid

@doc raw"""
    hardsigmoid(x) = max(0, min(1, (x + 2.5) / 6))
Piecewise linear approximation of sigmoid. Also, this function can be called with `hardσ`. This is the expression:
```math
hardsigmoid(x) = \left\{
\begin{array}{ll}
1 & (x \geq \frac{1}{4}) \\
\frac{1}{5} x & (- \frac{1}{4} \lt x \lt \frac{1}{4}) \\
0 & (x \leq - \frac{1}{4})
\end{array}
\right.
```
"""
hardsigmoid(x) = max(0, min(1, (x + 2.5) / 6))
const hardσ = hardsigmoid

@doc raw"""
    lisht(x)
This is the expression:
```math
lisht(x) = x\tanh(x)
```
"""
lisht(x) = x*tanh(x)

@doc raw"""
    relu(x) = max(0, x)
`relu` is `Rectified Linear Unit`. This is the expression:
```math
relu(x) = \left\{
\begin{array}{ll}
x & (x \geq 0) \\
0 & (x \lt 0)
\end{array}
\right.
```
"""
relu(x) = max(0, x)

@doc raw"""
    leakyrelu(x; α=0.01) = (x>0) ? x : α*x
Leaky Rectified Linear Unit. This is the expression:
```math
leakyrelu(x) = \left\{
\begin{array}{ll}
\alpha x & (x \lt 0) \\
x & (x \geq 0)
\end{array}
\right.
```
"""
leakyrelu(x; α=0.01f0) = max(x, α*x)

@doc raw"""
    rrelu(min, max)
Randomized Rectified Linear Unit. The expression is the as [`leakyrelu`](@ref), but `α` is a random number between `min` and `max`.
Also, since this function is defined as a structure, use it as follows:
```
Dense(10=>5, rrelu(0.001, 0.1))
```
"""
struct rrelu
    α::Union{Float64, Float32}
    function rrelu(min::Float64, max::Float64)
        new(round(rand()*(max-min+1))+min)
    end
end

(rrelu::rrelu)(x) = max(x, rrelu.α*x)

@doc raw"""
    prelu(; α=0.01)
Parametric Ractified LinearUnit. The expression is the as [`leakyrelu`](@ref), but `α` is determined by learning. Also, when using this function, use [`Denseσ`](@ref) instead of [`Dense`](@ref).
"""
struct prelu
    α::Union{Float64, Float32}
    prelu(; α=0.01) = new(α)
end

(prelu::prelu)(x) = max(x, prelu.α*x)

@doc raw"""
    relu6(x)
Relu function with an upper limit of 6. This is the expression:
```math
relu6(x) = \left\{
\begin{array}{ll}
6 & (x \gt 6) \\
x & (x \geq 0) \\
0 & (x \lt 0)
\end{array}
\right.
```
"""
relu6(x) = min(6, max(0, x))

@doc raw"""
    elu(x, α=1)
Exponential Linear Unit activation function. You can also specify the coefficient explicitly, e.g. elu(x, 1). This is the expression:
```math
elu(x, α) = \left\{
\begin{array}{ll}
x & (x \geq 0) \\
\alpha(e^x-1) & (x \lt 0)
\end{array}
\right.
```
"""
elu(x; α=1) = (x<0) ? (exp(x)-1)α : x

@doc raw"""
    celu(x; α=1)
Continuously Differentiable Exponential Linear Unit. This is the expression:
```math
\alpha = 1 \\
celu(x) = \left\{
\begin{array}{ll}
x & (x \geq 0) \\
\alpha(e^\frac{x}{\alpha}-1) & (x \lt 0)
\end{array}
\right.
```
"""
celu(x; α=1) = (x>=0) ? x : α*(exp(x/α)-1)

@doc raw"""
    gelu(x)
Gaussian Error Linear Unit. This is the expression(``\phi`` is a distribution function of standard normal distribution.):
```math
gelu(x) = x\phi(x)
```
However, in the implementation, it is calculated with the following expression.
```math
\sigma(x) = \frac{1}{1+e^{-x}} \\
gelu(x) = x\sigma(1.702x)
```
"""
gelu(x) = σ(1.702*x)x

@doc raw"""
    selu(x)
Scaled exponential linear units. This is the expression
```math
\lambda = 1.0507009873554804934193349852946 \\
\alpha = 1.6732632423543772848170429916717 \\
selu(x) = \lambda \left\{
\begin{array}{ll}
x & (x \geq 0) \\
\alpha(e^x-1) & (x \lt 0)
\end{array}
\right.
```
"""
function selu(x)
    λ = oftype(float(x), selu_λ)
    α = oftype(float(x), selu_α)
    return ((x > 0) ? x : (exp(x)-1)α)*λ
end

const selu_λ = 1.0507009873554804934193349852946
const selu_α = 1.6732632423543772848170429916717

@doc raw"""
    trelu(x; θ=1)
Threshold gated Rectified Linear Unit. This is the expression:
```math
\theta = 1 \\
trelu(x) = \left\{
\begin{array}{ll}
x & (x \gt 0) \\
0 & (x \leq 0)
\end{array}
\right.
```
"""
trelu(x; θ=1) = (x<=θ) ? 0 : x

@doc raw"""
    logσ(x)
logarithmic sigmoid function. This is the expression:
```math
\sigma(x) = \frac{1}{1+e^{-x}} \\
logsigmoid(x) = \log(\sigma(x))
```
"""
logsigmoid(x) = log(σ(x))
const logσ = logsigmoid

@doc raw"""
    logcosh(x)
Log-Cosh function. This is the expression:
```math
logcosh(x) = \log(\cosh(x))
```
"""
logcosh(x) = log(cosh(x))

@doc raw"""
    hardtanh(x)
Linear tanh function. This is the expression:
```math
hardtanh(x) = \left\{
\begin{array}{ll}
1 & (x \geq 1) \\
x & (-1 \lt x \lt 1) \\
-1 & (x \leq -1)
\end{array}
\right.
```
"""
hardtanh(x) = min(1, max(-1, x))

@doc raw"""
    tanhshrink(x)
Shrink tanh function. This is the expression:
```math
tanhshrink(x) = 1-\tanh(x)
```
"""
tanhshrink(x) = 1-tanh(x)

@doc raw"""
    softshrink(x; λ=0.5)
This is the expression:
```math
\lambda=0.5 \\
softshrink(x) = \left\{
\begin{array}{ll}
x-\lambda & (x \gt \lambda) \\
0 & (-\lambda \leq x \leq \lambda) \\
x+\lambda & (x \lt -\lambda) \\
\end{array}
\right.
```
"""
softshrink(x; λ=0.5) = min(x+λ, max(x-λ, 0))

@doc raw"""
    softsign(x) = x / (1+abs(x))
The softsign activation function. This is the expression:
```math
softsign(x) = \frac{x}{1+|x|}
```
"""
softsign(x) = x / (1 + abs(x))

@doc raw"""
    softplus(x) = log(1 + exp(x))
the softplus activation function. This is the expression:
```math
softplus(x) = \ln(1+e^x)
```
"""
softplus(x) = log(1 + exp(x))

@doc raw"""
    mish(x) = x * tanh(softplus(x))
The mish function. This is the expression:
```math
softplus(x) = \ln(1+e^x) \\
mish(x) = x\tanh(softplus(x))
```
"""
mish(x) = x * tanh(softplus(x))

@doc raw"""
    swish(x; β=1)
The swish function. This is the expression:
```math
\sigma(x) = \frac{1}{1+e^{-x}} \\
swish(x) = x\sigma(\beta x)
```
"""
swish(x; β=1) = x * σ(β*x)

@doc raw"""
    gaussian(x)
The Gauss Function. This is the expression:
```math
Gaussian(x) = e^{-x^{2}}
```
"""
gaussian(x) = exp(-x^2)

@doc raw"""
    GCU(x)
Growing Cosine Unit. This is the expression:
```math
GCU(x) = x\cos(x)
```
"""
GCU(x) = x*cos(x)

@doc raw"""
    SQU(x)
Shifted Quadratic Unit. SQU is a biologically inspired activation that enables single neurons to learn the XOR function. This is the expression:
```math
SQU(x) = x^{2}+x
```
"""
SQU(x) = x^2+x

@doc raw"""
    NCU(x)
Non-Monotonic Cubic Unit. This is the expression:
```math
NCU(x) = x-x^{3}
```
"""
NCU(x) = x-x^3

@doc raw"""
    SSU(x)
Shifted Sinc Unit. This is the expression:
```math
SSU(x) = \pi sinc(x - \pi)
```
"""
SSU(x) = pi*sinc(x-pi)

@doc raw"""
    DSU(x)
Decaying Sine Unit. This is the expression:
```math
DSU(x) = \frac{\pi}{2}(sinc(x-\pi)-sinc(x+\pi))
```
"""
DSU(x) = (pi/2)*(sinc(x-pi)-sinc(x+pi))


using DiffEqSensitivity, OrdinaryDiffEq, DiffEqBase, ForwardDiff
using Test

function fb(du,u,p,t)
  du[1] = dx = p[1]*u[1] - p[2]*u[1]*u[2]
  du[2] = dy = -p[3]*u[2] + p[4]*u[1]*u[2]
end

function jac(J,u,p,t)
  (x, y, a, b, c) = (u[1], u[2], p[1], p[2], p[3])
  J[1,1] = a + y * b * -1
  J[2,1] = y
  J[1,2] = b * x * -1
  J[2,2] = c * -1 + x
end

f = ODEFunction(fb,jac=jac)
p = [1.5,1.0,3.0,1.0]; u0 = [1.0;1.0]
prob = ODEProblem(f,u0,(0.0,10.0),p)
loss(sol) = sum(sol)
v = ones(4)

H  = second_order_sensitivities(loss,prob,Vern9(),saveat=0.1,abstol=1e-12,reltol=1e-12)
Hv = second_order_sensitivity_product(loss,v,prob,Vern9(),saveat=0.1,abstol=1e-12,reltol=1e-12)

_loss(p) = loss(concrete_solve(prob,Vern9(),u0,p,saveat=0.1,abstol=1e-12,reltol=1e-12))
H2 = ForwardDiff.hessian(_loss,p)
H2v = H*v

@test H ≈ H2
@test Hv ≈ H2v


# Natural Round
round2(x; digits = 0) = sign(x) * floor( abs(x) * 10.0^digits + 0.5 ) / (10.0^digits)

# Cumulative distribution function
function CDF(x, F::EG)
    if x < minimum(F.table.scale) return 0 end
    if x > maximum(F.table.scale) return 1 end
    F.table.cumprob[F.table.scale .== x][1]
end

# Percentile rank function
function PRF(x, F::EG)
    if x < (minimum(F.table.scale) - F.interval/2.0) return 0.0 end
    if x ≥ (maximum(F.table.scale) + F.interval/2.0) return 100.0 end
    x′ = round2(x)
    Fx1 = CDF(x′-F.interval, F)
    Fx = CDF(x′, F)
    P = 100*(Fx1+(x-x′+F.interval/2.0)*(Fx-Fx1))[1]
    return P
end

# Percentile Function
function p_search_descend(P, F::EG, offset)
    x = nothing;iter = length(F.table.scale)
    while x === nothing
        iter -= 1
        x =  100CDF(F.table.scale[iter], F) > P ? nothing : F.table.scale[iter+offset]
    end
    return x
end
function p_search_ascend(P, F::EG, offset)
    x = nothing;iter = 0
    while x === nothing
        iter += 1
        x = 100CDF(F.table.scale[iter], F) < P ? nothing : iter == 1 ? 0.0 : F.table.scale[iter+offset]
    end
    return x
end

# The inverse of the percentile rank function (Percentile function)
function PFu(P, F::EG)
    if P ≥ 100.0 return (maximum(F.table.scale) + .5) end
    xu = P > 50.0 ? p_search_descend(P, F, 1) : p_search_ascend(P, F, 0)
    x = (P/100 - CDF(xu-F.interval, F)) / (CDF(xu, F) - CDF(xu-F.interval, F))
    return isinf(x) || isnan(x) ? xu -F.interval/2.0 : x + xu -F.interval/2.0
end
function PFl(P, F::EG)
    if P ≤ 0.0 return -.5 end
    xl = P > 50.0 ? p_search_descend(P, F, 0) : p_search_ascend(P, F, -1)
    x = (P/100 - CDF(xl, F)) / (CDF(xl+F.interval, F) - CDF(xl, F))
    return isinf(x) || isnan(x) ? xl + F.interval/2.0 : x + xl + F.interval/2.0
end

# equipercentile equating
mutable struct SGEquateResult <: SGEquateMethod
    method
    table
    estimates
    data
end
"""
    Equipercentile(X::EG, Y::EG; case = :middle)

Equipercentile Equating under equivalent (single) group design. The smoothed frequency can be used.
A table returned by the function contains 2 columns. The first one stands for the base scale score in the test X.
The second one is equated score from test Y, which is equipercentile score on the base scale.

`case` represents which equating case use. Pass to the symbols below.

- `:upper` Calculating scores correspond to arbitrary percentile P, use the smallest integer score with a cumulative percent that is greater than P.
- `:lower` (default) Contrary to above case, use the largest integer score with a cumulative percent that is less than P
- `:both` (Not for practice) Show both case of upper and lower.
- `:middle` Use midpoint case upper between lower.

In my example cases, it seems that equate package in R uses `lower` case to equate.
"""
function Equipercentile(X::EG, Y::EG; case = :lower)
    scaleX = X.table.scale
    eYxu = zeros(Float64, length(scaleX))
    eYxl = zeros(Float64, length(scaleX))
    for (i,v) in enumerate(scaleX)
        P = PRF(v, X)
        eYxu[i] = PFu(P, Y)
        eYxl[i] = PFl(P, Y)
    end
    if case == :upper
        eYx = eYxu
    elseif case == :lower
        eYx = eYxl
    elseif case == :both
        eYx = string.(eYxu, "_", eYxl)
    elseif case == :middle
        eYx = (eYxu .+ eYxl) ./ 2.0
    end
    tbl = DataFrame(scaleX = scaleX, eYx = eYx)
    return SGEquateResult(
        Symbol("Equipercentile($(case))"),
        tbl,
        nothing,
        (X = X, Y = Y)
    )
end
# linear equating
"""
    Linear(X::EG, Y::EG)

Linear equating under the equivalent group desing.
This method, equate to match first 2 moments, is so simple to comprehend the equating result.
"""
function Linear(X::EG, Y::EG)
    μX = mean(X.raw); σX = std(X.raw)
    μY = mean(Y.raw); σY = std(Y.raw)
    slope = σY/σX; intercept = μY - slope*μX
    lYx = @. X.table.scale * slope + intercept
    tbl = DataFrame(scaleX = X.table.scale, lYx = lYx)
    return SGEquateResult(
        :Linear,
        tbl,
        (slope = slope, intercept = intercept),
        (X = X, Y = Y)
    )
end

# mean equating
"""
    Mean(X::EG, Y::EG)

Mean equating under the equivalent group desing.
This method, equate to match only first moments = mean, is so simple to comprehend the equating result.
"""
function Mean(X::EG, Y::EG)
    μX = mean(X.raw)
    μY = mean(Y.raw)
    slope = 1.0; intercept = μY - μX
    lYx = @. X.table.scale * slope + intercept
    tbl = DataFrame(scaleX = X.table.scale, lYx = lYx)
    return SGEquateResult(
        :Mean,
        tbl,
        (slope = slope, intercept = intercept),
        (X = X, Y = Y)
    )
end


@doc raw"""
    LocalLevel(y::Vector{Fl}) where Fl

The local level model is defined by:
```math
\begin{gather*}
    \begin{aligned}
        y_{t} &=  \mu_{t} + \varepsilon_{t} \quad \varepsilon_{t} \sim \mathcal{N}(0, \sigma^2_{\varepsilon})\\
        \mu_{t+1} &= \mu_{t} + \eta_{t} \quad \eta_{t} \sim \mathcal{N}(0, \sigma^2_{\eta})\\
    \end{aligned}
\end{gather*}
```

# Example
```jldoctest
julia> model = LocalLevel(rand(100))
LocalLevel model
```

See more on [Nile river annual flow](@ref)

# References
 * Durbin, James, & Siem Jan Koopman. (2012). "Time Series Analysis by State Space Methods: Second Edition." Oxford University Press. pp. 9
"""
mutable struct LocalLevel <: StateSpaceModel
    hyperparameters::HyperParameters
    system::LinearUnivariateTimeInvariant
    results::Results

    function LocalLevel(y::Vector{Fl}) where Fl

        # Define system matrices
        Z = ones(Fl, 1)
        T = ones(Fl, 1, 1)
        R = ones(Fl, 1, 1)
        d = zero(Fl)
        c = zeros(Fl, 1)
        H = one(Fl)
        Q = ones(Fl, 1, 1)

        system = LinearUnivariateTimeInvariant{Fl}(y, Z, T, R, d, c, H, Q)

        # Define hyperparameters names
        names = ["sigma2_ε", "sigma2_η"]
        hyperparameters = HyperParameters{Fl}(names)

        return new(hyperparameters, system, Results{Fl}())
    end
end

# Obligatory methods
function default_filter(model::LocalLevel)
    Fl = typeof_model_elements(model)
    a1 = zero(Fl)
    P1 = Fl(1e6)
    steadystate_tol = Fl(1e-5)
    return ScalarKalmanFilter(a1, P1, 1, steadystate_tol)
end

function initial_hyperparameters!(model::LocalLevel)
    Fl = typeof_model_elements(model)
    observed_variance = var(model.system.y[findall(!isnan, model.system.y)])
    initial_hyperparameters = Dict{String,Fl}(
        "sigma2_ε" => observed_variance, "sigma2_η" => observed_variance
    )
    set_initial_hyperparameters!(model, initial_hyperparameters)
    return model
end

function constrain_hyperparameters!(model::LocalLevel)
    constrain_variance!(model, "sigma2_ε")
    constrain_variance!(model, "sigma2_η")
    return model
end

function unconstrain_hyperparameters!(model::LocalLevel)
    unconstrain_variance!(model, "sigma2_ε")
    unconstrain_variance!(model, "sigma2_η")
    return model
end

function fill_model_system!(model::LocalLevel)
    model.system.H = get_constrained_value(model, "sigma2_ε")
    model.system.Q[1] = get_constrained_value(model, "sigma2_η")
    return model
end

function reinstantiate(::LocalLevel, y::Vector{Fl}) where Fl
    return LocalLevel(y)
end

has_exogenous(::LocalLevel) = false


using OrdinaryDiffEq, PyPlot, LinearAlgebra

const tol = 1e-8

function compute_max_lyapunov_exponent(rhs_fn, x0, params, δ0, Δt, N)
    # solve the reference trajectory
    problem = ODEProblem(rhs_fn, x0, (0, N*Δt), params)
    solver = AutoTsit5(Rosenbrock23())
    ref_sol = solve(problem, solver, reltol=tol, abstol=tol, dense=true)
    # neighboring trajectory
    du = ref_sol.u[2] - ref_sol.u[1] # vector along trajectory
    v = rand(size(du, 1))
    pert = v - dot(du, v) / dot(du, du) * du # vector orthogonal to trajectory
    pert *= δ0 / norm(pert) # make pertubation small
    x_test = x0 + pert
    # result
    s = 0.0
    for i in 1:N
        t = i*Δt
        # advance neighboring trajectory
        p = ODEProblem(rhs_fn, x_test, ((i - 1)*Δt, t), params)
        neighboring_sol = solve(p, solver,  reltol=tol, abstol=tol, dense=true)
        # compute seperation
        δ = norm(neighboring_sol(t) - ref_sol(t))
        # update result
        s += log(δ / δ0)
        # rescale the neighboring trajectory
        x_ref = ref_sol(t)
        x_test = x_ref + δ0 / δ * (x_test - x_ref)
    end
    # result
    return s / (Δt * N)
end

function lorentz_rhs(du, u, p, t)
    β, σ, ρ = p
    x, y, z = u
    du[1] = σ * (y - x)
    du[2] = - x * z - ρ * x - y
    du[3] = x * y - β * z
end

β = 8 / 3.
σ = 10.0
ρ_range = 20:0.1:100
λs = [compute_max_lyapunov_exponent(lorentz_rhs, [1.0, 1.0, 1.0],
           [β, σ, ρ], 1e-5, 0.001, 20)
     for ρ = ρ_range]
plot(ρ_range, λs)
xlabel("\$\\rho\$")
ylabel("\$\\lambda_1\$")


export first_permuted_prime
export isprime
export sieve_eratosthenes

"""
Creates permutations of numbers and returns when the first prime value is reached.
"""
function first_permuted_prime(elementlist::Array, multiplier::Array, k::Integer=1)
    if k == length(elementlist)
        if elementlist[end] % 2 == 1
            val = sum(elementlist .* multiplier)
            if isprime(val)
                return val
            else
                return -1
            end
        else
            return -1
        end
    else
        for i in k:length(elementlist)
            elementlist[k], elementlist[i] = elementlist[i], elementlist[k]
            v = first_permuted_prime(elementlist, multiplier, k + 1)
            if v > 0
                return v
            end
            elementlist[k], elementlist[i] = elementlist[i], elementlist[k]
        end
    end
    return -1
end

"""
Reasonably fast test for primality.
"""
function isprime(n)
    if n == 2 || n == 3
        return true
    end
    if n < 2 || n % 2 == 0
        return false
    end
    if n < 9
        return true
    end
    if n % 3 == 0
        return false
    end
    r = typeof(n)(floor(sqrt(n)))
    f = 5
    while f <= r
        if n % f == 0
            return false
        end
        if n % (f + 2) == 0
            return false
        end
        f += 6
    end
    return true
end

"""
Implements the Sieve of Eratosthenes to return all prime numbers up "n".
https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
"""
function sieve_eratosthenes(n::Integer=500, nstart::Integer=2)::Array{Integer}
    primes = trues(n)
    primes[1] = false

    for i in 2:floor(Integer, sqrt(n))
        if primes[i]
            @inbounds primes[(i+i):i:n] .= false
        end
    end
    primes[1:nstart-1] .= false
    return findall(primes)
end


# This file contains functions to calculate polygon centroid and mean center

"""
    meancenter(P)
Calculate the mean center of a polygon or vector of polygons ``P``.
"""
function meancenter end

function meancenter(P::Union{AbstractPolygon,AbstractMultiPolygon})::Tuple{Float64, Float64}
    xy = _getpointsPoligon(P)

    cx = mean(xy[1])
    cy = mean(xy[2])

    return cx, cy
end

function meancenter(P::Vector{T} where T <:Union{Missing,AbstractPolygon,AbstractMultiPolygon})::Tuple{Vector{Float64}, Vector{Float64}}
    cxy = meancenter.(P)

    cx = first.(cxy)
    cy = last.(cxy)

    return cx, cy
end

"""
    centroid(P)
Calculate the centroid of a polygon or vector of polygons ``P``.
"""
function centroid end

function centroid(P::Union{AbstractPolygon,AbstractMultiPolygon})::Tuple{Float64, Float64}
    xy = _getpointsPoligon(P)

    xo = xy[1]
    yo = xy[2]

    # Formula: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon

    # If the last point is not equal to the first point, add a new point similar to the first point.
    if (xo[end] != xo[1]) | (yo[end] != yo[1])
        x = copy(xo)
        y = copy(yo)

        push!(x, x[1])
        push!(y, y[1])
    else
        x = xo
        y = yo
    end

    # Loop to calculate elements of A and centroids
    n = length(x)
    A = 0
    xprodsum = 0
    yprodsum = 0

    for i in 1:n-1
        xycross = x[i] * y[i + 1] - x[i + 1] * y[i]
        xprod = (x[i] + x[i + 1]) * xycross
        yprod = (y[i] + y[i + 1]) * xycross

        A += xycross
        xprodsum += xprod
        yprodsum += yprod
    end
    A = A / 2

    # Centroids
    cx = 1 / (6 * A) * xprodsum
    cy = 1 / (6 * A) * yprodsum

    return cx, cy
end

function centroid(P::Vector{T} where T <:Union{Missing,AbstractPolygon,AbstractMultiPolygon})::Tuple{Vector{Float64}, Vector{Float64}}
    cxy = centroid.(P)

    cx = first.(cxy)
    cy = last.(cxy)

    return cx, cy
end


using LinearAlgebra

"""
# Description
Find the conical hull defined by the following constraints:
  1ᵀx = c
  Ax ≤ b
  x ≥ 0
# Returns
* `::PolyhedralCone`: conical hull of constraints
"""
function cone_from_constraints(A::Matrix{T}, b::Vector{T}, c::T) where T<: Real
    m, n = size(A)
    m == length(b) || error("A and b must have consistent dimensions")
    c > 0 || error("c must be strictly positive")
    A_poly = Array{T}(undef, m+n, n)
    A_poly[1:m,:] = broadcast(*, b, ones(T,m,n)) - A
    A_poly[m+1:n+m,:] = Array{T}(LinearAlgebra.I, n, n)
    return PolyhedralCone{T}(A_poly)
end

function cone_from_constraints(A::Matrix{T}, b::Vector{T}, c::T) where T <: Rational
    m, n = size(A)
    m == length(b) || error("A and b must have consistent dimensions")
    c > 0 || error("c must be strictly positive")
    A_poly = Array{T}(undef, m+n, n)
    A_poly[1:m,:] = broadcast(*, b, ones(T,m,n)) - A
    A_poly[n+1:n+m,:] = Array{T}(LinearAlgebra.I, n, n)
    return PolyhedralCone{T}(cones.intmat(A_poly))
end

"""
# Description
Finds the conical hull of of the region define
by the following constraints:

1ᵀ x = 1
x ≤ u
x ≥ 0
# Returns
* `::PolyhedralCone` conical hull from constraints
"""
function quota_cone(u::Vector{T}) where T <: Real
    n = length(u)
    return cone_from_constraints(Array{T}(LinearAlgebra.I, n, n), u, one(T))
end

function checkRiskRegionArgs(μ::Vector{Float64}, Σ::Matrix{Float64},
                             K::Cone, α::Float64)
        if length(μ) != size(Σ, 1) ArgumentError("μ and Σ must have consistent dimensions") end
        if !LinearAlgebra.isposdef(Σ) ArgumentError("Σ must be a positive definite matrix") end
        if length(K) != length(μ) ArgumentError("Cone must be in same dimension as mean vector") end
        if α <= 0 ArgumentError("α must be strictly positive") end
end


include("05_one_site_operator_custom.jl")

println("
#######################################################
# Tutorial 6                                          #
#                                                     #
# 2-site state                                        #
#######################################################
")

i1 = Index(2, "S=1/2")
i2 = Index(2, "S=1/2")

@show i1 ≠ i2

# Uninitialized ITensor
ZpZm = ITensor(i1, i2)

# State |Z+⟩₁|Z-⟩₂ ≡ |Z+Z-⟩
ZpZm[i1 => 1, i2 => 2] = 1

# Order doesn't matter!
ZpZm[i2 => 2, i1 => 1] = 1

# Or construct from single site basis states:
# State |Z+⟩₁
Zp1 = state("Z+", i1)
# State |Z+⟩₂
Zp2 = state("Z+", i2)

# State |Z-⟩₁
Zm1 = state("Z-", i1)
# State |Z-⟩₂
Zm2 = state("Z-", i2)

# State |Z+Z-⟩ = |Z+⟩₁|Z-⟩₂
ZpZm = Zp1 * Zm2

# State |Z+Z-⟩ = |Z-⟩₂|Z+⟩₁ = |Z+⟩₁|Z-⟩₂
# Order doesn't matter!
ZpZm = Zm2 * Zp1

# State |Z-Z+⟩
ZmZp = Zm1 * Zp2

# State |Z+Z+⟩
ZpZp = Zp1 * Zp2

Xp1 = state("X+", i1)
Xp2 = state("X+", i2)

Xm1 = state("X-", i1)
Xm2 = state("X-", i2)

# State |Z-⟩|Z-⟩
ZmZm = Zm1 * Zm2

# Cat state (|Z+⟩|Z-⟩ + |Z-⟩|Z+⟩) / √2

# With element setting:
Cat = ITensor(i1, i2)
Cat[i1 => 1, i2 => 2] = 1 / √2
Cat[i1 => 2, i2 => 1] = 1 / √2

# Or build from single site basis states:
Cat = (Zp1 * Zm2 + Zm1 * Zp2) / √(2)
@show (dag(Cat) * Cat)[]
@show inner(Cat, Cat)
@show norm(Cat)

entanglement_entropy(s) = sum(sn -> iszero(sn) ? zero(sn) : -sn^2 * log(sn^2), s)

# Compact syntax for matrix decompositions like SVD,
# which reveal the rank/entanglement:
_, S, _ = svd(ZmZp, i1)
s = diag(S)
@show s
@show entanglement_entropy(s)

_, S, _ = svd(Cat, i1)
s = diag(S)
@show s
@show entanglement_entropy(s)


"This code defines some simple finite difference approximations, applies them to
approximate u'(x), and compares the result to the exact solution."

# run file by typing "include("fd_simple.jl")", copying and pasting into the REPL, or
# selecting code and pressing Ctrl/Command-Enter

Dfwd(u,x,h) = (u(x+h) - u(x))/h
Dback(u,x,h) = (u(x) - u(x-h))/(h)
D2(u,x,h) = (u(x+h) - u(x-h))/(2*h)

u(x) = sin(1+pi*x)
dudx(x) = pi*cos(1+pi*x)

x̄ = .5
h = .001
@show Dfwd(u,x̄,h) - dudx(x̄)
@show Dback(u,x̄,h) - dudx(x̄)
@show D2(u,x̄,h) - dudx(x̄)


#___  ____       ___ _____   _   _       _ _
#|  \/  | |     |_  /  __ \ | | | |     (_| |
#| .  . | |       | | /  \/ | | | |_ __  _| |_ ___
#| |\/| | |       | | |     | | | | '_ \| | __/ _ \
#| |  | | |___/\__/ | \__/\ | |_| | | | | | || (_) |
#_______\_____\____/ \____/  _____|_|___|_____\_____ _____ _____
#| ___ \        (_)         | | \ \ / / / __  |  _  / __  |  _  |
#| |_/ _ __ ___  _  ___  ___| |_ \ V /  `' / /| |/' `' / /| |/' |
#|  __| '__/ _ \| |/ _ \/ __| __|/   \    / / |  /| | / / |  /| |
#| |  | | | (_) | |  __| (__| |_/ /^\ \ ./ /__\ |_/ ./ /__\ |_/ /
#\_|  |_|  \___/| |\___|\___|\__\/   \/ \_____/\___/\_____/\___/
#              _/ |
#             |__/
#
# This code is part of the proposal of the team "MLJC UniTo" - University of Turin
# for "ProjectX 2020" Climate Change for AI.
# The code is licensed under MIT 3.0
# Please read readme or comments for credits and further information.

# Compiler: Julia 1.5.3
#= Short description of this file: LOTKA-VOLTERRA EQUATIONS.

Numerical approach using DifferentialEquations.jl and Neural ODE layer approach
using DiffEqFlux.jl based on Chris Rackauckas' paper " DiffEqFlux.jl -
A Julia Library for Neural Differential Equations"
(https://arxiv.org/abs/1902.02376 )
=#

using Flux, DiffEqFlux, DifferentialEquations, Plots

#= To solve the equations numerically, we first define a problem type by giving
the equation, the initial condition, and timespan to solve over.
=#
function lotka_volterra(du,u,p,t) #Define equations
  x, y = u
  α, β, δ, γ = p
  du[1] = dx = α*x - β*x*y
  du[2] = dy = -δ*y + γ*x*y
end
u0 = [1.0,1.0] #initial condition
tspan = (0.0,10.0) #timespan
p = [1.5,1.0,3.0,1.0] #parameters
prob = ODEProblem(lotka_volterra,u0,tspan,p)

#= Solve the ODE problem using 5th order Tsitouras method (Tsit5) and plot the
solution.
=#
sol = solve(prob,Tsit5())
plot(sol)

# Data generation from the above ODE solution
sol = solve(prob,Tsit5(),saveat=0.1)
A = sol[1,:] # length 101 vector
t = 0:0.1:10.0
scatter!(t,A) #plot (t,A) over the ODE's solution

#= Build a neural network with the function as our single layer,
and define a loss function as the squared distance of the above generated data
from 1.
 =#

p = param([2.2, 1.0, 2.0, 0.4]) # Initial Parameter Vector
function predict_rd() # Our 1-layer neural network
  diffeq_rd(p,prob,Tsit5(),saveat=0.1)[1,:]
end
loss_rd() = sum(abs2,x-1 for x in predict_rd()) # loss function

#= 100 epoch neural network training to minimize loss function and thus get the
optimized parameters.
=#

data = Iterators.repeated((), 100)
opt = ADAM(0.1)
cb = function () #callback function to observe training
  display(loss_rd())
  # using `remake` to re-create our `prob` with current parameters `p`
  display(plot(solve(remake(prob,p=Flux.data(p)),Tsit5(),saveat=0.1),ylim=(0,6)))
end
# Display the ODE with the initial parameter values.
cb()

Flux.train!(loss_rd, [p], data, opt, cb = cb)

#= Flux trains the neural network, finding its parameters (p) that minimize the
cost function: the forward pass of the neural network includes solving an ODE.
=#


function solve_ode(u0, tspan, p, time_interval=1)
        function SIR(du,u,p,t)
                du[1]=-p[1]*u[1]*u[2]/(u[1]+u[2]+u[3])
                du[2]=p[1]*u[1]*u[2]/(u[1]+u[2]+u[3]) - p[2]*u[2]
                du[3] = p[2]*u[2]
        end
        problem = ODEProblem(SIR, u0, tspan,p)
        sol=solve(problem, saveat=time_interval)
        return sol
end

function solve_ode_seasonal_forcing(u0, tspan, p,time_interval=1)
        function SIR_forcing(du,u,p,t)
                L, beta0, beta1, theta, gamma = p
                # 17 weeks (~ 4 months) seasonal?
                beta_t = beta0*(1 + beta1*cos( (2*pi*t - theta)/26 ) )
                # du[1]=(Ntot-u[1]-u[2])/L - beta_t*u[2]*u[1]/Ntot
                # du[2]=beta_t*u[1]*u[2]/Ntot - u[2]/D
                du[1] =  -beta_t*u[2]*u[1]/(u[1]+u[2]+u[3]) + u[3]/L
                du[2] = beta_t*u[1]*u[2]/(u[1]+u[2]+u[3]) - gamma*u[2]
                du[3] = gamma*u[2]

        end
        problem = ODEProblem(SIR_forcing, u0, tspan,p)
        sol=solve(problem, saveat=time_interval)
        return sol
end

function solve_seir(u0, tspan, p, time_interval=1)
        function SEIR(du,u,p,t)
                du[1]=p[1]*(u[1]+u[2]+u[3]) - p[1]*u[1] - p[2]*u[1]*u[3]/(u[1]+u[2]+u[3]+u[4])
                du[2]=p[2]*u[1]*u[3]/(u[1]+u[2]+u[3]+u[4]) - (p[1] + p[3])*u[2]
                du[3] = p[3]*u[2] - (p[4]+p[1])*u[3]
                du[4] = p[4]*u[3] - p[1]*u[4]
        end
        problem = ODEProblem(SEIR, u0, tspan,p)
        sol=solve(problem, saveat=time_interval)
        return sol
end


using Statistics
using LinearAlgebra
using Polynomials: Poly
import PyPlot; const plt = PyPlot

function leastsquares(x, y, Ω; order::Integer = 1)
    X = ones((length(x), order+1))
    for n in 1:order
        X[:, n+1] = x.^n
    end

    if ndims(Ω) == 1
        Ω = diagm(0 => Ω)
    end

    w = inv(Ω)  # Weights defined as 1/σ²
    Xᵀ = transpose(X)
    kω = inv(Xᵀ*w*X)*Xᵀ*w
    coeffs = kω*y
    Σ = kω*Ω*kω'
    coeffs, Σ
end

function leastsquares(x, y; order::Integer = 1)
    X = ones((length(x), order+1))
    for n in 1:order
        X[:, n+1] = x.^n
    end

    Xᵀ = transpose(X)
    inv(Xᵀ*X)*Xᵀ*y
end

function plotresiduals(x, y, coefficients; ax=nothing)
    if ax ≡ nothing
        fig, ax = plt.subplots(ncols=2)
    end
    p = Poly(coefficients)
    ŷ = p.(y)
    xrange = range(minimum(x), maximum(x), length=100)
    ŷrange = p.(xrange)

    ê = ŷ - y
    ax[1].scatter(x, y)
    ax[1].scatter(x, ŷ)
    ax[1].plot(xrange, ŷrange, "--")
    ax[1].set_title("Fit")
    ax[2].scatter(x, ê)
    ax[2].set_title("Residuals")
    return ax
end