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


# -*- 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


"""
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


@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


"""
    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)


"""
    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))


# 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


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


using Polynomials
using Plots
include("funkcje.jl")
return

prec = 0.01
idealne = [(cos(t), sin(t)) for t in -pi:0.001:pi]
return

function makeSpline(coordinates)
    function CalculateCs()
        μ = [NaN64 for k in 1:n]
        α = vcat([0.], [3. * (A[k+1] - A[k])/h[k] - 3. * (A[k] - A[k-1])/h[k-1] for k in 2:n])

        C = [0. for k in 1:nPlus1]
        l = [0. for k in 1:nPlus1]
        z = [0. for k in 1:nPlus1]

        l[1] = 1.
        μ[1] = 0.
        z[1] = 0.

        for k in 2:n
            l[k] = 2 * (xs[k+1] - xs[k-1]) - h[k-1] * μ[k-1]
            μ[k] = h[k] / l[k]
            z[k] = (α[k] - h[k-1]*z[k-1]) / l[k]
        end

        l[nPlus1] = 1.
        z[nPlus1] = 0.
        C[nPlus1] = 0.

        for k in n:(-1):1
            C[k] = z[k] - μ[k]*C[k+1]
        end
        return C
    end
    nPlus1 = length(coordinates)
    n = nPlus1 - 1
    # x1 x2 ... xk+1
    xs = map(pair->pair[1], coordinates)
    # y1 y2 ... yk+1
    # ys = map(pair->pair[2], coordinates)
    A = map(pair->pair[2], coordinates)

    h = [xs[k+1] - xs[k] for k in 1:n]
    C = CalculateCs()

    B = [(A[k+1] - A[k]) / h[k] - h[k]*(C[k+1] + 2. * C[k]) / 3. for k in 1:n]
    D = [(C[k+1] - C[k]) / (3. * h[k]) for k in 1:n]

    outputSet = [Poly(NaN) for k in 1:n]
    intervals = myzip(xs,xs[2:end])

    for k in 1:n
        xk = xs[k]
        polynom = A[k] + B[k]*poly([xk]) + C[k]*(poly([xk])^2) + D[k]*(poly([xk]) ^ 3)
        outputSet[k] = polynom
    end
    return myzip(outputSet, intervals)
end

function calculateSpline(splajn)
#     prec = 0.01
    coors = []
    angles = []

    for i in 1:length(splajn)
        xk = splajn[i][2][1]
        xkPlus1 = splajn[i][2][2]
        wielomian = splajn[i][1]
        przedzial = [x for x in xk:prec:(xkPlus1-prec)]

        coors = vcat(coors, map(wielomian, przedzial))
        angles = vcat(angles, przedzial)
    end

    return angles, coors
end

function circle(n, r = 1)
    ps = [t for t in -pi:2*pi/n:pi]
    xs = r * cos.(ps)
    ys = r * sin.(ps)

    xspline = makeSpline(myzip(ps, xs))
    yspline = makeSpline(myzip(ps, ys))

    angles, xvalues = calculateSpline(xspline)
    angles, yvalues = calculateSpline(yspline) #jak zrobi się na odwrót kolejności to wychodzą ładne rysunki błędów

    xerrors = abs.(cos.(angles) - xvalues)
    yerrors = abs.(sin.(angles) - yvalues)

    errors = sqrt.([x^2 for x in xerrors] + [y^2 for y in yerrors])

    return [(angles, errors), (xvalues, yvalues), (xs, ys)]
end

function plotCircle(c, n)
    plot!(c[2], lab = "n = " * string(n), linestyle = :dash)
#     scatter!(c[3], lab = "Zadane punkty")
end

function mainCircle(ns :: Array{Int})

    Plots.plot(size = (600*1.1, 600))

    title!("Splajn")

    xlabel!("Os x")
    ylabel!("Os y")

    plot!(idealne, lab = "idealne kolo", color = :black)
    cns = [(circle(n), n) for n in ns]
    for cn in cns
        plotCircle(cn[1], cn[2])
    end

    savefig("splajn")
end
function mainCircle(n :: Int)

    Plots.plot(size = (600*1.1, 600))

    title!("Splajn")

    xlabel!("Os x")
    ylabel!("Os y")

    plot!(idealne, lab = "idealne kolo", color = :black)
    c = circle(n)
    plotCircle(c, n)

    savefig("splajn")
end

function plotError(c, n)
    plot!(c[1], lab = "n = " * string(n))
end
function mainError(ns :: Array{Int})
    Plots.plot(size = (600*1.1, 600))

    title!("Splajn Bledy")

    xlabel!("Os x")
    ylabel!("Os y")

    cns = [(circle(n), n) for n in ns]
    for cn in cns
        plotError(cn[1], cn[2])
    end

    savefig("splajnbledy")
end
function mainError(n :: Int)

    Plots.plot(size = (600*1.1, 600))

    title!("Splajn Blad")

    xlabel!("Os x")
    ylabel!("Os y")

    c = circle(n)
    plotError(c, n)

    savefig("splajnblad")
end

function helix(n, p, r = 1)
    ps = [t for t in -pi:pi/n:pi]
    xs = r * cos.(ps)
    ys = r * sin.(ps)

    xspline = makeSpline(myzip(ps, xs))
    yspline = makeSpline(myzip(ps, ys))

    angles, xvalues = calculateSpline(xspline)
    angles, yvalues = calculateSpline(yspline)

    pts = [p*t for t in angles]

    Plots.plot(size = (600*1.1, 600), leg = false)
    plot!(pts, xvalues, yvalues)
    plot!(xvalues, pts, yvalues)
    plot!(xvalues, yvalues, pts)
    title!("Helisa Splajn")
    xlabel!("Os x")
    ylabel!("Os y")
end

function B(t)
    c = (4/3)*(sqrt(2) - 1)

    P0 = [0,1]
    P1 = [c,1]
    P2 = [1,c]
    P3 = [1,0]

    return (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3*P3
end

function circle()
    n = 1000
    prec = 1/n
    ts = [t for t in 0:prec:1]

    xvalues, yvalues = myunzip([B(t) for t in ts])

    angles = [t for t in 0:pi/(2*n+1):pi/2]

    xerrors = abs.(sin.(angles) - xvalues)
    yerrors = abs.(cos.(angles) - yvalues)

    errors = sqrt.([x^2 for x in xerrors] + [y^2 for y in yerrors])

    xvs = vcat(xvalues, yvalues, -xvalues, -yvalues) #cosinus
    yvs = vcat(-yvalues, xvalues, yvalues, -xvalues) #sinus

    xvalues = xvs
    yvalues = yvs

    return [(angles, errors), (xvalues, yvalues)]
end

function silnia(k)::BigInt
    if k == 0
        return 1
    else
        return k * silnia(k-1)
    end
end

function ilorazysilni(k)::Tuple{Array{BigFloat,1}, Array{BigFloat,1}}
    xis = [BigFloat(1.) for i in 0:k]
    yis = [BigFloat(1.) for i in 0:k]
    xis[1] = yis[1] = BigFloat(1.)
    for j in 2:k
        i = j - 1
        xis[j] = BigFloat(xis[i] * BigFloat(k+i-1) * BigFloat(k - i) / BigFloat(i))
        yis[j] = xis[j] * (k+i) / (k-i)
    end
    yis[k+1] = yis[k] * 2
    return xis, yis
end

function Hk(k, α, facts, e, f)
    suma = .0
    for i in 0:e
        hi = f(pi*(k+i)/2 + α) / (2*α)^(k+i+1)
        fact = facts[i+1]
        suma += (hi*fact)
    end
    return suma / silnia(k)
end

function Xk(k, α)
    return Hk(k, α, ilorazysilni(k)[1], k-1, cos)
end
function Yk(k, α)
    return Hk(k, α, ilorazysilni(k)[2], k, sin)
end

function Hn(t, n, α, Hks, c1, c2)
    suma = .0
    c = t^2 - α^2
    for k in 1:n
        suma += Hks[k]*c^k
    end
    suma *= c1
    suma += c2
    return suma
end

function Xn(t, n, α, Xks)
    return Hn(t, n, α, Xks, 2*α, cos(α))
end
function Yn(t, n, α, Xks)
    return Hn(t, n, α, Xks, 2*t, t*sin(α)/α)
end

function arc(n, α, r)
    Xks = [Xk(k, α) for k in 1:n]
    Yks = [Yk(k, α) for k in 1:n]

    angles = [t for t in -α:prec:α]

    xvalues = r * [Xn(t, n, α, Xks) for t in angles]
    yvalues = r * [Yn(t, n, α, Yks) for t in angles]

    xerrors = abs.(r * cos.(angles) - xvalues)
    yerrors = abs.(r * sin.(angles) - yvalues)

    angles = convert.(Float64, round.(angles; digits = 10))
    xvalues = convert.(Float64, round.(xvalues; digits = 10))
    yvalues = convert.(Float64, round.(yvalues; digits = 10))
    xerrors = convert.(Float64, round.(xerrors; digits = 10))
    yerrors = convert.(Float64, round.(yerrors; digits = 10))

    errors = sqrt.([x^2 for x in xerrors] + [y^2 for y in yerrors])

    return [(angles,errors), (xvalues, yvalues)]
end
function circle(n, α = pi, r = 1)
    a = arc(n, α, r)
    angles, errors = a[1]
    xvalues, yvalues = a[2]

    if α == pi
        xpoints = [cos(pi), cos(-pi)]
        ypoints = [sin(pi), sin(-pi)]
        return [(angles, errors), (xvalues, yvalues), (xpoints, ypoints)]
    end
    if α == pi/2
        xvalues = vcat(xvalues, reverse(-xvalues))
        yvalues = vcat(yvalues, reverse(yvalues))

        xpoints = [cos(pi/2), cos(-pi/2)]
        ypoints = [sin(pi/2), sin(-pi/2)]
        return [(angles, errors), (xvalues, yvalues), (xpoints, ypoints)]
    end
    if α == pi/4
        xvs = vcat(reverse(xvalues), reverse(yvalues), -xvalues, yvalues)
        yvs = vcat(reverse(yvalues), reverse(-xvalues), yvalues, xvalues)
        xvalues = xvs
        yvalues = yvs

        xpoints = [cos(pi/4), cos(3*pi/4), cos(5*pi/4), cos(7*pi/4)]
        ypoints = [sin(pi/4), sin(3*pi/4), sin(5*pi/4), sin(7*pi/4)]

        return [(angles, errors), (xvalues, yvalues), (xpoints, ypoints)]
    end
end

function helix(n, p, α = pi, r = 1)
    Xks = [Xk(k, α) for k in 1:n]
    Yks = [Yk(k, α) for k in 1:n]

    angles = [t for t in -α:prec:α]

    xvalues = r * [Xn(t, n, α, Xks) for t in angles]
    yvalues = r * [Yn(t, n, α, Yks) for t in angles]
    zvalues = [p * t for t in angles]

    return [xvalues, yvalues, zvalues]
end

II rok/I semestr/ANM/Pracownia 3/prog/program.jl


export  KalmanFilter,
        kalmanfilter,
        kalmansmoother

struct KalmanFilter
    means::Matrix{Float}
    covs::Array{Float,3}
    # Storage for recycling in kalman smoother
    means_::Matrix{Float}
    covs_::Array{Float,3}
end

struct KalmanSmoother
    means::Matrix{Float}
    covs::Array{Float,3}
end

function kalmanfilter(lg::LinearGaussian, observations::Matrix{Float},
                      mu0::Vector{Float}, C0::Matrix{Float}
                      )::KalmanFilter
    @assert issymmetric(C0) && isposdef(C0) "Cov mat must be sym + pos def"

    nsteps = size(observations, 2)

    kf_means = zeros(lg.dimx, nsteps)
    kf_covs  = zeros(lg.dimx, lg.dimx, nsteps)

    kf_means[:,1]  = mu0
    kf_covs[:,:,1] = C0

    kf_means_ = copy(kf_means)
    kf_covs_  = copy(kf_covs)

    for k = 2:nsteps
        # intermediate (k|k-1)
        mu_ = lg.A * kf_means[:,k-1]
        C_  = lg.A * kf_covs[:,:,k-1] * lg.A' + lg.Q
        G_  = (C_ * lg.B') / (lg.R + lg.B*C_*lg.B')
        # update (k|k)
        kf_means[:,k]  = mu_ + G_*(observations[:,k]-lg.B*mu_)
        kf_covs[:,:,k] = (eye(lg.dimx) - G_*lg.B)*C_
        # storage for smoothing
        kf_means_[:,k]  = mu_
        kf_covs_[:,:,k] = C_
    end
    KalmanFilter(kf_means, kf_covs, kf_means_, kf_covs_)
end

"""
    kalmansmoother(lg, observations, kf)

Two-filter smoother in linear gaussian case (everything is tractable).
"""
function kalmansmoother(lg::LinearGaussian, observations::Matrix{Float},
                        kf::KalmanFilter)::KalmanSmoother
    ks_means = similar(kf.means)
    ks_covs  = similar(kf.covs)
    # Nsteps
    K = size(observations,2)
    # Pre-computations
    iQA, iRB = lg.Q\lg.A, lg.R\lg.B
    Pbi      = lg.B'*iRB
    Pbi_K    = copy(Pbi)
    c        = iRB'*observations[:,K]
    # Initialisation
    ks_covs[:,:,K] = inv( inv(kf.covs_[:,:,K]) + Pbi_K )
    ks_means[:,K]  = ks_covs[:,:,K]*( kf.covs_[:,:,K]\kf.means_[:,K] + c )
    # main loop
    for k = (K-1):-1:1
        # Kalman Smoother with 2 Filter Smoother update
        G   = (eye(lg.dimx) + lg.Q*Pbi)\lg.A
        Pbi = Pbi_K+iQA'*(lg.A-G)
        c   = iRB'*observations[:,k] + G'*c
        #
        ks_covs[:,:,k] = inv( inv(kf.covs_[:,:,k]) + Pbi )
        ks_means[:,k]  = ks_covs[:,:,k]*( kf.covs_[:,:,k]\kf.means_[:,k] + c )
    end
    KalmanSmoother(ks_means, ks_covs)
end


"""
    loglik(η, x)

Compute the simple (linear) birth and death process log-likelihood where
``η = (λ, μ)^{\\prime}``, `λ` is the birth rate, `μ` is the death rate, and `x`
is the observed sample.

Define `i` as the size of the population at time 0 and `j` as the population
size at time `t`. Let ``α = (μ e^{(λ - μ) t} - μ) / (λ e^{(λ - μ) t} - μ)`` and
``β = (λ e^{(λ - μ) t} - λ) / (λ e^{(λ - μ) t} - μ)``. Transition probability
`p(j | i, t, λ, μ)` is equal to (Bailey, 1964):
``\\sum_{h = 0}^{\\min(i, j)} \\binom{i}{h} \\binom{i + j - h - 1}{i - 1}
α^{i - h} β^{j - h} (1 - α - β)^{h}``.

Suppose to observe the process at time points `t[0], t[1], ..., t[s], ..., t[S]`
with `t[S] <= T`, at which the population size is
`n[0], n[1], ..., n[s], ..., n[S]`. By the Markov property, log-likelihood is
``l(λ, μ | x) = \\log p(n[S] | n[S], T - t[S], λ, μ) +
\\sum_{s = 1}^{S} \\log p(n[s] | n[s - 1], t[s] - t[s - 1], λ, μ)``.
If we observe `M` independent processes with same parameters `(λ, μ)`, then
``l(λ, μ | x[1], ..., x[M]) = \\sum_{m = 1}^{M} l(λ, μ | x[m]).``

If the process is observed continously over time period `[0, T]`, log-likelihood
simplifies to (Darwin, 1956, Equation (24)):
``l(λ, μ | x) = B \\log λ + D \\log μ - (λ + μ) X + \\sum_{s=0}^{S - 1} n[s]``.
`B` and `D` are the total number of births and deaths observed in `[0, T]`
respectively. `X` is defined as ``n[0] (t[1] - t[0]) + n[1] (t[2] - t[1]) + ...
+ n[S - 1] (t[S] - t[S-1]) + n[S] (T - t[S])``.

# References:

Bailey, N. T. J. (1964). The elements of stochastic processes with applications
to the natural sciences. Wiley, New York, NY, USA. ISBN 0-471-04165-3.

Darwin, J. H. (1956). The behaviour of an estimator for a simple birth and death
process. Biometrika, 43(1/2), 23-31. https://doi.org/10.2307/2333575
"""
function loglik(
  η::Vector{F},
  x::ObservationContinuousTime
)::F where {
  F <: AbstractFloat
}
  x.tot_births * log(η[1]) +
  x.tot_deaths * log(η[2]) -
  (η[1] + η[2]) * x.integrated_jump +
  x.sum_log_n
end

function loglik(
  η::Vector{F},
  x::Vector{ObservationContinuousTime}
)::F where {
  F <: AbstractFloat
}
  mapreduce(y -> loglik(η, y), +, x)
end

function loglik(
  η::Vector{F},
  x::ObservationDiscreteTimeEven
) where {
  F <: AbstractFloat
}
  l = zeros(F, x.n)

  for i = 1:x.n
    itr = zip(x.state[1:(end - 1), i], x.state[2:end, i])
    l[i] = mapreduce(y -> trans_prob(y..., x.u, η), +, itr)
  end

  sum(l)
end

function loglik(
  η::Vector{F},
  x::ObservationDiscreteTimeUneven
) where {
  F <: AbstractFloat
}
  itr = zip(x.state[1:(end - 1), i], x.state[2:end, i], x.waiting_time)
  mapreduce(y -> trans_prob(y..., η), +, itr)
end

function loglik(
  η::Vector{F},
  x::Vector{ObservationDiscreteTimeUneven}
)::F where {
  F <: AbstractFloat
}
  mapreduce(y -> loglik(η, y), +, x)
end


module CF

import ..d

#cross entropy cost function

logloss{F <: AbstractFloat}(x::F) = -log(x)
dlogloss{F <: AbstractFloat}(x::F) = -one(F)/x

d(::typeof(logloss)) = dlogloss

crossentropy{F <: AbstractFloat}(expected::Bool, result::F) = (expected) * logloss(result) + (!expected) * logloss(one(F)-result)
crossentropy{F <: AbstractFloat}(expected::Array{Bool}, result::Array{F}) = sum(crossentropy.(expected, result))

dcrossentropy{F <: AbstractFloat}(expected::Bool, result::F) = (expected) * d(logloss)(result) - (!expected) * d(logloss)(one(F) - result)
dcrossentropy{F <: AbstractFloat}(expected::Array{Bool}, result::Array{F}) = dcrossentropy.(expected, result)

d(::typeof(crossentropy)) = dcrossentropy

#mean square cost function

meansquare{F <: AbstractFloat}(result::F, expected::F) = sqr(expected - result)
dmeansquare{F <: AbstractFloat}(result::F, expected::F) = F(2.0) * (result + expected)

d(::typeof(meansquare)) = dmeansquare
end


using MultivariateOrthogonalPolynomials, FastTransforms, BlockBandedMatrices, Plots, LinearAlgebra, StaticArrays
pyplot()

####
# Solving
#
#   (Δ + k^2*I) * u = f
#
# in a unit disk |𝐱| ≤ 1,
# using orthogonal polynomials (in x and y) in a disk
# with the weight (1-|𝐱|^2) = (1-x^2-y^2)
####

Z = Zernike(1)
W = Weighted(Z) # w*Z
xy = axes(Z, 1);
x, y = first.(xy), last.(xy);
Δ = Z \ (Laplacian(xy) * W)
S = Z \ W # identity

k = 20
L = Δ + k^2 * S # discretisation of Helmholtz
f = @.(cos(x * exp(y)))

u = W * (L \ (Z \ f))
contourf(u)

# One can also fix the discretisation size

N = 20
Zₙ = Z[:,Block.(1:N)]
Wₙ = W[:,Block.(1:N)]
Lₙ = L[Block.(1:N),Block.(1:N)]

u = Wₙ * (Lₙ \ (Zₙ \ f))
contourf(u)

# We can also do eigenvalues of the Laplacian
N = 20
Δₙ = Δ[Block.(1:N),Block.(1:N)]
Sₙ = S[Block.(1:N),Block.(1:N)]

λ,Q = eigen(Symmetric(Matrix(Δₙ)), Symmetric(Matrix(Sₙ)))

contourf(Wₙ * Q[:,end-10])


using ApproxFun

 # Semicircle law
x=Fun(identity,-2..2)
w=sqrt(4-x^2)/(2π)

lanczos(w,5)

# Marchenco–Pastur law

r = .5
lmax = (1+sqrt(r))^2
lmin = (1-sqrt(r))^2

x= Fun(identity,lmin..lmax)

w=sqrt((lmax-x)*(x-lmin))/(π*x)

lanczos(w,5)

# Wachter law

a = 5; b= 10;
c = sqrt(a/(a+b)*(1-1/(a+b)))
d = sqrt(1/(a+b)*(1-a/(a+b)))

lmax = (c+d)^2
lmin = (c-d)^2

x = Fun(identity,lmin..lmax)

w = (a+b)*sqrt((x-lmin).*(lmax-x))/(2π*x*(1-x))

lanczos(w,5)


#=
Comparing two numbers written in index form like 211 and 37 is not difficult, as any calculator would confirm that 211 = 2048 < 37 = 2187.

However, confirming that 632382518061 > 519432525806 would be much more difficult, as both numbers contain over three million digits.

Using base_exp.txt (right click and 'Save Link/Target As...'), a 22K text file containing one thousand lines with a base/exponent pair on each line, determine which line number has the greatest numerical value.

NOTE: The first two lines in the file represent the numbers in the example given above.
=#
using DelimitedFiles
function calc()
  combined = readdlm(dirname(@__FILE__()) * "/../Resources/p99.txt", ',', Int64)
  bases = vec(combined[1:1000])
  exponents = vec(combined[1001:2000])
  max, line = 0,0
  for i in 1:1000
    result = exponents[i]*log10(bases[i])
    if result > max
      max, line = result, i
    end
  end
  line
end
@time println(calc())


abstract type AbstractQGramDistance <: StringSemiMetric end

"""
	QGram(q::Int)

Creates a QGram distance.

The distance corresponds to

``||v(s1, q) - v(s2, q)||``

where ``v(s, q)`` denotes the vector on the space of q-grams of length q,
that contains the number of times a q-gram appears for the string s
"""
struct QGram <: AbstractQGramDistance
	q::Int
end
eval_start(::QGram) = 0
@inline function eval_op(::QGram, c::Integer, n1::Integer, n2::Integer)
	c + abs(n1 - n2)
end
eval_end(::QGram, c::Integer) = c

"""
	Cosine(q::Int)

Creates a Cosine distance.

The distance corresponds to

`` 1 - v(s1, q).v(s2, q)  / ||v(s1, q)|| * ||v(s2, q)||``

where ``v(s, q)`` denotes the vector on the space of q-grams of length q,
that contains the  number of times a q-gram appears for the string s
"""
struct Cosine <: AbstractQGramDistance
	q::Int
end
eval_start(::Cosine) = (0, 0, 0)
@inline function eval_op(::Cosine, c::NTuple{3, <:Integer}, n1::Integer, n2::Integer)
	(c[1] + n1^2, c[2] + n2^2, c[3] + n1 * n2)
end
eval_end(::Cosine, c::NTuple{3, <:Integer}) = 1 - c[3] / sqrt(c[1] * c[2])

"""
	Jaccard(q::Int)

Creates a Jaccard distance.

The distance corresponds to

``1 - |Q(s1, q) ∩ Q(s2, q)| / |Q(s1, q) ∪ Q(s2, q))|``

where ``Q(s, q)``  denotes the set of q-grams of length n for the string s
"""
struct Jaccard <: AbstractQGramDistance
	q::Int
end
eval_start(::Jaccard) = (0, 0, 0)
@inline function eval_op(::Jaccard, c::NTuple{3, <:Integer}, n1::Integer, n2::Integer)
	(c[1] + (n1 > 0), c[2] + (n2 > 0), c[3] + (n1 > 0) * (n2 > 0))
end
eval_end(::Jaccard, c::NTuple{3, <:Integer}) = 1 - c[3] / (c[1] + c[2] - c[3])

"""
	SorensenDice(q::Int)

Creates a SorensenDice distance.

The distance corresponds to

``1 - 2 * |Q(s1, q) ∩ Q(s2, q)|  / (|Q(s1, q)| + |Q(s2, q))|)``

where ``Q(s, q)``  denotes the set of q-grams of length n for the string s
"""
struct SorensenDice <: AbstractQGramDistance
	q::Int
end
eval_start(::SorensenDice) = (0, 0, 0)
@inline function eval_op(::SorensenDice, c::NTuple{3, <:Integer}, n1::Integer, n2::Integer)
	(c[1] + (n1 > 0), c[2] + (n2 > 0), c[3] + (n1 > 0) * (n2 > 0))
end
eval_end(::SorensenDice, c::NTuple{3, <:Integer}) = 1 - 2 * c[3] / (c[1] + c[2])

"""
	Overlap(q::Int)

Creates a Overlap distance.

The distance corresponds to

``1 - |Q(s1, q) ∩ Q(s2, q)|  / min(|Q(s1, q)|, |Q(s2, q)|)``

where ``Q(s, q)``  denotes the set of q-grams of length n for the string s
"""
struct Overlap <: AbstractQGramDistance
	q::Int
end
eval_start(::Overlap) = (0, 0, 0)
@inline function eval_op(::Overlap, c::NTuple{3, <:Integer}, n1::Integer, n2::Integer)
	(c[1] + (n1 > 0), c[2] + (n2 > 0), c[3] + (n1 > 0) * (n2 > 0))
end
eval_end(::Overlap, c::NTuple{3, <:Integer}) = 1 - c[3] / min(c[1], c[2])

"""
	NMD(q::Int)
	NMD(q::Int)

Creates a NMD (Normalized Multiset Distance) as introduced by Besiris and
Zigouris 2013. The goal with this distance is to behave similarly to a normalized
compression distance without having to do any actual compression (and thus being
faster to compute).

The distance corresponds to

``(sum(max.(m(s1), m(s2)) - min(M(s1), M(s2))) / max(M(s1), M(s2))``

where ``m(s)`` is the vector of q-gram counts for string ``s`` and ``M(s)`` is the
sum of those counts.

For details see:
https://www.sciencedirect.com/science/article/pii/S1047320313001417
"""
struct NMD <: AbstractQGramDistance
	q::Int
end
eval_start(::NMD) = (0, 0, 0)
@inline function eval_op(::NMD, c::NTuple{3, <:Integer}, n1::Integer, n2::Integer)
	(c[1] + n1, c[2] + n2, c[3] + max(n1, n2))
end
eval_end(::NMD, c::NTuple{3, <:Integer}) = (c[3] - min(c[1], c[2])) / max(c[1], c[2])

"""
	MorisitaOverlap(q::Int)

Creates a MorisitaOverlap distance, a general, statistical measure of
dispersion which can also be used on dictionaries such as created
from q-grams. See https://en.wikipedia.org/wiki/Morisita%27s_overlap_index
This is more fine-grained than many of the other QGramDistances since
it is based on the counts per q-gram rather than only which q-grams are
in the strings.

The distance corresponds to

``(2 * sum(m(s1) .* m(s2)) / (sum(m(s1).^2)*M(s2)/M(s1) + sum(m(s2).^2)*M(s1)/M(s2))``

where ``m(s)`` is the vector of q-gram counts for string ``s`` and ``M(s)`` is the
sum of those counts.
"""
struct MorisitaOverlap <: AbstractQGramDistance
	q::Int
end
eval_start(::MorisitaOverlap) = (0, 0, 0, 0, 0)
@inline function eval_op(::MorisitaOverlap, c::NTuple{5, <:Integer}, n1::Integer, n2::Integer)
	(c[1] + n1, c[2] + n2, c[3] + n1^2, c[4] + n2^2, c[5] + n1 * n2)
end
eval_end(::MorisitaOverlap, c::NTuple{5, <:Integer}) = 1 - 2 * c[5] / (c[3] * c[2] / c[1] + c[4] * c[1] / c[2])

#==========================================================================
QGramIterator
==========================================================================#
@doc """
Return an iterator corresponding to the the q-gram of an iterator.
When the iterator is a String, qgrams are SubStrings.

### Arguments
* `s` iterator
* `q::Integer`: length of q-gram

## Examples
```julia
for x in qgrams("hello", 2)
	println(x)
end
```
"""
qgrams

struct QGramIterator{S <: Union{AbstractString, AbstractVector}}
	s::S   # Collection
	q::Int # Length of Qgram
	function QGramIterator{S}(s, q) where {S <: Union{AbstractString, AbstractVector}}
		q > 0 || throw(ArgumentError("The qgram length must be higher than zero"))
		new(s, q)
	end
end
function QGramIterator(s::Union{AbstractString, AbstractVector}, q::Integer)
	QGramIterator{typeof(s)}(s, q)
end
Base.length(qgram::QGramIterator) = max(length(qgram.s) - qgram.q + 1, 0)

# q-grams of AbstractString
function Base.iterate(qgram::QGramIterator{<: AbstractString},
	state = (1, nextind(qgram.s, 0, qgram.q)))
	istart, iend = state
	iend > ncodeunits(qgram.s) && return nothing
	element = SubString(qgram.s, istart, iend)
	nextstate = nextind(qgram.s, istart), nextind(qgram.s, iend)
	element, nextstate
end
Base.eltype(qgram::QGramIterator{SubString{S}}) where {S} = SubString{S}
Base.eltype(qgram::QGramIterator{S}) where {S <: AbstractString} = SubString{S}
qgrams(s::AbstractString, q::Integer) = QGramIterator(s, q)

# q-grams of General Iterators
function Base.iterate(qgram::QGramIterator{<: AbstractVector}, state = firstindex(qgram.s))
	state + qgram.q - 1 > lastindex(qgram.s) && return nothing
	view(qgram.s, state:(state + qgram.q - 1)), state + 1
end
Base.eltype(qgram::QGramIterator{<: AbstractVector}) = typeof(first(qgram))
qgrams(s::AbstractVector, q::Integer) = QGramIterator(s, q)
qgrams(s, q::Integer) = QGramIterator(collect(s), q)

#==========================================================================
Compute QGramDistances on general iterators
==========================================================================#
# For two iterators s1 and s2, that define a length and eltype method,
# this returns an iterator that,
# for each element in s1 ∪ s2, returns (numbers of times it appears in s1, numbers of times it appears in s2)
function _count(qgrams1, qgrams2)
	K = promote_type(eltype(qgrams1), eltype(qgrams2))
	d = Dict{K, Tuple{Int, Int}}()
	sizehint!(d, length(qgrams1) + length(qgrams2))
	# I use a faster way to change a dictionary key
	# see setindex! in https://github.com/JuliaLang/julia/blob/master/base/dict.jl#L380
	for x1 in qgrams1
		index = Base.ht_keyindex2!(d, x1)
		if index > 0
			d.age += 1
			@inbounds d.keys[index] = x1
			@inbounds d.vals[index] = (d.vals[index][1] + 1, 0)
		else
			@inbounds Base._setindex!(d, (1, 0), x1, -index)
		end
	end
	for x2 in qgrams2
		index = Base.ht_keyindex2!(d, x2)
		if index > 0
			d.age += 1
			@inbounds d.keys[index] = x2
			@inbounds d.vals[index] = (d.vals[index][1], d.vals[index][2] + 1)
		else
			@inbounds Base._setindex!(d, (0, 1), x2, -index)
		end
	end
	return values(d)
end

function (dist::AbstractQGramDistance)(s1, s2)
	(s1 === missing) | (s2 === missing) && return missing
	c = eval_start(dist)
	for (n1, n2) in _count(qgrams(s1, dist.q), qgrams(s2, dist.q))
		c = eval_op(dist, c, n1, n2)
	end
	eval_end(dist, c)
end

#==========================================================================
Compute QGramDistances on QGramDicts, iterators that store a dictionary associating qgrams to the number of their occurences
==========================================================================#

"""
	QGramDict(s, q::Integer = 2)

An iterator with a pre-computed dictionary of its qgrams. This enables faster calculation of QGram
distances.

Note that the qgram length must correspond with the q length used
in the distance.

## Examples
```julia
str1, str2 = "my string", "another string"
qd1 = QGramDict(str1, 2)
qd2 = QGramDict(str2, 2)
evaluate(Overlap(2), qd1, qd2)
```
"""
struct QGramDict{S, K}
	s::S
	q::Int
	counts::Dict{K, Int}
end
Base.length(s::QGramDict) = length(s.s)
Base.iterate(s::QGramDict, args...) = iterate(s.s, args...)

function QGramDict(s, q::Integer = 2)
	(s isa QGramDict) && (s.q == q) && return s
	qgs = qgrams(s, q)
	countpairs = countdict(qgs)
	QGramDict{typeof(s), eltype(qgs)}(s, q, countpairs)
end

# Turn a sequence of qgrams to a count dict for them, i.e. map each
# qgram to the number of times it has been seen.
function countdict(qgrams)
	d = Dict{eltype(qgrams), Int}()
	for qg in qgrams
		index = Base.ht_keyindex2!(d, qg)
		if index > 0
			d.age += 1
			@inbounds d.keys[index] = qg
			@inbounds d.vals[index] = d.vals[index][1] + 1
		else
			@inbounds Base._setindex!(d, 1, qg, -index)
		end
	end
	return d
end

function (dist::AbstractQGramDistance)(qc1::QGramDict, qc2::QGramDict)
	dist.q == qc1.q == qc2.q || throw(ArgumentError("The distance and the QGramDict must have the same qgram length"))
	d1, d2 = qc1.counts, qc2.counts
	c = eval_start(dist)
	for (s1, n1) in d1
		index = Base.ht_keyindex2!(d2, s1)
		if index <= 0
			c = eval_op(dist, c, n1, 0)
		else
			c = eval_op(dist, c, n1, d2.vals[index])
		end
	end
	for (s2, n2) in d2
		index = Base.ht_keyindex2!(d1, s2)
		if index <= 0
			c = eval_op(dist, c, 0, n2)
		end
	end
	eval_end(dist, c)
end

#==========================================================================
Compute QGramDistances on QGramSortedVectors, iterators that store a sorted vector associating qgrams to the number of their occurences
Note that QGramSortedVectors require qgrams to have a natural order
==========================================================================#

"""
	QGramSortedVector(s, q::Integer = 2)

An iterator with a pre-computed sorted vector of its qgrams. This enables faster calculation of QGram
distances.

Since qgrams are sorted in lexicographic order QGram distances can be
calculated even faster than when using a QGramDict. However, the
sorting means that updating the counts after creation is less
efficient. However, for most use cases QGramSortedVector is preferred
over a QgramDict.

Note that the qgram length must correspond with the q length used
in the distance.

## Examples
```julia
str1, str2 = "my string", "another string"
qs1 = QGramSortedVector(str1, 2)
qs2 = QGramSortedVector(str2, 2)
evaluate(Jaccard(2), qs1, qs2)
```
"""
struct QGramSortedVector{S, K}
	s::S
	q::Int
	counts::Vector{Pair{K, Int}}
end
Base.length(s::QGramSortedVector) = length(s.s)
Base.iterate(s::QGramSortedVector, args...) = iterate(s.s, args...)

function QGramSortedVector(s, q::Integer = 2)
	(s isa QGramSortedVector) && (s.q == q) && return s
	qgs = qgrams(s, q)
	# todo: maybe more efficient to create sorteddict directly
	countpairs = collect(countdict(qgs))
	sort!(countpairs, by = first)
	QGramSortedVector{typeof(s), eltype(qgs)}(s, q, countpairs)
end

function (dist::AbstractQGramDistance)(qc1::QGramSortedVector, qc2::QGramSortedVector)
	dist.q == qc1.q == qc2.q || throw(ArgumentError("The distance and the QGramSortedVectors must have the same qgram length"))
	d1, d2 = qc1.counts, qc2.counts
	c = eval_start(dist)
	i1 = i2 = 1
	while true
		# length can be zero
		if i2 > length(d2)
			for i in i1:length(d1)
				@inbounds c = eval_op(dist, c, d1[i][2], 0)
			end
		break
		elseif i1 > length(d1)
			for i in i2:length(d2)
				@inbounds c = eval_op(dist, c, 0, d2[i][2])
			end
		break
		end
		@inbounds s1, n1 = d1[i1]
		@inbounds s2, n2 = d2[i2]
		cmpval = Base.cmp(s1, s2)
		if cmpval == -1 # s1 < s2
			c = eval_op(dist, c, n1, 0)
			i1 += 1
		elseif cmpval == 1 # s1 > s2
			c = eval_op(dist, c, 0, n2)
			i2 += 1
		else # s1 == s2
			c = eval_op(dist, c, n1, n2)
			i1 += 1
			i2 += 1
		end
	end
	eval_end(dist, c)
end


using Distributions, PlotRecipes, Statistics

# Order of variables in function does not matter (since cor(y,x) = cor(x,y))
function correlation_ttest(y,x; h0=0.0)
    n = length(y)
    v = n - 2
    X = [ones(n) x]
    b = inv(X'X)*X'y
    res = y .- X*b
    s2 = sum(res.^2)/v
    vb = s2.*inv(transpose(X)*X)
    seb = sqrt(vb[2,2])
#    that = abs(b[2]/seb)
    ts = (seb*std(x)/std(y)).*rand(TDist(v),1000000) .+ (b[2]*std(x)/std(y))
    that = abs(mean(ts) - h0)/std(ts)
    todds = (1.0 + (that^2)/v)^(0.5*(v+1))
    pval = 2.0*minimum([cdf(TDist(v),that) (1.0 - cdf(TDist(v),that))])
	ps = [0.005; 0.025; 0.5; 0.95; 0.975]
	q = quantile(ts,[0.005,0.025,0.5,0.95,0.975])
	qs = [ps q]
	plt = plot(ts,st=:density, label="posterior", fill=(0,0.4,:blue),alpha=0.4, title="Posterior for Correlation Coefficient")
	vline!([cor(y,x) h0],linecolor = [:green :black],label=["mean" "H0"])
	return todds, pval, qs, plt
end

# Example of use (vary coefficient on x to increase/reduce correlation)
#n = 50
#x = randn(n)
#y = 1.0 .+ 1.0.*x .+ randn(50)
#cor(x,y)
# cor(x,y) = bhat*sd(x)/sd(y)

#results = correlation_ttest(x,y, h0=0.5)
#results[1]
#results[2]
#results[3]
#results[4]
# savefig("trash.png")


"""
    GumbelCopula{d,T}

Fields:
  - θ::Real - parameter

Constructor

    GumbelCopula(d, θ)

The [Gumbel](https://en.wikipedia.org/wiki/Copula_(probability_theory)#Most_important_Archimedean_copulas) copula in dimension ``d`` is parameterized by ``\\theta \\in [1,\\infty)``. It is an Archimedean copula with generator :

```math
\\phi(t) = \\exp{-t^{\\frac{1}{θ}}}
```
"""
struct GumbelCopula{d,T} <: ArchimedeanCopula{d}
    θ::T
end
GumbelCopula(d,θ) = θ >= 1 ? GumbelCopula{d,typeof(θ)}(θ) : @error "Theta must be greater than 1."
ϕ(  C::GumbelCopula,       t) = exp(-t^(1/C.θ))
ϕ⁻¹(C::GumbelCopula,       t) = (-log(t))^C.θ
τ(C::GumbelCopula) = (C.θ-1)/C.θ
τ⁻¹(::Type{GumbelCopula},τ) =1/(1-τ)

function radial_dist(C::GumbelCopula)
    α = 1/C.θ
    β = 1
    γ = cos(π/(2C.θ))^C.θ
    δ = C.θ == 1 ? 1 : 0
    AlphaStable([α,β,γ,δ]...) # for the type promotion...
end

# S(α, β, γ , δ) denotes a stable distribution in
# 1-parametrization [16, p. 8] with characteristic exponent α ∈ (0, 2], skewness β ∈ [−1, 1], scale
# γ ∈ [0,∞), and location δ ∈ R


using ControlSystems, OrdinaryDiffEq, NLopt, BlackBoxOptim, ForwardDiff
p0          = [0.2,0.8,1] # Initial guess
K(kp,ki,kd) = pid(kp=kp, ki=ki, kd=kd)
K(p)        = K(p...)

# Define process model
ζ  = 0.1
ω  = 1.; ω² = ω^2
P  = tf(ω²,[1, 2ζ*ω, ω²])*tf(1,[1,1])

Ω  = exp10.(LinRange(-1,2,150))  # Frequency vector to eval constraints
h  = 0.1 # Sample time for time-domain evaluation
Tf = 60.  # Time horizon
t  = 0:h:Tf-h

Ms = 1.4 # Maximum allowed magnitude of sensitivity function
Mt = 1.4 # Maximum allowed magnitude of complimentary sensitivity function

p  = copy(p0)

function timedomain(p)
    C     = K(p[1], p[2], p[3])
    S     = 1/(1+P*C) # Sensitivity fun
    PS    = ss(P*S)   # TF from load disturbance to output
    s     = Simulator(PS, (t,x) -> [1]) # Sim. unit step load disturbance
    ty    = eltype(p) # So that all inputs to solve have same numerical type (ForwardDiff.Dual)
    x0    = zeros(PS.nx) .|> ty
    tspan = (ty(0.),ty(Tf))
    sol   = solve(s, x0, tspan, Tsit5())
    y     = PS.C*sol(t) # y = C*x
    C,y
end

function freqdomain(p)
    C     = K(p[1], p[2], p[3])
    S     = 1/(1+P*C) # Sensitivity fun
    T     = tf(1.) - S# Comp. Sensitivity fun
    Sw    = vec(bode(S,Ω)[1]) # Freq. domain constraints
    Tw    = vec(bode(T,Ω)[1]) # Freq. domain constraints
    Sw,Tw
end

# Evaluates and plots candidate controller
function evalsol(p::Vector)
    C,y = timedomain(p)
    Sw,Tw = freqdomain(p)
    plot(t,y', layout=2, show=false)
    plot!(Ω, [Sw Tw] , lab=["Sw" "Tw"], subplot=2, xscale=:log10, yscale=:log10, show=false)
    plot!([Ω[1],Ω[end]], [Ms,Ms], c = :black, l=:dash, subplot=2, show=false, lab="Ms")
    plot!([Ω[1],Ω[end]], [Mt,Mt], c = :purple, l=:dash, subplot=2, lab="Mt")
    gui()
    false
end
evalsol(res::BlackBoxOptim.OptimizationResults) = evalsol(best_candidate(res))

function constraintfun(p)
    Sw,Tw = freqdomain(p)
    [maximum(Sw)-Ms; maximum(Tw)-Mt]
end

function costfun(p)
    C,y = timedomain(p)
    mean(abs,y) # ~ Integrated absolute error IAE
end

function runopt(p, costfun, constraintfun;
    f_tol = 1e-5,
    x_tol = 1e-3,
    c_tol = 1e-8,
    f_cfg = ForwardDiff.GradientConfig(costfun, p),
    g_cfg = ForwardDiff.JacobianConfig(constraintfun, p),
    lb = zeros(length(p)))

    c1 = constraintfun(p)
    np = length(p)
    nc = length(c1)

    function f(p::Vector, grad::Vector)
        if length(grad) > 0
            grad .= ForwardDiff.gradient(costfun,p,f_cfg)
        end
        costfun(p)
    end

    function c(result, p::Vector, grad)
        if length(grad) > 0
            grad .= ForwardDiff.jacobian(constraintfun,p,g_cfg)'
        end
        result .= constraintfun(p)
    end

    opt = Opt(:LD_SLSQP, np)
    lower_bounds!(opt, lb)
    xtol_rel!(opt, x_tol)
    ftol_rel!(opt, f_tol)

    min_objective!(opt, f)
    inequality_constraint!(opt, c, c_tol*ones(nc))
    minf,minx,ret = NLopt.optimize(opt, p)
end

f_cfg = ForwardDiff.GradientConfig(costfun, p)
g_cfg = ForwardDiff.JacobianConfig(constraintfun, p)
@time minf,minx,ret = runopt(1p0, costfun, constraintfun, x_tol=1e-6, c_tol=1e-12, f_cfg=f_cfg, g_cfg=g_cfg)
evalsol(minx)

# # Optimize costfun using derivative-free method
# res1 = compare_optimizers(costfun; SearchRange = (0.,2.), NumDimensions = length(p0), MaxTime = 20.0)
# res2 = bboptimize(costfun, NumDimensions = length(p0), MaxTime = 20.0)
# evalsol(res2)
# p = best_candidate(res2)


using DifferentialEquations, Plots

𝞪 = 1
u0 = 1/2
f(t,u) = 𝞪*u
tspan = (0.0, 1.0)

prob = ODEProblem(f, u0, tspan)

sol1 = solve(prob, Euler(), dt=1/2^1)
sol2 = solve(prob, Euler(), dt=1/2^2)
sol3 = solve(prob, Euler(), dt=1/2^4)

#[t+2u for (t,u) in zip(sol1.t,sol.u)]

x = 0.0:0.05:1.0
p = plot(x, sol1(x))
plot!(p, x, sol2(x))
plot!(p, x, sol3(x))

gui()


""" Julia Program to implement the Cutting a Rod problem

We are given a rod of length 'n' and the cost of the rod of different length from
1 to n. We need to find the maximum sell price that can be obtained by cutting the rod
at any lengths.

This problem can be solved by using Dynamic Programming
"""

# Build a dp Table in Botom-Up manner, such that the n'th element stores
# the maximum price obtained by a rod of length 'n'
function max_sell_price(m, cost)
    dp = zeros(Int, n + 1)
    for i in 1:n
        curr_max = -1
        for j in 0:(i - 1)
            # Maximum Price obtained by cutting a rod of length zero is zero
            if ((i - j - 1) == 0)
                curr_max = max(curr_max, cost[j + 1])
            else
                curr_max = max(curr_max, cost[j + 1] + dp[i - j - 1])
            end
        end
        #  Stores the maximum cost obtainable for a rod of length i
        dp[i] = curr_max
    end
    return dp[n]
end

print("What is the length of the rod? ")
n = readline()
n = parse(Int, n)
if (n <= 0)
    println("The given rod length is Invalid!!!")
    exit()
end
cost = Int[]
print("Enter the cost of the rod for length 1 to $n :  ")
cost = [parse(Int, num) for num in split(readline())]
max_price = max_sell_price(n, cost)
print("The maximized sell price of the: $max_price")

"""
Time Complexity- O(n^2), where 'n' is the length of the rod
Space Complexity- O(n)

SAMPLE INPUT AND OUTPUT

SAMPLE 1

What is the length of the rod? 5
Enter the cost of the rod for length 1 to 5: 2 5 7 8 10
The maximized sell price of the: 12

SAMPLE 2

What is the length of the rod? -3
The given rod length is Invalid!!!

"""