"""
Cumulative Distribution Function of a Standard Gaussian Random Variable

		y=normcdf(x)

Where:\n
		x         = point of evaluation.

		y      = probability that a standard gaussian random variable is below x.

# Example
```julia-repl
julia> normcdf(0.0)
0.5
```
"""
function normcdf(x::number) where {number <: Number}
    return (1.0 + erf(x / sqrt(2.0))) / 2.0
end

"""
Probability Distribution Function of a Standard Gaussian Random Variable

		y=normpdf(x)

Where:\n
		x         = point of evaluation.

		y      = value.

# Example
```julia-repl
julia> normpdf(0.0)
0.3989422804014327
```
"""
function normpdf(x::number) where {number <: Number}
    return exp(-0.5 * x .^ 2) / sqrt(2 * pi)
end

"""
Black & Scholes Price for European Options

		Price=blsprice(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Price      = price of the European Option.

# Example
```julia-repl
julia> blsprice(10.0,10.0,0.01,2.0,0.2,0.01)
1.1023600107733191
```
"""
function blsprice(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    d1 = (log(S0 / K) + (r - d + σ * σ * 0.5) * T) / (σ * sqrt(T))
    d2 = d1 - σ * sqrt(T)
    N_d1 = normcdf(d1)
    N_d2 = normcdf(d2)
    Price = 0.0
    if FlagIsCall
        Price = S0 * exp(-d * T) * N_d1 - K * exp(-r * T) * N_d2
    else
        Price = K * exp(-r * T) * (1 - N_d2) - S0 * exp(-d * T) * (1 - N_d1)
    end
    return Price
end

"""
Black Price for European Options

		Price=blkprice(F0,K,r,T,σ,FlagIsCall=true)

Where:\n
		F0         = Value of the Forward.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility.
		FlagIsCall = true for Call Options, false for Put Options.

		Price      = price of the European Option.

# Example
```julia-repl
julia> blkprice(10.0,10.0,0.01,2.0,0.2)
1.1023600107733191
```
"""
function blkprice(F0::num1, K::num2, r::num3, T::num4, σ::num5, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number}
    blscheck(F0, K, r, T, σ)
    Price = blsprice(F0, K, r, T, σ, r, FlagIsCall)
    return Price
end

"""
Black & Scholes Delta for European Options

		Δ=blsdelta(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Δ          = delta of the European Option.

# Example
```julia-repl
julia> blsdelta(10.0,10.0,0.01,2.0,0.2,0.01)
0.5452173371920436
```
"""
function blsdelta(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    d1 = (log(S0 / K) + (r - d + σ * σ * 0.5) * T) / (σ * sqrt(T))
    Δ = 0.0
    if FlagIsCall
        Δ = exp(-d * T) * normcdf(d1)
    else
        Δ = -exp(-d * T) * normcdf(-d1)
    end
    return Δ
end

"""
Black & Scholes Gamma for European Options

		Γ=blsgamma(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Γ          = gamma of the European Option.

# Example
```julia-repl
julia> blsgamma(10.0,10.0,0.01,2.0,0.2,0.01)
0.13687881535712826
```
"""
function blsgamma(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    d1 = (log(S0 / K) + (r - d + σ * σ * 0.5) * T) / (σ * sqrt(T))
    Γ = exp(-d * T) * normpdf(d1) / (S0 * σ * sqrt(T))
    return Γ
end

"""
Black & Scholes Vega for European Options

		ν=blsvega(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		ν          = vega of the European Option.

# Example
```julia-repl
julia> blsvega(10.0,10.0,0.01,2.0,0.2,0.01)
5.475152614285131
```
"""
function blsvega(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    d1 = (log(S0 / K) + (r - d + σ * σ * 0.5) * T) / (σ * sqrt(T))
    ν = S0 * exp(-d * T) * normpdf(d1) * sqrt(T)
    return ν
end

"""
Black & Scholes Rho for European Options

		ρ=blsrho(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		ρ          = rho of the European Option.

# Example
```julia-repl
julia> blsrho(10.0,10.0,0.01,2.0,0.2,0.01)
8.699626722294234
```
"""
function blsrho(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    d2 = (log(S0 / K) + (r - d - σ * σ * 0.5) * T) / (σ * sqrt(T))
    if FlagIsCall
        ρ = K * exp(-r * T) * normcdf(d2) * T
    else
        ρ = -K * exp(-r * T) * normcdf(-d2) * T
    end
    return ρ
end

"""
Black & Scholes Theta for European Options

		Θ=blstheta(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Θ          = theta of the European Option.

# Example
```julia-repl
julia> blstheta(10.0,10.0,0.01,2.0,0.2,0.01)
-0.26273403060652334
```
"""
function blstheta(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    sqrtT = sqrt(T)
    σ_sqrtT = σ * sqrtT

    d1 = (log(S0 / K) + (r - d + σ .^ 2 / 2) * T) / σ_sqrtT

    shift = -exp(-d * T) * S0 * normpdf(d1) * σ / 2 / sqrtT
    t1 = r * K * exp(-r * T)
    t2 = d * S0 * exp(-d * T)
    Θ = 0.0
    if FlagIsCall
        Θ = shift - t1 * normcdf(d1 - σ_sqrtT) + t2 * normcdf(d1)
    else
        Θ = shift + t1 * (1 - normcdf(d1 - σ_sqrtT)) + t2 * (normcdf(d1) - 1)
    end
    return Θ
end

"""
Black & Scholes Lambda for European Options

		Λ=blslambda(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Λ          = lambda of the European Option.

# Example
```julia-repl
julia> blslambda(10.0,10.0,0.01,2.0,0.2,0.01)
4.945909973725978
```
"""
function blslambda(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    Price = blsprice(S0, K, r, T, σ, d, FlagIsCall)
    Δ = blsdelta(S0, K, r, T, σ, d, FlagIsCall)
    Λ = Δ * S0 / Price
    return Λ
end

"Check input for Black Scholes Formula"
function blscheck(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    lesseq(x::Complex, y::Complex) = real(x) <= real(y)
    lesseq(x, y) = x <= y
    if (lesseq(S0, zero(num1)))
        error("Spot Price Cannot Be Negative")
    elseif (lesseq(K, zero(num2)))
        error("Strike Price Cannot Be Negative")
    elseif (lesseq(T, zero(num4)))
        error("Time to Maturity Cannot Be Negative")
    elseif (lesseq(σ, zero(num5)))
        error("Volatility Cannot Be Negative")
    end
    return
end

"Brent Method: Scalar Equation Solver"
function brentMethod(f::Function, x0::Number, x1::Number, xtol::AbstractFloat = 1e-14, ytol::AbstractFloat = 1e-15)
    if xtol < 0.0
        throw(ErrorException("x tollerance cannot be negative"))
    end
    if ytol < 0.0
        throw(ErrorException("y tollerance cannot be negative"))
    end
    EPS = eps(x0)
    maxiter = 80
    y0 = f(x0)
    y1 = f(x1)
    if (y0 * y1 > 0)
        throw(DomainError("There is no such volatility"))
    end
    if abs(y0) < abs(y1)
        # Swap lower and upper bounds.
        x0, x1 = x1, x0
        y0, y1 = y1, y0
    end
    x2 = x0
    y2 = y0
    x3 = x2
    bisection = true
    for _ = 1:maxiter
        # x-tolerance.
        if abs(x1 - x0) < xtol
            return x1
        end

        # Use inverse quadratic interpolation if f(x0)!=f(x1)!=f(x2)
        # and linear interpolation (secant method) otherwise.
        if abs(y0 - y2) > ytol && abs(y1 - y2) > ytol
            x = x0 * y1 * y2 / ((y0 - y1) * (y0 - y2)) + x1 * y0 * y2 / ((y1 - y0) * (y1 - y2)) + x2 * y0 * y1 / ((y2 - y0) * (y2 - y1))
        else
            x = x1 - y1 * (x1 - x0) / (y1 - y0)
        end

        # Use bisection method if satisfies the conditions.
        delta = abs(2EPS * abs(x1))
        min1 = abs(x - x1)
        min2 = abs(x1 - x2)
        min3 = abs(x2 - x3)
        if (x < (3x0 + x1) / 4 && x > x1) || (bisection && min1 >= min2 / 2) || (!bisection && min1 >= min3 / 2) || (bisection && min2 < delta) || (!bisection && min3 < delta)
            x = (x0 + x1) / 2
            bisection = true
        else
            bisection = false
        end
        y = f(x)
        if abs(x - x0) < xtol
            return x
        end
        x3 = x2
        x2 = x1
        if sign(y0) != sign(y)
            x1 = x
            y1 = y
        else
            x0 = x
            y0 = y
        end
        if abs(y0) < abs(y1)
            # Swap lower and upper bounds.
            x0, x1 = x1, x0
            y0, y1 = y1, y0
        end
    end
    throw(ErrorException("Max iteration exceeded, possible wrong result"))
end

export blsimpv
"""
Black & Scholes Implied Volatility for European Options

		σ=blsimpv(S0,K,r,T,Price,d=0.0,FlagIsCall=true,xtol=1e-14,ytol=1e-15)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		Price      = Price of the Option.
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		σ          = implied volatility of the European Option.

# Example
```julia-repl
julia> blsimpv(10.0,10.0,0.01,2.0,2.0)
0.3433730534290586
```
"""
function blsimpv(S0::num1, K::num2, r::num3, T::num4, Price::num5, d::num6 = 0.0, FlagIsCall::Bool = true, xtol::Real = 1e-14, ytol::Real = 1e-15) where {num1 <: Real, num2 <: Real, num3 <: Real, num4 <: Real, num5 <: Real, num6 <: Real}
    if (Price < num5(0))
        throw(ErrorException("Option Price Cannot Be Negative"))
    end
    blscheck(S0, K, r, T, 0.1, d)
    f(x) = (blsprice(S0, K, r, T, x, d, FlagIsCall) - Price)
    σ = brentMethod(f, 0.001, 1.2, xtol, ytol)

    return σ
end

export blkimpv
"""
Black Implied Volatility for European Options

		σ=blkimpv(F0,K,r,T,Price,FlagIsCall=true,xtol=1e-14,ytol=1e-15)

Where:\n
		F0         = Value of the Forward.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		Price      = Price of the Option.
		FlagIsCall = true for Call Options, false for Put Options.

		σ          = implied volatility of the European Option.

# Example
```julia-repl
julia> blkimpv(10.0,10.0,0.01,2.0,2.0)
0.36568658096623635
```
"""
function blkimpv(F0::num1, K::num2, r::num3, T::num4, Price::num5, FlagIsCall::Bool = true, xtol::Real = 1e-14, ytol::Real = 1e-15) where {num1 <: Real, num2 <: Real, num3 <: Real, num4 <: Real, num5 <: Real}
    blscheck(F0, K, r, T, 0.1)
    σ = blsimpv(F0, K, r, T, Price, r, FlagIsCall, xtol, ytol)
    return σ
end

##	ADDITIONAL Function

"""
Black & Scholes Psi for European Options

		Ψ=blspsi(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Ψ          = psi of the European Option.

# Example
```julia-repl
julia> blspsi(10.0,10.0,0.01,2.0,0.2,0.01)
-10.904346743840872
```
"""
function blspsi(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    d1 = (log(S0 / K) + (r - d + σ * σ * 0.5) * T) / (σ * sqrt(T))
    if FlagIsCall
        Ψ = -S0 * exp(-d * T) * normcdf(d1) * T
    else
        Ψ = S0 * exp(-d * T) * normcdf(-d1) * T
    end
    return Ψ
end

"""
Black & Scholes Vanna for European Options

		Vanna=blsvanna(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Vanna        = vanna of the European Option.

# Example
```julia-repl
julia> blsvanna(10.0,10.0,0.01,2.0,0.2,0.01)
0.2737576307142566
```
"""
function blsvanna(S0::num1, K::num2, r::num3, T::num4, σ::num5, d::num6 = 0.0, FlagIsCall::Bool = true) where {num1 <: Number, num2 <: Number, num3 <: Number, num4 <: Number, num5 <: Number, num6 <: Number}
    blscheck(S0, K, r, T, σ, d)
    d1 = (log(S0 / K) + (r - d + σ * σ * 0.5) * T) / (σ * sqrt(T))
    d2 = d1 - σ * sqrt(T)
    Vanna = -exp(-d * T) * normpdf(d1) * d2 / σ
    return Vanna
end


"""
Functions obtanied in:

- https://www.sfu.ca/~ssurjano/optimization.html
- Molga, M., & Smutnicki, C. (2005). Test functions for optimization needs. Test functions for optimization needs, 101, 48.

"""

# N-Dimensional

# ACKLEY FUNCTION
"""
# Description
*Dimensions*: d

The **Ackley function** is widely used for testing optimization algorithms.
In its two-dimensional form, as shown in the plot above, it is characterized
by a nearly flat outer region, and a large hole at the centre.
The function poses a risk for optimization algorithms, particularly hillclimbing algorithms,
to be trapped in one of its many local minima.

Recommended variable values are: **a = 20, b = 0.2 and c = 2π**.

# Input Domain:

The function is usually evaluated on the hypercube **xi ∈ [-32.768, 32.768],
for all i = 1, …, d,** although it may also be restricted to a smaller domain.

# Global Minimum:

**f(X\\*) = 0**, at **X\\* = (0,...,0)**

# Link:

*https://www.sfu.ca/~ssurjano/ackley.html*
"""
function Ackley(X...)
    d = length(X)
    return -20*exp(-0.2*sqrt((1/d)*sum(X.^2))) - exp((1/d)*sum(cos.(2 .*pi.*X))) + 20 + exp(1)
end

# SCHWEFEL’S FUNCTION
"""
# Description
*Dimensions*: d

**Schwefel’s function** is deceptive in that the global minimum is geometrically distant,
over the parameter space, from the next best local minima. Therefore, the search algorithms
are potentially prone to convergence in the wrong direction.

# Input Domain:

Test area is usually restricted to hyphercube **xi ∈ [−500, 500], for all i = 1, . . . , d**.

# Global Minimum:

**f(X\\*) = -418.9829d**, at **X\\* = (420.9687,...,420.9687)**
"""
Schwefel(X...) = sum(@. -X*sin(sqrt(abs(X))))

# GRIEWANK FUNCTION
"""
# Description:
*Dimensions*: d

The **Griewank function** has many widespread local minima, which are regularly distributed. The complexity is shown in the zoomed-in plots.

# Input Domain:
The function is usually evaluated on the hypercube **xi ∈ [-600, 600], for all i = 1, …, d**.

# Global Minimum:
**f(X\\*) = 0**, at **X\\* = (0,...,0)**
"""
function Griewank(X...)
    i = 1:length(X)
    A = sum(X.^2 ./4000)
    B = prod(cos.(X./sqrt.(i)))
    return A - B + 1
end

# ROSENBROCK FUNCTION
"""
*Description*:
# Dimensions: d

The **Rosenbrock function**, also referred to as the Valley or Banana function, is a popular test problem for gradient-based optimization algorithms. It is shown in the plot above in its two-dimensional form.

The function is unimodal, and the global minimum lies in a narrow, parabolic valley.
However, even though this valley is easy to find, convergence to the minimum is difficult (Picheny et al., 2012).

# Input Domain:
The function is usually evaluated on the hypercube **xi ∈ [-5, 10], for all i = 1, …, d**, although it may be restricted
to the hypercube **xi ∈ [-2.048, 2.048], for all i = 1, …, d**.

# Global Minimum:

**f(X\\*) = 0**, at **X\\* = (1,...,1)**

# Link:

https://www.sfu.ca/~ssurjano/rosen.html
"""
function Rosenbrock(X...)
    x1 = X[1:length(X)-1]
    x2 = X[2:length(X)]
    return sum(@. 100*(x2 - x1^2)^2 + (x1 - 1)^2)
end

# MICHALEWICZ FUNCTION
"""
# Description:
*Dimensions*: d

The **Michalewicz function** has d! local minima, and it is multimodal.
The parameter m defines the steepness of they valleys and ridges; a larger m leads to
a more difficult search. The recommended value of m is m = 10.

# Input Domain:
The function is usually evaluated on the hypercube **xi ∈ [0, π], for all i = 1, …, d**.

Global Minima:

at **d = 2**: **f(X\\*) = -1.8013**, at **X\\* = (2.20, 1.57)**
at **d = 5**: **f(X\\*) = -4.687658**
at **d = 10**: **f(X\\*) = -9.66015**

# Link:

*https://www.sfu.ca/~ssurjano/michal.html*
"""
function Michalewicz(X...)
    m = 10
    i = 1:length(X)
    return -sum(@. sin(X)*(sin(i*X^2 / pi)^(2*m)))
end

# 2-Dimensional

# BUKIN FUNCTION N. 6
"""
# Description:
*Dimensions*: 2

The **sixth Bukin function** has many local minima, all of which lie in a ridge.

# Input Domain:

The function is usually evaluated on the rectangle **x1 ∈ [-15, -5], x2 ∈ [-3, 3]**.

# Global Minimum:

**f(X\\*) = 0**, at **X\\* = (-10,1)**

# Link:

*https://www.sfu.ca/~ssurjano/bukin6.html*
"""
Bukin6(x,y) = 100*sqrt(abs(y - 0.01*x^2)) + 0.01*abs(x + 10)

# CROSS IN TRAY FUNCTION
"""
# Description:
*Dimensions*: 2

The **Cross-in-Tray function** has multiple global minima. It is shown here with a smaller domain in the second plot, so that its characteristic "cross" will be visible.

# Input Domain:

The function is usually evaluated on the square **xi ∈ [-10, 10], for all i = 1, 2**.

# Global Minima:

**f(X\\*) = -2.06261**, at **X\\* = (1.3491, -1.3491), (-1.3491, 1.3491), (-1.3491, -1.3491)** and **(1.3491, 1.3491)**

# Link:

*https://www.sfu.ca/~ssurjano/crossit.html*
"""
function Cross_in_tray(x,y)
    s = sqrt(x^2 + y^2)/pi
    return -0.0001*(abs(sin(x)*sin(y)*exp(abs(100-s)))+1)^0.1
end

# DROP WAVE FUNCTION
"""
# Description:
*Dimensions*: 2

The **Drop-Wave function** is multimodal and highly complex. The second plot above shows the function on a smaller input domain, to illustrate its characteristic features.

# Input Domain:

The function is usually evaluated on the square **xi ∈ [-5.12, 5.12], for all i = 1, 2**.

# Global Minimum:

**f(X\\*) = -1**, at **X\\* = (0,0)**

# Link:

*https://www.sfu.ca/~ssurjano/drop.html*
"""
function Drop_wave(X...)
    s = sum(X.^2)
    return -(1 + cos(12*sqrt(s)))/(0.5*(s) + 2)
end

# EGGHOLDER FUNCTION
"""
# Description:
*Dimensions*: 2

The **Eggholder function** is a difficult function to optimize, because of the large number of local minima.

# Input Domain:
The function is usually evaluated on the square **xi ∈ [-512, 512], for all i = 1, 2**.

# Global Minimum:

**f(X\\*) = -959.6407**, at **X\\* = (512,404.2319)**

# Link:

*https://www.sfu.ca/~ssurjano/egg.html*
"""
function Eggholder(x,y)
    A = (y + 47)*sin(sqrt(abs(y + x/2 + 47)))
    B = x*sin(sqrt(abs(x - (y + 47))))
    return -(A + B)
end


function quat(q0,q1,q2,q3)

  q_length=sqrt(q0*q0+q1*q1+q2*q2+q3*q3);

  q0=q0/q_length;
  q1=q1/q_length;
  q2=q2/q_length;
  q3=q3/q_length;

  phi=atan2(2*(q0*q1+q2*q3),1-2*(q1*q1+q2*q2));
  theta=asin(2*(q0*q2-q3*q1));
  psii=atan2(2*(q0*q3+q1*q2),1-2*(q2*q2+q3*q3));

  return phi, theta, psii
end

phi, theta, psi=quat(0.99619,0,0,0.08716)
psi*180/pi

function Quaternion_to_EulerianAngle(x, y, z, w)
  ysqr = y*y

  t0 = +2.0 * (w * x + y*z)
  t1 = +1.0 - 2.0 * (x*x + ysqr)
  X = atan2(t0, t1)*180/pi

  t2 = +2.0 * (w*y - z*x)
  t2 = ((t2 > 1.0) ? 1.0 : t2);
  t2 = ((t2 < -1.0) ? -1.0 : t2);
  Y = asin(t2)*180/pi

  t3 = +2.0 * (w * z + x*y)
  t4 = +1.0 - 2.0 * (ysqr + z*z)
  Z = atan2(t3, t4)*180/pi

  return X, Y, Z
end

type	Quaternion
  x
  y
  z
  w
end

function

function toQuaternion(roll, pitch, yaw)

	t0 = cos(yaw/2)
	t1 = sin(yaw/2)
	t2 = cos(roll/2)
	t3 = sin(roll/2)
	t4 = cos(pitch/2)
	t5 = sin(pitch/2)

  w = t0*t2*t4 + t1*t3*t5
	x = t0*t3*t4 - t1*t2*t5
	y = t0*t2*t5 + t1*t3*t4
	z = t1*t2*t4 - t0*t3*t5

  q = Quaternion(x,y,z,w)

	return q
end

roll = 10
pitch = 0
yaw = 0
toQuaternion(roll, pitch, yaw)
phi, theta, psi=Quaternion_to_EulerianAngle(0.99619,0,0,0.08716)

using PyCall

@pyimport tf.transformations as tf

roll = 0
pitch = 0
yaw = pi/2

Q = tf.quaternion_from_euler(roll, pitch, yaw)

roll, pitch, yaw = tf.euler_from_quaternion(Q)


module LoopFieldCalc

using Elliptic, Printf, Contour

const μ₀ = 4π * 1e-7

include("geometry.jl")

Base.@kwdef struct CurrentLoop
	radius::Float64
	current::Float64
	center::CartesianPoint
end

CurrentLoop(;radius, current, x, y, z) =
	CurrentLoop(radius, current, CartesianPoint(x, y, z))

CurrentLoop(;radius, current, z, r, θ) =
	CurrentLoop(radius, current, cylindrical2cartesian(CylindricalPoint(z, r, θ)))

CurrentLoop(radius, current, cyl::CylindricalPoint) =
	CurrentLoop(radius, current, cylindrical2cartesian(cyl))

B0(loop) = μ₀ * loop.current / 2 / loop.radius

field_at_point(loop, point::CylindricalPoint) = field_at_point(loop, cylindrical2cartesian(point))

"""
	field_at_point(loop, point::CartesianPoint)
Compute the field strength due to a current loop at a point in the cartesian plane. Returns
Bx, By, and Bz (three components of magnetic field vector at this point) as well as the
magnetic stream function λ and magnetic scalar potential χ.
"""
function field_at_point(loop, point::CartesianPoint)

	# Compute (z, r, θ), the coordinates of the point relative to the loop center in cylindrical coordinates
	x, y, z	= point.x, point.y, point.z
	x′, y′, z′ = loop.center.x, loop.center.y, loop.center.z
	z, r, θ = cartesian2cylindrical(x - x′, y - y′, z - z′)

	# Compute the field strength at z, r (using the fact that the loop is centered at z = 0, r= 0)
	I = loop.current
	rL = loop.radius

	Rmax² = z^2 + (r+rL)^2
	Rmin² = z^2 + (r-rL)^2
	Rmax = sqrt(Rmax²)
	Rmin = sqrt(Rmin²)

	k² = 4*r*rL / Rmax²
	ξ = atan(z, abs(r-rL))
	kp² = 1-k²

	K, E = Elliptic.ellipke(k²)
	Ei = Elliptic.E(ξ, kp²)
	Fi = Elliptic.F(ξ, kp²)
	Λ = 2/pi * (E*Fi + K*(Ei - Fi))

	Ω = 0.0
	# solid angle subtended by current loop at (z,r)
	if r < rL
		Ω = 2*π - 2*z/Rmax*K - π*Λ
	elseif r == rL
		Ω = π - 2*z/Rmax*K
	else
		Ω = - 2*z/Rmax*K + π*Λ
	end

	B₀ = μ₀ * I / 2/ rL;

	Bz = μ₀ * I / π ./ (2 * Rmin² * Rmax) * ((rL^2 - r^2 - z^2)*E + Rmin² * K)
	Br = μ₀ * I * z /π ./ (2 * Rmin² * Rmax * r) * ((rL^2 + r^2 + z^2)*E - Rmin² * K)
	λ = B₀*rL/2/pi * Rmax * ((2-k²)*K - 2*E)
	χ = I/4/pi * Ω

	if isnan(Br) || isinf(Br)
		Br = 0
	end

	if isnan(Bz) || isinf(Bz)
		Bz = 0
	end

	# Convert Bz and Br to Bx, By, Bz
	Bx, By, Bz = cylindrical2cartesian(Bz, Br, θ)

	return Bx, By, Bz, λ, χ
end

function field_on_grid(loops, xs, ys, zs)
	Nx, Ny, Nz = length(xs), length(ys), length(zs)

	Bx = zeros(Nx, Ny, Nz)
	By = zeros(Nx, Ny, Nz)
	Bz = zeros(Nx, Ny, Nz)

	for (i, x) in enumerate(xs), (j, y) in enumerate(ys), (k, z) in enumerate(zs)
		for loop in loops
			bx, by, bz, _, _ = field_at_point(loop, CartesianPoint(x, y, z))
			Bx[i, j, k] += bx
			By[i, j, k] += by
			Bz[i, j, k] += bz
		end
	end

	return Bx, By, Bz
end

"""
	write_field(filename, xs, ys, Bx, By)
Write magnetic field to Tecplot ASCII datafile for two-dimensional data
"""
function write_field(filename, xs, ys, Bx, By)
	split_fname = splitpath(filename)
	if length(split_fname) > 1 # If file is in folder, make sure that folder exists
		mkpath(joinpath(split_fname[1:end-1]...))
	end
	Nx, Ny= length(xs), length(ys)
	open(filename, "w") do f
		println(f, "VARIABLES = \"X\" \"Y\" \"Bx\" \"By\"")
		println(f, "ZONE I = $Nx, J = $Ny")
		for (j, y) in enumerate(ys), (i, x) in enumerate(xs)
			row = "$(x)\t$(y)\t$(Bx[i, j])\t$(By[i, j])"
			println(f, row)
		end
	end
end
"""

	write_field(filename, xs, ys, zs, Bx, By, Bz)
Write magnetic field to Tecplot ASCII datafile for three-dimensional data
"""
function write_field(filename, xs, ys, zs, Bx, By, Bz)
	split_fname = splitpath(filename)
	if length(split_fname) > 1 # If file is in folder, make sure that folder exists
		mkpath(joinpath(split_fname[1:end-1]...))
	end
	Nx, Ny, Nz = length(xs), length(ys), length(zs)
	open(filename, "w") do f
		println(f, "VARIABLES = \"X\" \"Y\" \"Z\" \"Bx\" \"By\" \"Bz\"")
		println(f, "ZONE I = $Nx, J = $Ny, K = $Nz")
		for (k, z) in enumerate(zs), (j, y) in enumerate(ys), (i, x) in enumerate(xs)
			row = "$(x)\t$(y)\t$(z)\t$(Bx[i, j, k])\t$(By[i, j, k])\t$(Bz[i, j, k])"
			println(f, row)
		end
	end
end

#=
function Λ₀(ξ::Float64, k::Float64)
	kprime = 1 - k
	K, E = Elliptic.ellipke(k)
	return 2/π * (E * Elliptic.F(ξ, kprime) +
				  K * (Elliptic.E(ξ, kprime) - Elliptic.F(ξ, kprime)))
end

function fieldatpoint(z, r, rL, I)
	Bz = 0.0
	Br = 0.0
	λ = 0.0
	χ = 0.0

	Rmax² = z^2 + (r+rL)^2
	Rmin² = z^2 + (r-rL)^2
	Rmax = sqrt(Rmax²)
	Rmin = sqrt(Rmin²)

	k² = 4*r*rL / Rmax²
	ξ = atan(z, abs(r-rL))
	kp² = 1-k²

	K, E = Elliptic.ellipke(k²)
	Ei = Elliptic.E(ξ, kp²)
	Fi = Elliptic.F(ξ, kp²)
	Λ = 2/pi * (E*Fi + K*(Ei - Fi))

	Ω = 0.0
	# solid angle subtended by current loop at (z,r)
	if r<rL
		Ω = 2*π - 2*z/Rmax*K - π*Λ
	elseif r==rL
		Ω = π - 2*z/Rmax*K
	else
		Ω = - 2*z/Rmax*K + π*Λ
	end

	μ₀ = VACUUM_PERMEABILITY

	B₀ = μ₀ * I / 2/ rL;

	Bz = μ₀ * I / π ./ (2 * Rmin² * Rmax) * ((rL^2 - r^2 - z^2)*E + Rmin² * K)
	Br = μ₀ * I * z /π ./ (2 * Rmin² * Rmax * r) * ((rL^2 + r^2 + z^2)*E - Rmin² * K)
	λ = B₀*rL/2/pi * Rmax * ((2-k²)*K - 2*E)
	χ = I/4/pi * Ω

	if isnan(Br) || isinf(Br)
		Br = 0
	end

	if isnan(Bz) || isinf(Bz)
		Bz = 0
	end

	return Bz, Br, λ, χ
end

#=function loopstreamfunc(z, r, rL, I)
	Rmax² = z^2 + (r+rL)^2
	Rmax = sqrt(Rmax²)
    k² = 4*r*rL / Rmax²
	B₀ = VACUUM_PERMEABILITY * I / 2/ rL

    E = Elliptic.E(k²)
	K = Elliptic.K(k²)

	λ = B₀*rL/2/pi * Rmax * ((2-k²)*K - 2*E)
    return λ
end

function loopscalarpotential(z, r, rL, I)
	Rmax² = z^2 + (r+rL)^2
	k² = 4*r*rL / Rmax²
	ξ = atan(z, abs(r-rL))

	Rmax = sqrt(Rmax²)

	Ω = 0.0

	if r<rL
		Ω = 2*π - 2*z/Rmax*Elliptic.K(k²) - π*Λ₀(ξ, k²)
	elseif r==rL
		Ω = π - 2*z/Rmax*Elliptic.K(k²)
	else
		Ω = - 2*z/Rmax*Elliptic.K(k²) + π*Λ₀(ξ, k²)
	end

	return I/4/π * Ω
end

function loopscalarpotential(points, loop::CurrentLoop)
	@views χ = loopscalarpotential.(
		points[:, 1] .- loop.z_coord,
		points[:, 2],
		loop.radius,
		loop.current,
	)
	return χ
end

function loopstreamfunc(points, loop::CurrentLoop)
	@views λ = loopstreamfunc.(
		points[:, 1] .- loop.z_coord,
		points[:, 2],
		loop.radius,
		loop.current,
	)
	return λ
end=#

function calculatefieldonmesh(zgrid, rgrid, loop::CurrentLoop)
	Bz = zero(zgrid)
	Br = zero(zgrid)
	λ = zero(zgrid)
	χ = zero(zgrid)

	for i in eachindex(zgrid)
		Bz[i], Br[i], λ[i], χ[i] = fieldatpoint.(
			zgrid[i] .- loop.z_coord,
			rgrid[i],
			loop.radius,
			loop.current,
		)
	end

	return Bz, Br, λ, χ
end

function extractfieldline(z, r, zgrid, rgrid, λgrid)
	zvals = zgrid[1, :]
	rvals = rgrid[:, 1]
	zind = findfirst(zval -> zval > z, zvals)
	rind = findfirst(rval -> rval > r, rvals)

	if zind == 1 && rind == 1
		λT = λ[1,1]
	elseif zind == 1
		λ_bot = λgrid[rind - 1, 1]
		λ_top = λgrid[rind, 1]
		rwt = (r - rgrid[rind - 1, 1]) / (rgrid[rind, 1] - rgrid[rind - 1, 1])
		λT = (λ_top - λ_bot) * rwt + λ_bot
	elseif rind == 1
		λ_left = λgrid[1, zind - 1]
		λ_right = λgrid[1, zind]
		zwt = (z - zgrid[1, zind - 1]) / (zgrid[1, zind] - zgrid[1, zind - 1])
		λT = (λ_right - λ_left) * zwt + λ_left
	else
		λ_br = λgrid[rind - 1, zind]
		λ_tr = λgrid[rind, zind]
		λ_bl = λgrid[rind - 1, zind - 1]
		λ_tl = λgrid[rind, zind - 1]
		zwt = (z - zgrid[1, zind - 1]) / (zgrid[1, zind] - zgrid[1, zind - 1])
		rwt = (r - rgrid[rind - 1, 1]) / (rgrid[rind, 1] - rgrid[rind - 1, 1])

		λ_top = zwt * (λ_tr - λ_tl) + λ_tl
		λ_bot = zwt * (λ_br - λ_bl) + λ_bl

		λT = (λ_top - λ_bot) * rwt + λ_bot
	end

	throatline = Contour.contour(zgrid, rgrid, λgrid, λT)
	zs, rs = coordinates(lines(throatline)[1])
	return [zs rs]
end

function save_field(z, r, Bz, Br, path)
	ni, nj = size(Bz)
	#for i in eachindex(Br)
	#	if Br[i] == 0.0
	#		Br[i] = 1e-16
	#	end
	#end
	open(path, "w") do io
		write(io, "VARIABLES = \"z\" \"r\" \"Bz\" \"Br\"\n")
		zonestr = @sprintf "ZONE I=%5d, J=%5d, F = POINT\n" ni nj
		write(io, zonestr)
		for j in 1:nj, i in 1:ni
			line = @sprintf "%e\t%e\t%e\t%e\n" z[i, j] r[i, j] Bz[i, j] Br[i, j]
			write(io, line)
		end
	end
end

function generate_boundary_file(nodes, casename)
	filename = "Boundary_" * casename * ".dat"
	npts = size(nodes, 1)
    open(filename, "w") do io
        write(io, string(npts))
        for i = 1:npts
            write(io, "\n", string(nodes[i, 1]), " ", string(nodes[i, 2]))
        end
    end
end=#

end


using NumericalMethodsforEngineers

a = [2. -3. 1.; 1. -1. -2.; 3. 1. -1.]
b = [7., -2., 0.]

(lower, upper) = lufac(a)
println("NumericalMethodsforEngineers lufac(a) - lower:")
println(lower)
println()
println("NumericalMethodsforEngineers lufac(a) - upper:")
println(upper)
println()
@show lower * upper
println()

subfor!(lower, b)
println("Updated RHS:")
println(b)
println()

subbac!(upper, b)
println("Solution vector:")
println(b)
println()

c = a\b

@show round(c, 14)
@show round(a * c, 14) == b
println()

println("Julia lufact(a):")
f = lufact(a);
println(f[:L])
println()
println(f[:U])
println()


function mytauchen(nz,ρ,σ,s::Int=3)
    """
    need package Distributions
    Discretize the following AR(1) process.
    z_t = ρz_{t-1} + ϵ_t

    Arguments:
    nz::Int64 :grid size # step 1
    ρ::Float64 :persistence of AR(1) process
    σ::Float64 :standard diviation of random components
    s::Int64 :decide maximum and minimum value of node often used s=3.

    return:
    z::array{float64,1} :discretized grid
    Π::array{float64,2} :transition matrix of z
    """
    d = Normal() # standard normal distribution

    ###########################################
    # step 2. decide max and min value of grid
    ##########################################

    σ_z = sqrt(σ^2/(1.0-ρ^2))
    zmin = -s*σ_z
    zmax = s*σ_z
    z = collect(linspace(zmin,zmax,nz))

    ###########################################
    # step 3. store width of between grid points
    ###########################################

    w = z[2] -z[1] #width of between grid points

    ###########################################
    # step 4. Get transition matrix
    ###########################################

    Π = zeros(nz,nz) # transition matrix

    for row in 1:nz
        # Do end points first
        @inbounds Π[row, 1] = cdf(d,(z[1] - ρ*z[row] + w/2) / σ)

        @inbounds Π[row, nz] = ccdf(d,(z[nz] - ρ*z[row] - w/2) / σ)
                               #1.0- cdf((z[nz] - ρ*z[row] - w/2) / σ)
    end

    # fill in the middle columns
    for row in 1:nz
        for col in 2:nz-1
            @inbounds Π[row, col] = (cdf(d,(z[col] - ρ*z[row] + w/2) / σ)
                                      -cdf(d,(z[col] - ρ*z[row] - w/2) / σ))
        end
    end

    return z,Π
end


const engvall = let res_init=zeros(5), jac_init=zeros(5,3), x_init=[1.0, 2.0, 0.0]

    function res(x, r)
        r[1] = x[1]^2 + x[2]^2 + x[3]^2 - 1.0
        r[2] = x[1]^2 + x[2]^2 + (x[3] - 2.0)^2 - 1.0
        r[3] = x[1] + x[2] + x[3] - 1.0
        r[4] = x[1] + x[2] - x[3] + 1.0
        r[5] = x[1]^3 + 3.0 * x[2] ^ 2 + (5.0 * x[3] - x[1] + 1.0)^2 - 36.0
        return r
    end

    function jac(x, jac)
        jac[1, 1] = 2.0 * x[1]
        jac[1, 2] = 2.0 * x[2]
        jac[1, 3] = 2.0 * x[3]
        jac[2, 1] = jac[1, 1]
        jac[2, 2] = jac[1, 2]
        jac[2, 3] = 2.0 * (x[3] - 2.0)
        jac[3, 1] = 1.0
        jac[3, 2] = 1.0
        jac[3, 3] = 1.0
        jac[4, 1] = 1.0
        jac[4, 2] = 1.0
        jac[4, 3] = -1.0
        t = 2.0 * (5.0 * x[3] - x[1] + 1.0)
        jac[5, 1] = 3.0 * x[1] ^ 2 - t
        jac[5, 2] = 6.0 * x[2]
        jac[5, 3] = 5.0 * t
        return jac
    end

    f(;scale=1, verbose=false, print_steps=false) = testone("engvall", res, jac,
                                                   res_init, jac_init, x_init;
                                                   scale=scale, verbose=verbose,
                                                   print_steps=print_steps)
end


""" Julia program to Check if a number is an Armstrong Number or not.
An armstrong number is a number such that the sum of its digits each
raised to the power of the number of digits.
"""

function check_armstrong(num)
    temp = num
    sum = 0
    cnt = floor(log10(num) + 1)
    while (temp > 0)
        rem = temp % 10
        sum = sum + (rem ^ cnt)
        temp = temp ÷ 10;
    end
    if(num == sum)
        return true
    else
        return false
    end
end

print("Enter the number: ")
num = readline()
num = parse(Int, num)
if(num < 0)
    println("The given number $num is not a Armstrong Number.")
    exit()
end
res = check_armstrong(num)
if res
    println("The given number $num is a Armstrong Number.")
else
    println("The given number $num is not a Armstrong Number.")
end

"""
Time Complexity: O(log(num)), where 'num' is the given number
Space Complexity: O(1)

SAMPLE INPUT AND OUTPUT

SAMPLE 1
Enter the number: 371
The given number 371 is a Armstrong Number.

SAMPLE 2
Enter the number: 1234
The given number 1234 is not a Armstrong Number.

"""


function subfor!(l::Matrix{Float64}, b::Vector{Float64})
# Forward-substitution on a Lower Triangle
  n = size(l, 1)
  for i in 1:n
    total = b[i]
    if i > 1
      for j in 1:i-1
        total -= l[i, j] * b[j]
      end
    end
    b[i] = total / l[i, i]
  end
end

# Forward-substitution
function subfor(l::Matrix{Float64}, b_in::Vector{Float64})
  b = deepcopy(b_in)
  n = size(l, 1)
  for i in 1:n
    total = b[i]
    if i > 1
      for j in 1:i-1
        total -= l[i, j] * b[j]
      end
    end
    b[i] = total / l[i, i]
  end
  b
end

export
  subfor!,
  subfor


#=
- Julia's arrays default to column vector behaviour
- Julia's indexes start at 1 and you can access elements using brackets
- Slices are inclusive of endpoints, and `end` means the last component.
- Steps can be informed in the middle
=#
x = [1, 2, 3, 4, 5]
x * 2
ones(5) + x
x[2]
x[1:2]
x[2:end]
x[1:2:end]
length(x)
# - Matrices are first-class citizens.
# - Create a matrix with brackets, spaces to change columns, and `;` to change lines.
# - Only in vectors `,` can be used instead of spaces (compat. Python).
# - You can concatenate matrices, vectors, and numbers with the same syntax.
A = [3 2 4; -1 0 1]
A * ones(3)
B = rand(3, 3)
A * B
B * A'
A[1:2, 1:2]
[
  B A';
  A ones(2,2)
]
# - Linear systems can be solved with `\ `
# - The LinearAlgebra stdlib module contains useful linear algebra tools, functions, and elements.
A = randn(50, 50)
b = A * ones(50)
x = A \ b
using LinearAlgebra
norm(b - A * x)
norm(x .- 1)
# - Everything has a concrete type, every type derives from some abstract type.
# - There is no implicit conversion, but sometimes it looks like there is.
typeof(2)
typeof(2.0)
Float64 |> supertype
Float64 |> supertype |> supertype
typeof(2 + 2.0)
typeof([2, 2.0])
typeof([2, true])
typeof([2, "a"])
# - There are 3 approaches to write functions
function foo(a, b)
  return (a + 1) * (b - 1)
end
foo(2, 3)
bar(x) = x^2 - 2
bar(5)
anon = (a, b, c) -> sqrt(a^2 + b^2 + c^2)
anon(-1, 0, 1)
# - `if CONDITION ... elseif ... else ... end`
# - `while CONDITION ... end`
# - `for VARIABLE in SET|RANGE|ITERATOR ... end`
# - You can specify types
function myfactorial(n::Int)
  if n < 0
    error("Nay nay")
  end
  prod = 1
  for i = 2:n
    prod *= i
  end
  return prod
end
myfactorial(4)
# - You can insert unicode characters with `\something[TAB]`.
function machine_eps(x::AbstractFloat)
  ϵ = x
  while x + ϵ != x
    ϵ /= 2
  end
  return 2ϵ
end
machine_eps(1.0), machine_eps(1e4), machine_eps(Float16(1.0)), machine_eps(1.0f0)
# - you can create a different signature
function machine_eps(x::Integer)
  return zero(x)
end
machine_eps(1), machine_eps(0x05), machine_eps(typemax(1))
# - you can set default positional arguments and keyword arguments
function newton(f, fder, x = 0.0; atol = 1e-6, rtol = 1e-6, max_iter = 1000)
  fx = f(x)
  iter = 0
  ϵ = atol + rtol * abs(fx)
  solved = abs(fx) < ϵ
  tired = iter ≥ max_iter
  while !(solved || tired)
    slope = fder(x)
    if abs(slope) < 1e-12
      error("0 derivative at x = $x")
    end
    x -= fx / slope
    fx = f(x)
    iter += 1
    solved = abs(fx) < ϵ
    tired = iter ≥ max_iter
  end
  return x, fx, iter
end
newton(x -> x - exp(-x), x -> 1 + exp(-x), 1.0)
# - You can use parametric types
# - The type must be inferred from somewhere, so `x` is not optional anymore
function newton_parametric(f, fder, x :: T; atol :: T = √eps(T), rtol :: T = √eps(T), max_iter :: Int = 1000) where T
  fx = f(x)
  iter = 0
  ϵ = atol + rtol * abs(fx)
  slope_ϵ = (√√eps(T))^3 # ϵₘ^(3/4)
  solved = abs(fx) < ϵ
  tired = iter ≥ max_iter
  while !(solved || tired)
    slope = fder(x)
    if abs(slope) < slope_ϵ
      error("0 derivative at x = $x")
    end
    x -= fx / slope
    fx = f(x)
    iter += 1
    solved = abs(fx) < ϵ
    tired = iter ≥ max_iter
  end
  return x, fx, iter
end
for T in [Float16, Float32, Float64, BigFloat]
  x, fx, iter = newton_parametric(x -> x^2 - 2, x -> 2x, one(T))
  println("√2 ≈ $x")
end
# - Formatted printing is possible with the `Printf` stdlib
# - `@printf` is a macro. Interpolation (`"$x"`) is not possible.
using Printf
for T in [Float16, Float32, Float64, BigFloat]
  x, fx, iter = newton_parametric(x -> x^2 - 2, x -> 2x, one(T))
  @printf("√2 ≈ %20.16e, (√2)² - 2 ≈ %20.16e\n", x, x^2 - 2)
end
# - In the REPL, `?` access the help mode.
# - `apropos` can be used to search among the docstrings
#=
---

## Exercises

1. Given `v` and `w` vectors, verify that `[v w]` and `[v, w]` are different. Notice the type of the second object.
1. Search for "dot product" using the `apropos` function.
1. Create a function that orthogonalizes a _list of vectors_ following the given theorem:
**Theorem**: Given $n$ linear independent vector $v_1, v_2, \dots, v_n$, define
$\displaystyle q_i = v_i - \sum_{j = 1}^{i-1} (v_i \cdot q_j) q_j, \ \forall \ i = 1,\dots,n$.
The vectors $q_1, q_2, \dots, q_n$ are orthogonal.
=#


# ---
# title: 204. Count Primes
# id: problem204
# author: AquaIndigo
# date: 2020-12-03
# difficulty: Easy
# categories: Hash Table, Math
# link: <https://leetcode.com/problems/count-primes/description/>
# hidden: true
# ---
#
# Count the number of prime numbers less than a non-negative number, `n`.
#
#
#
# **Example 1:**
#
#
#
#     Input: n = 10
#     Output: 4
#     Explanation: There are 4 prime numbers less than 10, they are 2, 3, 5, 7.
#
#
# **Example 2:**
#
#
#
#     Input: n = 0
#     Output: 0
#
#
# **Example 3:**
#
#
#
#     Input: n = 1
#     Output: 0
#
#
#
#
# **Constraints:**
#
#   * `0 <= n <= 5 * 106`
#
#
## @lc code=start
using LeetCode

function count_primes(n::Int)
    if n <= 1
        return 0
    end
    arr = fill(true, n - 1)
    for i in 2 : n - 1
        for j in i ^ 2 : i : (n - 1)
            arr[j] = false
        end
    end
    return sum(arr) - 1
end
## @lc code=end


using Symbolics, SymbolicUtils

def_taylor(x, ps) = sum([a*x^i for (i,a) in enumerate(ps)])
def_taylor(x, ps, p₀) = p₀ + def_taylor(x, ps)

function collect_powers(eq, x, ns; max_power=100)
    eq = substitute(expand(eq), Dict(x^j => 0 for j=last(ns)+1:max_power))

    eqs = []
    for i in ns
        powers = Dict(x^j => (i==j ? 1 : 0) for j=1:last(ns))
        push!(eqs, substitute(eq, powers))
    end
    eqs
end

function solve_coef(eqs, ps)
    vals = Dict()

    for i = 1:length(ps)
        eq = substitute(eqs[i], vals)
        vals[ps[i]] = Symbolics.solve_for(eq ~ 0, ps[i])
    end
    vals
end

n = 2
@variables ϵ t y[0:n](t) ∂∂y[0:n]

x = def_taylor(ϵ, y[2:end], y[1])

∂∂x = def_taylor(ϵ, ∂∂y[2:end], ∂∂y[1])

eq = ∂∂x * (1 + ϵ*x)^2 + 1

eqs = collect_powers(eq, ϵ, 0:n)

vals = solve_coef(eqs, ∂∂y)

D = Differential(t)
subs = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]

using ModelingToolkit, DifferentialEquations

sys = ODESystem(eqs, t)
sys = ode_order_lowering(sys)
states(sys)

# the initial conditions
# everything is zero except the initial velocity
u0 = zeros(2n+2)
u0[3] = 1.0   # y₀ˍt

prob = ODEProblem(sys, u0, (0, 3.0))
sol = solve(prob; dtmax=0.01)

X = 𝜀 -> sum([𝜀^(i-1) * sol[y[i]] for i in eachindex(y)])

using Plots

plot(sol.t, hcat([X(𝜀) for 𝜀 = 0.0:0.1:0.5]...))

n = 2
@variables ϵ t y[0:n](t) ∂y[0:n] ∂∂y[0:n]
x = def_taylor(ϵ, y[2:end], y[1])
∂x = def_taylor(ϵ, ∂y[2:end], ∂y[1])
∂∂x = def_taylor(ϵ, ∂∂y[2:end], ∂∂y[1])

eq = ∂∂x + 2*ϵ*∂x + x
eqs = collect_powers(eq, ϵ, 0:n)
vals = solve_coef(eqs, ∂∂y)

D = Differential(t)
subs1 = Dict(∂y[i] => D(y[i]) for i in eachindex(y))
subs2 = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
subs = subs1 ∪ subs2
eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]

sys = ODESystem(eqs, t)
sys = ode_order_lowering(sys)

# the initial conditions
u0 = zeros(2n+2)
u0[3] = 1.0   # y₀ˍt

prob = ODEProblem(sys, u0, (0, 50.0))
sol = solve(prob; dtmax=0.01)

X = 𝜀 -> sum([𝜀^(i-1) * sol[y[i]] for i in eachindex(y)])
T = sol.t
Y = 𝜀 -> exp.(-𝜀*T) .* sin.(sqrt(1 - 𝜀^2)*T) / sqrt(1 - 𝜀^2)    # exact solution

plot(sol.t, [Y(0.1), X(0.1)])


export std_dev, variance, dist, rms, angle, gram_schmidt

function std_dev(x)
  return vecnorm(x - mean(x))/sqrt(length(x))
end

function variance(x)
  return sum((x - mean(x)).^2)/length(x)
end

function dist(x, y)
  return vecnorm(x - y)
end

function rms(x)
  return vecnorm(x)/sqrt(length(x))
end

function angle(x, y)
  return acos(dot(x, y)/(vecnorm(x) * vecnorm(y)))
end

# Runs the Gram-Schmidt algorithm for the given set
# of vectors, returning either an orthonormal basis or
# the index of the first vector which can be written
# as a linear combination of the previous vectors
#
# parameters:
#   a   - An array of column vectors
#
# returns:
#   If 'a' is a set of n linearly independent vectors, then
#   gram_schmidt returns an array n of orthonormal vectors.
#
#   If 'a' is not a set of linearly independent vectors, then
#   gram_schmidt returns an array of m < n vectors.
#
# usage:
#   julia> gram_schmidt({[1;0],[0;1][1;1]})
#   1-element Array{Any,1}:
#   [1.0,0.0]
#
#   julia> gram_schmidt({[1;0;0],[0;1;0],[0;0;1]})
#   3-element Array{Any,1}:
#    [1.0,0.0,0.0]
#    [0.0,1.0,0.0]
#    [0.0,0.0,1.0]
#
function gram_schmidt(A)
  epsilon = 1e-6
  k = length(A)
  n = length(A[1])
  qs = []
  for i = 1:k
    q_i = A[i]
    # orthogonalization with previous vectors
    for j = 1:length(qs)
      q_i -= (qs[j]'A[i]).*qs[j]
    end
    # check for dependence, quit if q_i ~= 0
    if norm(q_i) <= epsilon
      break
    end
    # normalize and store the result
    q_i = q_i/norm(q_i)
    push!(qs,q_i)
  end
  return qs
end


"""
    zero_one_pack!(capacity::N, weights::V, values::V, dp::V) where {N <: Number,V <: AbstractVector}
This does 0-1 (each item can be chosen only once) knapsack :
pack capacity = capacity
weight of each item = weights
value of each item = values
dp array is what the function works on
It returns the ans (dp[capacity])
```
julia> dp=zeros(Int,30)
julia> zero_one_pack!(20,[1,3,11],[2,5,30],dp)
37
```
"""
function zero_one_pack!(
    capacity::N,
    weights::V,
    values::V,
    dp::V,
) where {N<:Number,V<:AbstractVector}
    for i in 1:length(weights)
        j = capacity
        while j > weights[i] # reversed loop
            dp[j] = max(dp[j], dp[j-weights[i]] + values[i])
            j -= 1
        end
        dp[weights[i]] = max(dp[weights[i]], values[i]) # dp[j]=max(dp[j],dp[0]+values[i])
    end
    return dp[capacity]
end

"""
This does complete/infinite (each item can be chosen infinite times) knapsack :
pack capacity = capacity
weight of each item = weights
value of each item = values
dp array is what the function works on
It returns the ans (dp[capacity])
```
julia> dp=zeros(Int,30)
julia> complete_pack!(20,[1,2,9],[1,3,20],dp)
43
```
"""
function complete_pack!(
    capacity::N,
    weights::V,
    values::V,
    dp::V,
) where {N<:Number,V<:AbstractVector}
    for i in 1:length(weights)
        dp[weights[i]] = max(dp[weights[i]], values[i]) # dp[j]=max(dp[j],dp[0]+values[i])
        for j in (weights[i]+1):capacity
            dp[j] = max(dp[j], dp[j-weights[i]] + values[i])
        end
    end
    return dp[capacity]
end


# # Entropy Maximization
#
# Here is a constrained entropy maximization problem:
#
# $$
# \begin{array}{ll}
#     \text{maximize}   & -\sum_{i=1}^n x_i \log x_i \\
#     \text{subject to} & \mathbf{1}' x = 1 \\
#                   & Ax \leq b
# \end{array}
# $$
#
# where $x \in \mathbf{R}^n$ is our optimization variable and $A \in \mathbf{R}^{m \times n}, b \in \mathbf{R}^{m}$.
#
# To solve this, we can simply use the `entropy` operation Convex.jl provides.

using Convex, SCS

n = 25;
m = 15;
A = randn(m, n);
b = rand(m, 1);

x = Variable(n);
problem = maximize(entropy(x), sum(x) == 1, A * x <= b)
solve!(problem, SCS.Optimizer(verbose=false))
problem.optval

#-

x.value


using Plots
using DifferentialEquations

function sirmodel(du, u, p, t)
    S = u[1]
    I = u[2]
    R = u[3]
    dS = -p[1]*S*I
    dI = p[1]*S*I-p[2]*I
    dR = p[2]*I
    du[1] = dS
    du[2] = dI
    du[3] = dR
end

function solveSIR(N, β, γ)

    tspan = (0.0, 160.0)
    u0 = [N-1.0, 1.0, 0.0]
    p = [β, γ]

    prob = ODEProblem(sirmodel, u0, tspan, p)

    sol = solve(prob)

    return sol
end

R0=2.0
N=10000
γ=0.1
β=γ*R0/N

sol = solveSIR(N, β, γ)

plot_graph = plot(sol, vars=(0,1), title="SIR model/Runge-Kutta R0=$R0 γ=$γ", label="Suscepible",
                  color=:blue, seriestype=:line, legend=:left, size=(1200,900))
plot!(plot_graph, sol, vars=(0,2), label="Infectious", color=:red)
plot!(plot_graph, sol, vars=(0,3), label="Removed", color=:green)

png(plot_graph, "plot_0.png")


module NewtonsMethod

using LinearAlgebra
using ForwardDiff

function newtonroot(f, f′; x₀, tol = 1E-7, maxiter = 1000)
    x_old = x₀
    normdiff = Inf
    n = 1

    while normdiff > tol && n <= maxiter
        x_new = x_old  - f(x_old)/f′(x_old)
        normdiff = norm(x_new - x_old)
        x_old = x_new

        if(n == maxiter && normdiff > tol)
            return nothing
        end
        n += 1
    end

    x_root = x_old
    return x_root
end

function newtonroot(f; x₀, tol = 1E-7, maxiter = 1000)
    f′ = x -> ForwardDiff.derivative(f, x)
    x_root = newtonroot(f, f′; x₀, tol, maxiter)
    return x_root
end

export newtonroot

end


# This file is a part of Julia. License is MIT: https://julialang.org/license

## Test case from Issue #445
##
## Added threaded implementation (2015-04-06)

using Base.Threads

#STOCKCORR - The original, unoptimised code that simulates two correlated assets
function stockcorr()
    ## Correlated asset information
    CurrentPrice = [78. 102.]     # Initial Prices of the two stocks
    Corr = [1. 0.4; 0.4 1.]       # Correlation Matrix
    T = 500                       # Number of days to simulate = 2years = 500days
    n = 1000000                   # Number of simulations
    dt = 1/250                    # Time step (1year = 250days)
    Div=[0.01 0.01]               # Dividend
    Vol=[0.2 0.3]                 # Volatility

    ## Market Information
    r = 0.03                      # Risk-free rate

    ## Define storages
    SimulPriceA = zeros(T,n)      # Simulated Price of Asset A
    SimulPriceA[1,:] = CurrentPrice[1]
    SimulPriceB = zeros(T,n)      # Simulated Price of Asset B
    SimulPriceB[1,:] = CurrentPrice[2]

    ## Generating the paths of stock prices by Geometric Brownian Motion
    UpperTriangle=chol(Corr)      # UpperTriangle Matrix by Cholesky decomposition

    for i = 1:n
       Wiener = randn(T-1,2)
       CorrWiener = Wiener*UpperTriangle
       for j = 2:T
          SimulPriceA[j,i] = SimulPriceA[j-1,i]*exp((r-Div[1]-Vol[1]^2/2)*dt+Vol[1]*sqrt(dt)*CorrWiener[j-1,1])
          SimulPriceB[j,i] = SimulPriceB[j-1,i]*exp((r-Div[2]-Vol[2]^2/2)*dt+Vol[2]*sqrt(dt)*CorrWiener[j-1,2])
       end
    end

    return (SimulPriceA, SimulPriceB)
end

# Run paths in parallel
# NOTE: this has to be in its own function due to #10718
function runpath!(n, Wiener, CorrWiener, SA, SB, T, UpperTriangle, k11, k12, k21, k22, rngs)
    @threads for i = 1:n
    #for i = 1:n
        randn!(rngs[threadid()], Wiener)
        #randn!(rngs[1], Wiener)
        A_mul_B!(CorrWiener, Wiener, UpperTriangle)
        @simd for j = 2:T
            @inbounds SA[j, i] = SA[j-1, i] * exp(k11 + k12*CorrWiener[j-1, 1])
            @inbounds SB[j, i] = SB[j-1, i] * exp(k21 + k22*CorrWiener[j-1, 2])
        end
    end
end

#precompile(runpath!, (Int64, Array{Float64,2}, Array{Float64,2}, Array{Float64,2}, Int64, Array{Float64, 2}, Float64, Float64, Float64, Float64))

# Threaded version
function pstockcorr(n)
    ## Correlated asset information
    const CurrentPrice = [78. 102.]     # Initial Prices of the two stocks
    const Corr = [1. 0.4; 0.4 1.]       # Correlation Matrix
    const T = 500                       # Number of days to simulate = 2years = 500days
    const dt = 1/250                    # Time step (1year = 250days)
    const Div=[0.01 0.01]               # Dividend
    const Vol=[0.2 0.3]                 # Volatility

    ## Market Information
    const r = 0.03                      # Risk-free rate

    ## Define storages
    SimulPriceA = zeros(T,n)            # Simulated Price of Asset A
    SimulPriceA[1,:] = CurrentPrice[1]
    SimulPriceB = zeros(T,n)            # Simulated Price of Asset B
    SimulPriceB[1,:] = CurrentPrice[2]

    ## Generating the paths of stock prices by Geometric Brownian Motion
    const UpperTriangle = full(chol(Corr))    # UpperTriangle Matrix by Cholesky decomposition

    # Optimization: pre-allocate these for performance
    # NOTE: the new GC will hopefully fix this, but currently GC time
    # kills performance if we don't do in-place computations
    Wiener = Array{Float64}(T-1, 2)
    CorrWiener = Array{Float64}(T-1, 2)

    # Runtime requirement: need per-thread RNG since it stores state
    rngs = [MersenneTwister(777+x) for x in 1:nthreads()]

    # Optimization: pre-computable factors
    # NOTE: this should be automatically hoisted out of the loop
    # NOTE: applied this to the MATLAB version as well
    k11 = (r-Div[1]-Vol[1]^2/2)*dt
    k12 = Vol[1]*sqrt(dt)
    k21 = (r-Div[2]-Vol[2]^2/2)*dt
    k22 = Vol[2]*sqrt(dt)

    # NOTE: this reduces reported GC allocation to 576 bytes (due to?)
    #runpath!(1, Wiener, CorrWiener, SimulPriceA, SimulPriceB, T, UpperTriangle, k11, k12, k21, k22, rngs)

    # run
    @time runpath!(n, Wiener, CorrWiener, SimulPriceA, SimulPriceB, T, UpperTriangle, k11, k12, k21, k22, rngs)

    return (SimulPriceA, SimulPriceB)
end

@time pstockcorr(1000000)
#ccall(:jl_threading_profile, Void, ())


using Optim
using LinearAlgebra: I, det, tr

# Rosenbrock
f(x) = (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
x0 = [0.0, 0.0]
# optimize(f, x0, LBFGS(), autodiff = :forward)
# optimize(f, x0, LBFGS(), autodiff = :forward)

# analytical gradient
function g!(G, x)
    G[1] = -2.0 * (1.0 - x[1]) - 400.0 * (x[2] - x[1]^2) * x[1]
    G[2] = 200.0 * (x[2] - x[1]^2)
end
res = optimize(f, g!, x0)

@show summary(res)
@show Optim.minimizer(res)
@show Optim.minimum(res)
@show Optim.iterations(res)
@show Optim.iteration_limit_reached(res)
@show Optim.f_calls(res)
@show Optim.converged(res)

#
# # analytical Hessian
# function h!(H, x)
#     H[1, 1] = 2.0 - 400.0 * x[2] + 1200.0 * x[1]^2
#     H[1, 2] = -400.0 * x[1]
#     H[2, 1] = -400.0 * x[1]
#     H[2, 2] = 200.0
# end
# optimize(f, g!, h!, x0, LBFGS())
#
lower = [-0.95, -2.1]
upper = [Inf, Inf]
initial_x = [0.0, 0.0]
# # requires using LineSearches
inner_optimizer = GradientDescent()
# res = optimize(f, g!, lower, upper, initial_x, Fminbox(inner_optimizer))

res = optimize(f, lower, upper, initial_x)

@show summary(res)
@show Optim.minimizer(res)
@show Optim.minimum(res)
@show Optim.iterations(res)
@show Optim.iteration_limit_reached(res)
@show Optim.f_calls(res)
@show Optim.converged(res)

# function fMat(Xm::Matrix{Float64})
#     # Xm = reshape(X,(3,3))
#     return det((Xm-I) * (Xm-I))
# end
# X0 = reshape(collect(1:1.0:9.0), (3,3))
# optimize(fMat, X0, LBFGS())
#
# function sq(x)
#     return x^2
# end
#
# function get_fn(a, b)::Function
#     function calc_fn(x::Vector{Float64})
#         return sum((x .* sq(a) .+ b).^2)
#     end
#     return calc_fn
# end
#
# f = get_fn(2.0,3.0)
# res = optimize(f, [1.0, 2.0], LBFGS())


using CSV, Distributions, HypothesisTests

data1 = CSV.read("../data/machine1.csv", header=false)[:,1]
data2 = CSV.read("../data/machine2.csv", header=false)[:,1]
xBar1, s1, n1 = mean(data1), std(data1), length(data1)
xBar2, s2, n2 = mean(data2), std(data2), length(data2)
delta0 = 0

sP = sqrt( ( (n1-1)*s1^2 + (n2-1)*s2^2 ) / (n1 + n2 - 2) )
testStatistic = ( xBar1-xBar2 - delta0 ) / ( sP * sqrt( 1/n1 + 1/n2) )
pVal = 2*ccdf(TDist(n1+n2 -2), abs(testStatistic))

println("Manually calculated test statistic: ", testStatistic)
println("Manually calculated p-value: ", pVal, "\n")
println(EqualVarianceTTest(data1, data2, delta0))


#=
Problem 4
A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.
Find the largest palindrome made from the product of two 3-digit numbers.
=#

function isPal(x)
  s = string(x)
  l = length(s)
  for i=1:l
    if (s[i] != s[l-i+1])
      return false
    end
  end
  return true
end

maxPal = 0
p = 0
for i=1:999, j=1:999
  p = i*j
  if (isPal(p) && p > maxPal)
    maxPal = p
  end
end

println("The largest palindrome is: $(maxPal)")


# It'st most appropriate to define a Julia iterable object for this task
# Julia doesn't have Python'st yield, the closest to it is produce/consume calls with Julia tasks
# but for various reasons they don't work out for this task
# This solution works with two integers, a Julia rational or a real

mutable struct ContinuedFraction{T<:Integer}
    n1::T # numerator or real
    n2::T # denominator or 1 if real
    t1::T # generated coefficient
end

# Constructors for all possible input types
ContinuedFraction{T<:Integer}(n1::T, n2::T) = ContinuedFraction(n1, n2, 0)
ContinuedFraction(n::Rational) = ContinuedFraction(numerator(n), denominator(n))
ContinuedFraction(n::AbstractFloat) = ContinuedFraction(Rational(n))

# Methods to make our object iterable
Base.start(::ContinuedFraction) = nothing
# Returns true if we've prepared the continued fraction
Base.done(cf::ContinuedFraction, st) = cf.n2 == 0
# Generates the next coefficient
function Base.next(cf::ContinuedFraction, st)
    cf.n1, (cf.t1, cf.n2) = cf.n2, divrem(cf.n1, cf.n2)
    return cf.t1, nothing
end

# Tell Julia that this object always returns ints (all coeffs are integers)
Base.eltype{T}(::Type{ContinuedFraction{T}}) = T

# Overload the default collect function so that we can collect the first maxiter coeffs of infinite continued fractions
# array slicing doesn't work as Julia crashes before the slicing due to our infinitely long array
function Base.collect(itr::ContinuedFraction, maxiter::Integer = 100)
    r = Array{eltype(itr)}(maxiter)
    i = 1
    for v in itr
        r[i] = v
        i += 1
        if i > maxiter break end
    end
    return r[1:i-1]
end

# Test cases according to task description with outputs in comments
println(collect(ContinuedFraction(1, 2)))       # => [0, 2]
println(collect(ContinuedFraction(3, 1)))       # => [3]
println(collect(ContinuedFraction(23, 8)))      # => [2, 1, 7]
println(collect(ContinuedFraction(13, 11)))     # => [1, 5, 2]
println(collect(ContinuedFraction(22, 7)))      # => [3, 7]
println(collect(ContinuedFraction(14142, 10000)))       # => [1, 2, 2, 2, 2, 2, 1, 1, 29]
println(collect(ContinuedFraction(141421, 100000)))     # => [1, 2, 2, 2, 2, 2, 2, 3, 1, 1, 3, 1, 7, 2]
println(collect(ContinuedFraction(1414214, 1000000)))   # => [1, 2, 2, 2, 2, 2, 2, 2, 3, 6, 1, 2, 1, 12]
println(collect(ContinuedFraction(14142136, 10000000))) # => [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 1, 2, 4, 1, 1, 2]

println(collect(ContinuedFraction(13 // 11)))   # => [1, 5, 2]
println(collect(ContinuedFraction(√2), 20))     # => [1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]


function lufac(a::Matrix{Float64})
  upper = zeros(size(a))
  upper[1,:] = a[1,:]
  lower = Matrix{Float64}(I, size(a,1), size(a,1))
  n = size(a, 1)
  for k in 1:n-1
    if abs(upper[k,k] > 1e-10)
      for i in k+1:n
        # L component
        for j in 1:i-1
          total = 0.0
          for l in 1:j-1
            total = total - lower[i, l] * upper[l, j]
          end
          lower[i, j] = (a[i, j] + total)/upper[j, j]
        end
        # U component
        for j in 1:n
          total = 0.0
          for l in 1:i-1
            total = total - lower[i, l] * upper[l, j]
          end
          upper[i, j] = a[i, j] +total
        end
      end
    else
      println("Zero pivot found in row $k")
    end
  end
  (lower, upper)
end


"""
    E = kepler(M, e; atol=1e-12, maxit=1000)

Solves [Kepler's equation](https://en.wikipedia.org/wiki/Kepler%27s_equation) for eccentric anomaly or
hyperbolic anomaly using Newton's method.
"""
function kepler(M::Real, e::Real; atol = 1e-12, maxit = 1000)
    if e == 1           # Parabolic case
        throw(ArgumentError("Parabolic case (e=1) has not been implemented"))
    elseif e < 1        # Elliptical case
        # # from Seppo Mikkola (slower, but better initial guess)
        # α = (1-e)/(4*e+0.5)
        # β = 0.5*M/(4*e+0.5)
        # z = copysign((abs(β) + √(β^2 + α^3))^(1/3), β)
        # s = z - α/z
        # s = s - 0.078*s^5/(1+e)
        # E = M + e*(3*s - 4*s^3)
        E = M
        # iterate until convergence
        err = 1+atol
        it = 1
        while (err > atol) && (it < maxit)
            it = it + 1
            Ep = E
            errp = err
            E = E - (E - e*sin(E) - M)/(1-e*cos(E))       # Newton's method
            # f = E - e*sin(E) - M
            # df = 1 - e*cos(E)
            # ddf = e*sin(E)
            # E = E - 2*f*df/(2*df^2 - f*ddf)               # Halley's method (slower, no accuracy improvement)
            err = abs(E-Ep)
            # if error gets worse, use bisection (makes up for poor initial guess)
            if err > errp
                E = (E+Ep)/2
            end
        end
        if it == maxit
            @warn "Kepler solver failed to converge: err = $err"
        end
        return E
    else                # Hyperbolic case
        if abs(M) > 4π
            H = sign(M) * 4π
        else
            H = M
        end
        err = 1+atol
        it = 1
        while (err > atol) && (it < maxit)
            it = it + 1
            Hp = H
            errp = err
            H = H + (M - e*sinh(H) + H)/(e*cosh(H) - 1)     # Newton's method
            err = abs(H-Hp)
            # if error gets worse, use bisection (makes up for poor initial guess)
            if err > errp
                H = (H+Hp)/2
            end
        end
        if it == maxit
            @warn "Kepler solver failed to converge: err = $err"
        end
        return H
    end
end


# okaty, this is the code from the sequential decision problem that stumped milton friedman, frmo here https://lectures.quantecon.org/jl/wald_friedman.html

using Distributions
using QuantEcon.compute_fixed_point, QuantEcon.DiscreteRV, QuantEcon.draw, QuantEcon.LinInterp
using Plots
pyplots() #can't remember if thi scauses issues outside of jupyter notebook
using LateXStrings

#okay, our los functions

# for given probability return expected los of choosing model 0

function expect_loss_choose_0(p::Real, L0::Real)
	return (1-p)*L0
end

#for given probability return expected lsos of choosing model 1

function expect_loss_choose_1(p::Real, L1::Real)
	return p*L1
end

#we need to evaluate  the expectation of our bellman equation J. so we need current probability that model 0 is correct, distributions are f0, f1, and a function that can evaluate bellman equatoin

function EJ(p::Real, f0::AbstractVector, f1::AbstractVector, J::LinInterp)
	#get current distribution we believe - i.e p*fo + 1-p*f1

	curr_dist = p*f0 + (1-p)*f1

	#get tomorrows expected distribution through bayes law
	tp1_dist = clamp.((p*f0) ./(p*f0 (1-p)*f1), 0,1)

	#evaluate the expectation
	EJ = dot(curr_dist, J.tp1_dist))
	return EJ
end

# evaluate valiye function for given continuation value

function expect_loss_cont(p::Real, c::Real, f0::AbstractVector, f1::AbstractVector, j::LinInterp)
	return c+EJ(p, f0, f1,J)
end

function bellman_operator(pgrid::AbstractVector, c::Real, f0::AbstractVector, f1::AbstractVector, L0::Real, L1::Real, J::AbstractVector)
	m = length(pgrid)
	@assert m == length(J)

	J_out = zeros(m)
	j_interp = LinInterp(pgrid, J)

	for (p_ind, p) in enumerate(pgrid)
		#payoff of chooseing model 0
		p_c_0 = expect_loss_choose_0(p, L0)
		p_c_1 expect_loss_choose_1(p,L1)
		p_con = expect_loss_cont(p,c,f0,f1,J_interp)

		J_out[p_ind] = min(p_c_0, p_c_1, p_con)
	end
	return J_out
end

# this gets us through the bellman equatino, no wwe just create two istributions over 50 values for k usingdiscrete beta distribution

p_m1 = linspace(0,1,50)
f0 = clamp.(pdf(Beta(1,1),p_m1),1e-8, Inf)
f0 = f0/sum(f0)
f1 = clam.(pdf(Beta(9,9), p_m1), 1e-8, Inf)
f1 = f1/sum(f1)

# to solve
pg = linspace(0,1,251)
J1 = compute_fixed_point(x->bellman_operator(pg, 0.5, f0,f1,5.0,5.0,x), zeros(length(pg)), err_tol=1e-6, print_skip = 5)

# okay, sorted, that's kind of crazy to be honest, but it might or might not work. dagnabbit julia. good ol' solving stuff marting freedman couldn't. there's a better implementation also with more comments and so forth, and we'll do that in a bit. I also need to do my actual phd work. But I kind of understand how this works now, and how powerful bellmans and dynamic programming isfor simple things like this!


module LossFunction

using LinearAlgebra
using Statistics

LOSSES = [
    :mse, :cee, :mae, :huber, :logcosh_loss, :poisson, :hinge, :smooth_hinge
]

for f in LOSSES
    @eval export $(f)
end

safe_log(x, miniv = 1e-8) = map(log, clamp.(x, miniv, Inf))

@doc raw"""
    mse(y, t; reduction="mean")
Mean Square Error. This is the expression:
```math
MSE(y, t) = \frac{\sum_{i=1}^{n} (t_{i}-y_{i})^{2}}{n}
```
"""
function mse(y::AbstractVector, t::AbstractVector; reduction::String="mean")
    loss = (y.-t).^2
    if reduction=="none"
        return loss
    elseif reduction=="sum"
        return sum(loss)
    elseif reduction=="mean"
        return mean(loss)
    else
        throw(ArgumentError("`reduction` must be either `none`, `sum` or `mean`"))
    end
end
mse(y::Number, t::Number) = (y-t)^2

@doc raw"""
    cee(y, t; reduction="mean")
Cross Entropy Error. This is the expression:
```math
CEE(y, t) = \frac{\sum_{i=1}^{n} t\ln y}{n}
```
"""
function cee(y::AbstractVector, t::AbstractVector; reduction::String="mean")
    loss = -t.*safe_log(y)
    if reduction=="none"
        return loss
    elseif reduction=="sum"
        return sum(loss)
    elseif reduction=="mean"
        return mean(loss)
    else
        throw(ArgumentError("`reduction` must be either `none`, `sum` or `mean`"))
    end
end

cee(y::Number, t::Number) = -t*safe_log(y)

@doc raw"""
    mae(y, t)
Mean Absolute Error. This is the expression:
```math
MAE(y, t) = \frac{\sum_{i=1}^{n} |t_{i}-y_{i}|}{n}
```
"""
function mae(y::AbstractVector, t::AbstractVector; reduction::String="mean")
    loss = abs.(t-y)
    if reduction=="none"
        return loss
    elseif reduction=="sum"
        return sum(loss)
    elseif reduction=="mean"
        return mean(loss)
    else
        throw(ArgumentError("`reduction` must be either `none`, `sum` or `mean`"))
    end
end

mae(y::Number, t::Number) = abs(t-y)

@doc raw"""
    huber(y, t; δ=1, reduction="mean")
Huber-Loss. If `δ` is large, it will be a function like [`mse`](@ref), and if it is small, it will be a function like [`mae`](@ref). This is the expression:
```math
a = |t_{i}-y_{i}| \\
Huber(y, t) = \frac{1}{n} \sum_{i=1}^{n} \left\{
\begin{array}{ll}
\frac{1}{2}a^{2} & (a \leq \delta) \\
\delta(a-\frac{1}{2}\delta) & (a \gt \delta)
\end{array}
\right.
```
"""
function huber(y::AbstractVector, t::AbstractVector; reduction::String="mean", δ=1)
    a = abs.(t-y)
    loss = @. ifelse(a<=δ, a^2/2, (a-δ/2)δ)
    if reduction=="none"
        return loss
    elseif reduction=="sum"
        return sum(loss)
    elseif reduction=="mean"
        return mean(loss)
    else
        throw(ArgumentError("`reduction` must be either `none`, `sum` or `mean`"))
    end
end

huber(y::Number, t::Number; δ=1) = ifelse(abs(t-y)<=δ, abs(t-y)^2/2, (abs(t-y)-δ/2)δ)

@doc raw"""
    logcosh_loss(y, t; reduction="mean")
Log Cosh. Basically, it's [`mae`](@ref), but if the loss is small, it will be close to [`mse`](@ref). This is the expression:
```math
Logcosh(y, t) = \frac{\sum_{i=1}^{n} \log(\cosh(t_{i}-y_{i}))}{n}
```
"""
function logcosh_loss(y::AbstractVector, t::AbstractVector; reduction::String="mean")
    loss = @. log(cosh(t-y))
    if reduction=="none"
        return loss
    elseif reduction=="sum"
        return sum(loss)
    elseif reduction=="mean"
        return mean(loss)
    else
        throw(ArgumentError("`reduction` must be either `none`, `sum` or `mean`"))
    end
end

logcosh_loss(y::Number, t::Number) = log(cosh(t-y))

@doc raw"""
    Poisson(y, t; reduction="mean")
Poisson Loss, Distribution of predicted value and loss of Poisson distribution. This is the expression:
```math
Poisson(y, t) = \frac{\sum_{i=1}^{n} y_{i}-t_{i} \ln y}{n}
```
"""
function poisson(y::AbstractVector, t::AbstractVector; reduction::String="mean")
    loss = @. y - t*log(y)
    if reduction=="none"
        return loss
    elseif reduction=="sum"
        return sum(loss)
    elseif reduction=="mean"
        return mean(loss)
    else
        throw(ArgumentError("`reduction` must be either `none`, `sum` or `mean`"))
    end
end

poisson(y::Number, t::Number) = y - t*log(y)

@doc raw"""
    hinge(y, t; reduction="mean")
Hinge Loss, for SVM. This is the expression:
```math
Hinge(y, t) = \frac{\sum_{i=1}^{n} \max(1-y_{i}t_{i}, 0)}{n}
```
"""
function hinge(y::AbstractVector, t::AbstractVector; reduction::String="mean")
    loss = @. max(1-y*t, 0)
    if reduction=="none"
        return loss
    elseif reduction=="sum"
        return sum(loss)
    elseif reduction=="mean"
        return mean(loss)
    else
        throw(ArgumentError("`reduction` must be either `none`, `sum` or `mean`"))
    end
end

hinge(y::Number, t::Number) = max(1-y*t, 0)

@doc raw"""
    smooth_hinge(y, t; reduction="mean")
Smoothing Hinge Loss. This is the expression:
```math
smoothHinge(y, t) = \frac{1}{n} \sum_{i=1}^{n} \left\{
\begin{array}{ll}
0 & (t_{i}y_{i} \geq 1) \\
\frac{1}{2}(1-t_{i}y_{i})^{2} & (0 \lt t_{i}y_{i} \lt 1) \\
\frac{1}{2} - t_{i}y_{i} & (t_{i}y_{i} \leq 0)
\end{array}
\right.
```
"""
function smooth_hinge(y::AbstractVector, t::AbstractVector; reduction::String="mean")
    z = t.*y
    loss = similar(z)
    for i in 1 : length(y)
        if z[i] >= 0
            loss[i] = 0
        elseif 0 < z[i] < 1
            loss[i] = (1-z[i])^2/2
        else
            loss[i] = 1/2-z[i]
        end
    end
    if reduction=="none"
        return loss
    elseif reduction=="sum"
        return sum(loss)
    elseif reduction=="mean"
        return mean(loss)
    else
        throw(ArgumentError("`reduction` must be either `none`, `sum` or `mean`"))
    end
end

function smooth_hinge(y::Number, t::Number)
    if t*y >= 0
        return 0
    elseif 0 < t*y < 1
        return (1-t*y)^2/2
    else
        return 1/2-t*y
    end
end

#TODO:Consider whether to implement Calback liverer information volume

for lossfunc in LOSSES
    #For Vector and Number
    @eval begin
        function $(lossfunc)(y::AbstractVector{T}, t::Number; reduction::String="mean") where {T}
            $(lossfunc)(y, fill(t, length(y)), reduction=reduction)
        end
    end
    #For Matrix and Number
    @eval begin
        function $(lossfunc)(y::AbstractMatrix{T}, t::Number; reduction::String="mean") where {T}
            if length(y) == 1
                return $(lossfunc)(y..., t)
            elseif size(y, 1)==1 || size(y, 2)==1
                return $(lossfunc)(vec(y), t, reduction=reduction)
            else
                throw(DimensionMismatch("LossFunctions don't support for Matrix!"))
            end
        end
    end
end

end


using LinearAlgebra
using Printf

include("back_substitution.jl")
include("qr_factorization.jl")

"""
Finds UV decomposition of rank k that minimizes f(U_i,V_i) = ||A - U_iV_i||_f by alternating minimization

# Arguments
- `A::Array{Float64,2}`: matrix of size m x n.
- `k::Integer`: the rank of the UV decomposition. k must be <= rank(A).
- `initV::Array{Float64,2}=rand(k, size(A)[2])`: initial V_0 from which the algorithm starts. V_0 must be full rank.
- `min_error::Integer=1e-8`: the algorithm will stop if f(U_i,V_i) - f(U_i+1,V_i+1) <= min_error.
- `max_iterations::Integer=100`: the algorithm will perform at most max iteration steps.
- `print_error::Bool=true`: if it is true, the algorithm will print the error at every iteration.
- `save errors::Bool=false`: if it is true, the algorithm will return an array containing the history of the errors, besides U and V
- `opt::Array{Float64,2}=A`: optimal factorization of rank k used to compute the residuals if save_errors is true. (Used only for tests)

# Return
- `U::Array{Float64,2}`: matrix of size m x k.
- `V::Array{Float64,2}`: matrix of size k x n.
- `errors::Array{Float64,1}`: only if print_error is true.
- `residuals::Array{Float64,1}`: only if print_error is true.
"""
function cresetqr(A, k; initV = rand(k, size(A)[2]), min_error=1e-8, max_iterations=100, print_error=true, save_errors=false, opt=A)
    (m, n) = size(A)

    # allocating matrices U and V in which the program will allocate the results
    U = zeros(m, k)
    V = zeros(k,n)
    V .= initV

    num_step = 0

    # number of iterations the algorithm have perfomed
    num_steps = 1

    # allocating matrices in which store QR factorization and back_substitution results
    QR_V, v_V, u_V = allocate_matrices(V')
    QR_U, v_U, u_U = allocate_matrices(U)
    W_V = zeros(n, m)
    Q_U = zeros(m, k)

    # computing the initial function value
    f = norm(A - U * V, 2)

    # allocating vectors in which store the error and the residuals during the algorithm execution
    if save_errors
        errors = zeros(max_iterations)
        residuals = zeros(max_iterations)
    end

    if print_error
        println("step\t ||A - UV||_f\t ||f_old-f||")
    end

    @views @inbounds for num_step = 1:max_iterations
        # gradient = [U*V*V' - A*V', U'*U*V - U'*A]

        # Q,R = qr(V')
        qr_factorization!(V', QR_V, v_V, u_V)

        # taking the R factor
        R = triu(QR_V)[1:k,:]

        # W = Q' * A'
        Q_t_times_A!(QR_V, A', W_V)

        # R * U' = W_V by back substitution
        back_substitution!(R, U', W_V[1:k,1:m])

        # Q,R = qr(U)
        qr_factorization!(U, QR_U, v_U, u_U)

        # taking the R factor
        R = triu(QR_U)[1:k,:]

        # taking the Q factor
        get_Q!(QR_U, Q_U)

        # R * V = Q_U' * A by back substitution
        back_substitution!(R, V, Q_U' * A)

        #additional steps for CRESETQR (w.r.t. RESETQR)
        U .= Q_U[:,1:k]
        V .= R * V

        #computing new function value
        f_old = f
        f = norm(A - U * V, 2)
        delta = norm(f_old - f, 2)

        if print_error
            @printf("%d)\t%.13f\t%.13f\n",num_step,f,delta)
        end

        #saving function value and residual
        if save_errors
            errors[num_step] = f
            residuals[num_step] = norm(U * V - opt)
            num_steps = num_step
        end

        #Stopping criteria check
        if delta <= min_error
            if print_error
                println("Reached good approximation with ", num_step, " iterations")
            end
            break
        end
    end

    if save_errors
        return U, V, errors[1:num_steps], residuals[1:num_steps]
    else
        return U, V
    end
end


# Simulate an inhomogeneous Poisson point process on a rectangle
# This is done by simulating a homogeneous Poisson process, which is then
# thinned according to a (spatially *dependent*) p-thinning.
# The intensity function is lambda(x,y)=exp(-(x^2+y^2)/s^2), where s>0.
# Author: H. Paul Keeler, 2019.
# Website: hpaulkeeler.com
# Repository: github.com/hpaulkeeler/posts
# For more details, see the post:
# hpaulkeeler.com/testing-the-julia-language-with-point-process-simulations/

#Note: Need the .+ for adding a scalar to an array
#Also need . for sqrt, exp, cos, sin etc and assinging scalars to arrays
#Best to use vectors instead of 1-D matrices eg x=rand(n),  NOT x=rand(n,1).

using Distributions #for random simulations
using Plots #for plotting
using BlackBoxOptim #for blackbox optimizing

#Simulation window parameters
xMin=-1;xMax=1;
yMin=-1;yMax=1;
xDelta=xMax-xMin;yDelta=yMax-yMin; #rectangle dimensions
areaTotal=xDelta*yDelta;

s=0.5; #scale parameter

#Point process parameters
function fun_lambda(x,y)
    100*exp.(-(x.^2+y.^2)/s^2); #intensity function
end

###START -- find maximum lambda -- START ###
#For an intensity function lambda, given by function fun_lambda,
#finds the maximum of lambda in a rectangular region given by
#[xMin,xMax,yMin,yMax].
#NOTE: Need xMin, xMax, yMin, yMax to be floats eg xMax=1. See boundSearch

function fun_Neg(x)
     -fun_lambda(x[1],x[2]); #negative of lambda
end
xy0=[(xMin+xMax)/2.0,(yMin+yMax)/2.0];#initial value(ie centre)

#Find largest lambda value
boundSearch=[(1.0xMin,1.0xMax), (1.0yMin, 1.0yMax)];
#WARNING: Values of boundSearch cannot be integers!
resultsOpt=bboptimize(fun_Neg;SearchRange = boundSearch);
lambdaNegMin=best_fitness(resultsOpt); #retrieve minimum value found by bboptimize
lambdaMax=-lambdaNegMin;
###END -- find maximum lambda -- END ###

#define thinning probability function
function fun_p(x,y)
    fun_lambda(x,y)/lambdaMax;
end

#Simulate a Poisson point process
numbPoints=rand(Poisson(areaTotal*lambdaMax)); #Poisson number of points
xx=xDelta*rand(numbPoints).+xMin;#x coordinates of Poisson points
yy=yDelta*(rand(numbPoints)).+yMin;#y coordinates of Poisson points

#calculate spatially-dependent thinning probabilities
p=fun_p(xx,yy);
#Generate Bernoulli variables (ie coin flips) for thinning
booleRetained=rand(numbPoints,1).<p; #points to be retained
xxRetained=xx[booleRetained]; yyRetained=yy[booleRetained];

#Plotting
plot1=scatter(xxRetained,yyRetained,xlabel ="x",ylabel ="y", leg=false);
display(plot1);


function fdd13!{T<:Number}(df::AbstractVector{T},f::AbstractVector{T},x::AbstractVector{T})
    # first derivative using a three point stencil
    npts=length(x)
    for i = 1:npts
        if i == 1
            df2=f[i+1]-f[i]
            df3=f[i+2]-f[i]
            h2=x[i+1]-x[i]
            h3=x[i+2]-x[i]
            df[i]=df2/h2 + (df3-df2)/(h2-h3) + df3/h3
        elseif i == npts
            df1=f[i-1]-f[i]
            df2=f[i-2]-f[i]
            h1=x[i-1]-x[i]
            h2=x[i-2]-x[i]
            df[i]=df1/h1 + (df2-df1)/(h1-h2) + df2/h2
        else
            df1=f[i-1]-f[i]
            df3=f[i+1]-f[i]
            h1=x[i-1]-x[i]
            h3=x[i+1]-x[i]
            df[i]=df1/h1 + (df3-df1)/(h1-h3) + df3/h3
        end
    end
end

function fdd23!{T<:Number}(df::AbstractVector{T},f::AbstractVector{T},x::AbstractVector{T})
    # first derivative using a three point stencil
    npts=length(x)
    for i = 1:npts
        if i == 1
            df[i] = 2((f[i+2]-f[i+1])/(x[i+2]-x[i+1])-(f[i+1]-f[i])/(x[i+1]-x[i]))/(x[i+2]-x[i])
        elseif i == npts
            df[i] = 2((f[i]-f[i-1])/(x[i]-x[i-1])-(f[i-1]-f[i-2])/(x[i-1]-x[i-2]))/(x[i]-x[i-2])
        else
            df[i] = 2((f[i+1]-f[i])/(x[i+1]-x[i])-(f[i]-f[i-1])/(x[i]-x[i-1]))/(x[i+1]-x[i-1])
        end
    end
end

function fdd1n!{T<:Number}(df::AbstractVector{T},f::AbstractVector{T},x::AbstractVector{T},order)
    # first derivative using a three point stencil
    npts=length(x)
    for i = 1:npts
        i1    = min(max(1,i-div(order-1,2)),npts-order+1)
        j     = i-i1+1          # i=1 => i1=1 => j=1; i=npts => i1=npts-order+1 => j = order
        df[i] = fdd1n_point(f,x,i1-1,j,order)
    end
end

function fdd1n_point{T}(f::AbstractVector{T},x::AbstractVector{T},shift,j,n)
    df=zero(T)
    for i=1:n
        coef=one(T)
        if i!=j
            for k=1:n
                if k!=i
                    coef /= x[i+shift]-x[k+shift]
                end
                if k!=i && k!=j
                    coef *= x[j+shift]-x[k+shift]
                end
            end
            df += coef*(f[i+shift]-f[j+shift])
        end
    end
    return df
end

# these functions don't work well on nonuniform meshes
function fdd15!(df,f,x)
    for i = 1:length(df)
        f_ξ = fdd15_uniform(f,i)
        x_ξ = fdd15_uniform(x,i)
        df[i] = f_ξ/x_ξ
    end
end

function fdd25!(df,f,x)
    for i = 1:length(df)
        x_ξ  = fdd15_uniform(x,i)
        x_ξξ = fdd25_uniform(x,i)
        f_ξ  = fdd15_uniform(f,i)
        f_ξξ = fdd25_uniform(f,i)
        df[i] = f_ξξ/x_ξ^2-x_ξξ*f_ξ/x_ξ^3
    end
end

function fdd15_uniform(f,i)
    nx = length(f)
    if i == 1
        df = (25*f[1]-48*f[2]+36*f[3]-16*f[4]+3*f[5])/12
    elseif i == 2
        df = (-3*f[1]-10*f[2]+18*f[3]-6*f[4]+f[5])/12
    elseif i == nx-1
        df = (f[nx-4]-6*f[nx-3]+18*f[nx-2]-10*f[nx-1]-3*f[nx])/12
    elseif i == nx
        df = (3*f[nx-4]-16*f[nx-3]+36*f[nx-2]-48*f[nx-1]+25*f[nx])/12
    else
        df = (f[i+2]-8f[i+1]+8f[i-1]-f[i-2])/12
    end
    return df
end

function fdd25_uniform(f,i)
    nx = length(f)
    if i == 1
        df = (35*f[i]-104*f[1+i]+114*f[2+i]-56*f[3+i]+11*f[4+i])/12
    elseif i == 2
        df = (11*f[-1+i]-20*f[i]+6*f[1+i]+4*f[2+i]-f[3+i])/12
    elseif i == nx-1
        df = -(f[-3+i]-4*f[-2+i]-6*f[-1+i]+20*f[i]-11*f[1+i])/12
    elseif i == nx
        df = (11*f[-4+i]-56*f[-3+i]+114*f[-2+i]-104*f[-1+i]+35*f[i])/12
    else
        df = -(f[-2+i]-16*f[-1+i]+30*f[i]-16*f[1+i]+f[2+i])/12
    end
    return df
end


using LinearAlgebra
using MAT
using Plots
using Statistics

file = matread("Data2.mat");
X = file["data"];

# Visualize the first two dimension of the data
scatter(X[:,1], X[:,2], aspect_ratio=:equal, leg=false)

# input: X - data points
# output: E - distance matrix
#
function get_E(X)
    n = size(X,1); # number of points
    E = zeros(n,n); # the diagonal entries are 0

    for ii=1:n
        for jj=ii+1:n
            E[ii,jj] = norm(X[ii,:] - X[jj,:]);
            E[jj,ii] = E[ii,jj];
        end
    end

    return E;
end

# get the distance matrix D
E = get_E(X);

# evaluate sigma
E_sort = sort(E, dims=2);

k = 7;
sigma_loc = E_sort[:, k+1];

# input 1: E - distance matrix
# input 2: sigma - constant
#
# output: K - kernal matrix
#
function get_K(E, sigma_loc)
    n = size(E,1);
    K = ones(n,n);

    for ii = 1:n
        for jj = ii+1:n
            K[ii,jj] = exp(-E[ii,jj]^2/(sigma_loc[ii]*sigma_loc[jj]));
            K[jj,ii] = K[ii,jj]
        end
    end

    return K;
end

# get the kernal matrix K
K = get_K(E, sigma_loc);

# input: K - kernal matrix
#
# output 1: Q
# output 2: d_sq - sqrt{D}
#
function get_Q(K)
    n = size(K,1);
    Q = zeros(n,n);
    d_sq = zeros(n);

    # d_sq = sqrt{D}
    for ii = 1:n
        d_sq[ii] = sqrt(sum(K[ii,:]));
    end

    # get components of Q
    for ii = 1:n
        for jj = 1:n
            Q[ii,jj] = K[ii,jj]/(d_sq[ii]*d_sq[jj]);
        end
    end

    return Q, d_sq;
end

# get Q and d_sq
Q, d_sq = get_Q(K);

# input 1: Q
# input 2: d_sq - sqrt{D}
#
# output 1: v - eigenvectors
# output 2: s - eigenvalues
#
function get_eig(Q, d_sq)

    n = size(Q, 1);

    U,S,V = svd(Q); # U and S contains eigenvectors and eigenvalues repectively
                    # which is arranged in descending power.

    v = zeros(n,n);

     for ii = 1 : n
        V[ii,:] = V[ii,:]/d_sq[ii];
    end

    for ii = 1 : n
        v[:,ii] = V[:,ii]/norm(V[:,ii]);
    end

    return v, S;
end

c = 5 ; # the desired reduced dimension

v, s = get_eig(Q , d_sq);
p1 = scatter(s[1:10], label="eigenvalues 1:10");
p2 = plot(log.(s), label="eigenvalues in log");
plot(p1, p2, layout=2)

function get_Y(v, S, c)

    n = size(v,1);
    Y = zeros(n,c);

    # get components of diffusion map Y
    for ii = 1:c
        Y[:,ii] = v[:,ii+1].*S[ii+1];
    end

    return Y ;
end

Y = get_Y(v, s, c);

# print diffution map
p1 = scatter(Y[:,1], Y[:,3], label="2D", aspect_ratio=:equal)
p2 = scatter(Y[:,1], Y[:,2], Y[:,3], label="3D", aspect_ratio=:equal)
plot(p1, p2, layout=2)


abstract type TensorProductBasis <: Function end

"""
Constructs a Chebyshev basis of the form [T_{0}(x), T_{1}(x), ..., T_{n-1}(x)] where T_j(.) is the j-th Chebyshev polynomial of the first kind.
```julia
ChebyshevBasis(n)
```
Arguments:
- `n`: number of terms in the polynomial expansion.
"""
struct ChebyshevBasis <: TensorProductBasis
    n::Int
end

function (basis::ChebyshevBasis)(x)
    return map(j -> cos(j*acos(x)), 1:basis.n)
end

"""
Constructs a sine basis of the form [sin(x), sin(2*x), ..., sin(n*x)].
```julia
SinBasis(n)
```
Arguments:
- `n`: number of terms in the sine expansion.
"""
struct SinBasis <: TensorProductBasis
    n::Int
end

function (basis::SinBasis)(x)
    return map(j -> sin(j*x), 1:basis.n)
end

"""
Constructs a cosine basis of the form [cos(x), cos(2*x), ..., cos(n*x)].
```julia
CosBasis(n)
```
Arguments:
- `n`: number of terms in the cosine expansion.
"""
struct CosBasis <: TensorProductBasis
    n::Int
end

function (basis::CosBasis)(x)
    return map(j -> cos(j*x), 1:basis.n)
end

#auxiliary function
function fourier(i::Int, x::Real)
    return iseven(i) ? cos(i*x/2) : sin(i*x/2)
end

"""
Constructs a Fourier basis of the form F_j(x) = j is even ? cos((j÷2)*x) : sin((j÷2)*x) => [F_0(x), F_1(x), ..., F_n(x)].
```julia
FourierBasis(n)
```
Arguments:
- `n`: number of terms in the Fourier expansion.
"""
struct FourierBasis <: TensorProductBasis
    n::Int
end

function (basis::FourierBasis)(x)
    return map(j -> fourier(j,x), 1:basis.n)
end

#auxiliary function
##Source: https://github.com/ranocha/PolynomialBases.jl/blob/master/src/legendre.jl
function legendre_poly(x, p::Integer)
    a::typeof(x) = one(x)
    b::typeof(x) = x

    if p <= 0
        return a
    elseif p == 1
        return b
    end

    for j in 2:p
        a, b = b, ((2j-1)*x*b - (j-1)*a) / j
    end

    b
end

"""
Constructs a Legendre basis of the form [P_{0}(x), P_{1}(x), ..., P_{n-1}(x)] where P_j(.) is the j-th Legendre polynomial.
```julia
LegendreBasis(n)
```
Arguments:
- `n`: number of terms in the polynomial expansion.
"""
struct LegendreBasis <: TensorProductBasis
    n::Int
end

function (basis::LegendreBasis)(x)
    f = k -> legendre_poly(x,k-1)
    return map(f, 1:basis.n)
end

"""
Constructs a Polynomial basis of the form [1, x, ..., x^(n-1)].
```julia
PolynomialBasis(n)
```
Arguments:
- `n`: number of terms in the polynomial expansion.
"""
struct PolynomialBasis <: TensorProductBasis
    n::Int
end

function (basis::PolynomialBasis)(x)
    return [evalpoly(x, (I+zeros(basis.n,basis.n))[k,:]) for k in 1:basis.n]
end


"""
[Napoleon' theorem](https://en.wikipedia.org/wiki/Napoleon%27s_theorem)
states that if equilateral triangles are constructed on the sides
of any triangle, either all outward or all inward, the lines connecting the centres of those
equilateral triangles themselves form an equilateral triangle.
"""
module Napoleon

using Plots
using SymPy
using PlaneGeometry
using PlaneGeometry.GeoPlots

export
    napoleon_proof, napoleon_draw, napoleon_rand, napoleon_tri

"""
    napoleon_draw(📐::Triangle, labels='A':'C')

Verify Napoleon's theorem for the triangle `📐`.  The function works the same ways as
`napoleon_tri` but return `(🖼, hold)` where `🖼` is a plot and `hold` indicates where the theorem
holds.
"""
function napoleon_draw(tri::Triangle, labels='A':'C')
    trishape=shape(tri)

    plot(trishape, leg=false, fill=(0, :green), aspect_ratio=:equal, fillalpha= 0.2)

    scatter!(trishape.x, trishape.y, color=:red, series_annotations = text.(labels, :bottom))

    outer_tri = outer_equitriangles(tri)
    for t in outer_tri
        plot!(shape(t), leg=false, fill=(0, :pink), aspect_ratio=:equal, fillalpha=0.7)
    end

    outer_circles = circumcircle.(vertices.(outer_tri))

    outer_centers = PlaneGeometry.center.(outer_circles);

    nap_tri = Triangle(outer_centers...);

    plt = plot!(shape(nap_tri), leg=false, fill=(0, :blue), fillalpha=0.7, aspect_ratio=:equal)

    hold = isequilateral(nap_tri)

    plt, hold
end

"""
    napoleon_draw(A, B, C)

Verify Napoleon's theorem.
"""
function napoleon_draw(A, B, C)
    tri = Triangle(A, B, C)

    napoleon_draw(tri)
end

"""
    napoleon_draw(xA, yA, xB, yB, xC, yC)

Verify Napoleon's theorem.
"""
function napoleon_draw(xA, yA, xB, yB, xC, yC)
    A = Point(xA, yA);
    B = Point(xB, yB);
    C = Point(xC, yC);

    napoleon_draw(A, B, C)
end

"""
    napoleon_rand()

Verify Napoleon's theorem for a random triangle.
"""
function napoleon_rand()
    pts = rand(0:1//10:1,6);
    # Todo: sometimes this fails. Fix it.
    plt, hold = napoleon_draw(pts...)
end

"""
    napoleon_proof()

Prove Napoleon's theorem.
"""
function napoleon_proof()
    @vars by cx positive=true;
    @vars cy;

    A = Point(0, 0); B = Point(0, by); C = Point(cx, cy);
    tri = Triangle(A, B, C)

    npt = napoleon_tri(tri)
    isequilateral(npt)
end

"""
    napoleon_tri(📐)

Find the triangle that connects the center of three outer equilateral triangles of `📐`.
"""
function napoleon_tri(tri)
    outer_tri = outer_equitriangles(tri);
    outer_circles = circumcircle.(vertices.(outer_tri));
    outer_centers = PlaneGeometry.center.(outer_circles);
    Triangle(outer_centers...)
end

end


"""
    gauss(x, y)

Input: A basis {x,y} of a lattice L in R^n.
Output: A minimal basis v1, v2 of the lattice L.

The basis v1, v2 is minimal if
    - v1 is the shortest nonzero vector in L and
    - v2 is the shortest vector in L which is not a multiple of v1.

# Example 2.9 (Bremner)
```
julia> x = [-56, 43]
julia> y = [95, -73]
julia> gauss(x,y)
([1,1], [-1,2])
```
"""
function gauss(x, y)
    if norm(x) <= norm(y)
        v1, v2 = x, y
    else
        v1, v2 = y, x
    end

    finished = false
    while (!finished)
        m = round_ties_down(dot(v2, v1) / dot(v1, v1))
        v2 = v2 - m * v1

        if norm(v1) <= norm(v2)
            finished = true
        else
            v1, v2 = v2, v1
        end
    end

    return v1, v2
end


function NaiveGauss(A::Array,B::Vector)

  #A = the matrix of coefficients 'A' must be squared
  #B = The Vector 'B' right hand side vector Vertical
    m,n =size(A)
    v = size(B,1)
    if m != n
        error("Matrix must be squared")
    elseif m != v
        error("Vector Length must be equal to Matrix")
    end

    # System of equations
    S=[A B]
    X=zeros(m)
    L=Matrix{Float64}(I,m,m)
    #Forward Elimination
    for j=1:m-1
       for i=j+1:m

            f=S[i,j]/S[j,j]
            S[i,:]=S[i,:].-f.*S[j,:]
            L[i,j]=f
        end
    end

    #Back Substitution
    X[m]=S[end,end]/S[end,end-1]

    for i=m-1:-1:1
        X[i]=(S[i,end] - (S[i,i+1:m]'*X[i+1:m]))/S[i,i]
    end

    return X
end


#### Riemann Matrix
import Base: size
export Riemann
"""
`Riemann` matrix

Riemann matrix is defined as `A = B[2:N+1, 2:N+1]`, where
`B[i,j] = i-1` if `i` divides `j`, and `-1` otherwise.
[Riemann hypothesis](http://en.wikipedia.org/wiki/Riemann_hypothesis) holds
if and only if `det(A) = O( N! N^(-1/2+epsilon))` for every `epsilon > 0`.

```julia
julia> Riemann(7)
7x7 Riemann{Int64}:
  1  -1   1  -1   1  -1   1
 -1   2  -1  -1   2  -1  -1
 -1  -1   3  -1  -1  -1   3
 -1  -1  -1   4  -1  -1  -1
 -1  -1  -1  -1   5  -1  -1
 -1  -1  -1  -1  -1   6  -1
 -1  -1  -1  -1  -1  -1   7
```

For more details see Friedrich Roesler, Riemann's hypothesis as an eigenvalue problem,
Linear Algebra and its Applications, Vol. 81, (1986)
http://www.sciencedirect.com/science/article/pii/0024379586902557
"""
struct Riemann{Int} <: AbstractMatrix{Int}
    n::Int
end # immutable

# Define its size

size(A::Riemann, dim::Integer) = A.n
size(A::Riemann)= size(A,1), size(A,1)

# Index into a Riemann
function getindex(A::Riemann,i::Integer,j::Integer)
#    return (i+1)%(j+1)==0 ? i : -1
    if i<=j && (j+1)%(i+1)==0
        return i
    else
        return -1
    end
end # getindex

# Dense version of Riemann
Matrix(A::Riemann) =[A[i,j] for i=1:size(A,1), j=1:size(A,2)]


using DiffEqBase, Markdown

#DAE Example Problems
export prob_dae_resrob

### DAE Problems

f = function (r, yp, y, p, tres)
    r[1]  = -0.04*y[1] + 1.0e4*y[2]*y[3]
    r[2]  = -r[1] - 3.0e7*y[2]*y[2] - yp[2]
    r[1] -=  yp[1]
    r[3]  =  y[1] + y[2] + y[3] - 1.0
end
u0 = [1.0, 0, 0]
du0 = [-0.04, 0.04, 0.0]

@doc doc"""
The Robertson biochemical reactions in DAE form

```math
\frac{dy₁}{dt} = -k₁y₁+k₃y₂y₃
```
```math
\frac{dy₂}{dt} =  k₁y₁-k₂y₂^2-k₃y₂y₃
```
```math
1 = y₁ + y₂ + y₃
```
where ``k₁=0.04``, ``k₂=3\times10^7``, ``k₃=10^4``. For details, see:
Hairer Norsett Wanner Solving Ordinary Differential Equations I - Nonstiff Problems Page 129
Usually solved on ``[0,1e11]``
"""
prob_dae_resrob = DAEProblem(f,du0,u0,(0.0,100000.0))


##
##  i n t e g r a t i o n . j l  Integration Routines
##

function trapz(x::Vector{Tx}, y::Vector{Ty}) where {Tx<:Number, Ty<:Number}
    # Trapezoidal integration rule
    local n = length(x)
    if (length(y) != n)
        error("Vectors 'x', 'y' must be of same length")
    end
    r = zero(zero(Tx) + zero(Ty))
    if n == 1; return r; end
    for i in 2:n
        r += (x[i] - x[i-1]) * (y[i] + y[i-1])
    end
    #= correction -h^2/12 * (f'(b) - f'(a))
    ha = x[2] - x[1]
    he = x[end] - x[end-1]
    ra = (y[2] - y[1]) / ha
    re = (y[end] - y[end-1]) / he
    r/2 - ha*he/12 * (re - ra)
    =#
    return r/2
end

function romberg(f::Function, a::Real, b::Real;
                   maxit = 50, tol = 1.0e-15)
    if a == b; return 0.0, 0.0; end
    sgn = 1.0
    if a > b
        a, b = b, a
        sgn = -1.0
    end

    local n::Int = 1; local iter::Int = 0
    I = zeros(maxit+1, maxit+1)

    err = 1.0
    while err > tol && iter < maxit
        iter += 1
        n *= 2
        h = (b - a)/ n
        S = f(a)
        for i = 1:(n-1)
            xi = a + h*i
            S += 2.0*f(xi)
        end
        I[iter+1, 1] = (S + f(b)) * h / 2.0
        for k = 2:(iter+1)
            j = 2 + iter - k
            I[j,k] = (4^(k-1)*I[j+1,k-1] - I[j,k-1]) / (4^(k-1)-1)
        end
        err = abs((I[1,iter+1] - I[2,iter]) / I[1,iter+1])
     end
     if iter >= maxit
         warn("Maximum number of iterations has been reached.")
     end
     if err == 0.0; err = tol; end

     return I[1, iter+1], err
end


using CSV, GLM, StatsBase, Random, Distributions, Plots, Measures; pyplot()
Random.seed!(0)

n, N = 10^3, 10^6

function waldWolfowitz(data)
    n = length(data)
    sgns = data .>= mean(data);
    nPlus, nMinus = sum(sgns), n - sum(sgns)
    wwMu = 2*nPlus*nMinus/n + 1
    wwVar = (wwMu-1)*(wwMu-2)/(n-1)

    R = 1
    for i in 1:n-1
        R += sgns[i] != sgns[i+1]
    end

    zStat = abs((R-wwMu)/sqrt(wwVar))
    2*ccdf(Normal(),zStat)
end

experimentPvals = [waldWolfowitz(rand(Normal(),n)) for _ in 1:N]
for alpha in [ 0.001, 0.005, 0.01, 0.05, 0.1]
    pva = sum(experimentPvals .< alpha)/N
    println("For alpha = $(alpha), p-value area = $(pva)")
end

p1 = histogram(experimentPvals,bins = 5n, legend = false,
    xlabel = "p-value", ylabel = "Frequency")

Fhat = ecdf(experimentPvals)

pGrid = 0:0.001:1
p2 = plot(pGrid,Fhat.(pGrid), legend =false, xlabel = "p-value", ylabel = "ECDF")
plot(p1, p2, size = (1000,400), margin = 5mm)