# This file is part of IntegerSequences.
# Copyright Peter Luschny. License is MIT.

(@__DIR__) ∉ LOAD_PATH && push!(LOAD_PATH, (@__DIR__))

module Polynomials

using Nemo
# import AbstractAlgebra.lead

export ModulePolynomials
export Poly, AltPoly, EgfPoly, AltEgfPoly
export Coeffs, CoeffSum, CoeffAltSum, CoeffConst, CoeffLeading, AltCoeffs
export Diagonal, Central, EgfCoeffs, AltEgfCoeffs, ReflectPoly

"""

Mostly convenient functions to deal with polynomials as often used in connection with ordinary and exponential generating functions. The naming scheme used is roughly described by:

```
   Poly       <-> Coeffs
   AltPoly    <-> Poly(AltCoeffs)
   EgfPoly    <-> Poly(EgfCoeffs)
   OgfPoly    <-> Poly(OgfCoeffs)
   AltEgfPoly <-> Poly(AltEgfCoeffs)
```

Here 'Alt' stands for alternating, 'Egf' for exponential generating function, 'Ogf' for ordinary generating function.

* Coeffs, CoeffSum, CoeffAltSum, CoeffConst, CoeffLeading, AltCoeffs, Diagonal, Central, EgfCoeffs, AltEgfCoeffs, Poly, AltPoly, EgfPoly, AltEgfPoly, ReflectPoly.
"""
const ModulePolynomials = ""

"""

Return the coefficients of the polynomial ``p``.
"""
Coeffs(p::fmpz_poly) = [coeff(p, k) for k ∈ 0:degree(p)]

"""

Return the coefficients of the polynomial ``p`` with alternating signs.
"""
AltCoeffs(p::fmpz_poly) = [(-1)^k * coeff(p, k) for k ∈ 0:degree(p)]

"""

Return the coefficients of the polynomial ``p`` multiplied by ``k!``.
"""
OgfCoeffs(p::fmpz_poly) = [factorial(k)*coeff(p, k) for k ∈ 0:degree(p)]

"""

Return the coefficients of the polynomial ``p`` divided by ``k!``. Note that integer division is used.
"""
EgfCoeffs(p::fmpz_poly) = [div(coeff(p, k), factorial(k)) for k ∈ 0:degree(p)]

"""

Return the coefficients of the polynomial ``p`` divided by ``(-1)^k k!``.
"""
AltEgfCoeffs(p::fmpz_poly) = [(-1)^k * div(coeff(p, k), factorial(k)) for k ∈ 0:degree(p)]

"""

Return the polynomial ``p`` with the coefficients C.
"""
function Poly(C)
    T, x = PolynomialRing(ZZ, "x")
    sum(c * x^k for (k, c) ∈ enumerate(C))
end

"""

Return the polynomial ``p`` with the coefficients C and alternating signs (i.e. with ``(-1)^k c[k] x^k)``.
"""
function AltPoly(C)
    T, x = PolynomialRing(ZZ, "x")
    sum((-1)^k * c * x^k for (k, c) ∈ enumerate(C))
end

"""

Return the polynomial ``p`` with the coefficients C used in the form ``c[k] x^k/k!``. Note that integer division is used.
"""
function EgfPoly(C)
    T, x = PolynomialRing(ZZ, "x")
    sum(div(c, factorial(k)) * x^k for (k, c) ∈ enumerate(C))
end

"""

Return the polynomial ``p`` with the coefficients C used in the form ``c[k] k! x^k``.
"""
function OgfPoly(C)
    T, x = PolynomialRing(ZZ, "x")
    sum(c * factorial(k) * x^k for (k, c) ∈ enumerate(C))
end

"""

Return the polynomial ``p`` with alternating signs attached to the coefficients .
"""
AltPoly(p::fmpz_poly) = Poly(AltCoeffs(p))
#    T, x = PolynomialRing(ZZ, "x")
#    sum((-1)^k*coeff(p, k)*x^k for k ∈ 0:degree(p))

"""

Return the polynomial ``p`` with coefficients in exponential form (i.e. with ``c[k] x^k/k!``).
"""
EgfPoly(p::fmpz_poly) = Poly(EgfCoeffs(p))
#    T, x = PolynomialRing(ZZ, "x")
#    sum(div(coeff(p, k), factorial(k))*x^k for k ∈ 0:degree(p))

"""

Return the polynomial ``p`` with coefficients in exponential form and alternating signs (i.e. with ``(-1)^k c[k] x^k/k!``).
"""
AltEgfPoly(p::fmpz_poly) = Poly(AltEgfCoeffs(p))
#    T, x = PolynomialRing(ZZ, "x")
#    sum((-1)^k*div(coeff(p, k), factorial(k))*x^k for k ∈ 0:degree(p))

"""

Return the list of the coefficients of the first ``len`` polynomials of the sequence of polynomials ``P`` as a triangle.
"""
Coeffs(P, len) = [[coeff(P(n), k) for k ∈ 0:degree(P(n))] for n ∈ 0:len-1]

"""

Return the sum of the coefficients of the polynomial ``p``.
"""
CoeffSum(p) = evaluate(p, 1)

"""

Return the sequence of the sum of coefficients of the sequence of polynomials ``P``.
"""
CoeffSum(P, len) = [CoeffSum(P(n)) for n ∈ 0:len-1]

"""

Return the alternating sum of the coefficients of the polynomial ``p``.
"""
CoeffAltSum(p) = evaluate(p, -1)

"""

Return the sequence of the alternating sums of the coefficients of the sequence of polynomials ``P``.
"""
CoeffAltSum(P, len) = [CoeffAltSum(P(n)) for n ∈ 0:len-1]

"""

Return the leading coefficient of the polynomial ``p``.
"""
CoeffLeading(p) = coeff(p, degree(p)) # lead(p)

"""

Return the sequence of the leading coefficient of the sequence of polynomials ``P``.
"""
Diagonal(P, len) = [CoeffLeading(P(n)) for n ∈ 0:len-1]

"""

Return the constant coefficient of the polynomial ``p``.
"""
CoeffConst(p) = coeff(p, 0)

"""

Return the sequence of the constant coefficients of the sequence of polynomials ``P``.
"""
CoeffConst(P, len) = [CoeffConst(P(n)) for n ∈ 0:len-1]

"""

Return the central column of the coefficients of the sequence of polynomials ``P``.
"""
Central(P, len) = [Coeffs(P(2n))[n+1] for n ∈ 0:len-1]

"""

Return the reflected polynomial of ``p``.
"""
function ReflectPoly(p::fmpz_poly)
    T, x = PolynomialRing(ZZ, "x")
    p(0) != 1 && throw(ValueError("Constant coefficient must be 1."))
    d = degree(p)
    x^d + sum(coeff(p, k) * x^(d - k) for k ∈ 1:d)
end

#START-TEST-########################################################

using Test, SeqUtils

function test()

    @testset "Polynomials" begin
        T, x = PolynomialRing(ZZ, "x")
        p = 63063000 * x^4 + 2702700 * x^3 + 16510 * x^2 + x
        @test Coeffs(p) == [0, 1, 16510, 2702700, 63063000]
        @test CoeffSum(p) == 65782211
        @test CoeffAltSum(p) == 60376809
        @test CoeffConst(p) == T(0)
        @test CoeffLeading(p) == 63063000

        q = x^2 - x - 1
        p = 1 - x - x^2
        r = ReflectPoly(p)
        @test r == q
    end

end

function demo()

    function P(m, n)
        R, x = PolynomialRing(ZZ, "x")
        function recP(m, n)
            n == 0 && return R(1)
            sum(binomial(m * n, m * k) * recP(m, n - k) * x for k ∈ 1:n)
        end
        recP(m, n)
    end

    # ... given a polynomial p:
    for m ∈ 0:4, n ∈ 0:5
        println("---> m: $m, n: $n")
        q = P(m, n)

        typeof(q) |> println
        q |> println
        AltPoly(q) |> println
        EgfPoly(q) |> println
        AltEgfPoly(q) |> println

        Coeffs(q) |> Println
        EgfCoeffs(q) |> Println
        AltEgfCoeffs(q) |> Println

        CoeffSum(q) |> println
        CoeffAltSum(q) |> println
        CoeffConst(q) |> println
        CoeffLeading(q) |> println
    end

    # ... given a sequence of polynomials P:
    for m ∈ 0:4
        println("---> m ", m)
        println("\nPolynomial:")
        Q(n) = P(m, n)
        println(Q(4))
        println("\nTriangle of coefficients:")
        Coeffs(Q, 7) |> Println
        println("\nSum of coefficients:")
        CoeffSum(Q, 7) |> Println
        println("\nAlternating sum of coefficients:")
        CoeffAltSum(Q, 7) |> Println
        println("\nLeading coefficients:")
        Diagonal(Q, 7) |> Println
        println("\nCentral column of coefficient triangle:")
        Central(Q, 7) |> Println
    end
end

function perf() end

function main()
    test()
    demo()
    perf()
end

main()

end # module


# Tests for the total correlation function

# Test total correlation formula
@test get_total_correlation(arr1, arr2, arr3) ≈ get_entropy(arr1) + get_entropy(arr2) + get_entropy(arr3) - get_entropy(arr1, arr2, arr3)
println("Total correlation formula passed.")


export SSDFun, HuberFun, MVFun, MVFunTotal

"""
	mis,dmis,d2mis = SSDFun(dc,dobs,Wd)

	Input:

		dc::Array   -  simulated data
		dobs::Array -  measured data
		Wd::Array   -  diagonal weighting

	Output:

		mis::Real   -  misfit, 0.5*|dc-dobs|_Wd^2
		dmis        -  gradient
		d2mis       -  diagonal of Hessian

"""
function SSDFun(dc::Union{Array{Float64},Array{Float32}},dobs::Union{Array{Float64},Array{Float32}},Wd::Union{Array{Float64},Array{Float32}})
	res   = vec(dc)-vec(dobs) # predicted - observed data
	Wd    = vec(Wd)
	mis   = .5*real(dot(Wd.*res,Wd.*res))  # data misfit
	dmis  = Wd.*(Wd.*res)
	d2mis = Wd.*Wd
	return mis, dmis, d2mis
end # function SSDFun

"""
	For complex data misfit is computed as 0.5*|real(dc)-(dobs)|_Wd^2 +  0.5*|complex(dc)-complex(dobs)|_W^2
"""
function SSDFun(dc::Array{ComplexF64},dobs::Array{ComplexF64},Wd::Array{ComplexF64})

	wdr   = vec(real(Wd)); wdi = vec(imag(Wd))
	# wdr.*dRe + im*wdi.*dIm
	res   = vec(dc)-vec(dobs)
	resw  = wdr.*real(res) + im*wdi.*imag(res)

	mis   = .5*real(dot(resw,resw))
	dmis  = sdiag(wdr.*wdr)*real(res) + 1im*sdiag(wdi.*wdi)*imag(res)
	d2mis =  wdr.*wdr + im*wdi.*wdi
	return mis,dmis,d2mis
end

"""
	mis,dmis,d2mis = HuberFun(dc,dobs,Wd,C)

	Computes misfit via

		misfit(dc,dobs) = sqrt(abs(Wd*res).^2 + eps)

	Input:
		dc::Array   -  simulated data
		dobs::Array -  measured data
		Wd::Array   -  diagional weighting
		eps         -  conditioning parameter (default=1e-3)

	Output:
		mis::Real   -  misfit
		dmis        -  gradient
		d2mis       -  diagonal of Hessian

"""
function HuberFun(dc::Array{Float64},dobs::Array{Float64},Wd::Array{Float64},eps=1e-3)
	# compute Huber distance
	res   = vec(dc-dobs)
	G     = sqrt.( abs.(Wd.*res).^2 .+ eps)
	mis   = sum(G)
	dmis  = sdiag(Wd./G)*(Wd.*res)
	d2mis = (Wd.*Wd)./G
	return mis,dmis,d2mis
end
# Old Huber fun
# function HuberFun(dc::Array{Float64},dobs::Array{Float64},Wd::Array{Float64},eps=1e-3)
# 	# compute Huber distance
# 	res   = dc-dobs
# 	G     = sqrt.( abs.(Wd.*res).^2 .+ eps)
# 	mis   = sum(G)
# 	dmis  = sdiag(Wd./G)*(Wd.*res)
# 	d2mis = (Wd.^Wd)./G
# 	return mis,dmis,d2mis
# end


"Tableau for explicit Euler method"
function getTableauExplicitEuler()
    a = zeros(Float64, 1, 1)
    b = [1.0]
    c = [0.0]
    o = 1

    TableauERK(:explicit_euler, o, a, b, c)
end

"Tableau for explicit midpoint method"
function getTableauExplicitMidpoint()
    a = [[0.0 0.0]
         [0.5 0.0]]
    b = [0.0, 1.0]
    c = [0.0, 0.5]
    o = 2

    TableauERK(:explicit_midpoint, o, a, b, c)
end

"Tableau for Runge's method"
function getTableauRunge()
    a = [[0.0 0.0]
         [1.0 0.0]]
    b = [0.5, 0.5]
    c = [0.0, 1.0]
    o = 2

    TableauERK(:runge, o, a, b, c)
end

"Tableau for Heun's method"
function getTableauHeun()
    a = [[0.0 0.0]
         [1.0 0.0]]
    b = [0.5, 0.5]
    c = [0.0, 1.0]
    o = 2

    TableauERK(:heun, o, a, b, c)
end

"Tableau for Kutta's method of order three"
function getTableauKutta()
    a = [[ 0.0 0.0 0.0]
         [ 0.5 0.0 0.0]
         [-1.0 2.0 0.0]]
    b = [1/6, 4/6, 1/6]
    c = [0.0, 0.5, 1.0]
    o = 3

    TableauERK(:kutta, o, a, b, c)
end

"Tableau for explicit Runge-Kutta method of order four (1/6 rule)"
function getTableauERK4()
    a = [[0.0 0.0 0.0 0.0]
         [0.5 0.0 0.0 0.0]
         [0.0 0.5 0.0 0.0]
         [0.0 0.0 1.0 0.0]]
    b = [1/6, 1/3, 1/3, 1/6]
    c = [0.0, 0.5, 0.5, 1.0]
    o = 4

    TableauERK(:erk4, o, a, b, c)
end

"Tableau for explicit Runge-Kutta method of order four (3/8 rule)"
function getTableauERK438()
    a = [[ 0.0  0.0  0.0  0.0]
         [ 1/3  0.0  0.0  0.0]
         [-1/3  1.0  0.0  0.0]
         [ 1.0 -1.0  1.0  0.0]]
    b = [1/8, 3/8, 3/8, 1/8]
    c = [0.0, 1/3, 2/3, 1.0]
    o = 4

    TableauERK(:erk438, o, a, b, c)
end

"Tableau for explicit Verner's method of order six"
function getTableauVerner()
    a = [[ 0.0          0.0     0.0           0.0       0.0          0.0  0.0         0.0]
         [ 1/6          0.0     0.0           0.0       0.0          0.0  0.0         0.0]
         [ 4/75         16/75   0.0           0.0       0.0          0.0  0.0         0.0]
         [ 5/6         -8/3     5/2           0.0       0.0          0.0  0.0         0.0]
         [-165/64       55/6   -425/64        85/96     0.0          0.0  0.0         0.0]
         [ 12/5        -8.0     4015/612     -11/36     88/255       0.0  0.0         0.0]
         [-8263/15000   124/75 -643/680      -81/250    2484/10625   0.0  0.0         0.0]
         [ 3501/1720   -300/43  297275/52632 -319/2322  24068/84065  0.0  3850/26703  0.0]]
    b = [3/40, 0.0, 875/2244, 23/72, 264/1955, 0.0, 125/11592, 43/616]
    c = [0.0, 1/6, 4/15, 2/3, 5/6, 1.0, 1/15, 1.0]
    o = 6

    TableauERK(:verner, o, a, b, c)
end


# using Distances

const start = time()

const DEBUG = length(ARGS) >=1 && ARGS[1] == "1"

function distance(solution::Array{Int32})
    cost = 0
    for i in 1:length(solution)-1
        cost += d[solution[i],solution[i+1]]
    end
    return cost
end

tic()
tic()
# file lines iterator object
# input = eachline(STDIN)

# state = start(input)
# read data size N
const n = parse(Int32, readline(STDIN))
const k = max(0.01*n, 25.0)

# 2xN node's coordinates matrix
data = Array{Tuple{Float64, Float64}}(n)

# read file data to Data matrix
for i in 1:n
    v = readline(STDIN)
    (i_v, x_v, y_v) = split(v)
    data[parse(Int32, i_v)] = (parse(Float64, x_v), parse(Float64, y_v))
end
# calculate nodes distances as NxN matrix

maxtime = parse(Float64, readline(STDIN))

# d = Array{Float64}(n, n)
# pairwise!(d, Euclidean(1e-12), data)

d = [hypot(x2-x1, y2-y1) for (x2, y2) in data, (x1, y1) in data]

currentsol = Array{Int32}(n+1)
currentsol[1] = currentsol[n+1] = Int32(1)

for i in 1:n
    d[i,i] = Inf
end

d1 = copy(d)
j = 1
for i in 2:length(currentsol)-1
    currentsol[i] = findfirst(d1[j,:].==min(d1[j,:]...))
    j = currentsol[i]
    d1[j,1] = Inf
    for visited_node in 1:i
        d1[j, currentsol[visited_node]] = Inf
    end
end

currentcost = distance(currentsol)

optimumsol = copy(currentsol)
optimumcost = currentcost

####### SETTINGS #######
# how many times to iterate (minimum)
const minItrs     = 10000000

# fraction of melting point for starting temperature
const meltPointF  = 0.7

# fraction of melting point for ending temperature
const targetTempF = 0.01

# fraction of stagnant minItrs allowed before reheating
const stagItrsF   = 0.1
########################

function iterate(t::Float64, xsol::Array{Int32}, xcost::Float64)
	ysol = copy(xsol)
    ycost = xcost

	t1 = rand(2:n-2)
	t2 = rand(t1+2:n)

    ycost = ycost -
    		d[ysol[t1-1], ysol[t1]] -
    		d[ysol[t1], ysol[t1+1]] +
    		d[ysol[t1-1], ysol[t2]] +
    		d[ysol[t2], ysol[t1+1]] -
    		d[ysol[t2-1], ysol[t2]] -
    		d[ysol[t2], ysol[t2+1]] +
    		d[ysol[t2-1], ysol[t1]] +
    		d[ysol[t1], ysol[t2+1]]

	ysol[t1], ysol[t2] = ysol[t2], ysol[t1]

	if ycost <= xcost
		return ysol, ycost
	elseif rand() < exp((xcost-ycost)/t)
		return ysol, ycost
	end
	return xsol, xcost
end

function iterateP(p::Float64, xsol::Array{Int32}, xcost::Float64)
	ysol = copy(xsol)
    ycost = xcost

	t1 = rand(2:n-2)
    t2Max = round(min(t1+k, n))
	t2 = rand(Int64(t1+2):Int64(t2Max))

    ycost = ycost -
    		d[ysol[t1-1], ysol[t1]] -
    		d[ysol[t1], ysol[t1+1]] +
    		d[ysol[t1-1], ysol[t2]] +
    		d[ysol[t2], ysol[t1+1]] -
    		d[ysol[t2-1], ysol[t2]] -
    		d[ysol[t2], ysol[t2+1]] +
    		d[ysol[t2-1], ysol[t1]] +
    		d[ysol[t1], ysol[t2+1]]

	ysol[t1], ysol[t2] = ysol[t2], ysol[t1]

	if ycost <= xcost
		return ysol, ycost
	elseif rand() < p
		return ysol, ycost
	end
	return xsol, xcost
end

const initialcost = currentcost
if DEBUG
    println(string("initial cost: ", initialcost))
    println(string("min iterations: ", minItrs))
    println(string("initial time: ", toq(), " seconds"))
end

testsol = copy(currentsol)
testcost = currentcost
minT = 10.0e10
maxT = 0

for i in 1:max(0.01*minItrs, 2.0)
    testsol, testcost = iterateP(0.001, testsol, testcost)

    minT = min(minT, testcost)
    maxT = max(maxT, testcost)
end

const meltPoint = (maxT - minT) * 10.0^(-1.0*log10(n))

const t0 = meltPoint * meltPointF

if DEBUG
    @printf("T0 = %.2f\n", t0)
end
t = t0

const tDecay = targetTempF ^ (1.0/minItrs)

itr = 1
optItr = 1
stagItrs = 0

function printresult()
    if DEBUG
        @printf("T = %.2f\n", t)
    	@printf("Optimum: %.5f\n", optimumcost)
    	@printf("%.2f%% improvement\n", 100.0-100*optimumcost/initialcost)
    	@printf("after %.2f%% of iterations\n", 100*optItr/itr)
    	@printf("done %.2f%% of minimum iterations\n", 100*itr/minItrs)
    	toc()
    else
        println(STDOUT, optimumcost)
        for city in optimumsol
            print(STDERR, city)
            print(STDERR, " ")
        end
        println(STDERR)
    end
end

atexit(printresult)

while t > t0*targetTempF

    if time() - start > maxtime - 1.0
        exit()
    end

	currentsol, currentcost = iterate(t, currentsol, currentcost)
	if currentcost < optimumcost
		optimumsol = copy(currentsol)
        optimumcost = currentcost
		optItr = itr
	else
		stagItrs += 1
	end

	if stagItrs == Int(stagItrsF*minItrs) && itr <= minItrs
		stagItrs = 0
        if DEBUG
            println("reheating")
        end
		t *= 1.0 + 0.7*(minItrs-itr)/minItrs
	end

	t *= tDecay

	if DEBUG && itr%(minItrs/5) == 0
		@printf("T = %.2f\n", t)
		@printf("cost: %.5f\n", currentcost)
		@printf("optimum: %.5f\n", optimumcost)
	end
    itr += 1
end


@doc raw"""
    Entropy <: AbstractImageBinarizationAlgorithm
    Entropy()

    binarize([T,] img, f::Entropy)
    binarize!([out,] img, f::Entropy)

An algorithm for finding the binarization threshold value using
the entropy of the image histogram.

# Output

Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If
`T` is not specified, it is inferred from `out` and `img`.

# Details

This algorithm uses the entropy of a one-dimensional histogram to produce a threshold
value.

Let ``f_1, f_2, \ldots, f_I`` be the frequencies in the various bins of the
histogram and ``I`` the number of bins. With ``N = \sum_{i=1}^{I}f_i``, let
``p_i = \frac{f_i}{N}`` (``i = 1, \ldots, I``) denote the probability
distribution of gray levels. From this distribution one derives two additional
distributions. The first defined for discrete values ``1`` to ``s`` and the
other, from ``s+1`` to ``I``. These distributions are

```math
A: \frac{p_1}{P_s}, \frac{p_2}{P_s}, \ldots, \frac{p_s}{P_s}
\quad \text{and} \quad
B: \frac{p_{s+1}}{1-P_s}, \ldots, \frac{p_n}{1-P_s}
\quad \text{where} \quad
P_s = \sum_{i=1}^{s}p_i.
```

The entropies associated with each distribution are as follows:

```math
H(A) = \ln(P_s) + \frac{H_s}{P_s}
```
```math
H(B) = \ln(1-P_s) + \frac{H_n-H_s}{1-P_s}
```
```math
\quad \text{where} \quad
H_s = -\sum_{i=1}^{s}p_i\ln{p_i}
\quad \text{and} \quad
H_n = -\sum_{i=1}^{I}p_i\ln{p_i}.
```

Combining these two entropy functions we have

```math
\psi(s) = \ln(P_s(1-P_s)) + \frac{H_s}{P_s} + \frac{H_n-H_s}{1-P_s}.
```
Finding the discrete value ``s`` which maximises the function ``\psi(s)`` produces
the sought-after threshold value (i.e. the bin which determines the threshold).

See Section 4 of [1] for more details on the derivation of the entropy.

# Arguments

The function argument is described in more detail below.

##  `img::AbstractArray`

The image that needs to be binarized. The image is automatically converted
to `Gray` in order to construct the requisite graylevel histogram.

# Example

Binarize the "cameraman" image in the `TestImages` package.

```julia
using TestImages, ImageBinarization

img = testimage("cameraman")
img_binary = binarize(img, Entropy())
```

# References
1. J. N. Kapur, P. K. Sahoo, and A. K. C. Wong, “A new method for gray-level picture thresholding using the entropy of the histogram,” *Computer Vision, Graphics, and Image Processing*, vol. 29, no. 1, p. 140, Jan. 1985.[doi:10.1016/s0734-189x(85)90156-2](https://doi.org/10.1016/s0734-189x%2885%2990156-2)
"""
struct Entropy <: AbstractImageBinarizationAlgorithm end

function (f::Entropy)(out::GenericGrayImage, img::GenericGrayImage)
    edges, counts = build_histogram(img,  256)
    t = find_threshold(HistogramThresholding.Entropy(), counts[1:end], edges)
    @simd for i in CartesianIndices(img)
      out[i] = img[i] < t ? 0 : 1
    end
    out
end

(f::Entropy)(out::GenericGrayImage, img::AbstractArray{<:Color3}) =
    f(out, of_eltype(Gray, img))


"""
```julia
nanstd(A; dims=:, mean=nothing, corrected=true)
```
Compute the variance of all non-`NaN` elements in `A`, optionally over dimensions specified by `dims`.
As `Statistics.var`, but ignoring `NaN`s.

A precomputed `mean` may optionally be provided, which results in a somewhat faster
calculation. If `corrected` is `true`, then _Bessel's correction_ is applied, such
that the sum is divided by `n-1` rather than `n`.

As an alternative to `dims`, `nanstd` also supports the `dim` keyword, which
behaves identically to `dims`, but also drops any singleton dimensions that have
been reduced over (as is the convention in some other languages).

## Examples
```julia
julia> using NaNStatistics

julia> A = [1 2; 3 4]
2×2 Matrix{Int64}:
 1  2
 3  4

julia> nanstd(A, dims=1)
1×2 Matrix{Float64}:
 1.41421  1.41421

julia> nanstd(A, dims=2)
2×1 Matrix{Float64}:
 0.7071067811865476
 0.7071067811865476
```
"""
nanstd(A; dims=:, dim=:, mean=nothing, corrected=true) = sqrt!(__nanvar(mean, corrected, A, dims, dim))
export nanstd

sqrt!(x::Number) = sqrt(x)
function sqrt!(A::AbstractArray)
    @turbo for i ∈ eachindex(A)
        A[i] = sqrt(A[i])
    end
    return A
end


#=Copyright (c) 2017 Gabriel Goh

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.=#

λ = 1
Rf(β, α) = [   β        λ;
            -α*β (1 - α*λ)]

α = 0.05
β = 0.4

ϵ = 8
S = inv([ 1  0 ;
          α  1 ])

function err(k)
    R = Rf(β, α)
    sum( ( ((R^(k-i))*([ϵ; -α*ϵ]) )[2] )^2 for i = 1:k )
end

function err2(k, α, β)
    R = Rf(β, α)
    x = [ϵ; -α*ϵ]
    s = 0
    z = []
    for i = 1:k
      s = s + x[2]^2
      x = R*x
      push!(z, s)
    end
    return z
end

err2(100)[50] - err(50)

function geosumgen(R)
  Λ, U = eig(R)
  (b,k) -> begin;
    Λsum = (1./(1 - Λ)).*(1 - Λ.^k);
    real(U*(Λsum.*(U\b)));
  end
end

function runmomentum(A,b,k, ϵ)
  z = zeros(length(b))
  w = 3*ones(length(b))
  for i = 1:k
    z = β*z + (A*w - b) + ϵ*randn(1)
    w = w - α*z
  end
  return (z[1],w[1])
end

function runmomentum2(A,b,k, ϵ)
  z = zeros(length(b))
  w = 3*ones(length(b))
  fx = []
  for i = 1:k
    z = β*z + (A*w - b) + ϵ*randn(1)
    w = w - α*z
    push!(fx, w[1]*w[1])
  end
  return fx
end

k = 150
d = mean([runmomentum2(1,0, k, ϵ) for i = 1:100])
e = err2(k)
d2 = [runmomentum(1,0, i, 0.)[2]^2 for i = 1:k]

plot(d,".")
plot(d2+e)
#plot(d2)

plot(err2(200, 0.01, 0))


module Stepwise

    using DataFrames
    using StatsBase
    using ..DataMod, ..Utils

    export stepwise

    # The code for `step` and `stepwise` below is inspired by the code by Bogumił Kamiński, found at
    # https://stackoverflow.com/questions/49794476/backward-elimination-forward-selection-in-multilinear-regression-in-julia

    function multibic(data::Data, rhs::Vector{Symbol})::Tuple{Float64, Vector{RegressionModel}}
        models = get_models(data, rhs)::Vector{RegressionModel}
        return (mean(bic.(models))::Float64, models)
    end
    function multibic(models::Vector{RegressionModel})::Float64
        return mean(bic.(models))
    end

    function step(data::Data, rhs::Vector{Symbol}, forward::Bool,
                  prevbest::Float64, prevmodel::Vector{RegressionModel},
                  verbose::Bool, debug::Bool)::Tuple{Vector{Symbol}, Bool, Float64, Vector{RegressionModel}}
        options = forward ? setdiff(data.xs, rhs) : rhs
        if isempty(options)
            return (rhs, false, prevbest, prevmodel)
        end
        bestbic = prevbest
        bestrhs = rhs
        bestmodel = prevmodel
        improved = false
        for opt in options
            thisrhs = forward ? [rhs; opt] : setdiff(rhs, opt)
            thisbic, thismodel = multibic(data, thisrhs)
            if debug
                print(opt); print(" - "); println(thisbic)
            end
            if thisbic < bestbic
                bestbic = thisbic
                bestrhs = thisrhs
                bestmodel = thismodel
                improved = true
            end
        end
        if verbose
            if improved
                println("$(forward ? "forward" : "backward") step - new rhs: $bestrhs\nwith mean BIC $bestbic")
            else
                println("$(forward ? "forward" : "backward") step could not improve BIC")
            end
        end
        return (bestrhs, improved, bestbic, bestmodel)
    end

    function stepwise(data::Data; verbose::Bool = true, debug::Bool = false)::Vector{RegressionModel}
        rhs = Symbol[]
        model = get_models(data, Symbol[])
        bestbic = multibic(model)
        verbose && println("BIC empty model: $bestbic")
        while true
            verbose && println("step")
            rhs, improvedfwd, bestbic, model = step(data, rhs, true, bestbic, model, verbose, debug)
            rhs, improvedbwd, bestbic, model = step(data, rhs, false, bestbic, model, verbose, debug)
            if !improvedfwd && !improvedbwd
                sort!(rhs)
                verbose && println("final rhs: $rhs")
                return model
            end
        end
    end

end

using .Stepwise
export stepwise


# This file is a part of AstroLib.jl. License is MIT "Expat".
# Copyright (C) 2016 Mosè Giordano.

function _posang{T<:AbstractFloat}(units::Integer, ra1::T, dec1::T, ra2::T, dec2::T)
    # Convert all quantities to radians.
    if units == 0
        # All radians
        ra1_rad  = ra1
        ra2_rad  = ra2
        dec1_rad = dec1
        dec2_rad = dec2
    elseif units == 1
        # Right ascensions are in hours, declinations in degrees.
        ra1_rad  = ra1*pi/12.0
        ra2_rad  = ra2*pi/12.0
        dec1_rad = deg2rad(dec1)
        dec2_rad = deg2rad(dec2)
    elseif units == 2
        # Right ascensions and declinations are in degrees.
        ra1_rad  = deg2rad(ra1)
        ra2_rad  = deg2rad(ra2)
        dec1_rad = deg2rad(dec1)
        dec2_rad = deg2rad(dec2)
    else
        # In any other case throw an error.
        error("units must be 0 (radians), 1 (hours, degrees) or 2 (degrees)")
    end
    radif = ra2_rad - ra1_rad
    angle = atan2(sin(radif), cos(dec1_rad)*tan(dec2_rad) -
                  sin(dec1_rad)*cos(radif))
    if units == 0
        return angle
    else
        return rad2deg(angle)
    end
end

"""
    posang(units, ra1, dec1, ra2, dec2) -> angular_distance

### Purpose ###

Compute rigorous position angle of point 2 relative to point 1.

### Explanation ###

Computes the rigorous position angle of point 2 (with given right ascension and
declination) using point 1 (with given right ascension and declination) as the
center.

### Arguments ###

* `units`: integer, can be either 0, or 1, or 2.  Describes units of inputs and
 output:
    * 0: everything (input right ascensions and declinations, and output
      distance) is radians
    * 1: right ascensions are in decimal hours, declinations in decimal degrees,
      output distance in degrees
    * 2: right ascensions and declinations are in degrees, output distance in
      degrees
* `ra1`:  right ascension or longitude of point 1
* `dec1`: declination or latitude of point 1
* `ra2`: right ascension or longitude of point 2
* `dec2`: declination or latitude of point 2

Both `ra1` and `dec1`, and `ra2` and `dec2` can be given as 2-tuples `(ra1,
dec1)` and `(ra2, dec2)`.

### Output ###

Angle of the great circle containing `[ra2, dec2]` from the meridian containing
`[ra1, dec1]`, in the sense north through east rotating about `[ra1, dec1]`.
See `units` argument above for units.

### Method ###

The "four-parts formula" from spherical trigonometry (p. 12 of Smart's Spherical
Astronomy or p. 12 of Green' Spherical Astronomy).

### Example ###

Mizar has coordinates (ra, dec) = (13h 23m 55.5s, +54° 55' 31'').  Its
companion, Alcor, has coordinates (ra, dec) = (13h 25m 13.5s, +54° 59' 17'').
Find the position angle of Alcor with respect to Mizar.

``` julia
posang(1, ten(13, 25, 13.5), ten(54, 59, 17), ten(13, 23, 55.5), ten(54, 55, 31))
# => -108.46011246802047
```

### Notes ###

* If `ra1`, `dec1` are scalars, and `ra2`, `dec2` are vectors, then the output
 is a vector giving the distance of each element of `ra2`, `dec2` to `ra1`,
 `dec1`.  Similarly, if `ra1`,`de1` are vectors, and `ra2`,` dec2` are scalars,
 then the output is a vector giving the distance of each element of `ra1`,
 `dec1` to `ra2`, `dec2`.  If both `ra1`, `dec1` and `ra2`, `dec2` are vectors
 then the output is a vector giving the distance of each element of `ra1`,
 `dec1` to the corresponding element of `ra2`, `dec2`.
* The function `sphdist` provides an alternate method of computing a spherical
 distance.
* Note that `posang` is not commutative: the position angle between A and B is
  \$\\theta\$, then the position angle between B and A is \$180 + \\theta\$.

Code of this function is based on IDL Astronomy User's Library.
"""
posang(units::Integer, ra1::Real, dec1::Real, ra2::Real, dec2::Real) =
    _posang(units, promote(float(ra1), float(dec1), float(ra2), float(dec2))...)

function posang{R1<:Real, D1<:Real}(units::Integer,
                                   ra1::AbstractArray{R1},
                                   dec1::AbstractArray{D1},
                                   ra2::Real,
                                   dec2::Real)
    @assert length(ra1) == length(dec1)
    dist = similar(ra1, typeof(float(one(R1))))
    for i in eachindex(ra1)
        dist[i] = posang(units, ra1[i], dec1[i], ra2, dec2)
    end
    return dist
end

function posang{R2<:Real, D2<:Real}(units::Integer,
                                   ra1::Real,
                                   dec1::Real,
                                   ra2::AbstractArray{R2},
                                   dec2::AbstractArray{D2})
    @assert length(ra2) == length(dec2)
    dist = similar(ra2, typeof(float(one(R2))))
    for i in eachindex(ra2)
        dist[i] = posang(units, ra1, dec1, ra2[i], dec2[i])
    end
    return dist
end

function posang{R1<:Real, D1<:Real, R2<:Real, D2<:Real}(units::Integer,
                                                       ra1::AbstractArray{R1},
                                                       dec1::AbstractArray{D1},
                                                       ra2::AbstractArray{R2},
                                                       dec2::AbstractArray{D2})
    @assert length(ra1) == length(dec1) == length(ra2) == length(dec2)
    dist = similar(ra1, typeof(float(one(R1))))
    for i in eachindex(ra1)
        dist[i] = posang(units, ra1[i], dec1[i], ra2[i], dec2[i])
    end
    return dist
end

### Tuples input
posang(units::Integer, radec1::Tuple{Real, Real}, ra2::Real, dec2::Real) =
    posang(units, radec1..., ra2, dec2)

posang(units::Integer, ra1::Real, dec1::Real, radec2::Tuple{Real, Real}) =
    posang(units, ra1, dec1, radec2...)

posang(units::Integer, radec1::Tuple{Real, Real}, radec2::Tuple{Real, Real}) =
    posang(units, radec1..., radec2...)


############################################
# Developer: Luiz Henrique Nogueira Lorena #
# 15/01/22                                 #
############################################

using Random
using CSV, DataFrames, Statistics
using JuMP, CPLEX
using PyCall, PyPlot
using ScikitLearn
using ScikitLearn.CrossValidation: train_test_split
@sk_import linear_model: LinearRegression
@sk_import metrics: r2_score

py"""
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

def printCorrelationMatrix(corr):

    sns.set_theme(style="white")

    # Generate a mask for the upper triangle
    mask = np.triu(np.ones_like(corr, dtype=bool))

    # Set up the matplotlib figure
    f, ax = plt.subplots(figsize=(14, 12))
    ax.set_title('Pearson Correlation')

    # Draw the heatmap with the mask and correct aspect ratio
    sns.heatmap(corr,
                mask=mask,
                cmap="coolwarm",
                vmax=1,
                vmin = -1,
                center=0.5,
                square=True,
                linewidths=.5,
                cbar_kws={"shrink": .4},
                annot = True,
                annot_kws = {"size": 13})

def printParetoFront(XX,XY):
    sns.set_theme(style="white",font_scale=3)

    # Set up the matplotlib figure
    f, ax = plt.subplots(figsize=(14, 12))
    ax.set_title('Pareto Front')

    d = {'Total X-X correlations': XX, 'Total X-Y correlations': XY}
    df = pd.DataFrame(data=d)

    sns.lineplot(data = df,
                 x='Total X-X correlations',
                 y='Total X-Y correlations',
                 markers=True,
                 marker='o',
                 markersize = 12)

def printR2(X,Y):
    sns.set_theme(style="whitegrid",font_scale=3)
    d = {'Points': X, 'r2': Y}
    df = pd.DataFrame(data=d)
    f, ax = plt.subplots(figsize=(14, 12))
    sns.barplot(x="Points", y="r2", data=df)
"""

function loadData(dataset)
    df = CSV.read(dataset, DataFrame)
    ρ = cor(Matrix(df))                # Pearson correlation
    py"printCorrelationMatrix"(ρ)
    display(gcf())
    savefig("results/correlationMatrix.pdf")
    ρ = abs.(cor(Matrix(df)))
    return Matrix(df[:,1:end-1]), Vector(df[:,end]), ρ[1:end-1,1:end-1], ρ[1:end-1,end]
end

mutable struct Solution
    id
    obj
    variables
    θ
    φ
    r2
end

function executeVariableSelectModel(ρij, ρ0i, α)

    N = size(ρij,1)

    m = Model(CPLEX.Optimizer)

    @variable(m, u[i in 1:N], Bin)
    @variable(m, v[i in 1:N, j in 1:N; i<j])

    @objective(m, Max, α * sum( abs(ρ0i[i]) * u[i] for i in 1:N ) - (1-α) * sum( abs(ρij[i,j]) * v[i,j] for i in 1:N-1, j in i+1:N))

    for i in 1:N-1
        for j in i+1:N
            @constraint(m, v[i,j] >= 0)
            @constraint(m, v[i,j] >= u[i] + u[j] - 1)
        end
    end

    set_silent(m)

    optimize!(m)
    u_opt = JuMP.value.(u)
    v_opt = JuMP.value.(v)
    obj = JuMP.objective_value(m)

    φ = 0.0
    M = zeros(Bool,N)
    for i in 1:N-1
        for j in i+1:N
            if v_opt[i,j] > 0.5
                M[i] = M[j] = true
                φ += ρij[i,j]
            end
        end
    end

    θ = 0.0
    variaveis = []
    for i in 1:N
        if M[i]
            push!(variaveis,i)
        end
        if u_opt[i] > 0.5
            θ += ρ0i[i]
        end
    end

    return Solution(0, obj, variaveis, θ, φ, 0.0)
end

function executeRegression(X,y)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
    lin_reg = fit!(LinearRegression(), X_train, y_train)
    y_predict = predict(lin_reg, X_test)
    r2 = r2_score(y_test, y_predict)
    return r2
end

function executeNair(dataset)

    # Load data
    X, y, ρij, ρ0i = loadData(dataset)

    # Total of variables
    n = size(X,2)

    # Array to store Pareto front solutions
    solutionID = 1
    solutions = Solution[]
    solutionsTested = []

    ###########################
    # Get initial SS0 and SSF #
    ###########################

    #######################################
    # SS0 - variable with higher ρ0i value
    bestvariable = sortperm(ρ0i, rev = true)[1]
    θq = ρ0i[bestvariable]
    φq = 0

    # r2 for SS0
    X_new = reshape(X[:,bestvariable], length(X[:,bestvariable]), 1)
    r2_SS0 = executeRegression(X_new,y)

    ######################
    # SSF - all variables
    θr = sum(ρ0i)
    φr = 0.0
    for i in 1:n-1
        for j in i+1:n
            φr += ρij[i,j]
        end
    end

    # r2 for SSF
    r2_SSF = executeRegression(X,y)

    # αl for the first iteration
    αl = (φq - φr)/(θq + φq - θr - φr)

    # Objective value for SS0 and SSF
    zα = (αl * θq) - ( (1-αl) * φq )

    # Store solutions from pareto frontier SSO and SSF
    push!(solutions, Solution(solutionID,zα, [bestvariable], θq, φq, r2_SS0))
    solutionID += 1
    push!(solutions, Solution(solutionID, zα, collect(1:n), θr, φr, r2_SSF))
    solutionID += 1
    push!(solutionsTested,(solutions[1].id,solutions[2].id))

    #######################################
    # Solve the ILP with the calculated αl
    solution = executeVariableSelectModel(ρij, ρ0i, αl)

    if solution.obj > zα
        # Execute r2 for the new point
        X_new = X[:,solution.variables]
        solution.r2 = executeRegression(X_new,y)
        solution.id = solutionID
        solutionID += 1
        insert!(solutions, 2, solution)
    end

    if size(solutions,1) == 3

        pointinserted = true

        while pointinserted == true

            pointinserted = false

            i = 1
            totalpontos = size(solutions,1)

            while i < totalpontos

                solution1 = solutions[i]
                solution2 = solutions[i+1]

                if (solution1.id, solution2.id) ∉ solutionsTested

                    # Calculate αl
                    αl = (solution1.φ - solution2.φ)/(solution1.θ + solution1.φ - solution2.θ - solution2.φ)

                    # Calculate zα for SS0
                    zα = (αl * solution1.θ) - ( (1-αl) * solution1.φ )

                    # Solve the ILP with the calculated αl
                    solutionNEW = executeVariableSelectModel(ρij, ρ0i, αl)

                    #if solutionNEW.obj > zα
                    if solutionNEW.obj - zα > 0.0001
                        # Execute r2 for the new point
                        X_new = X[:,solutionNEW.variables]
                        solutionNEW.r2 = executeRegression(X_new,y)
                        solutionNEW.id = solutionID
                        solutionID += 1
                        insert!(solutions,i+1,solutionNEW)
                        pointinserted = true
                        totalpontos += 1

                        push!(solutionsTested,(solution1.id, solution2.id))
                    end

                end

                i += 1
            end

        end

    end

    # Plot Pareto Front
    XX = []
    XY = []
    for sol in solutions
        push!(XX,sol.φ)
        push!(XY,sol.θ)
    end
    py"printParetoFront"(XX,XY)
    display(gcf())
    savefig("results/paretoFront.pdf")

    py"printR2"(collect(1:size(solutions,1)), [sol.r2 for sol in solutions])
    display(gcf())
    savefig("results/r2.pdf")

    df = DataFrame(
        Point = collect(1:size(solutions,1)),
        Obj = [sol.obj for sol in solutions],
        Variables = [Int.(sol.variables) for sol in solutions],
        θ = [sol.θ for sol in solutions],
        φ = [sol.φ for sol in solutions],
        r2 = [sol.r2 for sol in solutions]
    )

    CSV.write("results/resultado.csv",df)

    print(df)

    return solutions
end

#Random.seed!(0)
solutions = executeNair("datasets/2.txt");


@doc raw"""
    Yen <: AbstractImageBinarizationAlgorithm
    Yen()

    binarize([T,] img, f::Yen)
    binarize!([out,] img, f::Yen)

Computes the binarization threshold value using Yen's maximum correlation criterion for
bilevel thresholding.

# Output

Return the binarized image as an `Array{Gray{T}}` of size `size(img)`. If
`T` is not specified, it is inferred from `out` and `img`.

# Details

This algorithm uses the concept of *entropic correlation* of a gray level histogram to produce a threshold
value.

Let ``f_1, f_2, \ldots, f_I`` be the frequencies in the various bins of the
histogram and ``I`` the number of bins. With ``N = \sum_{i=1}^{I}f_i``, let
``p_i = \frac{f_i}{N}`` (``i = 1, \ldots, I``) denote the probability
distribution of gray levels. From this distribution one derives two additional
distributions. The first defined for discrete values ``1`` to ``s`` and the
other, from ``s+1`` to ``I``. These distributions are

```math
A: \frac{p_1}{P_s}, \frac{p_2}{P_s}, \ldots, \frac{p_s}{P_s}
\quad \text{and} \quad
B: \frac{p_{s+1}}{1-P_s}, \ldots, \frac{p_n}{1-P_s}
\quad \text{where} \quad
P_s = \sum_{i=1}^{s}p_i.
```
The entropic correlations associated with each distribution are

```math
C(A) = -\ln \sum_{i=1}^{s} \left( \frac{p_i}{P_s} \right)^2 \quad \text{and} \quad C(B) = -\ln \sum_{i=s+1}^{I} \left( \frac{p_i}{1 - P_s} \right)^2.
```

Combining these two entropic correlation functions we have

```math
\psi(s) = -\ln \sum_{i=1}^{s} \left( \frac{p_i}{P_s} \right)^2 -\ln \sum_{i=s+1}^{I} \left( \frac{p_i}{1 - P_s} \right)^2.
```
Finding the discrete value ``s`` which maximises the function ``\psi(s)`` produces
the sought-after threshold value (i.e. the bin which determines the threshold).

# Arguments

The function argument is described in more detail below.

##  `img::AbstractArray`

The image that needs to be binarized. The image is automatically converted
to `Gray` in order to construct the requisite graylevel histogram.

# Example

Binarize the "cameraman" image in the `TestImages` package.

```julia
using TestImages, ImageBinarization

img = testimage("cameraman")
img_binary = binarize(img, Yen())
```

# Reference

1. Yen JC, Chang FJ, Chang S (1995), “A New Criterion for Automatic Multilevel Thresholding”, IEEE Trans. on Image Processing 4 (3): 370-378, [doi:10.1109/83.366472](https://doi.org/10.1109/83.366472)
"""
struct Yen <: AbstractImageBinarizationAlgorithm end

function (f::Yen)(out::GenericGrayImage, img::GenericGrayImage)
    edges, counts = build_histogram(img,  256)
    t = find_threshold(HistogramThresholding.Yen(), counts[1:end], edges)
    @simd for i in CartesianIndices(img)
        out[i] = img[i] < t ? 0 : 1
    end
    out
end

(f::Yen)(out::GenericGrayImage, img::AbstractArray{<:Color3}) =
    f(out, of_eltype(Gray, img))


#=

Author: Benjamin Hemingway

This code adjusts the square matrix in the case it is not positive semi definite.

This code is based on the Matlab package nearestSPD version 1.1.0.0 by John D'Errico
=#
function spdMatrix(M::Array{T,2}) where T<:Real
  # symmetrize A into B
  B = (M + M')/2;
  # Compute the symmetric polar factor of B. Call it H.
  # Clearly H is itself SPD.
  out = LinearAlgebra.svd(B)
  H = out.Vt'*Diagonal(out.S)*out.Vt
  # get Ahat in the above formula
  Ahat = (B+H)/2
  # ensure symmetry
  Ahat = (Ahat + Ahat')/2;
  # test that Ahat is in fact PD. if it is not so, then tweak it just a bit.
  p = isposdef(Ahat)
  k = 0;
  while p==false
    k+=1
    # Ahat failed the chol test. It must have been just a hair off,
    # due to floating point trash, so it is simplest now just to
    # tweak by adding a tiny multiple of an identity matrix.
    mineig = minimum(eigvals(Ahat))
    Ahat += (-mineig*k.^2 + eps(mineig))*Matrix{Float64}(I,size(Ahat))
    p = isposdef(Ahat)
    if k>99
      error("Could not create Positive Definite Matrix")
    end
  end
  return Ahat
end


function sphere_vertex(x, y, z, scale)
    length = sqrt(x^2 + y^2 + z^2)
    return [(i * scale) / length for i in (x, y, z)]
end
function sphere_vertex(a::Vector{T}, scale) where T
    @assert length(a) == 3
    x, y, z = a[1], a[2], a[3]
    return sphere_vertex(x, y, z, scale)
end

function middle_point!(point_1, point_2, middle_point_cache, verts, scale)
    # We check if we have already cut this edge first
    # to avoid duplicated verts
    smaller_index = min(point_1, point_2)
    greater_index = max(point_1, point_2)
    key = "$smaller_index, $greater_index"
    if haskey(middle_point_cache, key)
        return middle_point_cache[key]
    end
    vert_1 = verts[point_1]
    vert_2 = verts[point_2]
    middle = [sum(i) / 2 for i in zip(vert_1, vert_2)]
    append!(verts, [sphere_vertex(middle, scale)])
    index = length(verts)
    middle_point_cache[key] = index
    return index
end # function

"""
Create icosphere
"""
function icosphere(subdiv = 3, scale = 1.0)
    PHI = (1. + sqrt(5.)) / 2.
    verts = [(sphere_vertex(-1., PHI, 0., scale)),
        (sphere_vertex(1., PHI, 0., scale)),
        (sphere_vertex(-1., -PHI, 0., scale)),
        (sphere_vertex(1., -PHI, 0., scale)),
        (sphere_vertex(0., -1., PHI, scale)),
        (sphere_vertex(0., 1., PHI, scale)),
        (sphere_vertex(0., -1., -PHI, scale)),
        (sphere_vertex(0., 1., -PHI, scale)),
        (sphere_vertex(PHI, 0., -1., scale)),
        (sphere_vertex(PHI, 0., 1., scale)),
        (sphere_vertex(-PHI, 0., -1., scale)),
        (sphere_vertex(-PHI, 0., 1., scale))]
    faces = [[1, 12, 6], # 5 faces around point 0
            [1, 6, 2],
            [1, 2, 8],
            [1, 8, 11],
            [1, 11, 12],
            [2, 6, 10], # Adjacent faces
            [6, 12, 5],
            [12, 11, 3],
            [11, 8, 7],
            [8, 2, 9],
            [4, 10, 5],  # 5 faces around 3
            [4, 5, 3],
            [4, 3, 7],
            [4, 7, 9],
            [4, 9, 10],
            [5, 10, 6],# Adjacent faces
            [3, 5, 12],
            [7, 3, 11],
            [9, 7, 8],
            [10, 9, 2]]

    middle_point_cache = Dict{String,Int}()
    for i in 1:subdiv
        faces_subdiv = Array{Int64,1}[]
        for tri in faces
            v1 = middle_point!(tri[1], tri[2], middle_point_cache, verts, scale)
            v2 = middle_point!(tri[2], tri[3], middle_point_cache, verts, scale)
            v3 = middle_point!(tri[3], tri[1], middle_point_cache, verts, scale)
            append!(faces_subdiv, [[tri[1], v1, v3]])
            append!(faces_subdiv, [[tri[2], v2, v1]])
            append!(faces_subdiv, [[tri[3], v3, v2]])
            append!(faces_subdiv, [[v1, v2, v3]])
            faces = faces_subdiv
        end
    end
    v = Vector{Point3f0}(verts)
    f = Vector{TriangleFace{Int}}(faces)
    sphere = normal_mesh(Mesh(v, f))
    return sphere
end


using DataStructures
entropy(s::AbstractString) = -sum(x -> x / length(s) * log2(x / length(s)), values(counter(s)))

function fibboword(n::Int64)
    # Initialize the result
    r = Array{String}(n)
    # First element
    r[1] = "0"
    # If more than 2, set the second element
    if n ≥ 2 r[2] = "1" end
    # Recursively create elements > 3
    for i in 3:n
        r[i] = r[i - 1] * r[i - 2]
    end
    return r
end

function testfibbo(n::Integer)
    fib = fibboword(n)
    for i in 1:length(fib)
        @printf("%3d%9d%12.6f\n", i, length(fib[i]), entropy(fib[i]))
    end
    return 0
end

println("  n\tlength\tentropy")
testfibbo(37)


# tests for functors

using Base.Test
using NumericExtensions

## basic operators

@test evaluate(Negate(),   2) == -2
@test evaluate(Add(),      2, 3) == 5
@test evaluate(Subtract(), 2, 3) == -1
@test evaluate(Multiply(), 2, 3) == 6
@test evaluate(Divide(),   2, 3) == 2 / 3
@test evaluate(Modulo(),   5, 3) == 2
@test evaluate(Pow(),      2, 3) == 8

@test evaluate(Greater(),      2, 3) == false
@test evaluate(Greater(),      2, 2) == false
@test evaluate(GreaterEqual(), 2, 3) == false
@test evaluate(GreaterEqual(), 2, 2) == true

@test evaluate(Less(),      3, 2) == false
@test evaluate(Less(),      3, 2) == false
@test evaluate(LessEqual(), 3, 2) == false
@test evaluate(LessEqual(), 2, 2) == true

@test evaluate(Equal(), 3, 2)    == false
@test evaluate(Equal(), 2, 2)    == true
@test evaluate(NotEqual(), 3, 2) == true
@test evaluate(NotEqual(), 2, 2) == false

@test evaluate(Not(), true)  == false
@test evaluate(Not(), false) == true
@test evaluate(And(), true, false) == false
@test evaluate(Or(),  true, false) == true

@test evaluate(BitwiseNot(), 1) == ~1
@test evaluate(BitwiseAnd(), 3, 6) == 2
@test evaluate(BitwiseOr(),  3, 6) == 7
@test evaluate(BitwiseXor(), 3, 6) == 5

# elementary math

x = 0.8
y = 0.6
z = 1.2

@test evaluate(MinFun(), x, y) == min(x, y)
@test evaluate(MaxFun(), x, y) == max(x, y)

@test evaluate(AbsFun(), -0.5) == 0.5
@test evaluate(Abs2Fun(), x) == abs2(x)
@test evaluate(SqrFun(),  x) == sqr(x)
@test evaluate(SqrtFun(), x) == sqrt(x)
@test evaluate(CbrtFun(), x) == cbrt(x)
@test evaluate(RcpFun(),  x) == rcp(x)
@test evaluate(RsqrtFun(), x) == rsqrt(x)
@test evaluate(RcbrtFun(), x) == rcbrt(x)

@test evaluate(HypotFun(), x, y) == hypot(x, y)

@test evaluate(IsfiniteFun(), 1.0) == true
@test evaluate(IsfiniteFun(), Inf) == false
@test evaluate(IsfiniteFun(), NaN) == false

@test evaluate(IsinfFun(), 1.0) == false
@test evaluate(IsinfFun(), Inf) == true
@test evaluate(IsinfFun(), NaN) == false

@test evaluate(IsnanFun(), 1.0) == false
@test evaluate(IsnanFun(), Inf) == false
@test evaluate(IsnanFun(), NaN) == true

@test evaluate(FloorFun(), x) == floor(x)
@test evaluate(CeilFun(), x)  == ceil(x)
@test evaluate(RoundFun(), x) == round(x)
@test evaluate(TruncFun(), x) == trunc(x)

@test evaluate(IfloorFun(), x) == ifloor(x)
@test evaluate(IceilFun(), x)  == iceil(x)
@test evaluate(IroundFun(), x) == iround(x)
@test evaluate(ItruncFun(), x) == itrunc(x)

@test evaluate(ModFun(), x, y) == mod(x, y)
@test evaluate(RemFun(), x, y) == rem(x, y)
@test evaluate(FldFun(), x, y) == fld(x, y)
@test evaluate(DivFun(), 8, 6) == 1

@test evaluate(ExpFun(), x)   == exp(x)
@test evaluate(Exp2Fun(), x)  == exp2(x)
@test evaluate(Exp10Fun(), x) == exp10(x)
@test evaluate(Expm1Fun(), x) == expm1(x)

@test evaluate(LogFun(), x)   == log(x)
@test evaluate(Log2Fun(), x)  == log2(x)
@test evaluate(Log10Fun(), x) == log10(x)
@test evaluate(Log1pFun(), x) == log1p(x)

@test evaluate(SinFun(), x) == sin(x)
@test evaluate(CosFun(), x) == cos(x)
@test evaluate(TanFun(), x) == tan(x)
@test evaluate(AsinFun(), x) == asin(x)
@test evaluate(AcosFun(), x) == acos(x)
@test evaluate(AtanFun(), x) == atan(x)
@test evaluate(Atan2Fun(), x, y) == atan2(x, y)

# ternary functors

@test evaluate(FMA(), x, y, z) == (x + y * z)
@test evaluate(IfelseFun(), true, y, z) == y
@test evaluate(IfelseFun(), false, y, z) == z

# Test unary functors

for e in [
    (Negate, -),
    (AbsFun, abs), (Abs2Fun, abs2), (SqrtFun, sqrt), (CbrtFun, cbrt),
    (FloorFun, floor), (CeilFun, ceil), (RoundFun, round), (TruncFun, trunc),
    (ExpFun, exp), (Exp2Fun, exp2), (Exp10Fun, exp10), (Expm1Fun, expm1),
    (LogFun, log), (Log2Fun, log2), (Log10Fun, log10), (Log1pFun, log1p),
    (SinFun, sin), (CosFun, cos), (TanFun, tan), (AsinFun, asin), (AcosFun, acos), (AtanFun, atan),
    (SinhFun, sinh), (CoshFun, cosh), (TanhFun, tanh), (AsinhFun, asinh), (AcoshFun, acosh), (AtanhFun, atanh),
    (ErfFun, erf), (ErfcFun, erfc), (GammaFun, gamma), (LgammaFun, lgamma), (DigammaFun, digamma),
    (IsfiniteFun, isfinite), (IsinfFun, isinf), (IsnanFun, isnan)]

    T = e[1]
    f = e[2]

    @test evaluate(T(), 1.0) === f(1.0)
    @test evaluate(T(), 1) === f(1)
end

# Test binary functors

for e in [
    (Add, +),
    (Subtract, -),
    (Multiply, *),
    (Divide, /),
    (Pow, ^),
    (MaxFun, max),
    (MinFun, min),
    (HypotFun, hypot),
    (Atan2Fun, atan2),
    (Greater, >),
    (GreaterEqual, >=),
    (Less, <),
    (LessEqual, <=),
    (Equal, ==),
    (NotEqual, !=)]

    T = e[1]
    f = e[2]

    @test evaluate(T(), 2.0, 3.0) === f(2.0, 3.0)
    @test evaluate(T(), 2, 3) === f(2, 3)
    @test evaluate(T(), 2.0, 3) === f(2.0, 3)
    @test evaluate(T(), 2, 3.0) === f(2, 3.0)
end


"""
    SomeVertexInCircumsphere(simplex1, r2, c2)

Decides whether some vertices of simplex1 lies inside the circumsphere of simplex2
(defined by the radius and centroid of simplex1, r2 and c2). Each column of simplex1 is a vertex.

Arguments
---------
c2::Vector{Float64} Column vector. Centroid of simplex2.
r2::Float64 Radius of simplex 2

Returns either 0 or 1; 1 if some of the vertices of simplex1 are contained in the circumsphere
of simplex 2, 0 otherwise.
"""
function SomeVertexInCircumsphere(simplex1, r2, c2)
    # The dimension
    n = size(simplex1, 1)

    i = 1
    some_vertex_in_circumsphere = false

    while i <= (n + 1) && !some_vertex_in_circumsphere
        # Difference between the i-th vertex of simplex1 and centroid of simplex2
        ith_vertex = simplex1[:, i] - c2
        tmp = heaviside0(r2^2 - transpose(ith_vertex) * ith_vertex) # Radius times norm

        if tmp == 1
            some_vertex_in_circumsphere = true
        end

        i = i + 1
    end

    return some_vertex_in_circumsphere
end


## Class shape transformation method
#==========================================================================================#

# Basic shape function
function shape_function(x, basis_func, coeffs, coeff_LE = 0)
    n     = length(coeffs)
    terms = basis_func.(x, n - 1, 0:n-1)
    dot(coeffs, terms) + coeff_LE * (x^0.5) * (1 - x)^(n - 0.5)
end

# Computing coordinates
CST_coordinates(class_func, basis_func, x, alphas, dz, coeff_LE, args...) = class_func(x) * shape_function(x, basis_func, alphas, coeff_LE) + x * dz

## Bernstein basis
#==========================================================================================#

bernstein_class(x, N1, N2) = x^N1 * (1 - x)^N2
bernstein_basis(x, n, k)   = binomial(n, k) * bernstein_class(x, k, n - k)

"""
    kulfan_CST(alpha_u, alpha_l,
               (Δz_u, Δz_l) = (0., 0.),
               (LE_u, LE_l) = (0., 0.),
               n            = 40)

Define a cosine-spaced foil with ``2n`` points using the Class Shape Transformation method on a Bernstein polynomial basis for the upper and lower coordinates.

The foil is defined by arrays of coefficients ``(α_u,~ α_l)`` for the upper and lower surfaces (not necessarily of the same lengths), trailing-edge displacement values ``(Δz_u,~ Δz_l)``, and coefficients for leading edge modifications on the upper and lower surfaces at the nose.
"""
function kulfan_CST(alpha_u, alpha_l, (dz_u, dz_l) = (0., 0.), (LE_u, LE_l) = (0., 0.), n :: Integer = 40, N1 = 0.5, N2 = 1.)
    # Cosine spacing for airfoil of unit chord length
    xs = cosine_spacing(0.5, 1, n)

    # λ-function for Bernstein polynomials
    bernie(x, alphas, dz, LE) = CST_coordinates(y -> bernstein_class(y, N1, N2), bernstein_basis, x, alphas, dz, LE)

    # Upper and lower surface generation
    upper_surf = [ bernie(x, alpha_u, dz_u, LE_u) for x ∈ xs ]
    lower_surf = [ bernie(x, alpha_l, dz_l, LE_l) for x ∈ xs ]

    # Counter-clockwise ordering
    @views Foil([ xs[end:-1:2] upper_surf[end:-1:2] ;
                  xs           lower_surf           ], "Kulfan CST")
end

"""
    camber_CST(α_c, α_t,
               (Δz_u, Δz_l) :: NTuple{2, Real},
               coeff_LE = 0.,
               n :: Integer = 40)

Define a cosine-spaced foil with ``2n`` points using the Class Shape Transformation method on a Bernstein polynomial basis for the camber and thickness coordinates.

The foil is defined by arrays of coefficients ``(α_c,~ α_t)`` for the upper and lower surfaces, trailing-edge spacing values ``(Δz_u,~Δz_l)``, and a coefficient for the leading edge modifications at the nose.
"""
function camber_CST(α_cam, α_thicc, dz_thicc = 0., coeff_LE = 0, n :: Integer = 40, N1 = 0.5, N2 = 1.)
    # Cosine spacing for airfoil of unit chord length
    xs = cosine_spacing(0.5, 1, n)

    # λ-function for Bernstein polynomials
    bernie(x, αs, dz = 0.) = CST_coordinates(y -> bernstein_class(y, N1, N2), bernstein_basis, x, αs, dz, coeff_LE)

    # Upper and lower surface generation
    cam   = [ bernie(x, α_cam) for x ∈ xs ]
    thicc = [ bernie(x, α_thicc, dz_thicc) for x ∈ xs ]

    Foil(camber_thickness_to_coordinates(xs, cam, thicc), "Camber-Thickness CST")
end

"""
    coordinates_to_CST(coords, num_dvs)

Convert coordinates to a specified number of CST variables by performing a least-squares solution.
"""
function coordinates_to_CST(coords, num_dvs)
    xs       = @views coords[:,1]
    S_matrix = reduce(hcat, @. bernstein_class(xs, 0.5, 1.0) * bernstein_basis(xs, num_dvs - 1, i) for i in 0:num_dvs - 1)
    alphas   = @views S_matrix \ coords[:,2]
end

"""
    camber_thickness_to_CST(coords, num_dvs)

Convert camber-thickness coordinates to a specified number of CST variables by performing a least-squares solution.
"""
function camber_thickness_to_CST(coords, num_dvs)
    xs, camber, thickness = (columns ∘ coordinates_to_camber_thickness)(coords)

    alpha_cam   = coordinates_to_CST([ xs camber ], num_dvs)
    alpha_thick = coordinates_to_CST([ xs thickness ], num_dvs)

    alpha_cam, alpha_thick
end


#############################
##  Numerical experiments associated with section 5.2.2
#############################
## This file contains the numerical experiments used to compare our method with
## Chebfun's Volterra integral equation implementation when high polynomial orders are required.
## We only include our own side of the implementation here. See the references in the paper for Chebfun.
##

using ApproxFun, MultivariateOrthogonalPolynomials, BandedMatrices, BlockBandedMatrices, SpecialFunctions, Plots, SparseArrays
using SparseVolterraExamples

#####
## First, we can check that the stated arctan function indeed approximates a step-like function.
## This is in Figure 6(a).
v(x,K) = atan(K*x)
    plot(x->v(x,10),0,1, legend=:true , xlabel = "x", label="k=10" ,ylabel = "u_2(x,K)",  legendfontsize=12, tickfontsize=10, thickness_scaling = 1.2 , grid=:none)
    plot!(x->v(x,50),0,1, legend=:true , xlabel = "x", label="k=50" ,ylabel = "u_2(x,K)",  legendfontsize=12, tickfontsize=10, thickness_scaling = 1.2 , grid=:none)
    plot!(x->v(x,100),0,1, legend=:true , xlabel = "x", label="k=100" , ylabel = "u_2(x,k)", legendfontsize=12, tickfontsize=10, thickness_scaling = 1.2 , grid=:none)
    plot!(x->v(x,200),0,1, legend=:bottomright , xlabel = "x", label="k=200" , ylabel = "u_2(x,k)", legendfontsize=12, tickfontsize=10, thickness_scaling = 1.2 , grid=:none)

#####
## First we define the Kernel and g(x,k) as in section 5.2.2.
gf(x,k) = k/(k^2*x^2+1)-(exp(x^2)*atan(k*x))/(2*k^2)+(exp(x^2)*x)/(2*k)-1/2*exp(x^2)*x^2*atan(k*x)
Kfun(x,y) = y*exp(x^2)

#####
## Solver function with step-by-step explanation for given k and with polynomial degree 'n'
function solveSec532(k,n,gf,Kfun)
    gF = Fun(x->gf(x,k),Jacobi(1,2, 0..1),n)             # Approximate g in the appropriate basis
    V = triVolterraFullKernelOpP01(Kfun,n,true,155)      # The following steps generate the appropriate Volterra operator
        V = reflectPabtoPba(n)*WLoweringP01P00(n)*V
        V = Conversion(Jacobi(0,0,0..1),Jacobi(1,2,0..1))[1:n,1:n]*V[1:n,1:n]
        V = Derivative(Jacobi(0,1,0..1),1)[1:n,1:n]-V
    coeff = [DirectEvalLHSP10at0(n);V[1:n-1,1:n]] \ [0;pad(gF.coefficients,n-1)] # Append evaluation and initial conditions and then solve the equation
    return Fun(Jacobi(0,1, 0..1),coeff) # returns solution Fun
end

#####
## Now we can compute and plot a specific example for k=100, plotting both the numerical approximation and the analytic solution.
## Computing errors for various k is a bit more work since automatic convergence testing is not currently implemented but a straightforward for loop can do it from here.
u = solveSec532(100,50,gf,Kfun)
plot(u, label="sparse method")
    plot!(x->v(x,100),0,1, legend=:bottomright , xlabel = "x", label="analytic" ,ylabel = "u_2(x,100)",  legendfontsize=12, tickfontsize=10, thickness_scaling = 1.2 , grid=:none)

#####
## We can also use BenchmarkTools to get a somewhat robust time estimation.
## Note however that this obviously strongly depends on the hardware you are using.
using BenchmarkTools
@benchmark solveSec532(100,50,gf,Kfun)
@benchmark solveSec532(200,300,gf,Kfun)

#####
## Now we plot the operator bandedness for the k=100 example, this is basically Figure 6(b).
function OperatorSec532(k,n,Kfun)
    V = triVolterraFullKernelOpP01(Kfun,n,true,155)    # The following steps generate the appropriate Volterra operator
        V = reflectPabtoPba(n)*WLoweringP01P00(n)*V
        V = Conversion(Jacobi(0,0,0..1),Jacobi(1,2,0..1))[1:n,1:n]*V[1:n,1:n]
        V = Derivative(Jacobi(0,1,0..1),1)[1:n,1:n]-V
        V = [DirectEvalLHSP10at0(n);V[1:n-1,1:n]] # Append evaluation and initial conditions
    return V # returns operator
end
#####
## Plots.jl's spy plot is not currently compatible with these types, so we instead convert to generic sparse for visualization.
## The exact bandedness properties depend on the chosen degrees and parameters but here is a standard example
spy(sparse(OperatorSec532(100,300,Kfun)),markersize=2.8,marker=:rect)

examples/Section 5-2-2 - High order solutions with step-like function.jl


using Jacobi
using Polynomials

# Script for writing the legerdre_RefLine file with Legendre polynomials up to
# a maximum degree of max_degree.
max_degree = 50

setprecision(BigFloat, 512)

function weights(points)
    N = length(points)
    return [ 2/((1-x^2)*(dlegendre(x, N)^2)) for x in points ]
end

open("legendre_line.jl", "w") do io
    for i = 1:max_degree
        # Legendre = Jacobi with α = β = 0
        x = jacobi_zeros(i, 0, 0, BigFloat)
        w = weights(x)/2
        println("Error in sum of the weights for degree $i: $(1-sum(w))")
        x = (x .+ 1)/2
        println(io, "@generated function gauss_quadrature(form::Val{:legendre},")
        println(io, "                                     shape::RefLine,")
        println(io, "                                     degree::Val{$i},")
        println(io, "                                     type::Type{T}) where {T}")
        println(io, """    weights = SVector(:(\$(T(big"$(w[1])"))),""")
        for j in 2:i
        println(io, """                      :(\$(T(big"$(w[j])"))),""")
        end
        println(io, "                       )")
        println(io, """    points = SVector(:(\$(NTuple{1,T}(big"$(x[1])"))),""")
        for j in 2:i
        println(io, """                     :(\$(NTuple{1,T}(big"$(x[j])"))),""")
        end
        println(io, "                      )")
        println(io, "    return weights, points")
        println(io, "end")
    end
end


using Clustering, RDatasets, Random, Measures, Plots; pyplot()
Random.seed!(0)

K = 3
df = dataset("cluster", "xclara")
data = copy(convert(Array{Float64}, df)')

seeds = initseeds(:rand, data, K)
xclaraKmeans = kmeans(data, K, init = seeds)

println("Number of clusters: ", nclusters(xclaraKmeans))
println("Counts of clusters: ", counts(xclaraKmeans))

df.Group  = assignments(xclaraKmeans)

p1 = scatter(df[:, :V1], df[:, :V2], c=:blue, msw=0)
     scatter!(df[seeds, :V1], df[seeds, :V2], markersize=12, c=:red, msw=0)

p2 = scatter( df[df.Group .== 1, :V1], df[df.Group .== 1, :V2], c=:blue, msw=0)
     scatter!( df[df.Group .== 2, :V1], df[df.Group .== 2, :V2], c=:red, msw=0)
     scatter!( df[df.Group .== 3, :V1], df[df.Group .== 3, :V2], c=:green, msw=0)

plot(p1,p2,legend=:none,ratio=:equal,
    size=(800,400), xlabel="V1", ylabel="V2", margin = 5mm)


#=
NacaThicknessFunction.jl
Jacob Child
February 3, 2022
Description:
NACA 4-series Thickness Formula The NACA 4-series formula for airfoil thickness
along the chord is given by the following polynomial function, where m is the value of maximum
thickness (as a percentage of the chord), and x is the x-position along the chord. (This formulation
is for airfoils with sharp trailing edges.)
t = 10m(.2969sqrt(x) - .1620x - .3537x^2 + .2843x^3 - .1015x^4)

Pseudo code- Have a function to ask for and generate x values in an array
Then have a function to calculate various thicknesses
Format as table? and output to screen
***use docstrings***
=#

"""
xinput(x, step)
Takes in the desired length, l, and step size and outputs an x array

"""
function x_input(L, step)
    x = [0:step:L;] #makes array stopping at L with step size step
    return x
end

"""
thickness_calculations(x)
Takes in the x coordinates, calculates thickness, and ouputs the array

"""
function thickness_calculations(x)
    m = 0.10
    t = 10*m.*(.2969*sqrt.(x) - .1260*x - .3537*x.^2 + .2843*x.^3 - .1015*x.^4)
    return t
end

#using Pkg  #Just for the first run through
#Pkg.add("DataFrames")  #just for the first run through

println("Hello! We are going to calculate the thickness along an airfoil \n")
println("\n Enter the Chord length")
L = readline()
L = parse(Int64, L)

println("\n Enter the desired step size to calculate the thickness at")
step = readline()
step = parse(Float64, step)

#println("Type of Inputs", typeof(L), typeof(step))

x = x_input(L, step)
t = thickness_calculations(x)

println(t) #If Running in VS Code Uncomment this as I couldn't get DataFrame to work

#using DataFrames #Only seems to work in Julia Terminal, not VS Code Terminal? ***research

#DataFrame()

#DataFrame("X" => x, "Thickness" => t)


using RAFF

#
# This example is the "Basic Usage" example in the documentation
#

# Matrix with data
A=[-2.0  5.0;
   -1.5  3.25;
   -1.0  2.0 ;
   -0.5  1.25;
    0.0  1.0 ;
    0.5  2.55;
    1.0  2.0 ;
    1.5  3.25;
    2.0  5.0 ;]

# Define the model to fit data
model(x, θ) = θ[1] * x[1]^2 + θ[2]

# Number of parameters in the model
n = 2

# Run RAFF
output = raff(model, A, n)

# Print output
println(output)


function tournament_selection(P, a = rand(1:length(P)))
    # chose two different solutions at random
    b = rand(1:length(P))
    while a == b
        b = rand(1:length(P))
    end

    # perform selection
    P[a].rank < P[b].rank || (P[a].rank == P[b].rank && P[a].crowding > P[b].crowding ) ? P[a] : P[b]
end

function gen_β(β, η, D, R)
    α = 2.0 .- β .^ (-  η - 1.0 )
    mask = R .<= 1.0 ./ α
    s = 1.0 / (η + 1.0)
    βq = [ mask[i] ?  (R[i] * α[i])^s : (1.0 / (2.0 - R[i]*α[i]))^s for i in 1:D]
    βq
end

function SBX_crossover(vector1, vector2, bounds, η=15, p_variable = 0.9)
    xu = view(bounds, 2,:)
    xl = view(bounds, 1,:)
    D = length(vector1)

    do_crossover = ones(Bool, D)
    do_crossover[rand(D) .> p_variable] .= false
    do_crossover[ abs.( vector2 - vector1 ) .<= eps() ] .= false

    y1 = min.( vector1, vector2 )
    y2 = max.( vector1, vector2 )
    Δ = max.(eps(), y2 - y1)

    R = rand(D)

    β = @. 1.0 + (2.0 * (y1 - xl) / Δ)
    βq = gen_β(β, η, D, R)
    c1 = @. 0.5*(y1 + y2 -  βq*Δ)

    β = @. 1.0 + (2.0 * (y1 - xl) / Δ)
    βq = gen_β(β, η, D, R)
    c2 = @. 0.5*(y1 + y2 +  βq*Δ)

    # swap
    mask = rand(Bool, D)
    cc = copy(c1)
    c1[mask] = c2[mask]
    c2[mask] = cc[mask]

    cc1 = copy(vector1)
    cc1[do_crossover] = c1[do_crossover]
    cc2 = copy(vector2)
    cc2[do_crossover] = c2[do_crossover]

    reset_to_violated_bounds!(cc1, bounds)
    reset_to_violated_bounds!(cc2, bounds)

    return cc1, cc2
end

function polynomial_mutation!(vector, bounds, η=20, prob = 1 / length(vector))
    do_mutation = rand(length(vector)) .< prob

    xu = view(bounds, 2,do_mutation)
    xl = view(bounds, 1,do_mutation)
    x = view(vector, do_mutation)

    δ1 = (x - xl) ./ (xu - xl)
    δ2 = (xu - x) ./ (xu - xl)

    D = length(xu)
    R = rand(D)
    mask = R .< 0.5
    s = η+1.0
    mut_pow = 1.0 / (η + 1.0)
    δq = [ mask[i] ?
            ^(2.0R[i] + (1. - 2.0R[i]) * ^(1.0 - δ1[i], s), mut_pow) - 1.0 :
            1.0 - (2.0 * (1.0 - R[i]) + 2.0 * (R[i] - 0.5) * ^(1.0 - δ2[i], s))^mut_pow
            for i in 1:D
        ]

    vector[do_mutation] = x + δq .* ( xu - xl)
    # correct using reset to bound
    #
    vector

end


using ApproxFun, Base.Test

## ODEs

d=Interval(-20000.,20000.)
x=Fun(identity,d)
u=[dirichlet(d);Derivative(d)^2+I]\[1.,0.]
u=[dirichlet(d);Derivative(d)^2+I]\[1.,0.]
@time u=[dirichlet(d);Derivative(d)^2+I]\[1.,0.]
println("Cos/Sin: should be ~0.017")

d=Interval(-1000.,5.)
x=Fun(identity,d)
u=[dirichlet(d);Derivative(d)^2-x]\[1.,0.]
u=[dirichlet(d);Derivative(d)^2-x]\[1.,0.]
@time u=[dirichlet(d);Derivative(d)^2-x]\[1.,0.]
println("Airy: 0.014356 seconds (1.08 k allocations: 8.015 MB)")

M=cache(ApproxFun.InterlaceOperator([dirichlet(d);Derivative(d)^2-x]);padding=true)
@time ApproxFun.resizedata!(M,12500,:)
println("Airy construct op: 0.005417 seconds (81 allocations: 5.279 MB)")

S=Chebyshev()
x=Fun(identity,S)
D=Derivative(S)
L=D^2+(7+2x+6x^2)
B=dirichlet(S)
n=20000
rhs=ones(n+2)
u=[B;L]\rhs
u=[B;L]\rhs
@time u=[B;L]\rhs
println("Poly: should be ~0.025")

S=Chebyshev()
x=Fun(identity,S)
D=Derivative(S)
L=D^2+cos(x)
B=dirichlet(S)
n=2000
rhs=ones(n+2)
u=linsolve([B;L],rhs;maxlength=Inf)
u=linsolve([B;L],rhs;maxlength=Inf)
@time u=linsolve([B;L],rhs;maxlength=Inf)
println("Cos: should be ~0.0075")

S=Chebyshev()
x=Fun(identity,S)
D=Derivative(S)
L=D^2+sin(x)
B=dirichlet(S)
n=2000
rhs=ones(n+2)
u=linsolve([B;L],rhs;maxlength=Inf)
u=linsolve([B;L],rhs;maxlength=Inf)
@time u=linsolve([B;L],rhs;maxlength=Inf)
println("Sin: should be ~0.008663 seconds (660 allocations: 2.987 MB)")

## Piecewise

x=Fun(identity,[-20.,-10.,-5.,0.,1.,15.])
sp=space(x)
D=Derivative(sp)

u=[dirichlet(sp);
    D^2-x]\[airyai(-20.)];
@time u=[dirichlet(sp);
    D^2-x]\[airyai(-20.)];

println("Piecewise Airy: should be ~0.008")

## Vector
d=Interval()
D=Derivative(d);
B=ldirichlet();
Bn=lneumann();

f=Fun(x->[exp(x),cos(x)],d)

A=[B 0;
   Bn 0;
   0 B;
   D^2-I 2.0I;
   0 D+I];

u=A\Any[0.,0.,0.,f]
@time u=A\Any[0.,0.,0.,f]
println("Systems: should be ~0.0008")

d=Interval(-300.,5.)
x=Fun(identity,d)
A=Derivative(d)^2-x
u=nullspace(A)
@test_approx_eq A[1:10,1:10] (A.')[1:10,1:10].'
@time u=nullspace(A)
println("Nullspace Airy: 0.052730 seconds (75.21 k allocations: 56.736 MB)")


function bisectionMethod(f, a, b, tol; maxiter = 1000) #f=@(x)x^2-3; a=1; b=2; (ensure change of sign between a and b) error=1e-4
    if sign(f(a)) == sign(f(b))
        throw(DomainError("Function has the same sign on both ends in bisectionMethod."))
    end
    c = (a + b) / 2

    _fc = f(c)
    _fa = f(a)
    iter = 1
    while abs(_fc) > tol
        if _fc < 0 && _fa < 0 || _fc > 0 && _fa > 0
            a = c
            _fa = _fc
        else
            b = c
        end
        c = (a + b) / 2
        _fc = f(c)
        iter += 1
        iter < maxiter || throw(
            DomainError("Maximum number of iterations reached in bisectionMethod $iter."),
        )
    end
    return c
end

function bisectionMethodError(f, a, b, tol; maxiter = 1000) #f=@(x)x^2-3; a=1; b=2; (ensure change of sign between a and b) error=1e-4
    c = (a + b) / 2
    ftry(x) =
        try
            f(x)
        catch
            1.0
        end

    bisectionMethod(ftry, a, b, tol; maxiter = maxiter)
end

const vh2o_0 = m / rhoh2o
find_bh2o(the) = (bh2o) -> vh2o_0 - vh2o(the, bh2o)
bh2o0(the) = bisectionMethodError(find_bh2o(the), zmax - hb, zmax, 1e-12) # (zmax=>bh2o=>zmax-hb)

function get_bh2o(z, θ, p)
    bh2o = p.bh2o_0 - z / cos(θ)
    # if bh2o < zmax - hb || bh2o > zmax
    #     @warn "bh2o value in boatode! out of range" bh2o
    # end
    return bh2o
end


###############################################################################
#
# Collection of methods generating random projectors.
#
# Improvements:
#  * random_matrix_4
#  * include sparse projectors
#  * rank-1 projectors
#
###############################################################################

"""
Random projector of size k x m
with a normal distribution
"""
function random_matrix_1(k, m)
  P = zeros(k, m) #init

  #normal distribution
  P = rand(k, m)

  return 1 / sqrt(k) * P
end

"""
Random projector of size k x m
with -1 or 1 both with probability 1/2
"""
function random_matrix_2(k, m)
  P = zeros(k, m) #init

  #-1 or 1 both with probability 1/2
  P = rand([-1, 1], (k, m))

  return 1 / sqrt(k) * P
end

"""
Random projector of size k x m
with -1,0,1 respectively with probability 1/6,4/6,1/6
"""
function random_matrix_3(k, m)
  P = zeros(k, m) #init

  #-1,0,1 respectively with probability 1/6,4/6,1/6
  P = rand([-1, 0, 0, 1], (k, m))

  return 1 / sqrt(k) * P
end

"""
Random projector of size k x m
with orthogonal projection on a random k-dimensional linear subspace of R^m
"""
function random_matrix_4(k, m)
  P = zeros(k, m) #init

  #orthogonal projection on a random k-dimensional
  #linear subspace of R^m
  #P = rand(k, m)
  throw("NotImplemented")
  return 1 / sqrt(k) * P
end

"""
Check the average sparsity of random projector over N random matrices of size k*m
"""
function random_projector_sparse(projector::Function, N::Int64, k::Int64, m::Int64)
  tot = 0 #nb of non-zero elements

  for i = 1:N
    tot += SparseArrays.nnz(SparseArrays.sparse(projector(k, m))) / (k * m)
  end

  return tot / N
end

"""
Check the average rank of random projector over N random matrices of size k*m
"""
function random_projector_rank(projector::Function, N::Int64, k::Int64, m::Int64)
  tot = 0 #average rank of the projector

  for i = 1:N
    tot += rank(projector(k, m))
  end

  return tot / N
end


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    laplacematrix(mesh; weights=:uniform, normalize=true)

The Laplace-Beltrami (a.k.a. Laplacian) matrix of the `mesh`.
Optionally specify the discretization `weights` as either
`:uniform` or `:cotangent` and `normalize` the rows by the
diagonal value.

## References

* Vallet, B & Lévy, B. 2008. [Spectral Geometry Processing with Manifold
  Harmonics](https://onlinelibrary.wiley.com/doi/10.1111/j.1467-8659.2008.01122.x)

* Zhang et al. 2007. [Spectral Methods for Mesh Processing and Analysis]
  (https://diglib.eg.org/handle/10.2312/egst.20071052.001-022)
"""
function laplacematrix(mesh; weights=:uniform, normalize=true)
  # convert to half-edge topology
  m = topoconvert(HalfEdgeTopology, mesh)

  # retrieve adjacency relation
  t = topology(m)
  𝒩 = Adjacency{0}(t)

  # initialize matrix
  n = nvertices(t)
  L = spzeros(n, n)

  # fill matrix with weights
  if weights == :uniform
    for i in 1:n
      js = 𝒩(i)
      for j in js
        L[i,j] = 1.0
      end
      L[i,i] = -1.0*length(js)
      if normalize
        for j in js
          L[i,j] /= -L[i,i]
        end
        L[i,i] /= -L[i,i]
      end
    end
  elseif weights == :cotangent
    v = vertices(m)
    for i in 1:n
      js = CircularVector(𝒩(i))
      for k in 1:length(js)
        j₋, j, j₊ = js[k-1], js[k], js[k+1]
        vᵢ, vⱼ =  v[i],  v[j]
        v₋, v₊ = v[j₋], v[j₊]
        αᵢⱼ = ∠(vⱼ, v₋, vᵢ)
        βᵢⱼ = ∠(vᵢ, v₊, vⱼ)
        L[i,j] = cot(αᵢⱼ) + cot(βᵢⱼ)
      end
      L[i,i] = -sum(L[i,js])
      if normalize
        for j in js
          L[i,j] /= -L[i,i]
        end
        L[i,i] /= -L[i,i]
      end
    end
  else
    throw(ArgumentError("invalid discretization weights"))
  end

  L
end


module TestReturns

using Base.Test
using TimeData
using Econometrics
using DataFrames
using Dates

## println("\n Running return function tests\n")

#########################
## disc2log / log2disc ##
#########################

## percentage returns
##-------------------

ret = Timematr(DataFrame(returns = [2, 3]))

## log2disc(disc2log(x)) = x
ret2 = Econometrics.log2disc(Econometrics.disc2log(ret, percent=true),
                             percent=true)

@test_approx_eq TimeData.core(ret) TimeData.core(ret2)

## no percentage returns
##----------------------

ret = Timematr(DataFrame(returns = [0.2, 0.3]))
ret2 = Econometrics.log2disc(Econometrics.disc2log(ret, percent=false),
                             percent=false)

@test_approx_eq TimeData.core(ret) TimeData.core(ret2)

###############
## price2ret ##
###############

prices = testcase(TimeData.Timenum, 2)
logRet = Econometrics.price2ret(prices, log = true)

## manually determine expected result
##-----------------------------------

idxs = prices.idx[2:end]
expTn = TimeData.Timenum(DataFrame(prices1 = @data([NA, 20, 30, 30]),
                                   prices2 = @data([10, NA, 10, NA])),
                         idxs)

## test
@test isequal(logRet, expTn)

## test Timematr
##--------------

prices = testcase(TimeData.Timematr, 1)
logRet = Econometrics.price2ret(prices, log = true)

idxs = prices.idx[2:end]
expTn = TimeData.Timematr(DataFrame(prices1 = [20, -10, 60],
                                    prices2 = [10, -20, 30]),
                          idxs)

## test
@test isequal(logRet, expTn)

## same for discrete returns
##--------------------------

prices = TimeData.testcase(TimeData.Timenum, 2)
discRet = Econometrics.price2ret(prices, log = false)

@test_approx_eq ((140-120) / 120) TimeData.core(discRet[2, 1])
@test_approx_eq ((120-110) / 110) TimeData.core(discRet[1, 2])
@test isna(TimeData.core(discRet[2, 2])[1])

###############
## ret2price ##
###############

##################################
## 1. example: NAs at beginning ##
##################################

prices = @data([NA, NA, 3, 4, 6])

## using log returns
##------------------

logRet = Econometrics.price2ret(prices, log = true)
expRets = @data([NA, NA, 1, 2])
@test isequal(logRet, expRets)

normedPrices = Econometrics.ret2price(logRet, log = true)
expNormed = @data([NA, NA, 0, 1, 3])
@test isequal(normedPrices, expNormed)

## using dicrete returns
##----------------------

discRet = Econometrics.price2ret(prices, log = false)
expRets = @data([NA, NA, 1/3, 0.5])
@test isequal(discRet, expRets)

normedPrices = Econometrics.ret2price(discRet, log = false)
expNormed = @data([NA, NA, 1, 1+1/3, 2])
@test isequal(normedPrices, expNormed)

################################
## 2. example: NAs in between ##
################################

prices = @data([110, 120, NA, 130, NA])

## using log returns
##------------------

logRet = Econometrics.price2ret(prices, log = true)
expRets = @data([10, NA, 10, NA])
@test isequal(logRet, expRets)

normedPrices = Econometrics.ret2price(logRet, log = true)
expNormed = @data([0, 10, NA, 20, NA])
@test isequal(normedPrices, expNormed)

## using dicrete returns
##----------------------

discRet = Econometrics.price2ret(prices, log = false)
expRets = @data([10/110, NA, 10/120, NA])
@test isequal(discRet, expRets)

normedPrices = Econometrics.ret2price(discRet, log = false)
expNormed = @data([1, 1+10/110, NA, 1+20/110, NA])
@test_approx_eq normedPrices[[1,2,4]] expNormed[[1,2,4]]

########################
## 3. example: no NAs ##
########################

prices = @data([100, 120, 110, 170, 160])

## using log returns
##------------------

logRet = Econometrics.price2ret(prices, log = true)
expRets = @data([20, -10, 60, -10])
@test isequal(logRet, expRets)

normedPrices = Econometrics.ret2price(logRet, log = true)
expNormed = @data([0, 20, 10, 70, 60])
@test isequal(normedPrices, expNormed)

## using dicrete returns
##----------------------

discRet = Econometrics.price2ret(prices, log = false)
expRets = @data([0.2, -10/120, 60/110, -10/170])
@test isequal(discRet, expRets)

normedPrices = Econometrics.ret2price(discRet, log = false)
expNormed = @data([1, 1.2, 1.1, 1.7, 1.6])
@test_approx_eq normedPrices[[1,2,4]] expNormed[[1,2,4]]

############################
## same tests for Timenum ##
############################

df = DataFrame()
df[:prices1] = @data([100, 120, 110, 170, 160])
df[:prices2] = @data([110, 120, NA, 130, NA])
df[:prices3] = @data([NA, NA, 3, 4, 6])

dats = [Date(2010,1,1):Date(2010,1,5)]
prices = Timenum(df, dats)

## using log returns
##------------------

logRet = Econometrics.price2ret(prices, log = true)
normedPrices = Econometrics.ret2price(logRet, log = true)
originalPrices = Econometrics.ret2price(logRet, prices, log = true)
@test isequal(prices, originalPrices)

## using dicrete returns
##----------------------

discRet = Econometrics.price2ret(prices, log = false)
normedPrices = Econometrics.ret2price(discRet, log = false)
originalPrices = Econometrics.ret2price(discRet, prices, log = false)
@test isapprox(prices, originalPrices)

########################
## tests for Timematr ##
########################

df = DataFrame()
df[:prices1] = @data([100, 120, 110, 170, 160])
dats = [Date(2010,1,1):Date(2010,1,5)]
prices = Timenum(df, dats)

## using log returns
##------------------

logRet = Econometrics.price2ret(prices, log = true)
normedPrices = Econometrics.ret2price(logRet, log = true)
originalPrices = Econometrics.ret2price(logRet, prices, log = true)
@test isequal(prices, originalPrices)

## using dicrete returns
##----------------------

discRet = Econometrics.price2ret(prices, log = false)
normedPrices = Econometrics.ret2price(discRet, log = false)
originalPrices = Econometrics.ret2price(discRet, prices, log = false)
@test isapprox(prices, originalPrices)

end


logistic(x::Float64) = 4*x*(1-x)

function bernoulli(x::Float64, l::Float64, u::Float64, nc::Float64)
  if l<=x && x<nc
    return ((u-l)*x+(nc-u)*l)/(nc-l)
  elseif nc<=x && x<=u
    return ((u-l)*x+(l-nc)*u)/(u-nc)
  else
    error("Input out of domain of Bernoulli map.")
  end
end

function nbernoulli(x::Float64, l::Float64, u::Float64, nc::Float64)
  if l<=x && x<nc
    return ((l-u)*x+(nc*u-l^2))/(nc-l)
  elseif nc<=x && x<=u
    return ((l-u)*x+(u^2-l*nc))/(u-nc)
  else
    error("Input out of domain of negative Bernoulli map.")
  end
end

function tent(x::Float64, l::Float64, u::Float64, nc::Float64)
  if l<=x && x<nc
    ((u-l)*x+(nc-u)*l)/(nc-l)
  elseif nc<=x && x<=u
    ((l-u)*x+(u^2-l*nc))/(u-nc)
  else
    error("Input out of domain of tent map.")
  end
end

function valley(x::Float64, l::Float64, u::Float64, nc::Float64)
  if l<=x && x<nc
    ((l-u)*x+(nc*u-l^2))/(nc-l)
  elseif nc<=x && x<=u
    ((u-l)*x+(l-nc)*u)/(u-nc)
  else
    error("Input out of domain of valley map.")
  end
end

function circular(x::Float64, nc::Float64)
  if -1.<=x && x<-sqrt(nc)
    y = -sqrt((1.-x^2)/(1.-nc))
  elseif -sqrt(nc)<=x && x<sqrt(nc)
    y = sqrt(1.-x^2/nc)
  elseif sqrt(nc)<=x && x<=1.
    y = -sqrt((1.-x^2)/(1.-nc))
  else
    error("Input out of domain of circular map.")
  end
end

immutable VDist <: ContinuousUnivariateDistribution
    nc::Float64

    function VDist(nc::Real)
      @assert -1.0<nc && nc<1.0 "Non-centrality parameter must be in (-1, 1) in the case circular map."
      new(float64(nc))
    end

    VDist() = VDist(0.0)
end

@continuous_distr_support VDist -1.0 1.0

function pdf(d::VDist, x::Real)
  if -1.0<=x && x<=0.0
    -2.0*(1.0-d.nc)*x
  elseif 0.0<x && x<=1.0
    2*d.nc*x
  else
    0.0
  end
end

function rand(d::VDist)
  u = rand()

  if 0.0<=u && u<1.0-d.nc
    -sqrt((u+d.nc-1.0)/(d.nc-1.0))
  elseif 1.0-d.nc<=u && u<=1.0
    sqrt((u+d.nc-1.0)/d.nc)
  end
end

mean(d::VDist) = (4.0*d.nc-2.0)/3.0

var(d::VDist) = 0.5-mean(d)^2