using SpecialFunctions, NearestNeighbors, Distances, Distributions, Random

"""
    n_ball(n::Number)
Computes the volume of a n-dimensional unit sphere.
"""
function n_ball(n::Number)
    return π^(n/2.) / gamma(n/2. + 1.)
end

"""
    kl_entropy(data::Array{Float64, 2}; k=5)
Compute the nearest-neighbor estimate of the differential entropy of data.

data is a 2d array, with every column representing one data point.
For further information, see

"A class of Rényi information estimators for multidimensional densities"
Nikolai Leonenko, Luc Pronzato, and Vippal Savani
The Annals of Statistics, 2008
https://projecteuclid.org/euclid.aos/1223908088

keyword arguments:
k=5: number of nearest neighbors
"""
function kl_entropy(data::Array{Float64, 2}; k=5)
    d, N = size(data)
    kdtree = KDTree(data)
    _, dist = knn(kdtree, data, k+1, true)
    H = log(N) - digamma(k) + log(n_ball(d)) + d/N * sum(map(l->log(l[end]), dist))
end

"""
    kl_mutual_information(x, y; k=5, bias_correction=true)
compute the nearest-neighbor 'KGS' estimate of the mutual information between x and y.

x and y are 2d arrays, with every column representing one data point.
For further information, see

"Estimating Mutual Information"
Alexander Kraskov, Harald Stoegbauer, and Peter Grassberger
Physical Review E
https://arxiv.org/pdf/cond-mat/0305641.pdf

"Demystifying Fixed k-Nearest Neighbor Information Estimators"
Weihao Gao, Sewoong Oh, Pramod Viswanath
EEE International Symposium on Information Theory - Proceedings
https://arxiv.org/pdf/1604.03006.pdf

keyword arguments:
k=5: number of nearest neighbors
bias_correction=true: flag to apply Gao's bias correction
"""
function kl_mutual_information(x, y; k=5, bias_correction=true)
    dist = bias_correction ? Euclidean() : Chebyshev()

    d_x, N = size(x)
    d_y, _ = size(y)

    xy = vcat(x,y)

    kdtree_x = KDTree(x, dist)
    kdtree_y = KDTree(y, dist)
    kdtree_xy = KDTree(xy, dist)

    I = 0.

    for i in 1:N
        _, dist = knn(kdtree_xy, xy[:,i], k+1, true)
        n_x = length(inrange(kdtree_x, x[:,i], dist[end], false))-1
        n_y = length(inrange(kdtree_y, y[:,i], dist[end], false))-1
        I += digamma(n_x+1) + digamma(n_y+1)
    end

    I = -I/N + log(N) + digamma(k)

    if bias_correction
        I += log( n_ball(d_x) * n_ball(d_y)/n_ball(d_x+d_y) )
    end

    return I
end

"""
    kl_renyi(data::Array{Float64, 2}, q; k=5)
Compute the nearest-neighbor estimate of the Renyi-alpha entropy of data.

data is a 2d array, with every column representing one data point.
For further information, see

"A class of Rényi information estimators for multidimensional densities"
Nikolai Leonenko, Luc Pronzato, and Vippal Savani
The Annals of Statistics, 2008
https://projecteuclid.org/euclid.aos/1223908088

keyword arguments:
k=5: number of nearest neighbors
"""
function kl_renyi(data, q, k=5)
    d, N = size(data)
    Vd = π^(d/2.) / gamma(d/2. + 1.)
    Ck = ( gamma(k)/gamma(k+1-q) )^(1/(1-q))
    kdtree = KDTree(data)
    _, dist = knn(kdtree, data, k+1, true)
    Iq = 1/N * sum(map(l->( (N-1)*Ck*Vd*l[end]^d )^(1-q), dist))

    return log(Iq)/(1-q)
end

"""
    kl_cond_mi(x, y, z; k=5, bias_correction=true)
compute the nearest-neighbor 'KGS' estimate of the conditional mutual information between x and y given z.

x, y, and z are 2d arrays, with every column representing one data point.
keyword arguments:
k=5: number of nearest neighbors
bias_correction=true: flag to apply Gao's bias correction
"""
function kl_cond_mi(x, y, z; k=5, bias_correction=true)
    dist = bias_correction ? Euclidean() : Chebyshev()

    xz = vcat(x,z)
    yz = vcat(y,z)
    xyz = vcat(x,y,z)

    d_x, N = size(x)
    d_y, _ = size(y)
    d_z, _ = size(z)

    kdtree_z = KDTree(z, dist)
    kdtree_xz = KDTree(xz, dist)
    kdtree_yz = KDTree(yz, dist)
    kdtree_xyz = KDTree(xyz, dist)

    CMI = 0.

    for i in 1:N
        _, dist = knn(kdtree_xyz, xyz[:,i], k+1, true)
        n_xz = length(inrange(kdtree_xz, xz[:,i], dist[end], false))-1
        n_yz = length(inrange(kdtree_yz, yz[:,i], dist[end], false))-1
        n_z = length(inrange(kdtree_z, z[:,i], dist[end], false))-1
        CMI = CMI + digamma(n_xz) + digamma(n_yz) - digamma(n_z)
    end

    CMI = digamma(k) - CMI/N

    if bias_correction
        CMI += log( (n_ball(d_x + d_z) * n_ball(d_y+d_z))/(n_ball(d_x+d_y+d_z)*n_ball(d_z)))
    end

    return CMI
end

"""
    kl_perm_mi_test(x, y; k=5, B=100, bias_correction=true)
compute permutation test of independence of x and y.

keyword arguments:
k=5: number of nearest neighbors to use for mutual information estimate
B=100: number of permutations
bias_correction=true: flag to apply Gao's bias correction
"""
function kl_perm_mi_test(x, y; k=5, B=100, bias_correction=true)
    MI = kl_mutual_information(x, y, k=k, bias_correction=bias_correction)
    samples = Float64[]

    for i in 1:B
        push!(samples, kl_mutual_information(x, y[:, shuffle(1:end)], k=k, bias_correction=bias_correction))
    end

    p = length(filter(d->MI<d, samples))/B
    return p
end

"""
    kl_perm_cond_mi_test(x, y, z; k=5, B=100, kp=5, bias_correction=true)
compute permutation test of conditional independence of x and y given z.

For further information, see:
"Conditional independence testing based on a nearest-neighbor estimator of conditional mutual information"
Jakob Runge
Proceedings of the 21st International Conference on Artificial Intelligence and Statistics (AISTATS) 2018, Lanzarote, Spain.
http://proceedings.mlr.press/v84/runge18a/runge18a.pdf

keyword arguments:
k=5: number of nearest neighbors to use for mutual information estimate
B=100: number of permutations
bias_correction=true: flag to apply Gao's bias correction

"""
function kl_perm_cond_mi_test(x, y, z; k=5, B=100, kp=5, bias_correction=true)
    d, N = size(z)
    CMI = kl_cond_mi(x,y,z,k=k,bias_correction=bias_correction)
    samples = Float64[]

    kdtree_z = KDTree(z, Chebyshev())
    z_knn, _ = knn(kdtree_z, z, kp+1)

    for b in 1:B
        # create permutation for independence test
        U = Int64[]
        P = collect(1:N)
        j = 0
        Ns = map(shuffle, z_knn)

        for i in shuffle(collect(1:N))
            j = Ns[i][1]
            m = 1
            while (j ∈ U) && (m < kp)
                m += 1
                j = Ns[i][m]
            end
            P[i]=j
            push!(U,j)
        end
        push!(samples, kl_cond_mi(x[:,P],y,z,k=k,bias_correction=bias_correction))
    end

    p = length(filter(d->CMI<d, samples))/B

    return p
end


## Packages
using ForwardDiff, LinearAlgebra, Plots

## Parameters
β = 0.99;
σ = 2;
γ = 1;

## Utility functions
u(x) = x^(1 - σ) / (1 - σ);
v(x) = x^(1 + 1/γ) / (1 + 1/γ);
U(c, n) = u(c) - v(n);

## Differentials
Uc(c) = ForwardDiff.derivative(u, c);
Ucc(c) = ForwardDiff.derivative(Uc, c);

Un(n) = -ForwardDiff.derivative(v, n);
Unn(n) = ForwardDiff.derivative(Un, n);

## Discretizes g and θ
include("Tauchen.jl")
g, Pg = Tauchen(0.95, (1.2/15)^2, 5, (1 - 0.95) * log(0.15));
θ, Pθ = Tauchen(0.95, (2/400)^2, 5, (1 - 0.95) * log(1));
s = vec(collect(Iterators.product(θ, g)));
Ps = kron(Pg, Pθ)

include("NewtonRoot.jl");

## This function calculates Ramsey Allocation, given Φ and initial state s0
function RamseyAllocation(Φ::Real, s0::Tuple)

    function time1_allocation(s::Tuple)
        log_θ, log_g = s[1], s[2];
        θ, g = exp(log_θ), exp(log_g);
        n(c::Real) = (g + c) / θ;
        res1(c::Real) = (1 + Φ) * (Uc(c) + Un(n(c))) + Φ * (c * Ucc(c) + n(c) * Unn(n(c)));
        c = NewtonRoot(c -> res1(c), 0.5);

        return c, n(c)
    end

    c_time1 = [time1_allocation(s[i])[1] for i in 1:length(s)];
    n_time1 = [time1_allocation(s[i])[2] for i in 1:length(s)];

    function time0_allocation(s::Tuple)
        log_θ, log_g = s[1], s[2];
        θ, g = exp(log_θ), exp(log_g);
        n(c::Real) = (g + c) / θ;
        b0(c::Real) = 4 * θ * n(c);
        res0(c::Real) = (1 + Φ) * (Uc(c) + Un(n(c))) + Φ * (c * Ucc(c) + n(c) * Unn(n(c))) - Φ * Ucc(c) * b0(c)
        c = NewtonRoot(c -> res0(c), 0.5)

        return c, n(c)
    end

    c_time0, n_time0 = time0_allocation(s0)[1], time0_allocation(s0)[2];

    return c_time1, n_time1, c_time0, n_time0

end

## Updating the value of Φ:
c_time1, n_time1, c_time0, n_time0 = RamseyAllocation(0.12615792845184043, s[1])

sum_mat = inv(1.0I - β * Ps) * (Uc.(c_time1) .* c_time1 .+ Un.(n_time1) .* n_time1);
implementCons = Uc(c_time0) * (4 * exp(s[1][1]) * n_time0) - Uc(c_time0) * c_time0 - Un(n_time0) * n_time0 - β * sum([Ps[1,i] * sum_mat[i] for i in 1:length(sum_mat)]);

b_time1 = sum_mat ./ Uc.(c_time1);

τ_time1 = 1.0 .+ Un.(n_time1) ./ Uc.(c_time1);

## Root finding method for Φ

function search_Φ(Φ::Real, s0::Tuple)
    c_time1, n_time1, c_time0, n_time0 = RamseyAllocation(Φ, s0);

    i_s0 = findfirst(isequal(s0), s);

    sum_mat = inv(1.0I - β * Ps) * (Uc.(c_time1) .* c_time1 .+ Un.(n_time1) .* n_time1); #this sum is independent of s0

    implementCons = Uc(c_time0) * (4 * exp(s0[1]) * n_time0) - Uc(c_time0) * c_time0 - Un(n_time0) * n_time0 - β * sum([Ps[i_s0,i] * sum_mat[i] for i in 1:length(sum_mat)]);

    return implementCons

end

Φ_est = NewtonRoot(x -> search_Φ(x, s[1]), 0.1) # 0.12615792845184043

#----------------- SIMULATING RAMSEY POLICY WITH STATE CONTINGENT DEBT ----------------------#
include("Finite_Markov_Chains.jl")
state_tran = mc_sample_path(Ps, init = 1, sample_size = 1000);

c_sim = [c_time1[i] for i in state_tran];
n_sim = [n_time1[i] for i in state_tran];
b_sim = [b_time1[i] for i in state_tran];
τ_sim = [τ_time1[i] for i in state_tran];

plot(c_sim, label = "Simulated path C")
plot(n_sim, label = "Simulated path N")
plot(b_sim, label = "Simulated path b")
plot(τ_sim, label = "Simulated path taxes")


mutable struct Point
    x::Float64
    y::Float64
end

mutable struct Vector2D
    x::Float64
    y::Float64
end

p = Point(2, 5) #> Point(2.0,5.0)
v = Vector2D(3, 2) #> Vector2D(3.0,2.0)

# Example with sum:
# +(p, v) #> MethodError: ERROR: `+` has no method matching +(::Point, ::Vector2D)

import Base.+
+(p::Point,    q::Point) = Point(p.x + q.x, p.y + q.y)
+(u::Vector2D, v::Vector2D) = Point(u.x + v.x, u.y + v.y)
+(u::Vector2D, p::Point) = Point(u.x + p.x, u.y + p.y)
# +(p, v) #> ERROR: MethodError: `+` has no method matching +(::Point, ::Vector2D)
+(p::Point,    v::Vector2D) = Point(p.x + v.x, p.y + v.y)
+(p, v) #> Point(5.0,7.0)

# Example with dot product:
# *(p, v) #> ERROR: MethodError: `*` has no method matching *(::Point, ::Vector2D)

import Base.*
*(p::Point,    q::Point) = p.x * q.x + p.y * q.y
*(u::Vector2D, v::Vector2D) = u.x * v.x + u.y * v.y
*(u::Vector2D, p::Point) = u.x * p.x + u.y * p.y
# *(p, v) #> ERROR: MethodError: `*` has no method matching *(::Point, ::Vector2D)
*(p::Point,    v::Vector2D) = p.x * v.x + p.y * v.y
*(p, v) #> 16.0

VecOrPoint = Union{Vector2D, Point}

isa(p, VecOrPoint) #> true
isa(v, VecOrPoint) #> true

+(u::VecOrPoint, v:: VecOrPoint) = VecOrPoint(u.x + v.x, u.y + v.y)
+(p, v) #> Point(5.0,7.0)

*(u::VecOrPoint, v:: VecOrPoint) = u.x * v.x + u.y * v.y
*(p, v) #> 16.0


using ApproxFun
using Plots
using BenchmarkTools

function sir_eqn(S,I,u0,p)
  (β,γ) = p
  (S0,I0) = u0
   return [S(0)-S0,
           I(0)-I0,
           S' + β*S*I,
           I' - β*S*I + γ*I]
end;

tmax = 40.0
t=Fun(identity, 0..tmax);

u0 = [990.0,10.0]; # S,I

p = [0.0005,0.25]; # β,γ

S,I = newton((S,I)->sir_eqn(S,I,u0,p), u0 .* one(t); maxiterations=50);

plot(S,label="S",xlabel="Time",ylabel="Number")
plot!(I,label="I")

@benchmark newton((S,I)->sir_eqn(S,I,u0,p), u0 .* one(t); maxiterations=50)


using PhysicalConstants.CODATA2018: FineStructureConstant, ReducedPlanckConstant, ElectronMass, SpeedOfLightInVacuum

"""
comptonShift(θ, E)

The fractional energy of the scattered X-ray resulting from a Compton event with an incident X-ray energy of `E` eV at a scatter angle of `θ` radians.
"""
comptonShift(θ, E) = 1.0 / (1.0 + (E / mₑ) * (1.0 - cos(θ)))
comptonShift(θ, cxr::CharXRay) = comptonShift(θ, energy(cxr))

"""
comptonEnergy(θ, E)

The energy of the scattered X-ray resulting from a Compton event with an incident X-ray energy of `E` eV at a scatter angle of `θ` radians.
"""
comptonEnergy(θ, E) = E * comptonShift(θ, E)
comptonEnergy(θ, cxr::CharXRay) = comptonEnergy(θ, energy(cxr))

"""
comptonAngular(θ, E)

The angular distribution function of Compton scattered X-rays of incident energy `E` scattered to an angle `θ`.

Based on the Klein-Nishina formula for Compton scattering. It has been normalized so that the integral over dΩ = 2π⋅sin(θ) dθ equals one.
"""
function comptonAngular(θ, E)
  p = comptonShift(θ, E)
  den = (4π*mₑ*((E*(E^3 + 9*E^2*mₑ + 8*E*mₑ^2 + 2*mₑ^3))/(2E +mₑ)^2 + (E^2 - 2*E*mₑ - 2*mₑ^2)*atan(E/(E + mₑ))))/E^3
  return p^2*(p + 1.0/p - sin(θ)^2)/den
end
comptonAngular(θ, cxr::CharXRay) = comptonAngular(θ, energy(cxr))

"""
  comptonDifferential(θ, E)

Differential crosssection dσ/dΩ = dσ/(sin(θ) dθ dϕ) for Compton scattering in cm².
"""
function comptonDifferential(θ, E)
  α, rc =  convert(Float64,FineStructureConstant), convert(Float64,(ReducedPlanckConstant/(ElectronMass*SpeedOfLightInVacuum))/u"cm")
  p = comptonShift(θ, E)
  return 0.5*(α*rc*p)^2*(p + 1.0/p - sin(θ)^2)
end


function FFT_prepare(f)
	for _i in 1:f.len
		p=0
		i=_i-1
		for j in 1:f.bits
			p=(p*2)+ ( (i%2)==1 ? 1 : 0 )
			i=fld(i,2)
		end
		#println(_i-1," ",p)
		f.exchangeMap[_i]=p+1
	end
	for i in 1:fld(f.len,2)
		ang=-2*pi*(i-1)/f.len
		f.e[i]=cos(ang)+sin(ang)*im
	end
	return f
end

struct FFT
	bits::Integer
	len::Integer
	exchangeMap
	e
	FFT(bits)=FFT_prepare(new(	bits,
					2^bits,
					zeros(Int,2^bits),
					zeros(Complex,2^(bits-1))))
end

function (fft::FFT)(f)
	F=zeros(Complex,2^(fft.bits) )
	for i in 1:fft.len
		F[fft.exchangeMap[i]]=f[i]
	end

	for i in 0:(fft.bits-1)
		unitSize_2=2^i
		unitSize=2*unitSize_2
		unitNum=2^(fft.bits-i-1)

		#=
		for j in 1:unitSize:
			coef=fft.e[(j-1)*unitNum+1]
			for k in 0:(unitNum-1):
				pPre= unitSize*k+j
				pPost=pPre+unitSize_2
				pre=F[pPre]
				post=F[pPost]
				F[pPre]=pre+coef*post
				F[pPost]=pre-coef*post
		=#
		for k in 0:(unitNum-1)
			offset=unitSize*k+1
			for j in 0:(unitSize_2-1)
				coef=fft.e[j*unitNum+1]
				pPre=offset+j
				pPost=pPre+unitSize_2
				pre=F[pPre]
				post=F[pPost]*coef
				#println([pre,post,coef])
				F[pPre]=pre+post
				F[pPost]=pre-post
			end
		end
	end
	return F./fft.len
end

f2=FFT(2)
f2=FFT(2)
println(f2)
println(f2([0,1,2,3]))

println()
f3=FFT(3)
println(f3)
println(f3(0:7))
F10=FFT(10)
for i in 1:(10)
	F10(rand(1024))
end


include("differencial.jl")
"""
`RK4(F,A,dt)`
Runge-Kutta法で刻み時間`dt`秒後のFの値を計算する関数
## Arguments
- `F::Any`: 計算したい時間`t`の関数の`t`秒時点の値 関数`dif`で微分値を求められるもの
- `A::Any`:微分計算に必要な定数の組
- `dt::Number`  :刻み時間
## Returns
- `dt`秒後のFの値
"""
function RK4(F,A,dt)
    f=dif(F,A)
    k1=dt.*f
    k2=dt.*dif((F.+k1),A)
    k3=dt.*dif((F.+0.5.*(k1.+k2)),A)
    k4=dt.*dif((F.+k1.+k3),A)
    return F.+(k1.+2.0.*k2.+2.0.*k3.+k4)./6
end


module OLS

using Distributions
export ols
export olstable

function ols(y,x)
	x   = hcat(ones(size(x, 1)), x)
	b   = (x'*x)\x'*y
	r   = y-x*b

	nobs = size(y, 1)         # number of observations
	ncoef = size(x, 2)        # number of coefficients
	df_e = nobs - ncoef       # degrees of freedom, error
	df_r = ncoef - 1          # degrees of freedom, regression

	tss = (y-mean(y))'*(y-mean(y))
	rss = r'*r
	ess = tss-rss

	R2  = ess./tss                      # R-squared
	R2adj = 1 - (1 - R2) * ((nobs - 1) / (nobs - ncoef)) # R2 adjusted
	F = (R2 / df_r) / ((1 - R2) / df_e) # model F-statistic

	s2  = rss/(nobs-ncoef)    # Residual variance
	s2  = s2[1,1] 						# convert to float
	cov = s2.*inv(x'*x)                 # OLS variance-covariance matri
	se  = sqrt(diag(cov))              # OLS standard errors
	t   = b./se 						# T-value
	p   = ccdf(TDist(df_e), abs(t))*2      # P-values
    return b, se, t, R2, R2adj, F, s2, p
end

# Print function
function olstable(title,lbly,lblx,b,se,t,R2,R2adj,F,s2,p)
# Print output in a table
println("")
println(title)
println(string("Dependent Variable: ", lbly))
println("========================================================")
println("Variable     Parm. Val.  Std. Err.  t-Value    Pr(>|t|)")
for (i, j, s, t, p) in zip(lblx, b, se ,t, p)
@printf("%-10s",i), @printf("%11.4f",j), @printf("%11.4f", s), @printf("%11.4f", t), @printf("%11.4f\n", p)
end
println("--------------------------------------------------------")
println(string("R-squared:           " , round(R2[1,1],2)))
println(string("Adjusted R-Squared:  " , round(R2adj[1,1],2)))
println(string("Sigma-squared:       " , round(s2[1,1],2)))
println(string("F-Statistic :        " , round(F[1,1],2)))
println("========================================================")
end

end # module


function chisq_goodness(x::AbstractVector{T}) where {T <: Real}
    # figure out number of bins, which depends on length(x)
    nbins = round(Int, ceil(2 * (length(x)^(2/5))))

    # create a `range` object
    bin_range = range(-3, 3, length = nbins + 1)

    # use StatsBase `fit` function to count number of observations per bin
    bincounts = fit(Histogram, x, bin_range).weights

    # use `pdf` function to find expected number of observations per bin
    bin_centers = bin_range[1:end-1] .+ bin_range.step.hi / 2
    expected_pdf = pdf.(Normal(0, 1), bin_centers)
    bin_expected = floor.(Int, expected_pdf .* sum(bincounts) / sum(expected_pdf))

    # calcuate pearson stats (X2)
    𝜒² = pearson_chi(bincounts, bin_expected)

    # calculate X2/DF and return it
    return 𝜒² / nbins
end

function pearson_chi(
    observed::AbstractVector{T},
    expected::AbstractVector{T}
) where {T <: Integer}
    χ² = 0
    for i = 1:length(observed)
        χ² += (observed[i] - expected[i])^2 / expected[i]
    end
    return χ²
end


# this is a little Monte Carlo exercise that illustrates that
# the OLS estimator is unbiased when we have strong exogeneity
using Plots
reps = 1000 # number of Monte Carlo reps.
n = 20 # sample size
sig = 3.0  # st. dev. of errors

x = [ones(n,1) randn(n,1)]  # x is fixed over repeated samples
beta = [1.0, 2.0] # true beta
PopRegLine = x*beta

e = sig*randn(n,reps) # reps will be in columns
y = PopRegLine .+ e

betas = inv(x'x)*x'y

betas[2,:] .-=  2.0
histogram(betas[2,:], label = "")
gui()
#savefig("Unbiased.svg")


include("misc.jl")
include("findMin.jl")

function softmaxObj(w,X,y, k)
        (n, d) = size(X)
        f = 0
        for i = 1:n
          b = 0
          for c = 1:k
            b += exp(w[:, c]' * X[i, :])
          end
          f += -w[:, y[i]]' * X[i, :] + log(b)
        end
	g = zeros(d,k)
        for i = 1:n
          b = 0
          for c = 1:k
            b += exp(w[:, c]' * X[i, :])
          end
          for c = 1:k
            if y[i] == c
	      g[:,c] += -X[i, :]
            end
            g[:,c] += 1/b * exp(w[:, c]' * X[i, :]) * X[i, :]
          end
        end
        #a = -w[:, y]*X
        #b = w[:, ones(k, size(y,2)) * (1:Int(k))']*X
	#f = sum(a + log(b))
	display(f)
	display(g)
	return (f,g)
end

# Multi-class softmax classifier
function softmaxClassifier(X,y)
	(n,d) = size(X)
	k = maximum(y)

	W = zeros(d,k)

	display(size(W))

	# Each binary objective has the same features but different lables
	funObj(w) = softmaxObj(w,X,y, k)

	W = findMin(funObj, W, verbose=true)#, derivativeCheck=true)

	# Make linear prediction function
	#predict(Xhat) = findmax(Xhat*W)[2]
	predict(Xhat) = mapslices(indmax,Xhat*W,2)

	return LinearModel(predict,W)
end


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

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

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

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

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

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

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

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

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

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

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

function findRoot(polyn, c)
    interval = polyn[2]
    xk = interval[1]
    xkPlus1 = interval[2]
    polynomial = polyn[1]

    rooty = roots(polynomial - c)
    filter!(z -> imag(z) == 0., rooty)
    rooty = map(z -> real(z), rooty)

    filter!(x -> xk <= x <= xkPlus1, rooty)
    return rooty
end

function findRoots(splines, c)
    Rootki = map(p -> findRoot(p,c), splines)
    filter!(r -> length(r) != 0, Rootki)
    return Rootki
end

function PlotMain(coor, splajn, pierwiastki, c, a, b, name, prec = 0.01)
    function PlotSpline()
        xs = []
        ys = []
        # ysPrim = []
        # ysPrimPrim = []
        for i in 1:length(splajn)
            xk = splajn[i][2][1]
            xkPlus1 = splajn[i][2][2]
            wielomian = splajn[i][1]
            xs = vcat(xs, [x for x in xk:prec:xkPlus1])
            ys = vcat(ys, map(wielomian, [x for x in xk:prec:xkPlus1]))
            # ysPrim = vcat(ysPrim, map(polyder(wielomian), [x for x in xk:prec:xkPlus1]))
            # ysPrimPrim = vcat(ysPrimPrim, map(polyder(polyder(wielomian)), [x for x in xk:prec:xkPlus1]))
        end
        plot!(plotka, xs, ys, lab = "Naturalna funkcja sklejana s(x)", linecolor=:blue)
        # plot!(plotka, xs, ysPrim, lab = "Naturalna funkcja sklejana s'(x)", linecolor=:green)
        # plot!(plotka, xs, ysPrimPrim, lab = "Naturalna funkcja sklejana s''(x)", linecolor=:black)
    end
    function PlotRoots()
        scatter!(plotka, pierwiastki, map(x->c, pierwiastki), lab = "Pierwiastki równania f(x) = c", color=:orange, markersize = 11)
    end
    function PlotC()
        plot!(plotka, [a,b], [c,c], lab = "y=c",  linecolor=:black, linewidth=5)
    end
    function PlotF()
        xs, ys = myunzip(coor)
        scatter!(plotka, xs, ys, lab = "Zadane punkty (x,f(x))", color=:green)
    end

    plotka = plot(legend = :topright)
    xlabel!(plotka, "Os x")
    ylabel!(plotka, "Os y")
    title!(string("Wykres s(x), y=c, pierwiastków s(x) = y", name))
    PlotSpline()
    PlotRoots()
    PlotC()
    PlotF()
    display(plotka)
    savefig(plotka, name)
end

function main(coor, c, name="")
    coor = sort(coor)
    splajn = makeSpline(coor)
    pierwiastki = VectorToList(findRoots(splajn, c))

    a = coor[1][1]
    b = coor[end][1]

    PlotMain(coor, splajn, pierwiastki, c, a, b, name)
    return pierwiastki
end

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


# Functions for calculating exact overlap between shapes.
#
# Original cython version by Thomas Robitaille released under a
# 3-clause BSD-style license.

# Return area of a circle arc between (x1, y1) and (x2, y2) with radius r
# reference: http://mathworld.wolfram.com/CircularSegment.html
function area_arc(x1, y1, x2, y2, r)
  a = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))
  theta = 2. * asin(0.5 * a / r)
  return 0.5 * r * r * (theta - sin(theta))
end

# Area of a triangle defined by three verticies
area_triangle(x1, y1, x2, y2, x3, y3) =
    0.5 * abs(x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2))

# Core of circular overlap routine.
# Assumes that xmax >= xmin >= 0.0, ymax >= ymin >= 0.0.
# (can always modify input to conform to this).
function circoverlap_core(xmin, ymin, xmax, ymax, r)

    xmin2 = xmin * xmin
    ymin2 = ymin * ymin
    r2 = r * r
    (xmin2 + ymin2 > r2) && return 0.0

    xmax2 = xmax * xmax
    ymax2 = ymax * ymax
    (xmax2 + ymax2 < r2) && return (xmax-xmin) * (ymax-ymin)

    a = xmax2 + ymin2  # (corner 1 distance)^2
    b = xmin2 + ymax2  # (corner 2 distance)^2

    if (a < r2 && b < r2)
        x1 = sqrt(r2 - ymax2)
        y1 = ymax
        x2 = xmax
        y2 = sqrt(r2 - xmax2)
        return ((xmax - xmin) * (ymax - ymin) -
                area_triangle(x1, y1, x2, y2, xmax, ymax) +
	            area_arc(x1, y1, x2, y2, r))

    elseif (a < r2)
        x1 = xmin
        y1 = sqrt(r2 - xmin2)
        x2 = xmax
        y2 = sqrt(r2 - xmax2)
        return (area_arc(x1, y1, x2, y2, r) +
                area_triangle(x1, y1, x1, ymin, xmax, ymin) +
                area_triangle(x1, y1, x2, ymin, x2, y2))

    elseif (b < r2)
        x1 = sqrt(r2 - ymin2)
        y1 = ymin
        x2 = sqrt(r2 - ymax2)
        y2 = ymax
        return (area_arc(x1, y1, x2, y2, r) +
                area_triangle(x1, y1, xmin, y1, xmin, ymax) +
                area_triangle(x1, y1, xmin, y2, x2, y2))

    else
        x1 = sqrt(r2 - ymin2)
        y1 = ymin
        x2 = xmin
        y2 = sqrt(r2 - xmin2)
        return (area_arc(x1, y1, x2, y2, r) +
                area_triangle(x1, y1, x2, y2, xmin, ymin))
    end
end

# Area of overlap of a rectangle and a circle
function circoverlap(xmin, ymin, xmax, ymax, r)

    # some subroutines demand that r > 0
    (r <= 0.0) && return 0.0

    if (0.0 <= xmin)
        (0.0 <= ymin) && return circoverlap_core(xmin, ymin, xmax, ymax, r)
        (0.0 >= ymax) && return circoverlap_core(-ymax, xmin, -ymin, xmax, r)
        return (circoverlap(xmin, ymin, xmax, 0.0, r) +
                circoverlap(xmin, 0.0, xmax, ymax, r))

    elseif (0.0 >= xmax)
        (0.0 <= ymin) && return circoverlap_core(-xmax, ymin, -xmin, ymax, r)
        (0.0 >= ymax) && return circoverlap_core(-xmax, -ymax, -xmin, -ymin, r)
        return (circoverlap(xmin, ymin, xmax, 0.0, r) +
                circoverlap(xmin, 0.0, xmax, ymax, r))

    else
        (0.0 <= ymin) && return (circoverlap(xmin, ymin, 0.0, ymax, r) +
                                 circoverlap(0.0, ymin, xmax, ymax, r))
        (0.0 >= ymax) && return (circoverlap(xmin, ymin, 0.0, ymax, r) +
                                 circoverlap(0.0, ymin, xmax, ymax, r))
        return (circoverlap(xmin, ymin, 0.0, 0.0, r) +
                circoverlap(0.0, ymin, xmax, 0.0, r) +
                circoverlap(xmin, 0.0, 0.0, ymax, r) +
                circoverlap(0.0, 0.0, xmax, ymax, r))
    end
end

# -----------------------------------------------------------------------------
# ellipse-rectangle overlap

# internal types
immutable Point
    x::Float64
    y::Float64
end

immutable Intersections
    p1::Point
    p2::Point
end

# rotate -> a, b, c = b, c, a

# Check if a point (x,y) is inside a triangle
function in_triangle(x, y, x1, y1, x2, y2, x3, y3)
    c = 0
    c += ((y1 > y) != (y2 > y)) && (x < (x2-x1) * (y-y1) / (y2-y1) + x1)
    c += ((y2 > y) != (y3 > y)) && (x < (x3-x2) * (y-y2) / (y3-y2) + x2)
    c += ((y3 > y) != (y1 > y)) && (x < (x1-x3) * (y-y3) / (y1-y3) + x3)

    return c % 2 == 1
end

# Intersection of a line defined by two points with a unit circle
function circle_line(x1, y1, x2, y2)

    tol = 1.e-10

    dx = x2 - x1
    dy = y2 - y1

    if abs(dx) < tol && abs(dy) < tol
        return Intersections(Point(2.0, 2.0), Point(2.0, 2.0))

    elseif abs(dx) > abs(dy)

        # Find the slope and intercept of the line
        a = dy / dx
        b = y1 - a * x1

        # Find the determinant of the quadratic equation
        delta = 1.0 + a*a - b*b
        if (delta > 0.)  # solutions exist
	    delta = sqrt(delta)
            p1x = (-a*b - delta) / (1. + a*a)
            p2x = (-a*b + delta) / (1. + a*a)
            return Intersections(Point(p1x, a * p1x + b),
                                 Point(p2x, a * p2x + b))
        else             # no solutions exist
            return Intersections(Point(2.0, 2.0), Point(2.0, 2.0))
        end

    else

        # Find the slope and intercept of the line
        a = dx / dy
        b = x1 - a * y1

        # Find the determinant of the quadratic equation
        delta = 1.0 + a*a - b*b
        if (delta > 0.)  # solutions exist
	    delta = sqrt(delta)
	    p1y = (-a*b - delta) / (1. + a*a)
            p2y = (-a*b + delta) / (1.0 + a*a)
            return Intersections(Point(a * p1y + b, p1y),
                                 Point(a * p2y + b, p2y))
        else  # no solution, return values > 1
            return Intersections(Point(2.0, 2.0), Point(2.0, 2.0))
        end
    end
end

# The intersection of a line with the unit circle. The intersection
# closest to (x2, y2) is chosen.
function circle_segment_single2(x1, y1, x2, y2)
    inter = circle_line(x1, y1, x2, y2)

    dx1 = abs(inter.p1.x - x2)
    dy1 = abs(inter.p1.y - y2)
    dx2 = abs(inter.p2.x - x2)
    dy2 = abs(inter.p2.y - y2)

    if (dx1 > dy1)  # compare based on x-axis
        return (dx1 > dx2) ? inter.p2 : inter.p1
    else
        return (dy1 > dy2) ? inter.p2 : inter.p1
    end
end

# Intersection(s) of a segment with the unit circle. Discard any
# solution not on the segment.
function circle_segment(x1, y1, x2, y2)
    inter = circle_line(x1, y1, x2, y2)
    pt1 = inter.p1
    pt2 = inter.p2

    if ((pt1.x > x1 && pt1.x > x2) || (pt1.x < x1 && pt1.x < x2) ||
        (pt1.y > y1 && pt1.y > y2) || (pt1.y < y1 && pt1.y < y2))
        pt1 = Point(2.0, 2.0)
    end

    if ((pt2.x > x1 && pt2.x > x2) || (pt2.x < x1 && pt2.x < x2) ||
        (pt2.y > y1 && pt2.y > y2) || (pt2.y < y1 && pt2.y < y2))
        pt2 = Point(2.0, 2.0)
    end

    if pt1.x > 1. && pt2.x < 2.
        return Intersections(pt1, pt2)
    else
        return Intersections(pt2, pt1)
    end
end

# Given a triangle defined by three points (x1, y1), (x2, y2), and
# (x3, y3), find the area of overlap with the unit circle.
function triangle_unitcircle_overlap(x1, y1, x2, y2, x3, y3)

    # Find distance of all vertices to circle center
    d1 = x1*x1 + y1*y1
    d2 = x2*x2 + y2*y2
    d3 = x3*x3 + y3*y3

    # Order vertices by distance from origin
    if (d1 < d2)

        if (d2 < d3)

        elseif (d1 < d3)
	    x2, x3 = x3, x2
            y2, y3 = y3, y2
            d2, d3 = d3, d2

        else
            # rotate -> a, b, c = b, c, a
            x1, x2, x3 = x3, x1, x2
            y1, y2, y3 = y3, y1, y2
            d1, d2, d3 = d3, d1, d2
        end
    else
        if d1 < d3
            x1, x2 = x2, x1
            y1, y2 = y2, y1
            d1, d2 = d2, d1
        elseif d2 < d3
            x1, x2, x3 = x2, x3, x1
            y1, y2, y3 = y2, y3, y1
            d1, d2, d3 = d2, d3, d1
        else
            x1, x3 = x3, x1
            y1, y3 = y3, y1
            d1, d3 = d3, d1
        end
    end

    # Determine number of vertices inside circle
    in1 = d1 < 1.0
    in2 = d2 < 1.0
    in3 = d3 < 1.0

    # Determine which vertices are on the circle
    on1 = abs(d1 - 1.0) < 1e-10
    on2 = abs(d2 - 1.0) < 1e-10
    on3 = abs(d3 - 1.0) < 1e-10

    if (on3 || in3)  # triangle completely within circle
        area = area_triangle(x1, y1, x2, y2, x3, y3)

    elseif (in2 || on2)
        # If vertex 1 or 2 are on the edge of the circle, then we use
        # the dot product to vertex 3 to determine whether an
        # intersection takes place.
        intersect13 = !on1 || (x1*(x3-x1) + y1*(y3-y1) < 0.0)
        intersect23 = !on2 || (x2*(x3-x2) + y2*(y3-y2) < 0.0)

        if (intersect13 && intersect23)
	    pt1 = circle_segment_single2(x1, y1, x3, y3)
	    pt2 = circle_segment_single2(x2, y2, x3, y3)
	    area = (area_triangle(x1, y1, x2, y2, pt1.x, pt1.y) +
		    area_triangle(x2, y2, pt1.x, pt1.y, pt2.x, pt2.y) +
		    area_arc(pt1.x, pt1.y, pt2.x, pt2.y, 1.0))
        elseif (intersect13)
	  pt1 = circle_segment_single2(x1, y1, x3, y3)
	  area = (area_triangle(x1, y1, x2, y2, pt1.x, pt1.y) +
		  area_arc(x2, y2, pt1.x, pt1.y, 1.0))
        elseif (intersect23)
	  pt2 = circle_segment_single2(x2, y2, x3, y3)
	  area = (area_triangle(x1, y1, x2, y2, pt2.x, pt2.y) +
		  area_arc(x1, y1, pt2.x, pt2.y, 1.0))
        else
	    area = area_arc(x1, y1, x2, y2, 1.0)
        end

    elseif in1
        # Check for intersections of far side with circle
        inter = circle_segment(x2, y2, x3, y3)
        pt1 = inter.p1
        pt2 = inter.p2
        pt3 = circle_segment_single2(x1, y1, x2, y2)
        pt4 = circle_segment_single2(x1, y1, x3, y3)

        if pt1.x > 1.0  # indicates no intersection

	    # check if the pixel vertex (x1, y2) and the origin are on
	    # different sides of the circle segment. If they are, the
	    # circle segment spans more than pi radians.
	    # We use the formula (y-y1) * (x2-x1) > (y2-y1) * (x-x1)
	    # to determine if (x, y) is on the left of the directed
	    # line segment from (x1, y1) to (x2, y2)
	    if (((0.-pt3.y) * (pt4.x-pt3.x) > (pt4.y-pt3.y) * (0.-pt3.x)) !=
	        ((y1-pt3.y) * (pt4.x-pt3.x) > (pt4.y-pt3.y) * (x1-pt3.x)))
	        area = (area_triangle(x1, y1, pt3.x, pt3.y, pt4.x, pt4.y) +
		        pi - area_arc(pt3.x, pt3.y, pt4.x, pt4.y, 1.0))
	    else
	        area = (area_triangle(x1, y1, pt3.x, pt3.y, pt4.x, pt4.y) +
		        area_arc(pt3.x, pt3.y, pt4.x, pt4.y, 1.0))
	    end
        else
            # ensure that pt1 is the point closest to (x2, y2)
	    if (((pt2.x-x2)*(pt2.x-x2) + (pt2.y-y2)*(pt2.y-y2)) <
	        ((pt1.x-x2)*(pt1.x-x2) + (pt1.y-y2)*(pt1.y-y2)))
                pt1, pt2 = pt2, pt2
            end
	    area = (area_triangle(x1, y1, pt3.x, pt3.y, pt1.x, pt1.y) +
		    area_triangle(x1, y1, pt1.x, pt1.y, pt2.x, pt2.y) +
		    area_triangle(x1, y1, pt2.x, pt2.y, pt4.x, pt4.y) +
		    area_arc(pt1.x, pt1.y, pt3.x, pt3.y, 1.0) +
		    area_arc(pt2.x, pt2.y, pt4.x, pt4.y, 1.0))
	end

    else
        inter = circle_segment(x1, y1, x2, y2)
        pt1 = inter.p1
        pt2 = inter.p2
        inter = circle_segment(x2, y2, x3, y3)
        pt3 = inter.p1
        pt4 = inter.p2
        inter = circle_segment(x3, y3, x1, y1)
        pt5 = inter.p1
        pt6 = inter.p2

        if (pt1.x <= 1.0)
	    xp = 0.5 * (pt1.x + pt2.x)
	    yp = 0.5 * (pt1.y + pt2.y)
	    area = (triangle_unitcircle_overlap(x1, y1, x3, y3, xp, yp) +
		    triangle_unitcircle_overlap(x2, y2, x3, y3, xp, yp))

        elseif (pt3.x <= 1.)
	    xp = 0.5 * (pt3.x + pt4.x)
	    yp = 0.5 * (pt3.y + pt4.y)
	    area = (triangle_unitcircle_overlap(x3, y3, x1, y1, xp, yp) +
		    triangle_unitcircle_overlap(x2, y2, x1, y1, xp, yp))

        elseif (pt5.x <= 1.)
	    xp = 0.5 * (pt5.x + pt6.x)
	    yp = 0.5 * (pt5.y + pt6.y)
	    area = (triangle_unitcircle_overlap(x1, y1, x2, y2, xp, yp) +
		    triangle_unitcircle_overlap(x3, y3, x2, y2, xp, yp))

        else  # no intersections
	  return in_triangle(0., 0., x1, y1, x2, y2, x3, y3) ? pi : 0.0
        end
    end

    return area
end

# exact overlap between a rectangle defined by (xmin, ymin, xmax,
# ymax) and an ellipse with major and minor axes rx and ry
# respectively and position angle theta.
function ellipoverlap(xmin, ymin, xmax, ymax, a, b, theta)
    cos_m_theta = cos(-theta)
    sin_m_theta = sin(-theta)

    scale = a * b  # areas will be shrunk by this scale

    # Reproject rectangle to a frame in which ellipse is a unit circle
    x1 = (xmin * cos_m_theta - ymin * sin_m_theta) / a
    y1 = (xmin * sin_m_theta + ymin * cos_m_theta) / b
    x2 = (xmax * cos_m_theta - ymin * sin_m_theta) / a
    y2 = (xmax * sin_m_theta + ymin * cos_m_theta) / b
    x3 = (xmax * cos_m_theta - ymax * sin_m_theta) / a
    y3 = (xmax * sin_m_theta + ymax * cos_m_theta) / b
    x4 = (xmin * cos_m_theta - ymax * sin_m_theta) / a
    y4 = (xmin * sin_m_theta + ymax * cos_m_theta) / b

    # Divide resulting quadrilateral into two triangles and find
    # intersection with unit circle
    return scale * (triangle_unitcircle_overlap(x1, y1, x2, y2, x3, y3) +
		    triangle_unitcircle_overlap(x1, y1, x4, y4, x3, y3))
end


module Clusters

export create_uniform_cluster_ncube, create_uniform_cluster_nsphere, compute_centroid

using LinearAlgebra, Plots

"""
    create_uniform_cluster_ncube(center, perturbation, num_points)

Creates a cluster of size `num_points`, where each point is uniformly randomly
selected from the volume of an n-cube centered at `center`, aligned with the
standard basis, and with edge length 2 * `perturbation`

Returns a `num_points` * length(`center`) matrix, where each row is a point in
the cluster.
"""
function create_uniform_cluster_ncube(center::AbstractVector{<:Number},
                                      perturbation::Number,
                                      num_points::Integer)::AbstractMatrix{<:AbstractFloat}
    # (num_points, 1)
    ones_vec = ones(Integer, (num_points, 1))
    # (1, length(center))
    center_T = transpose(center)
    # (num_points, length(center)), where each row is a copy of center
    center_vecs = ones_vec * center_T

    # Generate uniformly random values between [0, perturbation) and negate some
    # randomly in order to avoid floating point precision errors and preserve
    # symmetry of the distribution.
    random_perturbations =
        perturbation * rand(Float64, (num_points, length(center)))
    random_flips = rand([-1, 1], (num_points, length(center)))

    return center_vecs + (random_perturbations .* random_flips)
end

"""
    create_uniform_cluster_nsphere(center, perturbation, num_points)

Creates a cluster of size `num_points`, centered around `center`, where each
point is uniformly randomly selected from the volume of an n-sphere with radius
'perturbation'.

Returns a `num_points` * length(`center`) matrix, where each row is a point in
the cluster.
"""
function create_uniform_cluster_nsphere(center::AbstractVector{<:Number},
                                        perturbation::Number,
                                        num_points::Integer)::AbstractMatrix{<:AbstractFloat}
    # (num_points, 1)
    ones_vec = ones(Integer, (num_points, 1))
    # (1, length(center))
    center_T = transpose(center)
    # (num_points, length(center)), where each row is a copy of center
    center_vecs = ones_vec * center_T

    # num_points * length(center) standard normal random variables
    rand_vecs = randn(Float64, (num_points, length(center)))
    # Take the norm along each column.
    # num_points
    norms = map(LinearAlgebra.norm,
                [rand_vecs[n,:] for n in 1:size(rand_vecs, 1)])
    # num_points uniform in [0, 1)
    uniform_coeffs = rand(Float64, num_points)

    origin_cluster = perturbation *
        Diagonal(uniform_coeffs .^ (1/length(center))) *
        Diagonal(1 ./ norms) *
        rand_vecs

    return center_vecs + origin_cluster
end

"""
    compute_centroid(points)

Computes the centroid of a matrix of points.

`points` is an m * n matrix that describes m points (rows) of dimension n
(columns).

Returns a single vector of size `n` corresponding to the centroid.
"""
function compute_centroid(
        points::AbstractMatrix{<:Number})::AbstractVector{<:AbstractFloat}
    point_sum = sum(points, dims=1)[:]
    return point_sum ./ size(points, 1)
end

"""
    plot_cluster(points)

Plots a matrix of points, where each row represents the coordinates of a single
point.
"""
function plot_cluster(points::AbstractMatrix{<:Number})
    plt = plot([points[:,n] for n in 1:size(points, 2)]...,
               seriestype = :scatter,
               showaxis = :show,
               aspect_ratio = :equal)
    display(plt)
end

end # module


# # Usage Guide

# In this example, we will present the basics of using Ripserer. We start by loading some
# packages.

using Distances
using Plots
using Ripserer
using Random # hide
Random.seed!(1337) # hide
gr() # hide
nothing # hide

# ## Using Ripserer With Point Cloud Data

# Let's start with generating some points, randomly sampled from a noisy circle.

function noisy_circle(n; r=1, noise=0.1)
    points = NTuple{2,Float64}[]
    for _ in 1:n
        θ = 2π * rand()
        push!(points, (r * sin(θ) + noise * rand(), r * cos(θ) + noise * rand()))
    end
    return points
end

circ_100 = noisy_circle(100)
scatter(circ_100; aspect_ratio=1, legend=false, title="Noisy Circle")

# !!! tip "Point-like data types"
#     Ripserer can interpret various kinds of data as point clouds. The limitation is that
#     the data set should be an `AbstractVector` with elements with the following
#     properties:
#     * all elements are collections numbers;
#     * all elements have the same length.
#     Examples of element types that work are `Tuple`s,
#     [`SVector`](https://github.com/JuliaArrays/StaticArrays.jl)s, and
#     [`Point`](https://github.com/JuliaGeometry/GeometryBasics.jl)s.

# To compute the Vietoris-Rips persistent homology of this data set, run the
# following.

ripserer(circ_100)

# You can use the `dim_max` argument to set the maximum dimension persistent homology is
# computed in.

result_rips = ripserer(circ_100; dim_max=3)

# The result can be plotted as a persistence diagram or as a barcode.

plot(result_rips)
barcode(result_rips)
plot(plot(result_rips), barcode(result_rips)) # hide

# We can also plot a single diagram or a subset of all diagrams in the same manner. Keep in
# mind that the result is just a vector of [`PersistenceDiagram`](@ref)s. The
# zero-dimensional diagram is found at index 1.

plot(result_rips[2])
barcode(result_rips[2:end]; linewidth=2)
plot(plot(result_rips[2]), barcode(result_rips[2:end]; linewidth=2)) # hide

# Plotting can be further customized using the standard attributes from
# [Plots.jl](http://docs.juliaplots.org/latest/).

plot(result_rips; markeralpha=1, markershape=:star, color=[:red, :blue, :green, :purple])

# ## Changing Filtrations

# By default, calling [`ripserer`](@ref) will compute persistent homology with the
# [`Rips`](@ref) filtration. To use a different filtration, we have two options.

# The first option is to pass the filtration constructor as the first argument. Any keyword
# arguments the filtration accepts can be passed to [`ripserer`](@ref) and it will be
# forwarded to the constructor.

ripserer(EdgeCollapsedRips, circ_100; threshold=1, dim_max=3, metric=Euclidean())

# The second option is to initialize the filtration object first and use that as an argument
# to [`ripserer`](@ref). This can be useful in cases where constructing the filtration takes
# a long time.

collapsed_rips = EdgeCollapsedRips(circ_100; threshold=1, metric=Euclidean())
ripserer(collapsed_rips; dim_max=3)

# ## Distance Matrix Inputs

# In the previous example, we got our result by passing a collection of points to
# [`ripserer`](@ref). Under the hood, [`Rips`](@ref) and [`EdgeCollapsedRips`](@ref)
# actually work with distance matrices. Let's define a distance matrix of the shortest
# paths on a [regular icosahedron](https://en.wikipedia.org/wiki/Regular_icosahedron) graph.

# ```@raw html
# <img src="https://upload.wikimedia.org/wikipedia/commons/8/83/Icosahedron_graph.svg" height="200" width="200">
# ```

icosahedron = [
    0 1 2 2 1 2 1 1 2 2 1 3
    1 0 3 2 1 1 2 1 2 1 2 2
    2 3 0 1 2 2 1 2 1 2 1 1
    2 2 1 0 3 2 1 1 2 1 2 1
    1 1 2 3 0 1 2 2 1 2 1 2
    2 1 2 2 1 0 3 2 1 1 2 1
    1 2 1 1 2 3 0 1 2 2 1 2
    1 1 2 1 2 2 1 0 3 1 2 2
    2 2 1 2 1 1 2 3 0 2 1 1
    2 1 2 1 2 1 2 1 2 0 3 1
    1 2 1 2 1 2 1 2 1 3 0 2
    3 2 1 1 2 1 2 2 1 1 2 0
]
nothing # hide

# To compute the persistent homology, simply feed the distance matrix to [`ripserer`](@ref).

result_icosa = ripserer(icosahedron; dim_max=2)

# ## Thresholding

# In our next example, we will show how to use thresholding to speed up computation. We
# start by defining a sampling function that generates ``n`` points from the square
# ``[-4,4]\times[-4,4]`` with a circular hole of radius 1 in the middle.

function cutout(n)
    points = NTuple{2,Float64}[]
    while length(points) < n
        x, y = (8rand() - 4, 8rand() - 4)
        if x^2 + y^2 > 1
            push!(points, (x, y))
        end
    end
    return points
end

# We sample 2000 points from this space.

cutout_2000 = cutout(2000)
scatter(cutout_2000; markersize=1, aspect_ratio=1, legend=false, title="Cutout")

# We calculate the persistent homology and time the calculation.

@time result_cut = ripserer(cutout_2000)
nothing # hide

#

plot(result_cut)

# Notice that while there are many 1-dimensional classes, one of them stands out. This class
# represents the hole in the middle of our square. Since the intervals are sorted by
# persistence, we know the last interval in the diagram will be the most persistent.

most_persistent = result_cut[2][end]

# Notice the death time of this interval is around 1.83 and that no intervals occur after
# that time. This means that we could stop computing when we reach this time and the result
# should not change. Let's try it out.

@time result_cut_thresh_2 = ripserer(cutout_2000; threshold=2)
nothing # hide

#

plot(result_cut_thresh_2; title="Persistence Diagram, threshold=2")

# Indeed, the result is exactly the same, but it took less than a third of the time to
# compute.

@assert result_cut_thresh_2 == result_cut # hide
result_cut_thresh_2 == result_cut

# If we pick a threshold that is too low, we still detect the interval, but its death time
# becomes infinite.

@time result_cut_thresh_1 = ripserer(cutout_2000; threshold=1)
nothing # hide

#

result_cut_thresh_1[2][end]

# ## Persistence Diagrams

# The result of a computation is returned as a vector of
# [`PersistenceDiagram`](@ref)s. Let's take a closer look at one of those.

diagram = result_cut[2]

# The diagram is a structure that acts as a vector of
# [`PersistenceInterval`](@ref)s. As such, you can use standard Julia
# functions on the diagram.

# For example, to extract the last three intervals by birth time, you can do
# something like this.

sort(diagram; by=birth, rev=true)[1:3]

# To find the [`persistence`](@ref)s of all the intervals, you can use broadcasting.

persistence.(diagram)

# Unlike regular vectors, a [`PersistenceDiagram`](@ref) has additional metadata attached
# to it. To see all metadata, use
# [`propertynames`](https://docs.julialang.org/en/v1/base/base/#Base.propertynames).

propertynames(diagram)

# You can access the properties with the dot syntax.

diagram.field

# The attributes `dim` and `threshold` are given special treatment and can be extracted
# with appropriately named functions.

dim(diagram), threshold(diagram)

# Now, let's take a closer look at one of the intervals.

interval = diagram[end]

# An interval is very similar to a tuple of two `Float64`s, but also has some metadata
# associated with it.

interval[1], interval[2]

# [`birth`](@ref), [`death`](@ref), [`persistence`](@ref), and [`midlife`](@ref) can be used
# to query commonly used values.

birth(interval), death(interval), persistence(interval), midlife(interval)

# Accessing metadata works in a similar manner as with diagrams.

propertynames(interval)

#

interval.birth_simplex

#

interval.death_simplex

# ## Simplices

# In the previous section, we saw each interval has an associated [`birth_simplex`](@ref)
# and [`death_simplex`](@ref). These values are of the type [`Simplex`](@ref). Let's take a
# closer look at simplices.

simplex = interval.death_simplex

# [`Simplex`](@ref) is an internal data structure that uses some tricks to increase
# efficiency. For example, if we were to
# [`dump`](https://docs.julialang.org/en/v1/base/io-network/#Base.dump) it, we notice the
# vertices are not actually stored in the simplex itself.

dump(simplex)

# To access the vertices, we use [`vertices`](@ref).

vertices(simplex)

# Other useful attributes a simplex has are [`index`](@ref), [`dim`](@ref), and
# [`birth`](@ref).

index(simplex), dim(simplex), birth(simplex)

# A few additional notes on simplex properties.

# * A `D`-dimensional simplex is of type `Simplex{D}` and has `D + 1` vertices.
# * [`vertices`](@ref) are always sorted in descending order.
# * [`index`](@ref) and [`dim`](@ref) can be used to uniquely identify a given simplex.
# * [`birth`](@ref) determines when a simplex is added to a filtration.

# ## Conclusion

# This concludes the basic usage of Ripserer. For more detailed information, please check
# out the [API](@ref) page, as well as other examples.


# ---
# title: 134. Gas Station
# id: problem134
# author: AquaIndigo
# date: 2020-11-18
# difficulty: Medium
# categories: Greedy
# link: <https://leetcode.com/problems/gas-station/description/>
# hidden: true
# ---
#
# There are _N_ gas stations along a circular route, where the amount of gas at
# station _i_ is `gas[i]`.
#
# You have a car with an unlimited gas tank and it costs `cost[i]` of gas to
# travel from station _i_ to its next station ( _i_ +1). You begin the journey
# with an empty tank at one of the gas stations.
#
# Return the starting gas station's index if you can travel around the circuit
# once in the clockwise direction, otherwise return -1.
#
# **Note:**
#
#   * If there exists a solution, it is guaranteed to be unique.
#   * Both input arrays are non-empty and have the same length.
#   * Each element in the input arrays is a non-negative integer.
#
# **Example 1:**
#
#
#
#     Input:
#     gas  = [1,2,3,4,5]
#     cost = [3,4,5,1,2]
#
#     Output: 3
#
#     Explanation: Start at station 3 (index 3) and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
#     Travel to station 4. Your tank = 4 - 1 + 5 = 8
#     Travel to station 0. Your tank = 8 - 2 + 1 = 7
#     Travel to station 1. Your tank = 7 - 3 + 2 = 6
#     Travel to station 2. Your tank = 6 - 4 + 3 = 5
#     Travel to station 3. The cost is 5. Your gas is just enough to travel back to station 3.
#     Therefore, return 3 as the starting index.
#
#
# **Example 2:**
#
#
#
#     Input:
#     gas  = [2,3,4]
#     cost = [3,4,3]
#
#     Output: -1
#
#     Explanation: You can't start at station 0 or 1, as there is not enough gas to travel to the next station.
#     Let's start at station 2 and fill up with 4 unit of gas. Your tank = 0 + 4 = 4
#     Travel to station 0. Your tank = 4 - 3 + 2 = 3
#     Travel to station 1. Your tank = 3 - 3 + 3 = 3
#     You cannot travel back to station 2, as it requires 4 unit of gas but you only have 3.
#     Therefore, you can't travel around the circuit once no matter where you start.
#
#
#
## @lc code=start
using LeetCode

function can_complete_circuit(gas::Vector{Int}, cost::Vector{Int})
    gas .-= cost
    accumulate!(+, gas, gas)
    if gas[end] < 0
        return -1
    end
    return argmin(gas) % length(gas)
end
## @lc code=end


###################################################
# METODO DO GRADIENTE, COMO DESCRITO NO SLIDE 29 DE
#   https://leonardosecchin.github.io/files/otim1/4.1.Metodos_descida_gerais.pdf
# Autor: Leonardo D. Secchin
# Data : 31/01/2021
#
# Exemplos de uso:
#   gradiente(nlp);
#   gradiente(nlp, x0=[1;1]);
#   gradiente(nlp, eps=1e-8);
#   x, f, gradnorm, iter, status = gradiente(nlp, x0=[1;1], eps=1e-8, maxiter=2000, saidas=true);
# onde 'nlp' é a estrutura MathOptNLPModel do problema.
###################################################

# carrega pacotes necessários
using JuMP, NLPModels, NLPModelsJuMP, Printf, LinearAlgebra

# FUNÇÃO PRINCIPAL
function gradiente(nlp; x0=nothing, eps=1.0e-6, maxiter=1000, saidas=true)

    # DADOS DE ENTRADA
    # nlp     : estrutura MathOptNLPModel do problema
    # x0      : ponto inicial (opcional)
    # eps     : precisão para convergencia (opcional, padrão=1.0e-6)
    # maxiter : número máximo de iterações (opcional, padrão=1000)
    # saidas  : mensagens na tela?

    # DADOS DE SAÍDA
    # x       : último iterando
    # f       : f(x)
    # gradnorm: norma do infinito do gradiente de f
    # iter    : número de iterações
    # status  : 0=sucesso, 1=falha

    iter   = 0
    status = 1

    # parâmetro busca linear inexata (Armijo)
    eta = 0.5

    # define ponto inicial caso não fornecido
    if x0 == nothing
        # captura ponto inicial da estrutura nlp
        if nlp.meta.x0 != nothing
            x0 = nlp.meta.x0
        else
            # caso não exista, seta x0=origem
            x0 = zeros(Float64,nlp.meta.nvar)
        end
    end

    # f, gradiente e norma
    x        = float(x0)
    f        = obj(nlp, x)
    gradf    = grad(nlp, x)
    gradnorm = norm(gradf, Inf)

    # imprimi cabeçalho saídas
    if saidas
        @printf("\nit     \tf         |grad|\n==========================")
        @printf("\n%d\t%8.2e  %8.2e", iter, f, gradnorm)
    end

    while gradnorm > eps && iter < maxiter
        # direção descida
        d = -gradf

        # retorna novo iterando após busca linear (Armijo)
        x, f = armijo(nlp, x, f, gradf, d, eta)

        # atualiza dados do iterando
        gradf    = grad(nlp, x)
        gradnorm = norm(gradf, Inf)
        iter += 1

        # imprimi iteração corrente
        if saidas
            if mod(iter,20) == 0
                @printf("\n\nit     \tf         |grad|\n==========================")
            end
            @printf("\n%d\t%8.2e  %8.2e", iter, f, gradnorm)
        end
    end

    # status de sucesso
    if gradnorm <= eps
        status = 0
    end

    # saída
    if saidas
        println("\n\n*******************************")
        if status == 0
            println("PROBLEMA RESOLVIDO COM SUCESSO!")
        else
            println("FALHA NA RESOLUÇÃO.")
        end
        println("*******************************\n")
    end

    return x, f, gradnorm, iter, status
end

# BUSCA LINEAR
function armijo(nlp, x, f, gradf, d, eta)

    # calcula gradf' * d
    gtd = gradf' * d

    # passo inicial
    t = 1

    # Armijo com backtracking
    xnew = x + t*d
    fnew = obj(nlp, xnew)

    while t > 1e-20 && fnew > f + t*eta*gtd
        # divide t por 10
        t /= 10.0

        # nova tentativa
        xnew = x + t*d
        fnew = obj(nlp, xnew)
    end

    # retorna novo iterando
    return xnew, fnew
end


"""
    alpha(t::Real, lastspike, q, tau)
    alpha(t::Real, lastspike::AbstractArray{<:Real}, q::AbstractArray{<:Real}, tau::AbstractArray{<:Real})
    alpha(t::Real, lastspike::CuVecOrMat{<:Real}, q::CuVecOrMat{<:Real}, tau::CuVecOrMat{<:Real})

Evaluate an alpha synapse. Modeled as `(t - lastspike) * (q / τ) * exp(-(t - lastspike - τ) / τ) Θ(t - lastspike)`
  (where `Θ` is the Heaviside function).
Use `CuVector` instead of `Vector` for GPU support.

# Fields
- `t`: current time
- `lastspike`: last pre-synaptic spike time
- `q`: amplitude
- `tau`: time constant
"""
function alpha(t::Real, lastspike, q, tau)
    Δ = t - lastspike

    return (Δ >= 0 && Δ < Inf) * Δ * (q / tau) * exp(-(Δ - tau) / tau)
end
function alpha(t::Real, lastspike::AbstractArray{<:Real}, q::AbstractArray{<:Real}, tau::AbstractArray{<:Real})
    Δ = t .- lastspike
    I = @. Δ * (q / tau) * exp(-(Δ - tau) / tau)

    return map((δ, i) -> (δ >= 0) && (δ < Inf) ? δ * i : zero(i), Δ, I)
end
function alpha(t::Real, lastspike::CuVecOrMat{<:Real}, q::CuVecOrMat{<:Real}, tau::CuVecOrMat{<:Real})
    Δ = t .- lastspike

    return @. (Δ >= 0) * (Δ < Inf) * Δ * (q / tau) * exp(-(Δ - tau) / tau)
end


function gausshermite( n::Integer )
    x,w = unweightedgausshermite(n)
    w .*= exp.(-x.^2)
    x, w
end
function unweightedgausshermite( n::Integer )
    # GAUSSHERMITE(n) COMPUTE THE GAUSS-HERMITE NODES AND WEIGHTS IN O(n) time.
    if n < 0
        x = (Float64[],Float64[])
        return x
    elseif n == 0
        x = (Float64[],Float64[])
        return x
    elseif n == 1
        x = ([0.0],[sqrt(pi)])
        return x
    elseif n <= 20
       # GW algorithm
       x = hermpts_gw( n )
    elseif n <= 200
       # REC algorithm
       x = hermpts_rec( n )
    else
       # ASY algorithm
       x = hermpts_asy( n )
    end

    if mod(n,2) == 1                              # fold out
        w = [flipdim(x[2][:],1); x[2][2:end]]
        x = [-flipdim(x[1],1) ; x[1][2:end]]
    else
        w = [flipdim(x[2][:],1); x[2][:]]
        x = [-flipdim(x[1],1) ; x[1]]
    end
    w .*= sqrt(π)/sum(exp.(-x.^2).*w)
    (x, w)
end

function hermpts_asy( n::Integer )
    # Compute Hermite nodes and weights using asymptotic formula

    x0 = HermiteInitialGuesses( n ) # get initial guesses
    t0 = x0./sqrt(2n+1)
    theta0 = acos.(t0)               # convert to theta-variable
    val = x0;
    for k = 1:20
        val = hermpoly_asy_airy(n, theta0);
        dt = -val[1]./(sqrt(2).*sqrt(2n+1).*val[2].*sin.(theta0))
        theta0 .-= dt;                        # Newton update
        if norm(dt,Inf) < sqrt(eps(Float64))/10
           break
        end
    end
    t0 = cos.(theta0)
    x = sqrt(2n+1)*t0                          #back to x-variable
    w = x.*val[1] .+ sqrt(2).*val[2]
    w .= 1 ./ w.^2;            # quadrature weights

    (x, w)
end

function hermpts_rec( n::Integer )
    # Compute Hermite nodes and weights using recurrence relation.

    x0 = HermiteInitialGuesses( n )
    x0 .*= sqrt(2)
    val = x0
    for _ = 1:10
        val = hermpoly_rec.(n, x0)
        dx = first.(val)./last.(val)
        dx[ isnan.( dx ) ] .= 0
        x0 .= x0 .- dx
        if norm(dx, Inf)<sqrt(eps(Float64))
            break
        end
    end
    x0 ./= sqrt(2)
    w = 1 ./ last.(val).^2           # quadrature weights

    x = (x0, w)
end

function hermpoly_rec( n::Integer, x0)
    # HERMPOLY_rec evaluation of scaled Hermite poly using recurrence
    n < 0 && throw(ArgumentError("n = $n must be positive"))
    # evaluate:
    w = exp(-x0^2 / (4*n))
    wc = 0 # 0 times we've applied wc
    Hold = one(x0)
    # n == 0 && return (Hold, 0)
    H = x0
    for k = 1:n-1
        Hold, H = H, (x0*H/sqrt(k+1) - Hold/sqrt(1+1/k))
        while abs(H) ≥ 100 && wc < n # regularise
            H *= w
            Hold *= w
            wc += 1
        end
        k += 1
    end
    for _ = wc+1:n
        H *= w
        Hold *= w
    end

    # return (value, derivative):
    val = (H, -x0*H + sqrt(n)*Hold)
end

function hermpoly_rec( r::Base.OneTo, x0)
    isempty(r) && return [1.0]
    n = maximum(r)
    # HERMPOLY_rec evaluation of scaled Hermite poly using recurrence
    n < 0 && throw(ArgumentError("n = $n must be positive"))
    n == 0 && return [exp(-x0^2 / 4)]
    p = max(1,floor(Int,x0^2/100))
    w = exp(-x0^2 / (4*p))
    wc = 0 # 0 times we've applied wc
    ret = Vector{Float64}()
    Hold = one(x0)
    push!(ret, Hold)
    H = x0
    push!(ret, H)
    for k = 1:n-1
        Hold, H = H, (x0*H/sqrt(k+1) - Hold/sqrt(1+1/k))
        while abs(H) ≥ 100 && wc < p # regularise
            ret .*= w
            H *= w
            Hold *= w
            wc += 1
        end
        push!(ret, H)
        k += 1
    end
    ret .*= w^(p-wc)

    ret
end

hermpoly_rec( r::AbstractRange, x0) = hermpoly_rec(Base.OneTo(maximum(r)), x0)[r.+1]

function hermpoly_asy_airy(n::Integer, theta)
    # HERMPOLY_ASY evaluation hermite poly using Airy asymptotic formula in
    # theta-space.

    musq = 2n+1;
    cosT = cos.(theta)
    sinT = sin.(theta)
    sin2T = 2 .* cosT.*sinT
    eta = 0.5 .* theta .- 0.25 .* sin2T
    chi = -(3*eta/2).^(2/3)
    phi = (-chi./sinT.^2).^(1/4)
    C = 2*sqrt(pi)*musq^(1/6)*phi
    Airy0 = real.(airyai.(musq.^(2/3).*chi))
    Airy1 = real.(airyaiprime.(musq.^(2/3).*chi))

    # Terms in (12.10.43):
    a0 = 1; b0 = 1
    a1 = 15/144; b1 = -7/5*a1
    a2 = 5*7*9*11/2/144^2; b2 = -13/11*a2
    a3 = 7*9*11*13*15*17/6/144^3
    b3 = -19/17*a3

    # u polynomials in (12.10.9)
    u0 = 1; u1 = (cosT.^3-6*cosT)/24
    u2 = @. (-9*cosT^4 + 249*cosT^2 + 145)/1152
    u3 = @. (-4042*cosT^9+18189*cosT^7-28287*cosT^5-151995*cosT^3-259290*cosT)/414720

    #first term
    A0 = 1
    val = A0*Airy0

    #second term
    B0 = @. -(a0*phi^6 * u1+a1*u0)/chi^2
    val .+=  B0.*Airy1./musq.^(4/3)

    # third term
    A1 = @. (b0*phi^12 * u2 + b1*phi^6 * u1 + b2*u0)/chi^3
    val .+= A1.*Airy0/musq.^2

    # fourth term
    B1 = @. -(phi^18 * u3 + a1*phi^12 * u2 + a2*phi^6 * u1 + a3*u0)/chi^5
    val .+= B1.*Airy1./musq.^(4/3+2)

    val .= C.*val

    ## Derivative

    eta = .5*theta - .25*sin2T
    chi = -(3*eta/2).^(2/3)
    phi = (-chi./sinT.^2).^(1/4)
    C = sqrt(2*pi)*musq^(1/3)./phi

    # v polynomials in (12.10.10)
    v0 = 1;
    v1 = @. (cosT^3+6*cosT)/24
    v2 = @. (15*cosT^4-327*cosT^2-143)/1152
    v3 = @. (259290*cosT + 238425*cosT^3 - 36387*cosT^5 + 18189*cosT^7 - 4042*cosT^9)/414720

    # first term
    C0 = -(b0*phi.^6 .* v1 .+ b1.*v0)./chi
    dval = C0.*Airy0/musq.^(2/3)

    # second term
    D0 =  a0*v0
    dval = dval + D0*Airy1

    # third term
    C1 = @. -(phi^18 * v3 + b1*phi^12 * v2 + b2*phi^6 * v1 + b3*v0)/chi^4
    dval = dval + C1.*Airy0/musq.^(2/3+2)

    #fourth term
    D1 = @. (a0*phi^12 * v2 + a1*phi^6 * v1 + a2*v0)/chi^3
    dval = dval + D1.*Airy1/musq.^2

    dval = C.*dval

    val = (val, dval)
end

let T(t) = @. t^(2/3)*(1+5/48*t^(-2)-5/36*t^(-4)+(77125/82944)*t^(-6) -108056875/6967296*t^(-8)+162375596875/334430208*t^(-10))
    global function HermiteInitialGuesses( n::Integer )
        #HERMITEINTITIALGUESSES(N), Initial guesses for Hermite zeros.
        #
        # [1] L. Gatteschi, Asymptotics and bounds for the zeros of Laguerre
        # polynomials: a survey, J. Comput. Appl. Math., 144 (2002), pp. 7-27.
        #
        # [2] F. G. Tricomi, Sugli zeri delle funzioni di cui si conosce una
        # rappresentazione asintotica, Ann. Mat. Pura Appl. 26 (1947), pp. 283-300.

        # Error if n < 20 because initial guesses are based on asymptotic expansions:
        @assert n>=20

        # Gatteschi formula involving airy roots [1].
        # These initial guess are good near x = sqrt(n+1/2);
        if mod(n,2) == 1
            m = (n-1)>>1
            bess = (1:m)*pi
            a = .5
        else
            m = n>>1
            bess = ((0:m-1) .+ 0.5)*pi
            a = -.5
        end
        nu = 4*m + 2*a + 2

        airyrts = -T(3/8*pi*(4*(1:m) .- 1))

        airyrts_exact = [-2.338107410459762           # Exact Airy roots.
            -4.087949444130970
            -5.520559828095555
            -6.786708090071765
            -7.944133587120863
            -9.022650853340979
            -10.040174341558084
            -11.008524303733260
            -11.936015563236262
            -12.828776752865757]
        airyrts[1:10] = airyrts_exact  # correct first 10.

        x_init = sqrt.(abs.(nu .+ (2^(2/3)).*airyrts.*nu^(1/3) .+ (1/5*2^(4/3)).*airyrts.^2 .* nu^(-1/3) .+
            (11/35-a^2-12/175).*airyrts.^3 ./ nu .+ ((16/1575).*airyrts.+(92/7875).*airyrts.^4).*2^(2/3).*nu^(-5/3) .-
            ((15152/3031875).*airyrts.^5 .+ (1088/121275).*airyrts.^2).*2^(1/3).*nu^(-7/3)))
        x_init_airy = real( flipdim(x_init,1) )

        # Tricomi initial guesses. Equation (2.1) in [1]. Originally in [2].
        # These initial guesses are good near x = 0 . Note: zeros of besselj(+/-.5,x)
        # are integer and half-integer multiples of pi.
        # x_init_bess =  bess/sqrt(nu).*sqrt((1+ (bess.^2+2*(a^2-1))/3/nu^2) );
        Tnk0 = fill(pi/2,m)
        nu = (4*m+2*a+2)
        rhs = ((4*m+3) .- 4*(1:m))/nu*pi

        for k = 1:7
            val = Tnk0 .- sin.(Tnk0) .- rhs
            dval = 1 .- cos.(Tnk0)
            dTnk0 = val./dval
            Tnk0 = Tnk0 .- dTnk0
        end

        tnk = cos.(Tnk0./2).^2
        x_init_sin = @. sqrt(nu*tnk - (5 / (4 * (1-tnk)^2) - 1 / (1 - tnk)-1 + 3*a^2)/3 / nu)

        # Patch together
        p = 0.4985+eps(Float64)
        x_init = [x_init_sin[1:convert(Int,floor(p*n))] ;
        x_init_airy[convert(Int,ceil(p*n)):end]]

        if mod(n, 2) == 1
            x_init = [0 ; x_init]
            x_init = x_init[1:m+1]
        else
            x_init = x_init[1:m]
        end

        return x_init
    end
end

function hermpts_gw( n::Integer )
    # Golub--Welsch algorithm. Used here for n<=20.

    beta = sqrt.(0.5 .* (1:n-1))              # 3-term recurrence coeffs
    T = SymTridiagonal(zeros(n), beta)  # Jacobi matrix
    (D, V) = eigen(T)                      # Eigenvalue decomposition
    indx = sortperm(D)                  # Hermite points
    x = D[indx]
    w = sqrt(pi)*V[1,indx].^2            # weights

    # Enforce symmetry:
    ii = floor(Int, n/2)+1:n
    x = x[ii]
    w = w[ii]
    return (x,exp.(x.^2).*w)
end


function gmmresults()
    # example of GMM: draws from N(0,1)
    y = randn(1000,1)
    # 3 moment conditions
    moments = theta -> [y.-theta[1] (y.^2.0).-theta[2] (y.-theta[1]).^3.0]
    # first round consistent
    W = Matrix{Float64}(I,3,3)
    theta = [0.0, 1.0]
    thetahat, objvalue, D, ms, converged = gmm(moments, theta, W)
    # second round efficient
    W = inv(cov(ms))
    gmmresults(moments, thetahat, W, "GMM example, two step");
    # CUE
    gmmresults(moments, thetahat, "", "GMM example, CUE");
    return
end

function gmmresults(moments, theta, weight, title="", names="", efficient=true)
    n,g = size(moments(theta))
    if weight !="" # if weight provided, use it
        thetahat, objvalue, D, ms, converged = gmm(moments, theta, weight)
    else # do CUE
        thetahat, objvalue, D, ms, converged = gmm(moments, theta)
        weight = inv(NeweyWest(ms))
    end
    k,g = size(D)
    # estimate asymptotic variance
    V = inv(D*weight*D')
    if !efficient
        omega = NeweyWest(ms)
        V = V*D*weight*omega*weight*D'*V
    end
    V = V/n # adapt to sample size, for inference
    if names==""
        names = 1:k
        names = names'
    end
    se = sqrt.(diag(V))
    t = thetahat ./ se
    p = 2.0 .- 2.0*cdf.(Ref(TDist(n-k)),abs.(t))
    PrintDivider()
    if title !="" printstyled(title, color=:yellow); println() end
    print("GMM Estimation Results    Convergence: ")
    printstyled(converged, color=:green)
    println()
    println("Observations: ", n)
    println("Hansen-Sargan statistic: ", round(n*objvalue, digits=5))
    if g > k
        println("Hansen-Sargan p-value: ", round(1.0 - cdf(Chisq(g-k),n*objvalue), digits=5))
    end
    a =[thetahat se t p]
    println("")
    PrintEstimationResults(a, names)
    println()
    PrintDivider()
    return thetahat, objvalue, V, converged
end


export padm

const PADE_COEFFS = convert(Vector{Float64}, [1//1,
                              1//2,
                              8188362958855447//72057594037927936,
                              8734253822779143//576460752303423488,
                              2911417940926381//2305843009213693952,
                              145570897046319//2305843009213692962,
                              2797136075159//1860878688081779609])

"""
    padm(A; p=6)

Calculate matrix exponential using Pade approximants.

`padm` uses the irreducible (p, p)-degree rational Pade approximation to the
exponential function. The result is always a dense matrix.

# Input

- `A` -- matrix which can be dense or sparse
- `p` -- (optional, default: 6) degree of the rational Pade approximation to
         the exponential function

# Notes

This Julia implementation originated from Expokit's PADM Matlab code by
Roger B. Sidje, see below.

---

  E = padm( A, p )
  PADM computes the matrix exponential exp(A) using the irreducible
  (p,p)-degree rational Pade approximation to the exponential function.

  E = padm( A )
  p is internally set to 6 (recommended and generally satisfactory).

  See also CHBV, EXPOKIT and the MATLAB supplied functions EXPM and EXPM1.

  Roger B. Sidje (rbs@maths.uq.edu.au)
  EXPOKIT: Software Package for Computing Matrix Exponentials.
  ACM - Transactions On Mathematical Software, 24(1):130-156, 1998
"""
function padm(A; p::Int64=6)

    # Pade coefficients
    if p == 6
        c = copy(PADE_COEFFS)
    else
        c = Float64[]
        push!(c, 1.0)
        @inbounds for k = 1:p
            push!(c, c[end] * ((p+1-k)/(k*(2*p+1-k))))
        end
    end

    # scaling
    normA = opnorm(A, Inf)
    s = 0
    if normA > 0.5
        s = max(0, round(Int64, log(normA)/log(2), RoundToZero) + 2)
        A = A * 2.0^(-s) # scale!(A, 2.0^(-s))
    end

    # Horner evaluation of the irreducible fraction
    A2 = A * A
    Q = c[p+1]*Matrix{eltype(A)}(I, size(A))
    P = c[p]*Matrix{eltype(A)}(I, size(A))
    odd = 1
    @inbounds begin
        for k = p-1:-1:1
            if odd == 1
                Q = Q * A2 + c[k] * I
            else
                P = P * A2 + c[k] * I
            end
            odd = 1 - odd
        end
    end

    if odd == 1
        Q = Q * A
        Q = Q - P
        E = -(I + 2 * \(Q, Matrix(P)))
    else
        P = P * A
        Q = Q - P
        E = I + 2 * \(Q, Matrix(P))
    end

    # squaring
    @inbounds begin
        for k = 1:s
            E = E * E
        end
    end

    return E

end # padm


# indicator of the L1 norm ball with given radius

export IndBallL1

"""
**Indicator of a ``L_1`` norm ball**

    IndBallL1(r=1.0)

Returns the indicator function of the set
```math
S = \\left\\{ x : ∑_i |x_i| \\leq r \\right\\}.
```
Parameter `r` must be positive.
"""
struct IndBallL1{R <: Real} <: ProximableFunction
  r::R
  function IndBallL1{R}(r::R) where {R <: Real}
    if r <= 0
      error("parameter r must be positive")
    else
      new(r)
    end
  end
end

is_convex(f::IndBallL1) = true
is_set(f::IndBallL1) = true

IndBallL1(r::R=1.0) where {R <: Real} = IndBallL1{R}(r)

function (f::IndBallL1)(x::AbstractArray{T}) where T <: RealOrComplex
  if norm(x,1) - f.r > 1e-12
    return +Inf
  end
  return zero(T)
end

function prox!(y::AbstractArray{T}, f::IndBallL1, x::AbstractArray{T}, gamma::R=one(R)) where {R<: Real, T <: RealOrComplex{R}}
  # TODO: a faster algorithm
  if norm(x,1) - f.r < 1e-14
    y .= x
    return zero(T)
  else # do a projection of abs(x) onto simplex then recover signs
    n = length(x)
    p = abs.(view(x,:))
    sort!(p, rev=true)
    s = zero(R)
    @inbounds for i = 1:n-1
      s = s + p[i]
      tmax = (s - f.r)/i
      if tmax >= p[i+1]
        @inbounds for j in eachindex(x)
          y[j] = sign(x[j])*max(abs(x[j])-tmax, zero(R))
        end
        return zero(T)
      end
    end
    tmax = (s + p[n] - f.r)/n
    @inbounds for j in eachindex(x)
      y[j] = sign(x[j])*max(abs(x[j])-tmax, zero(R))
    end
    return zero(T)
  end
end

fun_name(f::IndBallL1) = "indicator of an L1 norm ball"
fun_dom(f::IndBallL1) = "AbstractArray{Real}, AbstractArray{Complex}"
fun_expr(f::IndBallL1) = "x ↦ 0 if ‖x‖_1 ⩽ r, +∞ otherwise"
fun_params(f::IndBallL1) = "r = $(f.r)"

function prox_naive(f::IndBallL1, x::AbstractArray{T}, gamma::Real=1.0) where T <: RealOrComplex
  # do a simple bisection (aka binary search) on λ
  L = 0.0
  U = maximum(abs, x)
  λ = L
  v = 0.0
  maxit = 120
  for iter in 1:maxit
    λ = 0.5*(L + U)
    v = sum(max.(abs.(x) .- λ, 0.0))
    # modify lower or upper bound
    (v < f.r) ? U = λ : L = λ
    # exit condition
    if abs(L - U) < 1e-15
      break
    end
  end
  return sign.(x) .* max.(0.0, abs.(x) .- λ), 0.0
end


abstract IndexFormula{T<:Number}

"""
refindex(formula, λ_μm) computes the refractive index for a given wavelength in μm.
"""
refindex{T}(::IndexFormula{T}, λ_μm::T) = zero(T);

type SellmeierFormula{T<:Number} <: IndexFormula{T}
  n::Int8
  A::T
  B::Array{T,1}
  C::Array{T,1}
  λn::Bool
  function SellmeierFormula(A,B,C,λn=true)
    n = length(B)
    if length(C) != n
      error("Coefficients B and C of different length")
    else
      new(n,A,B,C,λn)
    end
  end
end

function refindex{T}(formula::SellmeierFormula{T}, λ_μm::T)
  nsq=one(T)+formula.A
  λsq = λ_μm^2
  for i = 1:formula.n
    num = i==formula.n && !formula.λn ? formula.B[i] : formula.B[i] * λsq
    nsq += num/(λsq - formula.C[i])
  end
  return sqrt(nsq)
end

bk7 = SellmeierFormula{Float64}(0, [1.03961212, 0.231792344, 1.01046945], [6.00069867e-3, 2.00179144e-2, 1.03560653e2])
refindex(bk7,0.5)

using Gadfly
plot(x->refindex(bk7,x),0.21,1.6)


########################################################################
function ppca(X; Q=1)
########################################################################

    D, N = size(X)

    μ = vec(mean(X, dims=2))

    JITTER = 1e-8

    @printf("Running PPCA for %d number of data items of dimension %d, projected to %d components\n", N, D, Q)

    @printf("\t Fixing jitter to %f\n", JITTER)

    #---------------------------------------------
    function marginalLogLikelihood(W, σ)
    #---------------------------------------------

        sum(logpdf(MvNormal(μ, W*W' + σ*σ*I + JITTER*I), X))

    end

    #---------------------------------------------
    function unpack(param)
    #---------------------------------------------

        local W = reshape(param[1:end-1], D, Q)

        local σ = exp(param[end]) + 1e-8

        return W, σ

    end

    #---------------------------------------------
    function objective(param)
    #---------------------------------------------

        @assert(length(param) == D*Q + 1)

        local W, σ = unpack(param)

        try

            return -1.0 * marginalLogLikelihood(W, σ)

        catch err

            if isa(err, PosDefException)

                @warn("covariance is not positive definite, returning Inf")

                return Inf

            else

                throw(err)

            end

        end

    end

    #---------------------------------------------
    # Run optimiser
    #---------------------------------------------

    opt    = Optim.Options(show_trace = true, iterations = 100_000)

    result = optimize(objective, [randn(D*Q); randn()*3], NelderMead())# LBFGS(), opt, autodiff=:forward)

    W, σ   = unpack(result.minimizer)

    # @show W, μ, σ
    #
    # #---------------------------------------------
    # # Define projections
    # #---------------------------------------------
    #
    # M = W'*W  + σ*σ*I # eq. (12.41) in Bishop
    #
    # proj(x::Array{T,1} where T<:Real) = vec(M \ (W'*(x - μ)))[1]
    #
    # fwd(z::Real) = vec(W*z + μ)

    return W, μ, σ#, proj, fwd

end


"""
    QuadraticFit{T} <: UnivariateAlgorithm

The quadratic fit algorithm constructs a quadratic function from three points at each
iteration as an approximation to the objective. The analytical solution to the quadratic
model is examined as the new minimizer candidate to update the three points.
"""
struct QuadraticFit{T} <: UnivariateAlgorithm
    ϵ::T

    function QuadraticFit{T}(ϵ) where {T}
        0 < ϵ < 1 ? new{T}(ϵ) : error("`ϵ` must be in the interval (0, 1)")
    end
end

"""
    QuadraticFit([ϵ=0.01])

Initialize quadratic fit search algorithm with optional argument `ϵ` in the interval
(0, 1) to choose a midpoint as `mid = ϵ * lower + (1 - ϵ) * upper`.
"""
QuadraticFit(ϵ=0.5) = QuadraticFit{typeof(ϵ)}(ϵ)

function _optimize(f, lower::T, upper::T, alg::QuadraticFit;
        reltol, abstol, maxiter) where {T}
    ϵ = alg.ϵ
    mid = T(ϵ * lower + (1 - ϵ) * upper)
    y_lower, y_upper, y_mid = f(lower), f(upper), f(mid)
    converged = false
    iter = 0
    x = T(NaN)
    yx = (typeof(y_lower))(NaN)
    while iter < maxiter
        iter += 1
        x = T(0.5 * (y_lower * (mid^2 - upper^2) + y_mid * (upper^2 - lower^2) +
            y_upper * (lower^2 - mid^2)) /
            (y_lower * (mid - upper) + y_mid * (upper - lower) + y_upper * (lower - mid)))
        yx = f(x)
        x_tol = reltol * abs(x) + abstol
        if max(upper - x, x - lower) < 2x_tol
            converged = true
            break
        end
        if x > mid
            if yx > y_mid
                upper, y_upper = x, yx
            else
                lower, y_lower, mid, y_mid = mid, y_mid, x, yx
            end
        elseif x < mid
            if yx > y_mid
                lower, y_lower = x, yx
            else
                upper, y_upper, mid, y_mid = mid, y_mid, x, yx
            end
        end
    end
    return Solution(converged, iter, x, yx)
end


"""
        topsis(decisionMat, weights, fns)

Apply TOPSIS (Technique for Order of Preference by Similarity to Ideal Solution) method
for a given matrix and weights.

# Arguments:
 - `decisionMat::DataFrame`: n × m matrix of objective values for n candidate (or strategy) and m criteria
 - `weights::Array{Float64, 1}`: m-vector of weights that sum up to 1.0. If the sum of weights is not 1.0, it is automatically normalized.
 - `fns::Array{Function, 1}`: m-vector of function that are either minimize or maximize.

# Description
topsis() applies the TOPSIS method to rank n strategies subject to m criteria which are supposed to be either maximized or minimized.

# Output
- `::TopsisResult`: TopsisResult object that holds multiple outputs including scores and best index.

# Examples
```julia-repl
julia> df = DataFrame();
julia> df[:, :x] = Float64[9, 8, 7];
julia> df[:, :y] = Float64[7, 7, 8];
julia> df[:, :z] = Float64[6, 9, 6];
julia> df[:, :q] = Float64[7, 6, 6];

julia> w = Float64[4, 2, 6, 8];

julia> df
3×4 DataFrame
 Row │ x        y        z        q
     │ Float64  Float64  Float64  Float64
─────┼────────────────────────────────────
   1 │     9.0      7.0      6.0      7.0
   2 │     8.0      7.0      9.0      6.0
   3 │     7.0      8.0      6.0      6.0

julia> fns = makeminmax([maximum, maximum, maximum, maximum]);
julia> result = topsis(df, w, fns);

julia> result.bestIndex
2

julia> result.scores
3-element Array{Float64,1}:
 0.38768695492211824
 0.6503238218850163
 0.08347670030339041
```

# References
Hwang, C.L.; Yoon, K. (1981). Multiple Attribute Decision Making: Methods and Applications. New York: Springer-Verlag

Celikbilek Yakup, Cok Kriterli Karar Verme Yontemleri, Aciklamali ve Karsilastirmali
Saglik Bilimleri Uygulamalari ile. Editor: Muhlis Ozdemir, Nobel Kitabevi, Ankara, 2018

İşletmeciler, Mühendisler ve Yöneticiler için Operasyonel, Yönetsel ve Stratejik Problemlerin
Çözümünde Çok Kriterli Karar verme Yöntemleri, Editörler: Bahadır Fatih Yıldırım ve Emrah Önder,
Dora, 2. Basım, 2015, ISBN: 978-605-9929-44-8
"""
    function topsis(decisionMat::DataFrame, weights::Array{Float64,1}, fns::Array{Function,1})::TopsisResult

    w = unitize(weights)
    nalternatives, ncriteria = size(decisionMat)

    normalizedMat = normalize(decisionMat)

    weightednormalizedMat = w * normalizedMat

    # col_max = colmaxs(weightednormalizedMat)
    # col_min = colmins(weightednormalizedMat)
    col_max = apply_columns(fns, weightednormalizedMat)
    col_min = apply_columns(reverseminmax(fns), weightednormalizedMat)

    distances_plus  = zeros(Float64, nalternatives)
    distances_minus = zeros(Float64, nalternatives)

    scores = zeros(Float64, nalternatives)

    @inbounds for i in 1:nalternatives
        ithrow = weightednormalizedMat[i,:] |> Array{Float64,1}
		distances_plus[i]  = euclidean(col_max, ithrow)
		distances_minus[i] = euclidean(col_min, ithrow)
		scores[i] = distances_minus[i] / (distances_minus[i] + distances_plus[i])
    end

    best_index = sortperm(scores) |> last

    topsisresult = TopsisResult(
        decisionMat,
        w,
        normalizedMat,
        weightednormalizedMat,
        best_index,
        scores
    )

    return topsisresult
end


export minkowski_difference, pontryagin_difference

"""
    minkowski_difference(P::LazySet{N}, Q::LazySet{N}) where {N<:Real}

Concrete Minkowski difference (geometric difference) for a pair of
convex sets.

### Input

- `P` -- polytopic set
- `Q` -- compact convex set that is subtracted from `P`

### Output

An `HPolytope` that corresponds to the Minkowski difference of `P` minus `Q` if
`P` is bounded, and an `HPolyhedron` if `P` is unbounded.

### Notes

This function requires that the list of constraints of the set `P` is
available and that the set `Q` is bounded.

### Algorithm

This function implements Theorem 2.3 in [1], which we state next.

Suppose ``P`` is a polyhedron
```math
P = \\{z ∈ ℝ^n: sᵢᵀz ≤ rᵢ,~i = 1, …, N\\}.
```
where ``sᵢ ∈ ℝ^n, sᵢ ≠ 0``, and ``rᵢ ∈ ℝ``.
Assume ``ρ(sᵢ,Q)`` is defined for ``i = 1, …, N``. Then,

```math
P ⊖ Q = \\{z ∈ ℝ^n: sᵢᵀz ≤ rᵢ - ρ(sᵢ,Q),~i = 1, …, N\\}.
```

where ``⊖`` is defined as ``P ⊖ Q = \\{z ∈ ℝ^n: z + v ∈ P  ~∀~v ∈ Q\\}`` and is called
the *Minkowski difference* (also referenced as *Pontryagin difference*, or geometric difference).
It is denoted in [1] as the operation `P ~ Q`.

[1] Ilya Kolmanovsky and Elmer G. Gilbert (1997). *Theory and computation
of disturbance invariant sets for discrete-time linear systems.*
[Mathematical Problems in Engineering Volume 4, Issue 4, Pages
317-367.](http://dx.doi.org/10.1155/S1024123X98000866)
"""
function minkowski_difference(P::LazySet{N}, Q::LazySet{N}) where {N<:Real}

    @assert applicable(constraints_list, P)  "this function " *
        "requires that the list of constraints of its first argument is applicable, but it is not; " *
        "if it is bounded, try overapproximating with an `HPolytope` first"
    @assert isbounded(Q) "this function requires that its second argument is bounded, but it is not"

    A, b = tosimplehrep(P)
    g_PminusQ = [b[i] - ρ(A[i, :], Q) for i in eachindex(b)]
    if isbounded(P)
        return HPolytope(A, g_PminusQ)
    else
        return HPolyhedron(A, g_PminusQ)
    end
end

"""
    pontryagin_difference(P::LazySet{N}, Q::LazySet{N}) where {N<:Real}

An alias for the function `minkowski_difference`.

### Notes

Due to inconsistent naming conventions, both the name *Minkowski difference* and
*Pontryagin difference* are used to refer to the geometric difference of two sets.

"""
const pontryagin_difference = minkowski_difference

# concrete minkowski difference with singleton
minkowski_difference(X::LazySet, S::AbstractSingleton) = translate(X, -element(S))
minkowski_difference(X::LazySet, ::ZeroSet) = X


plan_clenshawcurtis(μ) = length(μ) > 1 ? FFTW.plan_r2r!(μ, FFTW.REDFT00) : fill!(similar(μ),1)'

"""
Compute nodes of the Clenshaw—Curtis quadrature rule.
"""
clenshawcurtisnodes(::Type{T}, N::Int) where T = chebyshevpoints(T, N; kind = 2)

"""
Compute weights of the Clenshaw—Curtis quadrature rule with modified Chebyshev moments of the first kind ``\\mu``.
"""
clenshawcurtisweights(μ::Vector) = clenshawcurtisweights!(copy(μ))
clenshawcurtisweights!(μ::Vector) = clenshawcurtisweights!(μ, plan_clenshawcurtis(μ))
function clenshawcurtisweights!(μ::Vector{T}, plan) where T
    N = length(μ)
    rmul!(μ, inv(N-one(T)))
    plan*μ
    μ[1] *= half(T); μ[N] *= half(T)
    return μ
end


"""
    sphere(D)

The well-known D-dimensional Sphere function.
"""
function sphere(D=10)
    # Objective function
    f(x) = sum(x.*x)

    bounds = Array([-100.0ones(D) 100.0ones(D)]')

    x = zeros(D)
    return f, bounds, [generateChild(x, f(x)) ]

end

"""
    discus(D)

The well-known D-dimensional Discus function.
"""
function discus(D = 10)
    # Objective function
    f(x) = 1e6x[1].^2 + sum(x[2:end] .^2)

    bounds = Array([-10.0ones(D) 10.0ones(D)]')

    x = zeros(D)
    return f, bounds, [generateChild(x, f(x))]
end

"""
    rastrigin(D)

The well-known D-dimensional Rastrigin function.
"""
function rastrigin(D = 10)

    # Objective function
    f(x) = 10D+ sum(x.*x - 10cos.(2π*x))

    bounds = Array([-5.0ones(D) 5.0ones(D)]')

    x = zeros(D)
    return f, bounds, [generateChild(x, f(x)) ]

end


# Special Pythagorean triplet

TRIPLET_SUM = 1000

function solve()
    for b in floor(Int, TRIPLET_SUM / (2 + 2^0.5)):(TRIPLET_SUM // 2)
        for a in 1:(b - 1)
            c = (a^2 + b^2)^0.5
            if a + b + c == TRIPLET_SUM
                return a * b * c
            end
        end
    end
end

print(Int(solve()))


# ---
# title: 413. Arithmetic Slices
# id: problem413
# author: Indigo
# date: 2021-06-20
# difficulty: Medium
# categories: Math, Dynamic Programming
# link: <https://leetcode.com/problems/arithmetic-slices/description/>
# hidden: true
# ---
#
# A sequence of numbers is called arithmetic if it consists of at least three
# elements and if the difference between any two consecutive elements is the
# same.
#
# For example, these are arithmetic sequences:
#
#
#
#     1, 3, 5, 7, 9
#     7, 7, 7, 7
#     3, -1, -5, -9
#
# The following sequence is not arithmetic.
#
#
#
#     1, 1, 2, 5, 7
#
#
#
# A zero-indexed array A consisting of N numbers is given. A slice of that array
# is any pair of integers (P, Q) such that 0 <= P < Q < N.
#
# A slice (P, Q) of the array A is called arithmetic if the sequence:
# A[P], A[P \+ 1], ..., A[Q - 1], A[Q] is arithmetic. In particular, this means
# that P + 1 < Q.
#
# The function should return the number of arithmetic slices in the array A.
#
#
#
# **Example:**
#
#
#
#     A = [1, 2, 3, 4]
#
#     return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself.
#
#
#
## @lc code=start
using LeetCode

function number_of_arithmetic_slices(nums::Vector{Int})
    dp, res = 0, 0
    for i in 3:length(nums)
        if nums[i] + nums[i - 2] == (nums[i - 1] << 1)
            res += (dp += 1)
        else
            dp = 0
        end
    end
    return res
end
## @lc code=end


# A CKAR(2) model
# Here we demonstrate the package's utilities in the context of a simple censored and kinked AR(2) model with two regimes.
#
# Consider the model
# $$\begin{align}
# y_t^*&=\phi_1^*y_{t-1}^*+\phi_1y_{t-1}+\phi_2^*y_{t-1}^*+\phi_2y_{t-1}+\epsilon_t,\\
# y_t&=\max\{y_t^*,0\}.
# \end{align}$$
#
# This features a 'positive regime' ($y^*\geq0$) and a 'negative regime' ($y^*<0$).
##
# Noting $y_t=\mathbf{1}\{y_t^*\geq0\}y_t^*$, we can rewrite the equation for $y_t^*$ as $$y_t^*=(\phi_1^*+\phi_1\mathbf{1}\{y_{t-1}\geq0\})y_{t-1}^*+(\phi_2^*+\phi_2\mathbf{1}\{y_{t-2}\geq0\})y_{t-2}^*+\epsilon_t,$$
# where $\mathbf{1}\{y\geq0\}$ is an indicator variable.
##
using ThresholdStability

function AR2_to_TAR(ϕs, ϕ_stars)
    ϕ1, ϕ2 = ϕs
    ϕ1_star, ϕ2_star = ϕ_stars
    Σ = []
    vals = [1, -1]
    for val1 in vals
        for val2 in vals
            A = zeros(2, 2); A[2, 1] = 1
            A[1, 1] = ϕ1_star + ϕ1 * indicator(val1, 0)
            A[1, 2] = ϕ2_star + ϕ2 * indicator(val2, 0)
            push!(Σ, A)
        end
    end
    return Vector{Array{Float64, 2}}(Σ)
    # NOTE Σ is of form where Σ[1] is with yₜ₋₁,yₜ₋₂ ≥ 0, Σ[2] is with yₜ₋₁ ≥ 0 and yₜ₋₂ < 0, Σ[3] is with yₜ₋₁ < 0 and yₜ₋₂ ≥ 0
    # and Σ[4] is with yₜ₋₁,yₜ₋₂ < 0.
end
##
# This yields a set of four $2\times2$ matrices, with each matrix corresponding to a different regime pair (positive-positive, positive-negative, negative-positive and negative-negative.)
#
# For exposition, consider $\phi_1 = 0.4$, $\phi_1^* = 0.2$, $\phi_2=0.2$, and $\phi_2^*=0.1$:
##
Σ4 = AR2_to_TAR([0.4, 0.2], [0.2, 0.1])
##
# The state space constraints for this model are given by `X4`:
##
E1, E2, E3, E4 = [1 0.; 0 1.], [1 0.; 0 -1.], [-1 0.; 0 1.], [-1 0.; 0 -1.]
D1 = zeros(1,2); D2, D3, D4 = copy(D1), copy(D1), copy(D1)
X4 = [[E1, D1], [E2, D2], [E3, D3], [E4, D4]]
##
# and the automaton can be constructed using
##
G = automaton_constructor(Σ4)
##
# We then have the discrete system
s4 = discreteswitchedsystem(Σ4, G, X4)
##
# Alternatively, by substituting for $y_t$ on the first lag only we can produce a set of two $3\times3$ matrices:
##
function AR2_to_companion(ϕs, ϕ_stars)
    ϕ1, ϕ2 = ϕs
    ϕ1_star, ϕ2_star = ϕ_stars
    Σ = []
    vals = [1, -1]
    for val in vals
        A = zeros(3, 3); A[2, 1] = 1; A[1, 2] = ϕ2_star; A[1, 3] = ϕ2
        A[1, 1] = ϕ1_star + ϕ1 * indicator(val, 0)
        A[3, 1] = indicator(val, 0)
        push!(Σ, A)
    end
    return Vector{Array{Float64, 2}}(Σ)

    # NOTE Σ has form s.t. Σ[1] is with yₜ₋₁ ≥ 0 and Σ[2] is with yₜ₋₁ < 0
end

Σ2 = AR2_to_companion([0.4, 0.2], [0.2, 0.1])
Σ2st = [Σ2[1], Σ2[2], Σ2[1], Σ2[2]]
##
# `Σ2` consists of the two matrices and `Σ2st` corresponds to the same partitioning of the state space as for `Σ4`.

In this latter case, the automaton is again `G` but the state space constraints are now given by `X2`:
##
E1, E2, E3, E4 = [1 0 0.; 0 1 0.], [1 0 0.; 0 -1 0.], [-1 0 0.; 0 1 0.], [-1 0 0.; 0 -1 0.]
D1, D3 = [0 1 -1.], [0 1 -1.]; D2, D4 = [0 0 1.], [0 0 1.]
X2 = [[E1, D1], [E2, D2], [E3, D3], [E4, D4]]
##
# and we construct the discrete switched systems
##
s2 = discreteswitchedsystem(Σ2)
s2st = discreteswitchedsystem(Σ2st, G, X2)
##
# For our choice of parameter values, this model is stable. In particular, the upper bounds on the joint spectral radius (JSR) of `Σ2`, the constrained joint spectral radius (CJSR) of `(Σ4, G)`, and the state-constrained joint spectral radii (SCJSR) of `(Σ4, G, X4)` and `(Σ2st, G, X2)` all agree in value, up to 4 significant figures:
##
@show γ_jsr2 = jsr(s2)
@show γ_cjsr4 = cjsr(s4)
@show γ_scjsr4 = sosbound_γ(s4, 2)
@show γ_scjsr2 = sosbound_γ(s2st, 2)
##
# Indeed, if $\phi_i,\phi_i^*\geq0$ for $i=1,2$, then the model will be stable if and only if
# $\sum_{i=1}^2(\phi_i+\phi_i^*)<1$.
#
# As expected, the model appears stable when plotted:
##
using Plots, Distributions
using LaTeXStrings
pyplot()
function simulate_AR2(y0, Σ, T, σ)  # for companion form
    y = zeros(3, T)
    y[:, 1] = y0
    for t in 1:T-1
        ϵ_t = [rand(Normal(0, σ)), 0., 0.]
        if y[1, t] ≥ 0.
            y[:, t+1] = Σ[1]*y[:, t] + ϵ_t
        else
            y[:, t+1] = Σ[2]*y[:, t] + ϵ_t
        end
    end
    return y
end

function plot_AR2(y0, Σ, T, σ; N = 20, row=1)
    ys = []
    ens_means = zeros(T)
    for i in 1:N
        y = simulate_AR2(y0, Σ, T, σ)
        y = y[row,:]
        push!(ys, y)
        ens_means .+= y
    end
    ens_means ./= N  # ensemble means

    E_y = simulate_AR2(y0, Σ, T, 0.)  # calculating deterministic results
    E_y = E_y[row,:]

    plot(ys, color = :grey, alpha = 0.1, label = "")
    plot!(ens_means, color = :grey, linewidth = 2, label = "Ensemble mean")
    plot!(E_y, color = :blue, linewidth = 2, linestyle = :dash, label = "Deterministic")
    plot!(xlabel=L"t", legend=:topright)
end

plot_AR2(3*ones(3), Σ2, 200, 1)
plot!(ylabel=L"y^*", yguidefontrotation=-90)
##
# Introducing negative parameters, we see that the different upper bounds can diverge.
#
# Consider $\phi_1=0.5$, $\phi_1^*=0.5$, $\phi_2=-0.47$, $\phi_2^*=-0.5$. When plotted, the system appears stable:
##
Σ = AR2_to_companion([0.5, -0.47], [0.5, -0.5])
plot_AR2(3*ones(3), Σ, 200, 1, row=1)
plot!(ylabel=L"y^*", yguidefontrotation=-90)
##
# However, this is a case where the conservatism of (the upper bounds on) the JSR and CJSR would prevent us from concluding the system is stable.
##
Σ = AR2_to_TAR([0.5, -0.47], [0.5, -0.5])
E1, E2, E3, E4 = [1 0.; 0 1.], [1 0.; 0 -1.], [-1 0.; 0 1.], [-1 0.; 0 -1.]
D1 = zeros(1,2); D2, D3, D4 = copy(D1), copy(D1), copy(D1)
X = [[E1, D1], [E2, D2], [E3, D3], [E4, D4]]  # state space constraints
G = automaton_constructor(Σ)
s = discreteswitchedsystem(Σ, G, X)
@show γ_jsr = jsr(s)  # > 1
@show γ_cjsr = cjsr(s)  # > 1
##
# However, the upper bound on the SCJSR is less conservative, yielding a bound below 1. We can thus conclude the system is stable.
##
@show γ_scjsr = sosbound_γ(s, 2)  # < 1
##
# Note that (the upper bound on) the SCJSR remains conservative, even if less so than the CJSR or JSR.
#
# For example, consider $\phi_1=\phi_1^*=0.6$ and $\phi_2=\phi_2^*=-0.6$. This appears stable:
##
Σ = AR2_to_companion([0.6, -0.6], [0.6, -0.6])
plot_AR2(3*ones(3), Σ, 200, 1, row=1)
plot!(ylabel=L"y^*", yguidefontrotation=-90)
##
# Plots for other initial values $y_0$ follow a similar pattern.
#
# However, the bound on the SCJSR exceeds 1 in this case (as does the bound for the CJSR)
##
Σ = AR2_to_TAR([0.6, -0.6], [0.6, -0.6])
s = discreteswitchedsystem(Σ, G, X)
@show γ_cjsr = cjsr(s)
@show γ_scjsr = sosbound_γ(s, 2)


export uncertainty_exponent, basins_fractal_dimension

"""
    basins_fractal_dimension(basins; kwargs...) -> V_ε, N_ε ,d
Estimate the [Fractal Dimension](@ref) `d` of the boundary between basins of attraction using
the box-counting algorithm.

The output `N_ε` is a vector with the number of the balls of radius `ε` (in pixels)
that contain at least two initial conditions that lead to different attractors. `V_ε`
is a vector with the corresponding size of the balls. The ouput `d` is the estimation
of the box-counting dimension of the boundary by fitting a line in the `log.(N_ε)`
vs `log.(1/V_ε)` curve. However it is recommended to analyze the curve directly
for more accuracy.

## Keyword arguments
* `range_ε = 2:maximum(size(basins))÷20` is the range of sizes of the ball to
  test (in pixels).

## Description

It is the implementation of the popular algorithm of the estimation of the box-counting
dimension. The algorithm search for a covering the boundary with `N_ε` boxes of size
`ε` in pixels.
"""
function basins_fractal_dimension(basins::AbstractArray; range_ε = 3:maximum(size(basins))÷20)

    dims = size(basins)
    num_step = length(range_ε)
    N_u = zeros(Int, num_step) # number of uncertain box
    N = zeros(Int, num_step) # number of boxes
    V_ε = zeros(1, num_step) # resolution

    # Naive box counting estimator
    for (k,eps) in enumerate(range_ε)
        Nb, Nu = 0, 0
        # get indices of boxes
        bx_tuple = ntuple(i -> range(1, dims[i] - rem(dims[i],eps), step = eps), length(dims))
        box_indices = CartesianIndices(bx_tuple)
        for box in box_indices
            # compute the range of indices for the current box
            ind = CartesianIndices(ntuple(i -> range(box[i], box[i]+eps-1, step = 1), length(dims)))
            c = basins[ind]
            if length(unique(c))>1
                Nu = Nu + 1
            end
            Nb += 1
        end
        N_u[k] = Nu
        N[k] = Nb
        V_ε[k] = eps
    end
    N_ε = N_u
    # remove zeros in case there are any:
    ind = N_ε .> 0.0
    N_ε = N_ε[ind]
    V_ε = V_ε[ind]
    # get exponent via liner regression on `f_ε ~ ε^α`
    b, d = linreg(vec(-log10.(V_ε)), vec(log10.(N_ε)))
    return V_ε, N_ε, d
end

"""
    uncertainty_exponent(basins; kwargs...) -> ε, N_ε ,α
Estimate the uncertainty exponent[^Grebogi1983] of the basins of attraction. This exponent
is related to the final state sensitivity of the trajectories in the phase space.
An exponent close to `1` means basins with smooth boundaries whereas an exponent close
to `0` represent complety fractalized basins, also called riddled basins.

The output `N_ε` is a vector with the number of the balls of radius `ε` (in pixels)
that contain at least two initial conditions that lead to different attractors.
The ouput `α` is the estimation of the uncertainty exponent using the box-counting
dimension of the boundary by fitting a line in the `log.(N_ε)` vs `log.(1/ε)` curve.
However it is recommended to analyze the curve directly for more accuracy.

## Keyword arguments
* `range_ε = 2:maximum(size(basins))÷20` is the range of sizes of the ball to
  test (in pixels).

## Description

A phase space with a fractal boundary may cause a uncertainty on the final state of the
dynamical system for a given initial condition. A measure of this final state sensitivity
is the uncertainty exponent. The algorithm probes the basin of attraction with balls
of size `ε` at random. If there are a least two initial conditions that lead to different
attractors, a ball is tagged "uncertain". `f_ε` is the fraction of "uncertain balls" to the
total number of tries in the basin. In analogy to the fractal dimension, there is a scaling
law between, `f_ε ~ ε^α`. The number that characterizes this scaling is called the
uncertainty exponent `α`.

Notice that the uncertainty exponent and the box counting dimension of the boundary are
related. We have `Δ₀ = D - α` where `Δ₀` is the box counting dimension computed with
[`basins_fractal_dimension`](@ref) and `D` is the dimension of the phase space.
The algorithm first estimates the box counting dimension of the boundary and
returns the uncertainty exponent.

[^Grebogi1983]: C. Grebogi, S. W. McDonald, E. Ott and J. A. Yorke, Final state sensitivity: An obstruction to predictability, Physics Letters A, 99, 9, 1983
"""
function uncertainty_exponent(basins::AbstractArray; range_ε = 2:maximum(size(basins))÷20)
    V_ε, N_ε, d = basins_fractal_dimension(basins; range_ε)
    return V_ε, N_ε, length(size(basins)) - d
end


using Plots
using BenchmarkTools
#2(a)
function vfsolvex(vnew, kgrid, tolerance, imax, σ=1.5)
    β=0.9

    v=vnew .+ 2*tolerance
    cartesianindex = Array{CartesianIndex{2}, length(v)}
    i=1

    c = kgrid .- kgrid'
    c[c .< 0] .= 0
    if σ==1
      u = log.(c)
    else
      u = (c.^(1-σ).-1)./(1-σ)
    end
    u[kgrid .- kgrid' .< 0] .= -Inf

    while maximum(abs.(v-vnew)) > tolerance && i <= imax
      v = vnew;

      (vnew, cartesianindex) = findmax(u .+ β*v', dims = 2);
      i += 1;
    end
    kprimeindex = getindex.(cartesianindex, 2)
    return (v=vnew, kprime=kgrid[kprimeindex], kprimeindex=kprimeindex)
  end

  kupper = 5
  klower = 0.01
  n = 500
  kgrid = collect(range(klower, stop=kupper, length = n))
  (v, kprime, kprimeindex) = vfsolvex(zeros(n), kgrid, 0.001, 1000)
  #(b)
  (v, kprime, kprimeindex) = vfsolvex(zeros(500), kgrid, 0.001, 1000);
   plot(kgrid, v, label = "v, σ=1.5")

  #(c)
  plot()
  for x in [1.25, 1.1, 1.001, 1.00001, 1]
    kgrid = collect(range(klower, stop=kupper, length=500))
    (v, kprime, kprimeindex) = vfsolvex(zeros(500), kgrid, 0.001, n, x);
    display(plot!(kgrid, v, label = "v, σ=$x"))
  end
#as σ->1, the value function is getting closer to log utility value function
#the vfsolvex autometically compute log utility solution when σ=1.
#We also solve the log utility problem analytically in PS 5, and the numerical and
#analytical solution coinside

#(d)
function policy(x)
    e=getindex(findall(kgrid .== x),1)
    return kprime[e]
end

function findindex(x)
    a=findmin(abs.(x .- kgrid), dims=1)
    return a[2]
end

function findkpath(T, k_0)
    w = findindex(k_0)
    kpath = zeros(T)
    k = kgrid[w]
        for i in 1:T
            k = policy(k)
            kpath[i] = k
        end
    return kpath
end

function plotkpath(T, k_0)
    w = findindex(k_0)
    kpath = zeros(T)
    k = kgrid[w]
        for i in 1:T
            k = policy(k)
            kpath[i] = k
        end
    time = collect(range(1, T, length = T))
    plot(time, kpath, label = "kpath")
end

findkpath(100, 5)
plotkpath(100, 5)
# this is a cake eating problem with no production, so capital always decline