@init global hp = safe_pyimport("healpy")

function θϕ_to_xy((θ,ϕ))
    r = 2cos(θ/2)
    x =  r*cos(ϕ)
    y = -r*sin(ϕ)
    x, y
end

function xy_to_θϕ((x,y))
    r = sqrt(x^2+y^2)
    θ = 2*acos(r/2)
    ϕ = -atan(y,x)
    θ, ϕ
end

function healpix_to_flat(healpix_map::Vector{T}, ::Type{P}; rot=(0,0,0)) where {Nside, θpix, T, P<:Flat{Nside,θpix}}

    Nside_sphere = hp.npix2nside(length(healpix_map))
    @unpack Δx = fieldinfo(P)

    # compute projection coordinate mapping
    xs = ys = Δx*((-Nside÷2:Nside÷2-1) .+ 0.5)
    xys = tuple.(xs,ys')[:]
    θϕs = xy_to_θϕ.(xys)
    (θs, ϕs) = first.(θϕs), last.(θϕs)

    # rotate the pole to the equator to match Healpy's azeqview convention, in
    # addition to applying the user rotation
    R = hp.Rotator((0,90,0), eulertype="ZYX") * hp.Rotator(rot, eulertype="ZYX")
    (θs′, ϕs′) = eachrow(R.get_inverse()(θs, ϕs))

    # interpolate map
    FlatMap{P,T}(reshape(hp.get_interp_val(healpix_map, θs′, ϕs′), Nside, Nside))

end

function flat_to_healpix(f::FlatMap, Nside_sphere; rot=(0,0,0))

    @unpack Δx, Nside = fieldinfo(f)

    (θs, ϕs) = hp.pix2ang(Nside_sphere, 0:(12*Nside_sphere^2-1))

    # rotate the pole to the equator to match Healpy's azeqview convention, in
    # addition to applying the user rotation
    R = hp.Rotator((0,90,0), eulertype="ZYX") * hp.Rotator(rot, eulertype="ZYX")
    (θs′, ϕs′) = eachrow(R(θs, ϕs))

    # compute projection coordinate mapping
    xys = θϕ_to_xy.(tuple.(θs′, ϕs′))
    xs = first.(xys) ./ Δx .+ Nside÷2 .+ 0.5
    ys = last.(xys) ./ Δx .+ Nside÷2 .+ 0.5

    # interpolate map
    @ondemand(Images.bilinear_interpolation).(Ref(f[:Ix]), xs, ys)

end


using PoincareInvariants
using GeometricIntegrators
using GeometricIntegrators.Utils
using Test


randn_tensor(dims, nonneg::Bool) = nonneg ? abs.(randn(dims)) : randn(dims)

rand_candecomp(r::Int64, dims::NTuple{N, Int};
               lambdas_nonneg::Bool=false, factors_nonneg::Bool=false) where N =
    CANDECOMP(ntuple(i -> randn_tensor((dims[i], r), factors_nonneg), N),
              randn_tensor(r, lambdas_nonneg))

rand_kruskal3(r::Int64, dims::NTuple{N, Int}, nonnegative::Bool) where N =
    compose(rand_candecomp(r, dims, lambdas_nonneg=nonnegative, factors_nonneg=nonnegative))

rand_tucker(core_dims::NTuple{N, Int}, dims::NTuple{N, Int};
            core_nonneg::Bool=false, factors_nonneg::Bool=false) where N =
    Tucker(ntuple(i -> randn_tensor((dims[i], core_dims[i]), factors_nonneg), N),
           randn_tensor(core_dims, core_nonneg))

function add_noise(tnsr::Array{T,N}, sn_ratio = 0.6, nonnegative::Bool = false) where {T <: Number, N}
    tnsr_noise = randn(size(tnsr)...)
    if nonnegative
        tnsr_noise .= max.(tnsr_noise, 0.0)
    end
    noise_scale = 10^(-sn_ratio/0.2)*norm(tnsr)/norm(tnsr_noise)
    return tnsr .+ noise_scale .* tnsr_noise
end


function smooth_poly_max(x::Float64)
    if x<=zero(Float64)
        return(zero(Float64))
    elseif x>= one(Float64)
        return(x)
    else
        return( 2x^2 - x^3)
    end
end

function smooth_poly_max(x, ϵ, κ)
    mult_λ = (κ+1)*ϵ
    smooth_poly_max((x-ϵ)/mult_λ)*mult_λ + ϵ
end

function supersmooth(x::Float64)
   if x<=zero(Float64)
        return(zero(Float64))
   else
        return(exp(-1/x))
   end
end

function supersmooth_max(x)
    x*(supersmooth(x)/(supersmooth(x) + supersmooth(1-x)))
end

function supersmooth_max(x, ϵ, κ)
    mult_λ = (κ+1)*ϵ
    supersmooth_max((x-ϵ)/mult_λ)*mult_λ + ϵ
end

beyonce_palette = ["#424395" "#EB549A" "#5EC2DA" "#EBC915" "#018AC4"  "#550133"]


using CompScienceMeshes
using Test

faces = readmesh(joinpath(@__DIR__, "assets/rectangle32.in"))
edges = skeleton(faces,1)
verts = skeleton(faces,0)
bnd = boundary(faces)

int_edges = submesh(interior_tpredicate(faces), edges)
# pred = interior_tpredicate(int_edges)

bnd_verts = skeleton(bnd,0)
function pred(node)
    !(node in cells(bnd_verts))
end
int_verts = submesh(pred, verts)

@test numcells(int_verts) == 9
@test numcells(edges) - numcells(int_edges) == 16


# Matern 5/2 isotropic covariance function

"""
    Mat52Iso <: MaternIso

Isotropic Matern 5/2 kernel (covariance)
```math
k(x,x') = σ²(1+√5|x-x'|/ℓ + 5|x-x'|²/(3ℓ²))\\exp(- √5|x-x'|/ℓ)
```
with length scale ``ℓ`` and signal standard deviation ``σ``.
"""
mutable struct Mat52Iso <: MaternIso
    "Length scale"
    ℓ::Float64
    "Signal variance"
    σ2::Float64
    "Priors for kernel parameters"
    priors::Array

    """
        Mat52Iso(ll::Float64, lσ::Float64)

    Create `Mat52Iso` with length scale `exp(ll)` and signal standard deviation `exp(lσ)`.
    """
    Mat52Iso(ll::Float64, lσ::Float64) = new(exp(ll), exp(2 * lσ), [])
end

function set_params!(mat::Mat52Iso, hyp::VecF64)
    length(hyp) == 2 || throw(ArgumentError("Matern 5/2 only has two parameters"))
    mat.ℓ, mat.σ2 = exp(hyp[1]), exp(2 * hyp[2])
end
get_params(mat::Mat52Iso) = Float64[log(mat.ℓ), log(mat.σ2) / 2]
get_param_names(mat::Mat52Iso) = [:ll, :lσ]
num_params(mat::Mat52Iso) = 2

Statistics.cov(mat::Mat52Iso, r::Float64) =
    (s = √5 * r / mat.ℓ; mat.σ2 * (1 + s + s^2 / 3) * exp(-s))

@inline dk_dll(mat::Mat52Iso, r::Float64) =
    (s = √5 * r / mat.ℓ; mat.σ2 / 3 * s^2 * (1 + s) * exp(-s))


immutable Geometric <: DiscreteUnivariateDistribution
    prob::Float64
    function Geometric(p::Real)
    	if 0.0 < p < 1.0
    		new(float64(p))
    	else
    		error("prob must be in (0, 1)")
    	end
    end
end

Geometric() = Geometric(0.5) # Flips of a fair coin

@_jl_dist_1p Geometric geom

function cdf(d::Geometric, q::Real)
    q < 0.0 ? 0.0 : -expm1(log1p(-d.prob) * (floor(q) + 1.0))
end

function ccdf(d::Geometric, q::Real)
    q < 0.0 ? 1.0 : exp(log1p(-d.prob) * (floor(q + 1e-7) + 1.0))
end

entropy(d::Geometric) = (-xlogx(1.0 - d.prob) - xlogx(d.prob)) / d.prob

insupport(d::Geometric, x::Number) = isinteger(x) && 0.0 <= x

kurtosis(d::Geometric) = 6.0 + d.prob^2 / (1.0 - d.prob)

mean(d::Geometric) = (1.0 - d.prob) / d.prob

function median(d::Geometric)
    iceil(-1.0 / log(2.0, 1.0 - d.prob)) - 1
end

modes(d::Geometric) = [0]

function mgf(d::Geometric, t::Real)
    p = d.prob
    if t >= -log(1.0 - p)
        error("MGF does not exist for all t")
    end
    return (p * exp(t)) / (1.0 - (1.0 - p) * exp(t))
end

function cf(d::Geometric, t::Real)
    p = d.prob
    return (p * exp(im * t)) / (1.0 - (1.0 - p) * exp(im * t))
end

skewness(d::Geometric) = (2.0 - d.prob) / sqrt(1.0 - d.prob)

var(d::Geometric) = (1.0 - d.prob) / d.prob^2


using InvariantMeasures
using ValidatedNumerics

using Plots
using LaTeXStrings

D = Mod1Dynamic(x -> 4*x + 0.01*InvariantMeasures.sinpi(8*x))
#D = Mod1Dynamic(x -> 16*x + 0.01*InvariantMeasures.sinpi(32*x))
#D = Mod1Dynamic(x->2*x+0.5*x*(1-x))
# D = PwMap(
# [x -> 17x/5,
#  x -> 34(x-5//17)^2/25 + 3(x-5//17),
#  x -> 34(x-10//17)^2/25 + 3(x-10//17),
#  x -> 17(x-15//17)/5
# ],
# [0, @interval(5/17), @interval(10/17), @interval(15/17), 1]
# )

num_norms = 30

B = Hat(1024)
Q = DiscretizedOperator(B, D)

trivial_norms = norms_of_powers_trivial(weak_norm(B), Q, num_norms)
computed_norms = norms_of_powers(weak_norm(B), num_norms, Q, integral_covector(B))

(dfly_strongs, dfly_norms) = norms_of_powers_dfly(B, D, num_norms)

norms = min.(trivial_norms, computed_norms, dfly_norms)
better_norms = refine_norms_of_powers(norms, num_norms)

pgfplotsx()
p = plot(trivial_norms,
    label = L"$\|Q\|^k$",
    yscale = :log10,
    legend = :bottomleft,
    title = "Various bounds for norms of powers",
    xlabel = L"$k$",
    ylabel = L"bound to $\|Q^k|_U\|$"
    )
plot!(p, computed_norms,
    label = "computational bounds")

plot!(p, dfly_norms,
    label = L"DFLY $2\times 2$ matrix bounds")

plot!(p, better_norms,
    label = "min(previous) + refinement")

# savefig(p, "norm_bounds_kinds.tikz")


"""
    Const <: Kernel

Constant kernel
```math
k(x,x') = σ²
```
with signal standard deviation ``σ``.
"""
mutable struct Const <: Kernel
    "Signal variance"
    σ2::Float64
    "Priors for kernel parameters"
    priors::Array

    """
        Const(lσ::Float64)

    Create `Const` with signal standard deviation `exp(lσ)`.
    """
    Const(lσ::Float64) = new(exp(2 * lσ), [])
end

function set_params!(cons::Const, hyp::VecF64)
    length(hyp) == 1 || throw(ArgumentError("Constant kernel only has one parameters"))
    cons.σ2 = exp(2.0*hyp[1])
end

get_params(cons::Const) = Float64[log(cons.σ2)/2.0]
get_param_names(cons::Const) = [:lσ]
num_params(cons::Const) = 1

Statistics.cov(cons::Const) = cons.σ2
function Statistics.cov(cons::Const, x::VecF64, y::VecF64)
    return cov(cons)
end

@inline dk_dlσ(cons::Const) = 2.0*cov(cons)
@inline function dKij_dθp(cons::Const, X::MatF64, i::Int, j::Int, p::Int, dim::Int)
    if p == 1
        return dk_dlσ(cons)
    else
        return NaN
    end
end
@inline function dKij_dθp(cons::Const, X::MatF64, data::EmptyData, i::Int, j::Int, p::Int, dim::Int)
    if p == 1
        return dKij_dθp(cons, X, i, j, p, dim)
    else
        return NaN
    end
end


using Test

using Example2018

## Polynomials form a ring
# 1. Addition is associative
# 2. Additive identity
# 3. Additive inverse
# 4. Additive commutes
## 5. Associative
# 5. Multiplication is ...

@test 2==2
@test 1==1

z = Polynomial([0])
p = Polynomial([1,2,3])
## Need to create function to compare polynomials, like substract them and find out it is a zero polynomial
@test p + z == p
@test z + p == p
@test p + (-p) == z

### 1,2,3 polynomial is not strong, now, let's introduce a test set

@testset "Polynomials form a ring" begin
z = Polynomial([0])

    for n in 1:100
        ##p = Polynomial([1,2,3])
        deg  = rand(0:100)
        p = Polynomial(rand(-1000000:100000, deg+1))
        @test p + z == p
        @test z + p == p
        @test p + (-p) == z
    end
end


# Interactive Expectation Maximization demo for the 'old faithful' data set
# This script should be executed from the commandline

using DataFrames, CSV
include("gmm_plot.jl")

function continueOrExit()
    # Helper function: wait for keypress, quit on q
    if (readline() == "q") exit() end
end

old_faithful = CSV.read("../datasets/old_faithful.csv")
X = convert(Matrix{Float64}, [old_faithful[1] old_faithful[2]]') # Every column is a data point
N = size(X, 2)

# Initialization
#clusters = MvNormal[MvNormal([3.;50.].*rand(2)+[2.;40.], diagm([.5;10^2])); MvNormal([3.;50.].*rand(2)+[2.;40.], diagm([.5;10^2]))]
clusters = [MvNormal([4.;60.], diagm([.5;10^2]));
            MvNormal([2.;80.], diagm([.5;10^2]))]
π_hat = [0.5;0.5]
γ = fill!(Matrix{Float64}(2,N), NaN)

ion()
plotGMM(X, clusters, γ)
print("Press [enter] for E-step or [q] to quit: "); continueOrExit()

while(true)
    # E-step: update γ
    norm = [pdf(clusters[1], X) pdf(clusters[2], X)] * π_hat
    γ[1,:] = (π_hat[1] * pdf(clusters[1],X) ./ norm)'
    γ[2,:] = 1 - γ[1,:]
    clf(); plotGMM(X, clusters, γ)
    print("Press [enter] for M-step or [q] to quit: "); continueOrExit()

    # M-step: update cluster parameters and π_hat
    m = sum(γ, 2)
    π_hat = m / N
    μ_hat = (X * γ') ./ m'
    for k=1:2
        μ_k = μ_hat[:,k]
        Z = (X .- μ_k)
        Σ_k = Hermitian(((Z .* (γ[k,:])') * Z') / m[k])
        clusters[k] = MvNormal(μ_k, convert(Matrix, Σ_k))
    end
    clf(); plotGMM(X, clusters, γ)
    print("Press [enter] for E-step or [q] to quit: "); continueOrExit()
end


using Test, Random, QPDAS
using LinearAlgebra, SparseArrays

@testset "CholeskySpecial" begin
    include("testCholeskySpecial.jl")
end

@testset "testCholeskySpecialShifted" begin
    include("testCholeskySpecialShifted.jl")
end

@testset "testCholeskySpecialShifted" begin
    include("testCholeskySpecialShiftedProjection.jl")
end

@testset "testBoxQP" begin
    include("testBoxQP.jl")
end

@testset "testQPNoScaling" begin
    include("testQPNoScaling.jl")
end

@testset "testQP" begin
    include("testQP.jl")
end

@testset "testQPsemidefinite" begin
    include("testQPsemidefinite.jl")
end


using SymEngine
using Compat
using Compat.Test
import Compat: MathConstants.γ, MathConstants.e, MathConstants.φ, MathConstants.catalan

include("test-dense-matrix.jl")

x = symbols("x")
y = symbols(:y)
@vars z

# Check Basic conversions
@test eltype([Basic(u) for u in [1, 1/2, 1//2, pi, e]]) == Basic

# make sure @vars defines in a local scope
let
    @vars w
end
@test_throws UndefVarError isdefined(w)
@test_throws Exception show(Basic())

a = x^2 + x/2 - x*y*5
b = diff(a, x)
@test b == 2*x + 1//2 - 5*y

c = x + Rational(1, 5)
c = expand(c * 5)
@test c == 5*x + 1

c = x ^ 5
@test diff(c, x) == 5 * x ^ 4

c = x ^ y
@test c != y^x

c = Basic(-5)
@test abs(c) == 5
@test abs(c) != 4

show(a)
println()
show(b)
println()

## mathfuns
@test abs(Basic(-1)) == 1
@test sin(Basic(1)) == subs(sin(x), x, 1)
@test sin(PI) == 0
@test subs(sin(x), x, pi) == 0
@test sind(Basic(30)) == 1 // 2

## calculus
x,y = symbols("x y")
n = Basic(2)
ex = sin(x*y)
@test diff(log(x),x) == 1/x
@test diff(ex, x) == y * cos(x*y)
@test diff(ex, x, 2) == diff(diff(ex,x), x)
@test diff(ex, x, n) == diff(diff(ex,x), x)
@test diff(ex, x, y) == diff(diff(ex,x), y)
@test diff(ex, x, y,x) == diff(diff(diff(ex,x), y), x)
@test series(sin(x), x, 0, 2) == x
@test series(sin(x), x, 0, 3) == x - x^3/6

## ntheory
@test mod(Basic(10), Basic(4)) == 2
for j in [-3, 3], p in [-5,5]
    @test mod(Basic(j), Basic(p)) == mod(j, p)
end
@test mod(Basic(10), 4) == 2               # mod(::Basic, ::Number)
@test_throws MethodError mod(10, Basic(4)) # no mod(::Number, ::Basic)
@test gcd(Basic(10), Basic(4)) == 2
@test lcm(Basic(10), Basic(4)) == 20
@test binomial(Basic(5), 2) == 10

## type information
a = Basic(1)
b = Basic(1//2)
c = Basic(0.125)
@test isa(SymEngine.BasicType(a+a), SymEngine.BasicType{Val{:Integer}})
@test isa(SymEngine.BasicType(a+b), SymEngine.BasicType{Val{:Rational}})
@test isa(SymEngine.BasicType(a+c), SymEngine.BasicType{Val{:RealDouble}})
@test isa(SymEngine.BasicType(b+c), SymEngine.BasicType{Val{:RealDouble}})
@test isa(SymEngine.BasicType(c+c), SymEngine.BasicType{Val{:RealDouble}})

## can we do math with items of BasicType?
a1 = SymEngine.BasicType(a)
tot = a1
for i in 1:100
    global tot
    tot = tot + a1
end
@test tot == 101
sin(a1)

# samples of different types:
# (Int, Rational{Int}, Complex{Int}, Float64, Complex{Float64})
samples = (1, 1//2, (1 + 2im), 1.0, (1.0 + 0im))
## subs - check all different syntaxes and types
ex = x^2 + y^2
for val in samples
    @test subs(ex, x, val) == val^2 + y^2
    @test subs(ex, (x, val)) == val^2 + y^2
    @test subs(ex, x => val) == val^2 + y^2
    @test subs(ex, SymEngine.CMapBasicBasic(Dict(x=>val))) == val^2 + y^2
    @test subs(ex, Dict(x=>val)) == val^2 + y^2
end
# This probably results in a number of redundant tests (operator order).
for val1 in samples, val2 in samples
    @test subs(ex, (x, val1), (y, val2)) == val1^2 + val2^2
    @test subs(ex, x => val1, y => val2) == val1^2 + val2^2
end

## lambidfy
@test abs(lambdify(sin(Basic(1))) - sin(1)) <= 1e-14
fn = lambdify(exp(PI/2*x))
@test abs(fn(1) - exp(pi/2)) <= 1e-14
for val in samples
    ex2 = sin(x + val)
    fn2 = lambdify(ex2)
    @test abs(fn2(val) - sin(2*val)) <= 1e-14
end
@test lambdify(x^2)(3) == 9

A = [x 2; x 1]
@test lambdify(A, [x])(0) == [0 2; 0 1]
@test lambdify(A)(0) == [0 2; 0 1]
A = [x 2]
@test lambdify(A, [x])(1) == [1 2]
@test lambdify(A)(1) == [1 2]
@test isa(convert.(Expr, [0 x x+1]), Array{Expr})

## N
for val in samples
    @test N(Basic(val)) == val
end

for val in [π, γ, e, φ, catalan]
    @test N(Basic(val)) == val
end

@test !isnan(x)
@test isnan(Basic(0)/0)

## generic linear algebra
x = symbols("x")
A = [x 2; x 1]
#@test det(A) == -x
#@test det(inv(A)) == - 1/x
#(A \ [1,2])[1] == 3/x

## check that unique work (hash)
x,y,z = symbols("x y z")
@test length(SymEngine.free_symbols([x*y, y,z])) == 3

## check that callable symengine expressions can be used as functions for duck-typed functions
@vars x
function simple_newton(f, fp, x0)
    x = float(x0)
    while abs(f(x)) >= 1e-14
        x = x - f(x)/fp(x)
    end
    x
end
@test abs(simple_newton(sin(x), diff(sin(x), x), 3) - pi) <= 1e-14

## Check conversions SymEngine -> Julia
z,flt, rat, ima, cplx = btypes = [Basic(1), Basic(1.23), Basic(3//5), Basic(2im), Basic(1 + 2im)]

@test Int(z) == 1
@test BigInt(z) == 1
@test Float64(flt) ≈ 1.23
@test Real(flt) ≈ 1.23
@test convert(Rational{Int}, rat) == 3//5
@test convert(Complex{Int}, ima) == 2im
@test convert(Complex{Int}, cplx) == 1 + 2im

@test_throws InexactError convert(Int, flt)
@test_throws InexactError convert(Int, rat)

x = symbols("x")
Number[1 2 3 x]
@test_throws ArgumentError Int[1 2 3 x]

t = BigFloat(1.23)
@test !SymEngine.have_component("mpfr") || t == convert(BigFloat, convert(Basic, t))

@test typeof(N(Basic(-1))) != BigInt

# Check that libversion works. VersionNumber should always be >= 0.2.0
# since 0.2.0 is the first public release
@test SymEngine.libversion >= VersionNumber("0.2.0")

# Check that constructing Basic from Expr works
@vars x y
@test Basic(:(-2*x)) == -2*x
@test Basic(:(-3*x*y)) == -3*x*y
@test Basic(:((x-y)*-3)) == (x-y)*(-3)
@test Basic(:(-y)) == -y
@test Basic(:(-2*(x-2*y))) == -2*(x-2*y)

@test Basic(0)/0 == NAN
@test subs(1/x, x, 0) == Basic(1)/0

d = Dict(x=>y, y=>x)
@test subs(x + 2*y, d) == y + 2*x

@test sin(x+PI/4) != sin(x)
@test sin(PI/2-x) == cos(x)

f = SymFunction("f")
@test string(f(x, y)) == "f(x, y)"
@test string(f([x, y])) == "f(x, y)"
@test string(f(2*x)) == "f(2*x)"

@funs g, h
@test string(g(x, y)) == "g(x, y)"
@test string(h(x, y)) == "h(x, y)"

if SymEngine.libversion >= VersionNumber("0.4.0")
    # Function symbols
    @funs f, g, h, s
    @vars n, m
    expr = Basic("f(n) + f(n+1) + g(m)^2 - 3*h(n)*s(n)^3")
    @test function_symbols(expr) == [h(n), f(n), s(n), g(m), f(n+1)]
    @test get_name(f(n+1)) == "f"
    @test get_args(f(n+1)) == [n+1]
    @test get_args(f(n, m^2)) == [n, m^2]

    # Coefficients
    @vars x y
    expr = x^3 + 3*x^2*y + 3*x*y^2 + y^3 + 1
    @test coeff(expr, x, Basic(3)) == 1
    @test coeff(expr, x, Basic(2)) == 3*y
    @test coeff(expr, x, Basic(1)) == 3*y^2
    if SymEngine.libversion >= VersionNumber("0.5.0")
        @test coeff(expr, x, Basic(0)) == y^3 + 1
    end
end


function center(U, mass)
    d = length(U[1].x)

    c = zeros(Float64, d)

    for i = 1:length(mass)
        c += mass[i] .* U[i].x
    end

    return c / sum(mass)
end

function center(U::Array)
    n = length(U)

    mass = getMass(U)

    return center(U, mass),
    argworst(U), # worst
    argbest(U)  # best
end

function getU(P::Array, K::Int, I::Vector{Int}, i::Int, N::Int)
    if i <= N - K
        U_ids = I[i:K+i]
    else
        j = (i:K+i) .% N
        U_ids = I[j.+1]
    end

    return P[U_ids]
end

function getU_ids(K::Int, I::Vector{Int}, i::Int, N::Int)
    if i <= N - K
        U_ids = I[i:K+i]
    else
        j = (i:K+i) .% N
        U_ids = I[j.+1]
    end

    return U_ids
end

function getU_ids(K::Int, I::Vector{Int}, i::Int, N::Int, feasible_solutions)
    # at least half of the population is feasible to generate random centers
    if length(feasible_solutions) >= 0.5N || length(feasible_solutions) == 0
        return getU_ids(K, I, i, N)
    end

    # center of mass is generated with at least one feasible solution
    K -= 1
    if i <= N - K
        U_ids = I[i:K+i]
    else
        j = (i:K+i) .% N
        U_ids = I[j.+1]
    end

    push!(U_ids, rand(feasible_solutions))

    return U_ids
end

function crossover(
    x::Vector{Float64},
    y::Vector{Float64},
    p_cr::Vector{Float64},
)
    D = length(x)
    tmp2 = zeros(D)
    for j = 1:D
        if rand() < p_cr[j]
            y[j] = x[j]
            tmp2[j] += 1
        end
    end

    return y, tmp2
end


# Optimization of bin counts for histograms, heatmaps, hexbin plots, etc.
#
# I'm using the penalized maximum-likelihood method proposed in
#   Birge, L, and Rozenholc, Y. (2006) How many bins should be put in a regular
#   histogram?
#
# There has been quite a bit written on this problem, but there are a number of
# methods that all seem to give good results with little difference. Birge's
# method is simple (it's just AIC with an extra logarithmic term), has a decent
# theoretical justification, and is general enough to apply to multidimensional
# and non-regular bin selecetion problems. Though, the penalty they use was
# optimized for regular histograms, so may need to be tweaked.
#
# The Birge penalty is
#    penalty(D) = D - 1 + log(D)^2.5
# where D is the number of bins. The 2.5 constant was arrived at emperically by
# optimizing over samples from example density functions.
#

# Penalized log-likelihood function for a histogram with d regular bins.
#
# Args:
#   d: Number of bins in the histogram.
#   n: Number of sample (which should equal sum(bincounts[1:d])).
#   bincounts: An array giving the number occurrences in each bin.
#   binwidth: Width of each bin in the histogram.
#
# Returns:
#   Log-likelihood with Birge's penalty applied.
#
function bincount_pll(d::Int, n::Int, bincounts::Vector{Int}, binwidth::Float64)
    ll = 0
    for i in 1:d
        if bincounts[i] > 0
            ll += bincounts[i] * log(bincounts[i] / (n * binwidth))
        end
    end
    ll - (d - 1 + log(d)^2.5)
end

# Optimize the number of bins for a regular one dimensional histogram.
#
# Args:
#   xs: A sample.
#
# Returns:
#   A tuple of the form (d, bincounts), where d gives the optimal number of
#   bins, and bincounts is an array giving the number of occurances in each bin.
#
function choose_bin_count_1d(xs::Vector)
    n = length(xs)
    if n <= 1
        return 1
    end

    x_min, x_max = min(xs), max(xs)
    span = x_max - x_min

    d_min = 3
    d_max = min(150, int(ceil(n / log(n))))
    bincounts = zeros(Int, d_max)

    d_best = d_min
    pll_best = -Inf

    # Brute force optimization: since the number of bins has to be reasonably
    # small to plot, this is pretty quick and very simple.
    for d in d_min:d_max
        binwidth = span / d
        bincounts[1:d] = 0

        for x in xs
            if !isconcrete(x)
                continue
            end
            bincounts[max(1, min(d, int(ceil((x - x_min) / binwidth))))] += 1
        end

        pll = bincount_pll(d, n, bincounts, binwidth)
        if pll > pll_best
            d_best = d
            pll_best = pll
        end
    end

    bincounts[1:d_best] = 0
    binwidth = span / d_best
    for x in xs
        if !isconcrete(x)
            continue
        end
        bincounts[max(1, min(d_best, int(ceil((x - x_min) / binwidth))))] += 1
    end

    (d_best, bincounts)
end

# Optimize the number of bins for regular two dimensional histograms.
#
# Args:
#   xs: Dimension one data.
#   ys: Dimension two data.
#
# Returns:
#   A tuple of the form (dx, dy, bincounts), where dx, dy gives the number of
#   bins in each respective dimension and bincounts is a dx by dy matrix giving
#   the count in each bin.
#
function choose_bin_count_2d(xs::Vector, ys::Vector)
    # For two demensions, I'm just going to optimize the marginal bin counts.
    # This might not be optimal, but its simple and fast.
    dx, _ = choose_bin_count_1d(xs)
    dy, _ = choose_bin_count_1d(ys)

    x_min, x_max = min(xs), max(xs)
    y_min, y_max = min(ys), max(ys)

    # bin widths
    wx = (x_max - x_min) / dx
    wy = (y_max - y_min) / dy

    bincounts = zeros(Int, (dy, dx))
    for (x, y) in zip(xs, ys)
        i = max(1, min(dx, int(ceil((x - x_min) / wx))))
        j = max(1, min(dy, int(ceil((y - y_min) / wy))))
        bincounts[j, i] += 1
    end

    (dx, dy, bincounts)
end


# Practical Machine learning
# Regression Analysis  - Logistic Regression example
# Chapter 10

## Initialization
using PyPlot
using NLopt

function costFunction(theta, X, y)

  # Initialize some useful values
  m = length(y) # number of training examples

  # You need to return the following variables correctly
  J = COSTFUNCTION(theta, X, y)
  grad = zeros(size(theta)[1])
  return (J, grad)
end

function plotData(X, y)

  # Create New Figure
  figure()
  hold(true)
  PLOTDATA(x,y)
  hold(false)
end

function plotDecisionBoundary(theta, X, y)
  # Plot Data
  plotData(X[:, 2:3], y)
  hold(true)

  if size(X, 2) <= 3
    # Only need 2 points to define a line, so choose two endpoints
    plot_x = [minimum(X[:, 2])-2,  maximum(X[:, 2])+2]

    # Calculate the decision boundary line
    plot_y = (-1./theta[3]).*(theta[2].*plot_x + theta[1])

    # Plot, and adjust axes for better viewing
    plot(plot_x, plot_y)

    # Legend, specific for the exercise
    # legend("Admitted", "Not admitted", "Decision Boundary")
    legend("Admitted", "Not admitted")
    axis([30, 100, 30, 100])
  else
    # Here is the grid range
    u = linspace(-1, 1.5, 50)
    v = linspace(-1, 1.5, 50)

    z = zeros(length(u), length(v))
    # Evaluate z = theta*x over the grid
    for j in 1:length(v), i in 1:length(u)
      z[i, j] = (mapFeature([u[i]], [v[j]]) * theta)[1]
    end
    z = z' # important to transpose z before calling contour

    # Plot z = 0
    # Notice you need to specify the range [0, 0]
    contour(u, v, z, [0, 0], linewidth=2)
  end
  hold(false)
end

function sigmoid(z)
  J = SIGMOID(z)
  return J
end

function  predict(theta, X)
  m = size(X, 1) # Number of training examples
  p = PREDICT(theta, X)
 return p
end

## Load Data
#  The first two columns contains the exam scores and the third column
#  contains the label.

data = readcsv("dataset1.txt")
X = data[:, 1:2]
y = data[:, 3]

## ==================== Plotting ====================

@printf("Plotting data with + indicating (y = 1) examples and o indicating (y = 0) examples.\n")

plotData(X, y)

# Put some labels
hold(true)
# Labels and Legend
xlabel("Exam 1 score")
ylabel("Exam 2 score")

# Specified in plot order
legend(["Admitted", "Not admitted"])
hold(false)

@printf("Program paused. Press enter to continue.\n")
readline()

## ============ Compute Cost and Gradient ============

#  Setup the data matrix appropriately, and add ones for the intercept term
m, n = size(X)

# Add intercept term to x and X_test
X = [ones(m, 1) X]

# Initialize fitting parameters
initial_theta = zeros(n + 1)

# Compute and display initial cost and gradient
cost, grad = costFunction(initial_theta, X, y)

@printf("Cost at initial theta (zeros): %f\n", cost)
@printf("Gradient at initial theta (zeros): \n")
@printf("%s", join(map(x -> @sprintf(" %f ", x), grad), "\n"))

@printf("\nProgram paused. Press enter to continue.\n")
readline()

## ============= Part 3: Optimizing using fminunc  =============
#  In this exercise, you will use a built-in function (fminunc) to find the
#  optimal parameters theta.

#  Set options for fminunc
#options = optimset("GradObj", "on", "MaxIter", 400)
options = Opt(:LN_NELDERMEAD, n+1)
min_objective!(options, (theta, grad) -> costFunction(theta, X, y)[1])
maxeval!(options, 400)
#  Run fminunc to obtain the optimal theta
#  This function will return theta and the cost
(cost, theta, _) = optimize(options, initial_theta)

# Print theta to screen
@printf("Cost at theta found by optimize: %f\n", cost)
@printf("theta: \n")
@printf("%s\n", join(map(x -> @sprintf(" %f ", x), theta), "\n"))

# Plot Boundary
plotDecisionBoundary(theta, X, y)

# Put some labels
hold(true)
# Labels and Legend
xlabel("Exam 1 score")
ylabel("Exam 2 score")

# Specified in plot order
legend(["Regression", "Admitted", "Not admitted"])
hold(false)

@printf("\nProgram paused. Press enter to continue.\n")
#readline()

prob = sigmoid([1 45 85] * theta)
@printf("For a student with scores 45 and 85, we predict an admission probability of %f\n\n", prob[1])

# Compute accuracy on our training set
p = predict(theta, X)

@printf("Train Accuracy: %f\n", mean(p .== y) * 100)


import Statistics: mean, var, cov

"""
    Product <: MultivariateDistribution

An N dimensional `MultivariateDistribution` constructed from a vector of N independent
`UnivariateDistribution`s.

```julia
Product(Uniform.(rand(10), 1)) # A 10-dimensional Product from 10 independent `Uniform` distributions.
```
"""
struct Product{
    S<:ValueSupport,
    T<:UnivariateDistribution{S},
    V<:AbstractVector{T},
} <: MultivariateDistribution{S}
    v::V
    function Product(v::V) where
        V<:AbstractVector{T} where
        T<:UnivariateDistribution{S} where
        S<:ValueSupport
        return new{S, T, V}(v)
    end
end

length(d::Product) = length(d.v)
function Base.eltype(::Type{<:Product{S,T}}) where {S<:ValueSupport,
                                                    T<:UnivariateDistribution{S}}
    return eltype(T)
end

_rand!(rng::AbstractRNG, d::Product, x::AbstractVector{<:Real}) =
    broadcast!(dn->rand(rng, dn), x, d.v)
_logpdf(d::Product, x::AbstractVector{<:Real}) =
    sum(n->logpdf(d.v[n], x[n]), 1:length(d))

mean(d::Product) = mean.(d.v)
var(d::Product) = var.(d.v)
cov(d::Product) = Diagonal(var(d))
entropy(d::Product) = sum(entropy, d.v)
insupport(d::Product, x::AbstractVector) = all(insupport.(d.v, x))

"""
    product_distribution(dists::AbstractVector{<:UnivariateDistribution})

Creates a multivariate product distribution `P` from a vector of univariate distributions.
Fallback is the `Product constructor`, but specialized methods can be defined
for distributions with a special multivariate product.
"""
function product_distribution(dists::AbstractVector{<:UnivariateDistribution})
    return Product(dists)
end

"""
    product_distribution(dists::AbstractVector{<:Normal})

Computes the multivariate Normal distribution obtained by stacking the univariate
normal distributions. The result is a multivariate Gaussian with a diagonal
covariance matrix.
"""
function product_distribution(dists::AbstractVector{<:Normal})
    µ = mean.(dists)
    σ = std.(dists)
    return MvNormal(µ, σ)
end


# ===================================
# Approximations in the infinity norm
# ===================================

"""
    box_approximation(S::LazySet{N})::Union{Hyperrectangle{N}, EmptySet{N}}
        where {N<:Real}

Overapproximate a convex set by a tight hyperrectangle.

### Input

- `S` -- convex set

### Output

A tight hyperrectangle.
"""
function box_approximation(S::LazySet{N}
                          )::Union{Hyperrectangle{N}, EmptySet{N}} where {N<:Real}
    return overapproximate(S, Hyperrectangle)
end

"""
    interval_hull

Alias for `box_approximation`.
"""
interval_hull = box_approximation

"""
    box_approximation_symmetric(S::LazySet{N}
                               )::Union{Hyperrectangle{N}, EmptySet{N}}
                                where {N<:Real}

Overapproximate a convex set by a tight hyperrectangle centered in the origin.

### Input

- `S` -- convex set

### Output

A tight hyperrectangle centered in the origin.

### Algorithm

The center of the box is the origin, and the radius is obtained by computing the
maximum value of the support function evaluated at the canonical directions.
"""
function box_approximation_symmetric(S::LazySet{N};
                                    )::Union{Hyperrectangle{N},
                                             EmptySet{N}} where {N<:Real}
    (c, r) = box_approximation_helper(S)
    if r[1] < 0
        return EmptySet{N}()
    end
    return Hyperrectangle(zeros(N, length(c)), abs.(c) .+ r)
end

# special case: empty set
box_approximation_symmetric(∅::EmptySet) = ∅

"""
    symmetric_interval_hull

Alias for `box_approximation_symmetric`.
"""
symmetric_interval_hull = box_approximation_symmetric

"""
    box_approximation_helper(S::LazySet{N}) where {N<:Real}

Common code of `box_approximation` and `box_approximation_symmetric`.

### Input

- `S` -- convex set

### Output

A tuple containing the data that is needed to construct a tightly
overapproximating hyperrectangle.

- `c` -- center
- `r` -- radius

### Algorithm

The center of the hyperrectangle is obtained by averaging the support function
of the given convex set in the canonical directions.
The lengths of the sides can be recovered from the distance among support
functions in the same directions.
"""
@inline function box_approximation_helper(S::LazySet{N}) where {N<:Real}
    zero_N = zero(N)
    one_N = one(N)
    n = dim(S)
    c = Vector{N}(undef, n)
    r = Vector{N}(undef, n)
    d = zeros(N, n)
    @inbounds for i in 1:n
        d[i] = one_N
        htop = ρ(d, S)
        d[i] = -one_N
        hbottom = -ρ(d, S)
        d[i] = zero_N
        c[i] = (htop + hbottom) / 2
        r[i] = (htop - hbottom) / 2
        if r[i] < 0
            # contradicting bounds => set is empty
            # terminate with first radius entry being negative
            r[1] = r[i]
            break
        end
    end
    return c, r
end

"""
    ballinf_approximation(S::LazySet{N}
                         )::Union{BallInf{N}, EmptySet{N}} where {N<:Real}

Overapproximate a convex set by a tight ball in the infinity norm.

### Input

- `S` -- convex set

### Output

A tight ball in the infinity norm.
"""
function ballinf_approximation(S::LazySet{N}
                              )::Union{BallInf{N}, EmptySet{N}} where {N<:Real}
    return overapproximate(S, BallInf)
end

# special case: empty set
ballinf_approximation(∅::EmptySet) = ∅


void merge(int[] a, int low, int mid, int high)
{
    int[] b = new int[1000];;
    int i = low, j = mid + 1, k = 0;

    while (i <= mid && j <= high) {
        if (a[i] <= a[j]) {
            b[k] = a[i];
            k++; i++;
        }
        else {
            b[k] = a[j];
            k++; j++;
        }
    }
    while (i <= mid) {
        b[k] = a[i];
        k++; i++;
    }

    while (j <= high) {
        b[k] = a[j];
        k++; j++;
    }

    k--;
    while (k >= 0) {
        a[low + k] = b[k];
        k--;
    }
}

void mergesort(int[] a, int low, int high)
{
    if (low < high) {
        int m = (high + low)/2;
        mergesort(a, low, m);
        mergesort(a, m + 1, high);
        merge(a, low, m, high);
    }
}

int main() {
  int[] array = new int[1000];
  int i = 0;
  while (i<array.length) {
    array[i] = 1000-i;
    i++;
  }
  mergesort(array, 0, 999);
  for (int x : array) {
    printInt(x);
  }
return 0;
}


function fractional_covering_number(graph::AbstractGraph)

    model = Model(GLPK.Optimizer)
    V = vertices(graph)
    @variable(model, x[V]>=0)
    for e = edges(graph)
        @constraint(model, x[src(e)]+x[dst(e)] >=1)
    end
    @objective(model, Min, sum(x))

    optimize!(model)

    return (value.(x),objective_value(model))
end

function fractional_matching_number(graph::AbstractGraph)

    model = Model(GLPK.Optimizer)
    E = edges(graph)
    @variable(model, x[E]>=0)
    for v = vertices(graph)
        Ev = edges_of(graph, v)
        @constraint(model, sum(x[i] for i in Ev) <= 1)
    end

    @objective(model, Max, sum(x))

    optimize!(model)

    return (value.(x),objective_value(model))
end

function fractional_coloring_number(graph::AbstractGraph)

    model = Model(GLPK.Optimizer)
    independent_sets = maximal_cliques(unweighted_complement(graph))

    @variable(model, x[independent_sets] >= 0)

    for v = vertices(graph)
        independent_v = [i for i in independent_sets if v in i]
        @constraint(model, sum(x[i] for i in independent_v) >= 1)
    end

    @objective(model, Min, sum(x))

    optimize!(model)

    return (value.(x),objective_value(model))
end


module _PermEn
export PermEn
using Combinatorics: permutations
using Statistics: std, var, mean
    """
        Perm, Pnorm, cPE = PermEn(Sig)

    Returns the permuation entropy estimates `Perm`, the normalised
    permutation entropy `Pnorm` and the conditional permutation entropy `cPE`
    for `m` = [1,2] estimated from the data sequence `Sig`  using the default
    parameters: embedding dimension = 2, time delay = 1, logarithm = base 2,
    normalisation = w.r.t #symbols (`m`-1)
    Note: using the standard PermEn estimation, `Perm` = 0 when `m` = 1.
    Note: It is recommeneded that signal length > 5m! (see [8] and Amigo et al., Europhys. Lett. 83:60005, 2008)

        Perm, Pnorm, cPE = PermEn(Sig, m)

    Returns the permutation entropy estimates `Perm` estimated from the data
    sequence `Sig` using the specified embedding dimensions = [1,...,`m`]
    with other default parameters as listed above.

        Perm, Pnorm, cPE = PermEn(Sig::AbstractArray{T,1} where T<:Real; m::Int=2, tau::Int=1, Typex::String="none", tpx::Union{Real,Nothing}=nothing, Logx::Real=2, Norm::Bool=false)

    Returns the permutation entropy estimates `Perm` for dimensions = [1,...,`m`]
    estimated from the data sequence `Sig` using the specified 'keyword' arguments:

    # Arguments:
    `m`     - Embedding Dimension, an integer > 1 \n
    `tau`   - Time Delay, a positive integer\n
    `Logx`  - Logarithm base, a positive scalar (enter 0 for natural log) \n
    `Norm`  - Normalisation of PermEn value:\n
              false -  normalises w.r.t log(# of permutation symbols [m-1]) - default
              true  -  normalises w.r.t log(# of all possible permutations [m!])
              * Note: Normalised permutation entropy is undefined for m = 1.
              ** Note: When Typex = 'uniquant' and Norm = true, normalisation
              is calculated w.r.t. log(tpx^m)\n
    `Typex`  - Permutation entropy variation, one of the following:
              {`"none", "uniquant", "finegrain", "modified", "ampaware",
              "weighted", "edge"}
              See the EntropyHub guide for more info on PermEn variations.    \n
    `tpx`   - Tuning parameter for associated permutation entropy variation.\n
              [uniquant]  'tpx' is the L parameter, an integer > 1 (default = 4).
              [finegrain] 'tpx' is the alpha parameter, a positive scalar (default = 1)
              [ampaware]  'tpx' is the A parameter, a value in range [0 1] (default = 0.5)
              [edge]      'tpx' is the r sensitivity parameter, a scalar > 0 (default = 1)
              See the EntropyHub guide for more info on PermEn variations.    \n

    # See also `XPermEn`, `MSEn`, `XMSEn`, `SampEn`, `ApEn`, `CondEn`

    # References:
        [1] Christoph Bandt and Bernd Pompe,
                "Permutation entropy: A natural complexity measure for time series."
                Physical Review Letters,
                88.17 (2002): 174102.

        [2] Xiao-Feng Liu, and Wang Yue,
                "Fine-grained permutation entropy as a measure of natural
                complexity for time series."
                Chinese Physics B
                18.7 (2009): 2690.

        [3] Chunhua Bian, et al.,
                "Modified permutation-entropy analysis of heartbeat dynamics."
                Physical Review E
                85.2 (2012) : 021906

        [4] Bilal Fadlallah, et al.,
                "Weighted-permutation entropy: A complexity measure for time
                series incorporating amplitude information."
                Physical Review E
                87.2 (2013): 022911.

        [5] Hamed Azami and Javier Escudero,
                "Amplitude-aware permutation entropy: Illustration in spike
                detection and signal segmentation."
                Computer methods and programs in biomedicine,
                128 (2016): 40-51.

        [6] Zhiqiang Huo, et al.,
                "Edge Permutation Entropy: An Improved Entropy Measure for
                Time-Series Analysis,"
                45th Annual Conference of the IEEE Industrial Electronics Soc,
                (2019), 5998-6003

        [8] Zhe Chen, et al.
                "Improved permutation entropy for measuring complexity of time
                series under noisy condition."
                Complexity
                1403829 (2019).

        [9] Maik Riedl, Andreas Müller, and Niels Wessel,
                "Practical considerations of permutation entropy."
                The European Physical Journal Special Topics
                222.2 (2013): 249-262.

    """
    function PermEn(Sig::AbstractArray{T,1} where T<:Real; m::Int=2, tau::Int=1,
        Typex::String="none", tpx::Union{Real,Nothing}=nothing, Logx::Real=2, Norm::Bool=false)

    Logx == 0  ? Logx = exp(1) : nothing

    N = size(Sig)[1]
    (N>10) ? nothing :   error("Sig:   must be a numeric vector")
    (m > 1) ? nothing :  error("m:     must be an integer > 1")
    (tau > 0) ? nothing :  error("tau:   must be an integer > 0")
    (Logx>0) ? nothing : error("Logx:     must be a positive number > 0")
    (lowercase(Typex) in ["none","uniquant","finegrain","modified","ampaware","weighted","edge"]) ?
     nothing : error("Typex:    must be one of the following strings -
        'uniquant','finegrain','modified','ampaware','weighted','edge'")
    (isnothing(tpx) || tpx>0) ? nothing : error("tpx:   the value of tpx relates to 'Type'.
        See the EntropyHub guide for further info on the 'tpx' value.")

    Sx = zeros(N,m)
    Perm = zeros(m)
    Pnorm = zeros(m)
    for k = 1:m
        Nx = N-(k-1)*tau
        Sx[1:Nx,k] = Sig[1+(k-1)*tau:N]
        Temp = sortind(Sx[1:Nx,1:k])
        Px = collect(permutations(collect(1:k)))
        Counter = zeros(length(Px))

        if lowercase(Typex) == "uniquant"
            Temp = sort(Sx[1:Nx,1:k], dims=2)
            S = zeros(size(Temp))
            if isnothing(tpx)
                tpx = 4;
            elseif tpx <= 1 || typeof(tpx)!=Int
                error("When Typex = 'Uniquant', L parameter (tpx) must be an integer > 1")
            end
            delta = (maximum(Sig)-minimum(Sig))/tpx
            S[:,1] = map(x -> searchsortedfirst(
                minimum(Sig):delta:maximum(Sig),x), Temp[:,1]) .- 1
            #S[findall(S[:,1].==0),1] .+= 1
            S[S[:,1].==0,1] .+= 1
            if k > 1
                S[:,2:k] = S[:,1] .+ floor.((Temp[:,2:k] .- Temp[:,1])/delta)
            end
            Px = unique(S,dims=1)
            Counter = zeros(size(Px,1))
            for n = 1:size(Px,1)
                Counter[n] = sum(all(S .- transpose(Px[n,:]).==0,dims=2))
            end
            Counter = Counter[Counter.!=0]
            Ppi = Counter/sum(Counter)
            Norm = true  # Might need to change back to 3

        elseif lowercase(Typex) == "finegrain"
            if k > 1
                if isnothing(tpx)
                    tpx = 1;
                elseif tpx <= 0
                    error("When Typex = 'finegrain', Alpha parameter (tpx) must be greater than 0")
                end
                q =  floor.(maximum(abs.(diff(Sx[1:Nx,1:k],dims=2)),dims=2)./
                                (tpx*std(abs.(diff(Sig)),corrected=false)))
                Temp = hcat(Temp, q)
                Px = unique(Temp,dims=1)
                Counter = zeros(size(Px,1))
                for n = 1:size(Px,1)
                    Counter[n] = sum(all(Temp .- transpose(Px[n,:]).== 0,dims=2))
                end
                Counter = Counter[Counter.!=0]
                Ppi = Counter./sum(Counter)
                #clear q n qt
            else
                Ppi = 1
            end

        elseif lowercase(Typex) == "modified"
            Tx = (diff(sort(Sx[1:Nx,1:k],dims=2),dims=2).==0)
            for km = 1:k-1
                Temp[Tx[:,km],km+1] = Temp[Tx[:,km],km];
            end
            Px = unique(Temp,dims=1)
            Counter = zeros(size(Px,1))
            for n = 1:size(Px,1)
                Counter[n] = sum(all(Temp .- transpose(Px[n,:]) .==0,dims=2))
            end
            Counter = Counter[Counter.!=0]
            Ppi = Counter/sum(Counter)
            #clear Tx km

        elseif lowercase(Typex) == "weighted"
            if k > 1
                Wj = var(Sx[1:Nx,1:k],corrected=false,dims=2)
                for n = 1:size(Px,1)
                    Counter[n] = sum(Wj[all(Temp .- transpose(Px[n]) .==0,dims=2)])
                end
                Counter = Counter[Counter.!=0]
                Ppi = Counter/sum(Wj)
                #clear Wj n
            else
                Ppi = 1;
            end

        elseif lowercase(Typex) == "ampaware"
            if k > 1
                if isnothing(tpx)
                    tpx = 0.5;
                elseif tpx<0 || tpx>1
                    error("When Typex = 'ampaware', the A parameter must be in the range [0 1]")
                end
                AA = sum(abs.(Sx[1:Nx,1:k]),dims=2)
                AB = sum(abs.(diff(Sx[1:Nx,1:k],dims=2)),dims=2)
                Ax = (tpx*AA/k) + ((1-tpx)*AB/(k-1));
                for n = 1:size(Px,1)
                    Counter[n] = sum(Ax[all(Temp.-transpose(Px[n]).==0,dims=2)])
                end
                Counter = Counter[Counter.!=0]
                Ppi = Counter/sum(Ax);
            else
                Ppi = 1;
            end
            #clear AA AB Ax

        elseif lowercase(Typex) == "edge"
            if isnothing(tpx)
                tpx = 1;
            elseif tpx <=0
                error("When Typex = 'Edge', the r sensitivity parameter (tpx) must be > 0")
            end
            if k > 1
                for n = 1:size(Px,1)
                    Sy = Sx[1:Nx,1:k]
                    Tx = diff(Sy[all(Temp .- transpose(Px[n]) .==0,dims=2)[:],:],dims=2)
                    Counter[n] = sum(mean(hypot.(Tx,1),dims=2).^tpx)
                end
                Counter = Counter[Counter.!=0]
                Ppi = Counter/sum(Counter)
            else Ppi = 1;
            end

        else
            for n = 1:size(Px,1)
                Counter[n] = sum(all(Temp .- transpose(Px[n]).==0,dims=2));
                #sum(all(Temp .- transpose(Px[n]).==0,dims=2));
            end
            Counter = Counter[Counter.!=0]
            Ppi = Counter/sum(Counter)
        end

        if round(sum(Ppi),digits=3) != 1
            @warn ("Potential error with probability calculation")
        end

        Perm[k] = -sum(Ppi.*(log.(Logx, Ppi)));
        if Norm
            if Norm && lowercase(Typex)=="uniquant"
                Pnorm[k] = Perm[k]/(log(Logx, tpx^k));
            else
                Pnorm[k] = Perm[k]/(log(Logx, factorial(k)));
            end
        else
            Pnorm[k] = Perm[k]/(k-1);
        end
    end
    cPE = diff(Perm);

    return Perm, Pnorm, cPE
    end

    function sortind(X)
        Y = zeros(Int, size(X))
        for k = 1:length(X[:,1])
            Y[k,:] = sortperm(X[k,:])
        end
        return Y
    end

end

"""
Copyright 2021 Matthew W. Flood, EntropyHub

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

For Terms of Use see https://github.com/MattWillFlood/EntropyHub
"""


using Optim

function count_histogram(counts)
    max_counts = 0
    for e in counts
        max_counts = max(max_counts, maximum(e))
    end
    hist = zeros(max_counts + 1)
    for e in counts
        for c in e
            hist[c + 1] += 1
        end
    end
    hist
end

function bipoisson(n, λ0, λ1, bright_ratio)
    if (bright_ratio < 0 || bright_ratio > 1 || λ0 < 0 || λ1 < 0)
        0.0
    else
        (exp(-λ0) * (1 - bright_ratio) * λ0.^n +
            exp(-λ1) * bright_ratio * λ1.^n) ./ gamma(n + 1)
    end
end

function fit_count_histogram(histo)
    ns = collect(0:(length(histo) - 1))
    function negloglike(params)
       -sum(histo .* log(bipoisson(ns, params...)))
    end

    mle = optimize(negloglike, [2.0, 40.0, 0.5])
    mle.minimum
end

function count_threshold(λ0, λ1)
    floor((λ1 - λ0) / log(λ1 / λ0))
end

function apply_threshold(counts, threshold)
    map(c -> count(n -> n > threshold, c) / length(c), counts)
end

function auto_threshold(shots)
    λ0, λ1, ratio = fit_count_histogram(count_histogram(shots))
    apply_threshold(shots, count_threshold(λ0, λ1))
end


using HomotopyContinuation2.ModelKit

@testset "ModelKit" begin
    @testset "SymEngine" begin
        @test Expression(MathConstants.catalan) isa Expression
        @test Expression(MathConstants.e) isa Expression
        @test Expression(MathConstants.pi) isa Expression
        @test Expression(MathConstants.γ) isa Expression
        @test Expression(Float16(2.1)) isa Expression
        @test Expression(Float32(2.1)) isa Expression
        @test Expression(big(2.1)) isa Expression
        @test Expression(Int32(2)) isa Expression
        @test Expression(Int16(2)) isa Expression
        @test Expression(Int128(2)) isa Expression
        @test Expression(2 // 3) isa Expression
        @test convert(BigFloat, Expression(big(2.1))) == big(2.1)
        @test ModelKit.is_number(Expression(2))
        @test ModelKit.to_number(Expression(big(2.1))) isa BigFloat
        @test ModelKit.to_number(Expression(2 // 3)) isa Rational{Int}
        @test ModelKit.to_number(Expression(2) // 3) isa Rational{Int}
        @test ModelKit.to_number(Expression(2) // Expression(3)) isa Rational{Int}
        @test ModelKit.to_number(Expression(big(2)^129)) isa BigInt
        @test complex(Expression(1), Expression(2)) == Expression(1) + im * 2
        @test complex(Expression(1), 2) == Expression(1) + im * 2
        @test complex(1, Expression(2)) == Expression(1) + im * 2
        @test +Expression(2) == 2
        @test Expression(HC2.DoubleDouble.DoubleF64(2)) == big(2.0)
    end

    @testset "Variables" begin
        @var a b x[1:2] y[1:2, 1:3]

        @test a isa Variable
        @test b isa Variable
        @test x isa Vector{Variable}
        @test length(x) == 2
        @test y isa Matrix{Variable}
        @test size(y) == (2, 3)
        @test join(x, " ") == "x₁ x₂"

        @test reshape(sort(vec(y)), 2, 3) == y
        @var x[9:11]
        @test sort(x) == x

        @var c, d
        @test c isa Variable
        @test d isa Variable
        @test variables(c) == [c]
        @test c === c
        @test c !== copy(c)

        let
            c2 = @unique_var c
            @test c != c2
        end

        f = a + b
        @test variables(f) == [a, b]
        @test nvariables(f) == 2
        g = x[1]
        @test variables([f, g]) == [a, b, x[1]]
        @test nvariables([f, g]) == 3
    end

    @testset "Subs" begin
        @var x y w z u
        f = x^2 * (x + y * w)

        @test subs(f, x => z) == z^2 * (z + w * y)
        @test subs([f], x => z) == [z^2 * (z + w * y)]
        @test subs(f, [x, y] => [z^2, z + 2]) == z^4 * (w * (2 + z) + z^2)
        @test subs(f, [x, y] => [z^2, z + 2], w => u) == z^4 * (u * (2 + z) + z^2)
        @test subs(f, x => z^2, y => 3, w => u) == z^4 * (3 * u + z^2)
        @test subs(f, Dict(x => z^2, y => 3, w => u)) == z^4 * (3 * u + z^2)
    end

    @testset "Evaluation" begin
        @var x y w
        f = x^2 * (x + y * w)
        @test evaluate([f, f], [x, y, w] => [2, 3, -5]) == [-52, -52]
        @test [f, 2f]([x, y, w] => [2, 3, -5]) == [-52, -104]
    end

    @testset "Linear Algebra" begin
        @var x[1:2, 1:2]
        @test det(x) == -x[2, 1] * x[1, 2] + x[2, 2] * x[1, 1]
        @test det(x') == -x[2, 1] * x[1, 2] + x[2, 2] * x[1, 1]
        @test det(transpose(x)) == -x[2, 1] * x[1, 2] + x[2, 2] * x[1, 1]
    end

    @testset "Differentiation" begin
        @var x y

        f = x^2 + y^2
        g = x^3 + y^3

        @test differentiate(f, x) == 2x
        @test differentiate(f, [x, y]) == [2x, 2y]

        @test differentiate([f, g], x) == [2x, 3 * x^2]
        @test differentiate([f, g], [x, y]) == [2x 2y; 3 * x^2 3 * y^2]
    end

    @testset "Expand" begin
        @var x y
        @test expand((x + y)^2) == 2 * x * y + x^2 + y^2
    end

    @testset "to_dict" begin
        @var x y a
        @test ModelKit.to_dict(x^2 + y * x * a + y * x, [x, y]) ==
              Dict([2, 0] => Expression(1), [1, 1] => a + 1)
        @test ModelKit.degree(x * y^5 + y^2, [x, y]) == 6
    end

    @testset "Modeling" begin
        @testset "Bottleneck" begin
            @var x y z
            f = [
                (0.3 * x^2 + 0.5z + 0.3x + 1.2 * y^2 - 1.1)^2 +
                (0.7 * (y - 0.5x)^2 + y + 1.2 * z^2 - 1)^2 - 0.3,
            ]

            I = let x = [x, y, z]
                n, m = length(x), length(f)
                @unique_var y[1:n] v[1:m] w[1:m]
                J = differentiate(f, x)
                f′ = f(x => y)
                J′ = J(x => y)
                Nx = (x - y) - J' * v
                Ny = (x - y) - J′' * w
                System([f; f′; Nx; Ny], [x; y; v; w])
            end
            @test I isa System
            @test size(I) == (8, 8)
        end

        @testset "Steiner" begin
            @var x[1:2] a[1:5] c[1:6] y[1:2, 1:5]

            #tangential conics
            f = sum([a; 1] .* monomials(x, 2))
            ∇ = differentiate(f, x)
            #5 conics
            g = sum(c .* monomials(x, 2))
            ∇_2 = differentiate(g, x)
            #the general system
            #f_a_0 is tangent to g_b₀ at x₀
            function Incidence(f, a₀, g, b₀, x₀)
                fᵢ = f(x => x₀, a => a₀)
                ∇ᵢ = ∇(x => x₀, a => a₀)
                Cᵢ = g(x => x₀, c => b₀)
                ∇_Cᵢ = ∇_2(x => x₀, c => b₀)

                [fᵢ; Cᵢ; det([∇ᵢ ∇_Cᵢ])]
            end
            @var v[1:6, 1:5]
            I = vcat(map(i -> Incidence(f, a, g, v[:, i], y[:, i]), 1:5)...)
            F = System(I, [a; vec(y)], vec(v))
            @test size(F) == (15, 15)
        end

        @testset "Reach plane curve" begin
            @var x y
            f = (x^3 - x * y^2 + y + 1)^2 * (x^2 + y^2 - 1) + y^2 - 5
            ∇ = differentiate(f, [x; y]) # the gradient
            H = differentiate(∇, [x; y]) # the Hessian

            g = ∇ ⋅ ∇
            v = [-∇[2]; ∇[1]]
            h = v' * H * v
            dg = differentiate(g, [x; y])
            dh = differentiate(h, [x; y])

            ∇σ = g .* dh - ((3 / 2) * h) .* dg

            F = System([v ⋅ ∇σ; f], [x, y])
            @test size(F) == (2, 2)
        end
    end

    @testset "Horner" begin
        # use fano scheme as test
        n = 4
        s = 1
        @var x[1:n]
        ν = monomials(x, 5)
        N = length(ν)
        q₀ = randn(ComplexF64, N - 1)
        @var q[1:N-1]
        F = sum(q[i] * ν[i] for i = 1:N-1) + 1

        @var a[1:n-1] b[1:n-1] t
        L = [a; 1] .* t + [b; 0]

        G = subs(F, x => L)

        FcapL = collect(values(ModelKit.to_dict(expand(G), [t])))

        @test all(FcapL) do f
            g = horner(f)
            expand(g) == f
        end
        @test all(FcapL) do f
            g = horner(f, [a; b])
            expand(g) == f
        end
    end

    @testset "Rand / dense poly" begin
        @var x y
        f, c = dense_poly([x, y], 3)
        @test length(c) == 10
        g = rand_poly(Float64, [x, y], 3)
        @test subs(f, c => coefficients(g, [x, y])) == g
        _, coeffs = exponents_coefficients(g, [x, y])
        @test subs(f, c => coeffs) == g
    end

    @testset "System" begin
        @var x y a b
        f = [(x + y)^3 + x^2 + x + 5y + 3a, 2 * x^2 + b]
        F = System(f, [x, y], [b, a])
        @test nvariables(F) == 2
        @test nparameters(F) == 2
        @test variables(F) == [x, y]
        @test parameters(F) == [b, a]
        show_F = """
        System of length 2
         2 variables: x, y
         2 parameters: b, a

         3*a + x + 5*y + x^2 + (x + y)^3
         b + 2*x^2"""
        @test sprint(show, F) == show_F
        @test degrees(F) == [3, 2]

        T = ModelKit.compile(F; optimizations = false)
        F2 = ModelKit.interpret(T)
        @test F == F2
        @test sprint(show, T) == "Compiled: $show_F"
        @test size(T) == size(F) == (2, 2)

        T2 = ModelKit.compile(F; optimizations = true)
        F3 = ModelKit.interpret(T2)
        @test expand.(F.expressions) == expand.(F3.expressions)
    end

    @testset "System variables groups + homogeneous" begin
        @var x y z
        f = System([
            (x^2 + y^2 + x * y - 3 * z^2) * (x + 3z),
            (x^2 + y^2 + x * y - 3 * z^2) * (y - x + 2z),
            2x + 5y - 3z,
        ])
        @test is_homogeneous(f)
        multi_degrees(f) == [1 1; 2 0]

        @var x y z v w
        g = System([x * y - 2v * w, x^2 - 4 * v^2], variable_groups = [[x, v], [y, w]])
        @test is_homogeneous(g)
        multi_degrees(g) == [1 1; 2 0]
    end

    @testset "Homotopy" begin
        @var x y z t

        h = [x^2 + y + z + 2t, 4 * x^2 * z^2 * y + 4z - 6x * y * z^2]
        H = Homotopy(h, [x, y, z], t)

        @test nvariables(H) == 3
        @test nparameters(H) == 0
        @test variables(H) == [x, y, z]
        @test parameters(H) == []
        show_H = """
        Homotopy in t of length 2
         3 variables: x, y, z

         2*t + y + z + x^2
         4*z - 6*x*y*z^2 + 4*x^2*y*z^2"""

        @test sprint(show, H) == show_H

        T = ModelKit.compile(H)
        H2 = ModelKit.interpret(T)
        @test H == H2
        @test sprint(show, T) == "Compiled: $show_H"
        @test size(T) == size(H) == (2, 3)
    end

    @testset "Codegen helpers" begin
        @test ModelKit.sqr(3 + 2im) == (3 + 2im)^2
        @test ModelKit.sqr(3) == 3^2
    end

    @testset "Instructions" begin

        @testset "Simple dt" begin
            @var x a b t
            a = 0.5
            b = 0.1
            h = (x - (t - (a + im * b))^2) * (x + (t - (a + im * b))^2)
            list, _ = ModelKit.instruction_list([h])
            D = ModelKit.DiffMap()
            D[:t, 1] = 1
            slp = @eval ModelKit begin
                function dt_test1(x, t)
                    $(ModelKit.to_expr(ModelKit.univariate_diff!(list, 1, D)))
                end
            end
            expr1 = ModelKit.expand(ModelKit.dt_test1(x, t))
            expr2 = ModelKit.expand(ModelKit.differentiate(h, t))
            @test expr1 == expr2
        end
        @testset "Higher order pow diff" begin
            for d = 2:10
                @var x
                f = x^d
                list, _ = ModelKit.instruction_list([f])

                D = ModelKit.DiffMap()
                D[:x, 1] = :x1
                D[:x, 2] = :x2
                D[:x, 3] = :x3

                @eval ModelKit begin
                    function __diff_4_pow(x, x1, x2, x3)
                        $(ModelKit.to_expr(ModelKit.univariate_diff!(list, 4, D)))
                    end
                end

                @var x1 x2 x3 λ

                @test expand(
                    subs(
                        differentiate(
                            subs(f, x => x .+ λ .* x1 .+ λ^2 .* x2 + λ^3 .* x3),
                            λ,
                            4,
                        ),
                        λ => 0,
                    ) / 24,
                ) == expand(ModelKit.__diff_4_pow(x, x1, x2, x3))
            end
        end

        @testset "Higher order mul" begin
            @var x y
            f = x * y
            list, _ = ModelKit.instruction_list([f])
            D = ModelKit.DiffMap()
            D[:x, 1] = :x1
            D[:x, 2] = :x2
            D[:x, 3] = :x3
            D[:y, 1] = :y1
            D[:y, 2] = :y2
            D[:y, 3] = :y3

            @eval ModelKit begin
                function __diff_4_mul__(x, y, (x1, x2, x3), (y1, y2, y3))
                    $(ModelKit.to_expr(ModelKit.univariate_diff!(list, 4, D)))
                end
            end
            @var x1 x2 x3 y1 y2 y3 λ

            @test expand(
                subs(
                    differentiate(
                        subs(
                            f,
                            x => x .+ λ .* x1 .+ λ^2 .* x2 + λ^3 .* x3,
                            y => y .+ λ .* y1 .+ λ^2 .* y2 + λ^3 .* y3,
                        ),
                        λ,
                        4,
                    ),
                    λ => 0,
                ) / 24,
            ) == expand(ModelKit.__diff_4_mul__(x, y, (x1, x2, x3), (y1, y2, y3)))
        end

        @testset "Higher order plus" begin
            @var x y
            f = x + y
            list, _ = ModelKit.instruction_list([f])

            D = ModelKit.DiffMap()
            D[:x, 1] = :x1
            D[:x, 2] = :x2
            D[:x, 3] = :x3
            D[:y, 1] = :y1
            D[:y, 2] = :y2
            D[:y, 3] = :y3

            @eval ModelKit begin
                function __diff_3_plus__(x, y, (x1, x2, x3), (y1, y2, y3))
                    $(ModelKit.to_expr(ModelKit.univariate_diff!(list, 3, D)))
                end
            end

            @var x1 x2 x3 y1 y2 y3 λ

            @test expand(
                subs(
                    differentiate(
                        subs(
                            f,
                            x => x .+ λ .* x1 .+ λ^2 .* x2 + λ^3 .* x3,
                            y => y .+ λ .* y1 .+ λ^2 .* y2 + λ^3 .* y3,
                        ),
                        λ,
                        3,
                    ),
                    λ => 0,
                ) / 6,
            ) == expand(ModelKit.__diff_3_plus__(x, y, (x1, x2, x3), (y1, y2, y3)))
        end
    end

    @testset "Homotopy codegen (Katsura(3))" begin
        n = 3
        @var x[0:n] ẋ[0:n] ẍ[0:n] x3[0:n] t γ
        K = [
            (
                sum(x[abs(l)+1] * x[abs(m - l)+1] for l = -n:n if abs(m - l) <= n) - x[m+1] for m = 0:n-1
            )...,
            x[1] + 2 * sum(x[i+1] for i = 1:n) - 1,
        ]

        h = γ .* t .* [x[1:n] .^ 2 .- 1; x[n+1] - 1] + (1 - t) .* K
        H = ModelKit.Homotopy(h, x, t, [γ])
        TH = ModelKit.compile(H)

        tx3 = TaylorVector{4}(zeros(Expression, 4, 4))
        y, y1, y2, y3 = vectors(tx3)
        y .= x
        y1 .= ẋ
        y2 .= ẍ
        y3 .= x3

        dt1 =
            ModelKit.taylor!(zeros(Expression, 4), Val(1), TH, TaylorVector{1}(tx3), t, [γ])
        dt2 =
            ModelKit.taylor!(zeros(Expression, 4), Val(2), TH, TaylorVector{2}(tx3), t, [γ])
        dt3 =
            ModelKit.taylor!(zeros(Expression, 4), Val(3), TH, TaylorVector{3}(tx3), t, [γ])
        dt4 =
            ModelKit.taylor!(zeros(Expression, 4), Val(4), TH, TaylorVector{4}(tx3), t, [γ])

        @var λ
        Hd1 = subs(H.expressions, t => t + λ)
        true_dt1 = subs(differentiate(Hd1, λ, 1), λ => 0)

        Hd2 = subs(H.expressions, x => x .+ λ .* ẋ, t => t + λ)
        true_dt2 = subs(differentiate(Hd2, λ, 2), λ => 0) / 2

        Hd3 = subs(H.expressions, x => x .+ λ .* ẋ .+ λ^2 .* ẍ, t => t + λ)
        true_dt3 = subs(differentiate(Hd3, λ, 3), λ => 0) / 6

        Hd4 = subs(H.expressions, x => x .+ λ .* ẋ .+ λ^2 .* ẍ .+ λ^3 .* x3, t => t + λ)
        true_dt4 = subs(differentiate(Hd4, λ, 4), λ => 0) / 24

        @test expand.(ModelKit.evaluate(TH, x, t, [γ])) == expand.(h)
        @test expand.(ModelKit.jacobian(TH, x, t, [γ])) == expand.(differentiate(h, x))
        @test expand.(dt1) == expand.(true_dt1)
        @test expand.(dt2) == expand.(true_dt2)
        @test expand.(dt3) == expand.(true_dt3)
        @test expand.(dt4) == expand.(true_dt4)
    end

    @testset "taylor! - system" begin
        @var x[1:2] ẋ[1:2] ẍ[1:2] x3[1:2] p[1:2] ṗ[1:2]
        f = [(x[1] + x[2])^3 + x[1]^2 + x[1] + 5 * x[2] + 3 * p[1], 2 * x[1]^2 + p[2]]
        F = System(f, x, p)
        TF = ModelKit.compile(F)

        tx = TaylorVector{4}([x'; ẋ'; ẍ'; x3'])
        tv = TaylorVector{5}(Expression, 2)
        v, v1, v2, v3, v4 = vectors(tv)
        @var λ

        ModelKit.taylor!(TaylorVector{3}(tv), TF, TaylorVector{2}(tx), p)
        Fd2 = subs(F.expressions, x => x .+ λ .* ẋ)
        true_v1 = subs(differentiate(Fd2, λ, 1), λ => 0)
        true_v2 = subs(differentiate(Fd2, λ, 2), λ => 0) / 2
        @test expand.(v) == expand.(f)
        @test expand.(v1) == expand.(true_v1)
        @test expand.(v2) == expand.(true_v2)

        ModelKit.taylor!(TaylorVector{4}(tv), TF, TaylorVector{3}(tx), p)
        Fd3 = subs(F.expressions, x => x .+ λ .* ẋ .+ λ^2 .* ẍ)
        true_v1 = subs(differentiate(Fd3, λ, 1), λ => 0)
        true_v2 = subs(differentiate(Fd3, λ, 2), λ => 0) / 2
        true_v3 = subs(differentiate(Fd3, λ, 3), λ => 0) / 6
        @test expand.(v) == expand.(f)
        @test expand.(v1) == expand.(true_v1)
        sub = variables(v2) => rand(1:10_000, 6)
        @test v2(sub) == true_v2(sub)
        @test v3(sub) == true_v3(sub)
    end
end


include("../src/RationalFunctionFields.jl")

using .RationalFunctionFields
using .RationalFunctionFields: load_generators, new_generating_set,
             field_generators, compute_groebner!, simplify_generators!,
             RationalFunctionField, field_generators

Singular = RationalFunctionFields.Singular
Logging = RationalFunctionFields.Logging

logger = Logging.SimpleLogger(stderr, Logging.Debug)
Logging.global_logger(logger)

set = load_generators("../data/LV_gens.txt")

println(set)
FF = RationalFunctionField(set)
compute_groebner!(FF)
simplify_generators!(FF)

println( "\ngenerators ", field_generators(FF) )


function ueberpruefeParameter(M,b,nx1,nx2,nx3,u,v,b3)

f = [2.35; 9.14; -0.31];
u1 = u[1];
u2 = u[2];
u3 = u[3];
#dritte Spalte der Matrix
v1 = v[1];
v2 = v[2];
v3 = v[3];

A = zeros(3,3);
#erste Spalte der Matrix
A[1,1] = -nx1;
A[2,1] = -nx2;
A[3,1] = -nx3;
#zweite Spalte der Matrix
A[1,2] = u1;
A[2,2] = u2;
A[3,2] = u3;
#dritte Spalte der Matrix
A[1,3] = v1;
A[2,3] = v2;
A[3,3] = v3;

SuS = norm(M*f);
LuL = norm(A*f);

if SuS-LuL <= 0.1 && norm(b)-norm(b3) <= 0.1
    println("Ihr habt das korrekte Gleichungssystem aufgestellt. Die Lösung eures Gleichungssystems lautet:")
    Lvec = M\b3;
    r = Lvec[1];
    s = Lvec[2];
    t = Lvec[3];
    print(round(r;digits=4),"\n");
    print(round(s;digits=4),"\n");
    print(round(t;digits=4));
elseif norm(b)-norm(b3) <= 0.1
    println("Überprüft erneut eure Matrix.")
    r = [];
    s = [];
    t = [];
elseif SuS-LuL <= 0.1
    println("Überprüft erneut euren Vektor b.")
    r = [];
    s = [];
    t = [];
elseif b[1]-b3[1] < 0.1 && b[2]-b3[2] < 0.1 && b[3]-b3[3] < 0.1
    println("Überprüft erneut euren Vektor b.")
    r = [];
    s = [];
    t = [];
elseif round(A[1,1];digits=2) == round(-M[1,1];digits=2) || round(A[1,1];digits=2) == round(-M[1,2];digits=2) || round(A[1,1];digits=2) == round(-M[3,1];digits=2)
    println("Überprüft noch einmal eure Umformungen. Ihr müsst alle Vektoren, die mit Parametern multipliziert werden auf die eine und den einzelnen auf die andere Seite stellen.")
    r = [];
    s = [];
    t = [];
else
    println("Überprüft erneut eure Eingabe und, ob ihr alle Umformungen korrekt gemacht habt.")
    r = [];
    s = [];
    t = [];
end
return r,s,t;
end


using GeometryTypes
function f(G, x)
    fx = (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2

    if !(G == nothing)
        G1 = -2.0 * (1.0 - x[1]) - 400.0 * (x[2] - x[1]^2) * x[1]
        G2 = 200.0 * (x[2] - x[1]^2)
        gx = Point(G1,G2)

        return fx, gx
    else
        return fx
    end
end

minimize(f, Point(3.0,3.0), NearlyNewton.GradientDescent(InverseApprox()), I, NearlyNewton.OptOptions())
minimize(f, Point(3.0,3.0), NearlyNewton.BFGS(InverseApprox()), I, NearlyNewton.OptOptions())
minimize(f, Point(3.0,3.0), NearlyNewton.DFP(InverseApprox()), I, NearlyNewton.OptOptions())
minimize(f, Point(3.0,3.0), NearlyNewton.SR1(InverseApprox()), I, NearlyNewton.OptOptions())

minimize(f, Point(3.0,3.0), NearlyNewton.GradientDescent(DirectApprox()), I, NearlyNewton.OptOptions())
minimize(f, Point(3.0,3.0), NearlyNewton.BFGS(DirectApprox()), I, NearlyNewton.OptOptions())
minimize(f, Point(3.0,3.0), NearlyNewton.DFP(DirectApprox()), I, NearlyNewton.OptOptions())
minimize(f, Point(3.0,3.0), NearlyNewton.SR1(DirectApprox()), I, NearlyNewton.OptOptions())


#=
runtime: in O(n^2.81)
accepts square matrices A,B (assumes nxn, mxm where n,m are powers of two)
returns matrix C
=#

function partition(A)
  #add try-catch
  #accepts square matrix A (assumes nxn, where n is power of two)
  n = length(A)[1]
  W = new matrix(  )
  X = new matrix(  )
  Y = new matrix(  )
  Z = new matrix(  )
  return W, X, Y, Z
end

function recursiveMultiply(A,B)
  n = length(A)[1]
  #matrix C
  C = new zeroesMatrix( length(A)[1,:],length(B)[:,1] )
  #trivial case
  if n == 1
     return A[1:1] * B[1:1]
  else
    partition(A)
    partition(B)
    #create 10 matrices (= Theta(n^2))
    S1 = B12 - B22
    S2 = A11 + A22
    S3 = A21 + A22
    S4 = B21 - B11
    S5 = A11 + A22
    S6 = B11 + B12
    S7 = A11 - A22
    S8 = B21 + B22
    S9 = A11 - A21
    S10 = B11 + B12
    #multiplications
    P1 = recursiveMultiply(A11, S1)
    P2 = recursiveMultiply(S2, B22)
    P3 = recursiveMultiply(S3, B11)
    P4 = recursiveMultiply(A22, S4)
    P5 = recursiveMultiply(S5, S6)
    P6 = recursiveMultiply(S7, S8)
    P7 = recursiveMultiply(S9, S10)
    #defining C
    C11 = P5 + P4 - P2 + P6
    C12 = P1 + P2
    C21 = P3 + P4
    C22 = P5 + P1 - P3 - P7
    return #build via blocks
  end
end


using StatsBase
using PyPlot

function linreg2(x, y, w::WeightVec)
    size(x) == size(y) || throw(DimensionMismatch("x and y must be the same size"))
    my = mean(y, w)
    mx, vx = mean(x, w)
    # don't need to worry about the scaling (n vs n - 1) since they cancel in the ratio
    b = cov(x, y)/vx
    a = my - b*mx
    return (a, b)
end

x = collect(linspace(0, 10, 20))
y = randn(20)
w = WeightVec(ones(20))
cov(x, y, w)
linreg(x, y)
linreg(x, y, ones(20))

linreg2(x, y, w)


M = [-1 1 0 0
    0 -1 1 0
    0 0 -1 1
    1 0 0 -1
]
using LinearAlgebra

Λ, V = eigen(M)
Λᵀ, Vᵀ = eigen(M')


using Flux

function naive_implementation()
    W₁ = param(rand(3,5))
    b₁ = param(rand(3))
    layer₁(x) = W₁ * x .+ b₁

    W₂ = param(rand(2,3))
    b₂ = param(rand(2))
    layer₂(x) = W₂ * x .+ b₂

    model(x) = layer₂(σ.(layer₁(x)))
    @show model(rand(5))
end

struct Affine
    W
    b
end

Affine(in::Integer, out::Integer) = Affine(param(randn(out, in)), param(randn(out)))

# overload call, so that object can be used as a function
(m::Affine)(x) = m.W * x .+ m.b

function struct_implementation()
    aff = Affine(10, 5)
    @show aff(rand(10))
end

function main()
    naive_implementation()
    struct_implementation()
end

main()


export Lowrank

struct Lowrank{T <: Real} <: ConvexSet
    k::T
end
```
This function projects the matrix y onto the closest matrix of rank k or less.
```
function project!(s::Lowrank{<:Real}, v::Matrix{T}, y::Matrix{T}) where {T <: Real}
    U, S, V = LinearAlgebra.svd(y)
    Z = zeros(size(y))
    for j = 1:s.k
      Z = Z .+ S[j]*U[:,j]*V[:,j]'
    end
    v = Z
end

function project(s::Lowrank{<:Real}, y::Matrix{<:Real})
    project!(s, similar(y), y)
end


module PolynomialBasis

using LinearAlgebra
import DynamicPolynomials
import StaticPolynomials

DP = DynamicPolynomials
SP = StaticPolynomials

include("lagrange_polynomials.jl")
include("hermite_polynomials.jl")
include("common_definitions.jl")
include("lagrange_basis.jl")
include("hermite_basis.jl")
include("lagrange_tensor_product_basis.jl")
include("hermite_tensor_product_basis.jl")
include("interpolation.jl")

export LagrangeBasis, CubicHermiteBasis, LagrangeTensorProductBasis,
        HermiteTensorProductBasis, gradient, hessian,
        InterpolatingPolynomial, update!, interpolation_points, basis,
        number_of_basis_functions

end # module


# Bayesian probit model with a Normal prior N(0, priorstd^2*I). The log-posterior and its gradient, as well as the
# metric tensor and its partial derivatives are passed to the model as functions

using Distributions, MCMC

# Create design matrix X and response variable y from vaso data array
vaso = readdlm(joinpath(dirname(@__FILE__),"vaso.txt"), ' ')
covariates, y = vaso[:, 1:end-1], vaso[:, end]
nsamples, npars = size(covariates)
covariates = (broadcast(-, covariates, mean(covariates, 1))./repmat(std(covariates, 1), nsamples, 1))
polynomialOrder = 1
X = ones(nsamples, npars*polynomialOrder+1)
for i = 1:polynomialOrder
  X[:, ((i-1)*npars+2):(i*npars+1)] = covariates.^i
end
npars += 1

# Define log-prior
priorstd = 10.
priorvar = priorstd^2
distribution = MvNormal(zeros(size(X, 2)), priorvar*eye(npars))
logprior(pars::Vector{Float64}) = logpdf(distribution, pars)
randprior() = rand(distribution)

# Define log-likelihood
function loglik(pars::Vector{Float64})
  XPars = X*pars
  normal = Normal()
  return (dot(logcdf(normal, XPars), y)+dot(logcdf(normal, -XPars), 1-y))
end

# Define log-posterior
log_posterior(pars::Vector{Float64}) = logprior(pars)+loglik(pars)

# Define gradient of log-posterior
function grad_log_posterior(pars::Vector{Float64})
  XPars = X*pars
  normal = Normal()
  return (X'*(y.*exp(-(XPars.^2+log(2*pi))/2-logcdf(normal, XPars))
    -(1-y).*exp(-(XPars.^2+log(2*pi))/2-logcdf(normal, -XPars)))-pars/priorvar)
end

# Define metric tensor
function tensor(pars::Vector{Float64})
  XPars = X*pars
  normal = Normal()
  vector = exp(-XPars.^2-logcdf(normal, XPars)-logcdf(normal, -XPars)-log(2*pi))
  return ((X'.*repmat(vector', npars, 1))*X+(eye(npars)/priorvar))
end

# Define derivatives of metric tensor
function deriv_tensor(pars::Vector{Float64})
  output = Array(Float64, npars, npars, npars)

  XPars = X*pars
  normal = Normal()
  vector01 = exp(-XPars.^2-2*logcdf(normal, XPars)-logcdf(normal, -XPars)-log(2*pi))

  for i = 1:npars
    vector02 = (vector01.*(exp(-(XPars.^2+log(2*pi))/2-logcdf(normal, -XPars))
      -2*(pdf(normal, XPars)+XPars.*cdf(normal, XPars))).*X[:, i])

    output[:, :, i] = (X'.*repmat(vector02', npars, 1))*X
  end

  return output
end

mcmodel = model(log_posterior, grad=grad_log_posterior, tensor=tensor, dtensor=deriv_tensor, init=randprior())

mcchain01 = run(mcmodel * RWM(0.5) * SerialMC(1001:10000))

acceptance(mcchain01)

mcchain02 = run(mcmodel * HMC(0.001) * SerialMC(1001:10000))

acceptance(mcchain02)

mcchain03 = run(mcmodel * RMHMC(0.5, EmpMCTuner(0.8, verbose=true)) * SerialMC(5001:10000))

acceptance(mcchain03)


using LinearAlgebra
using Printf

function exp_lanczos!(A::L,
                      v::AbstractVector,
                      τ::T, m::Integer,
                      vp::AbstractVector,
                      V::AbstractMatrix,
                      α::Vector{R},
                      β::Vector{R},
                      sub_v::Vector{T},
                      d_sub_v::AbstractVector,
                      sw::stegr_work;
                      atol::R = 1.0e-8,
                      rtol::R = 1.0e-4,
                      verbose::Bool = false) where {L<:LinearMap, T<:Number, R<:Real}
    β₀ = norm(v)
    copyto!(view(V,:,1), v)
    lmul!(one(T)/β₀, view(V,:,1))

    ε = atol + rtol * β₀
    verbose && @printf("Initial norm: β₀ %e, stopping threshold: %e\n", β₀, ε)

    j = 1
    jj = 1 # Which Krylov subspace to use, in the end

    for j = 1:m
        x,y = view(V,:,j),view(V,:,j+1)
        mul!(y,A,x)
        α[j] = real(dot(x,y))
        j > 1 && (y .-= β[j-1]*view(V,:,j-1))
        y .-= α[j]*x
        β[j] = norm(y)
        lmul!(one(T)/β[j], y)

        expT(view(α, 1:jj), view(β, 1:jj-1), τ, view(sub_v, 1:j), sw)
        σ = β[j]*abs(sub_v[j])
        verbose && @printf("iter %d, α[%d] %e, β[%d] %e, σ %e\n",j, j, α[j], j, β[j], σ)

        if σ < ε
            break
        else
            j != m && (jj += 1)
        end
    end
    verbose && println("Krylov subspace size: ", jj)
    copyto!(d_sub_v, sub_v)
    vp[:] = β₀*view(V,:,1:jj)*view(d_sub_v, 1:jj)
end

mutable struct LanczosExponentiator{T<:AbstractFloat,U<:Number} <: KrylovExponentiator
    m::Integer
    V::AbstractMatrix
    α::Vector{T}
    β::Vector{T}
    sub_v::Vector{U}
    d_sub_v::AbstractVector
    sw::stegr_work{T}
    atol::T
    rtol::T
    verbose::Bool
end
function LanczosExponentiator(m::Integer, v::AbstractVector;
                              atol::Float64 = 1.0e-8,
                              rtol::Float64 = 1.0e-4,
                              verbose::Bool = false)
    U = eltype(v)
    T = real(U)
    N = length(v)
    V = similar(v, U, N, m + 1)
    α = Array{real(U)}(undef,m)
    β = Array{real(U)}(undef,m)
    sub_v = Array{U}(undef,m)
    d_sub_v = similar(v, U, m)
    LanczosExponentiator(m, V, α, β, sub_v, d_sub_v,
                         stegr_work(real(U), LinearAlgebra.BlasInt(m)),
                         T(atol), T(rtol), verbose)
end

(LE::LanczosExponentiator{T,U})(Ω::L, τ::U,
                                v::AbstractVector, w::AbstractVector) where {L<:LinearMap, T<:AbstractFloat,U<:Number} =
                                    exp_lanczos!(Ω, v, τ, LE.m, w,
                                                 LE.V, LE.α, LE.β,
                                                 LE.sub_v, LE.d_sub_v,
                                                 LE.sw;
                                                 atol = LE.atol,
                                                 rtol = LE.rtol,
                                                 verbose = LE.verbose)

export LanczosExponentiator


# Example functions from Dietmar Ratz - An Optimized Interval Slope Arithmetic and its Application

dr1(x) = (x + sin(x)) * exp(-x^2)
dr2(x) = x^4 - 10x^3 + 35x^2 - 50x + 24
dr3(x) = (log(x + 1.25) - 0.84x) ^ 2
dr4(x) = 0.02x^2 - 0.03exp(-(20(x - 0.875))^2)
dr5(x) = exp(x^2)
dr6(x) = x^4 - 12x^3 + 47x^2 - 60x - 20exp(-x)
dr7(x) = x^6 - 15x^4 + 27x^2 + 250
dr8(x) = atan(cos(tan(x)))
dr9(x) = asin(cos(acos(sin(x))))

dr_functions = [dr1, dr2, dr3, dr4, dr5, dr6, dr7, dr8, dr9]


@testset "Finite differences" begin
    f = reshape(collect(1.0:25),5,5)
    @testset "Gradients" begin
        outputx = zeros(5,5)
        outputy = zeros(5,5)
        @testset "Simple" begin
            Swalbe.∇f!(outputx, outputy, f)
            @test isa(outputx, Array)
            @test isa(outputy, Array)
            #=
            Analytical results, tested with DiffEqOperators
            Dx = CenteredDifference{1}(1,2,1.0,5)
            Dy = CenteredDifference{2}(1,2,1.0,5)
            fpad = padarray(f, Pad(:circular, 1, 1))
            mul!(solx, Dx, fpad)
            mul!(soly, Dy, fpad)
            =#
            solx = [-1.5 -1.5 -1.5 -1.5 -1.5;
                     1.0 1.0 1.0 1.0 1.0;
                     1.0 1.0 1.0 1.0 1.0;
                     1.0 1.0 1.0 1.0 1.0;
                    -1.5 -1.5 -1.5 -1.5 -1.5]

            soly = [-7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5]
            # Test them
            @test all(outputx .== solx)
            @test all(outputy .== soly)
        end
        @testset "Four Arguments" begin
            a = fill(0.1, 5,5)
            Swalbe.∇f!(outputx, outputy, f, a)
            @test isa(outputx, Array)
            @test isa(outputy, Array)
            # Analytical results
            solx = [-1.5 -1.5 -1.5 -1.5 -1.5;
                     1.0 1.0 1.0 1.0 1.0;
                     1.0 1.0 1.0 1.0 1.0;
                     1.0 1.0 1.0 1.0 1.0;
                    -1.5 -1.5 -1.5 -1.5 -1.5]

            soly = [-7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5]
            # Test them
            @test all(outputx .== 0.1 .* solx)
            @test all(outputy .== 0.1 .* soly)
        end
        @testset "Five Arguments" begin
            a = fill(0.1, 5,5)
            b = zeros(5,5,8)
            Swalbe.∇f!(outputx, outputy, f, b, a)
            @test isa(outputx, Array)
            @test isa(outputy, Array)
            # Analytical results
            solx = [-1.5 -1.5 -1.5 -1.5 -1.5;
                     1.0 1.0 1.0 1.0 1.0;
                     1.0 1.0 1.0 1.0 1.0;
                     1.0 1.0 1.0 1.0 1.0;
                    -1.5 -1.5 -1.5 -1.5 -1.5]

            soly = [-7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5;
                    -7.5 5.0 5.0 5.0 -7.5]
            # Test them
            @test all(outputx .== 0.1 .* solx)
            @test all(outputy .== 0.1 .* soly)
        end
    end

    f1 = collect(1.0:25)
    @testset "Gradients 1D" begin
        output = zeros(25)
        dummy = zeros(25,2)
        @testset "Simple" begin
            Swalbe.∇f!(output, f1, dummy, ones(25))
            @test isa(output, Vector)

            sol = ones(25)
            sol[1] = -11.5
            sol[end] = -11.5
            # Test them
            @test all(output .== sol)

            Swalbe.∇f!(output, f1, dummy)
            @test isa(output, Vector)

            sol = ones(25)
            sol[1] = -11.5
            sol[end] = -11.5
            # Test them
            @test all(output .== sol)
        end

    end

    @testset "Laplacian" begin
        output = zeros(5,5)
        γ = -1.0
        Swalbe.∇²f!(output, f, γ)
        @test isa(output, Array)
        #=
        Analytical results, tested with DiffEqOperators
        Dxx = CenteredDifference{1}(2,2,1.0,5)
        Dyy = CenteredDifference{2}(2,2,1.0,5)
        fpad = padarray(f, Pad(:circular, 1, 1))
        A = Dxx + Dyy
        mul!(sol, A, fpad)
        =#
        sol = [-30.0 -5.0 -5.0 -5.0 20;
               -25.0 0.0 0.0 0.0 25.0;
               -25.0 0.0 0.0 0.0 25.0;
               -25.0 0.0 0.0 0.0 25.0;
               -20.0 5.0 5.0 5.0 30.0];

        @test all(isapprox.(output, sol; atol=1e-10))
    end

    @testset "Laplacian 1D" begin
    output = zeros(25)
    dummy = zeros(25,2)
    input = collect(1:25.0)
    Swalbe.∇²f!(output, input, dummy)
    @test isa(output, Vector)

    sol = zeros(25)
    sol[1] = 25;
    sol[end] = -25;

    @test all(output .== sol)

end

    @testset "Viewneighbors" begin
        f = reshape(collect(1.0:5*5*8),5,5,8)
        f1, f2, f3, f4, f5, f6, f7, f8 = Swalbe.viewneighbors(f)
        allviews = [f1, f2, f3, f4, f5, f6, f7, f8]
        for (index, value) in enumerate(allviews)
            @test all(value .== f[:,:,index])
        end
    end

    @testset "Viewneighbors 1D" begin
        f = reshape(collect(1.0:20),10,2)
        f1, f2 = Swalbe.viewneighbors_1D(f)
        allviews = [f1, f2]
        for (index, value) in enumerate(allviews)
            @test all(value .== f[:,index])
        end
    end
end


using ValidatedNumerics

using Test

@testset "Load and briefly test packages" begin

    @testset "IntervalArithmetic" begin
        @test 1..2 == Interval(1, 2)
    end

    @testset "IntervalRootFinding" begin
        roots = IntervalRootFinding.roots(x->x^2 - 2, -10..10)
        @test length(roots) == 2
    end

    @testset "IntervalConstraintProgramming" begin
        S = @constraint x^2 + y^2 <= 1
        X = (-∞..∞) × (-∞..∞)
        paving = pave(S, X, 1.0)

        @test length(paving.inner) == 3
        @test length(paving.boundary) == 7
    end

    @testset "IntervalContractors" begin
        X = 1..4
        Y = 5..10

        Z = X + Y

        Z = Z ∩ (13..15)

        Z, X, Y = plus_rev(Z, X, Y)

        @test X == 3..4
        @test Y == 9..10
    end

    @testset "IntervalOptimisation" begin
        f(x) = x^4 - 3x^3 + 2x

        globalmin, minimisers = minimise(f, -1e10..1e10, tol=1e-5)

        @test globalmin ⊆ (-4.15.. -4.14)
    end

    @testset "TaylorModels" begin
        x0 = Interval(0.0)
        ii0 = Interval(-0.5, 0.5)

        tpol = exp( Taylor1(2) )
        @test TaylorModels.bound_taylor1( tpol, ii0) == tpol(ii0.lo) .. tpol(ii0.hi)
    end
end


include("quadrature.jl")
# forward recurrence
I1 = log(3.0/2.0)
for n in 1:100
    global I1 = 1.0/n - 2.0*I1
end
println(I)

# backward recurrence
I1 = 1.0/200
for n in 200:-1:101
    global I1 = 1.0/(2.0*n) - I1/2
end
println(I1)

# check result
f(x) = x^100/(x+2)
q = ctr(f)
println(q)


#   Unit examples of the FourierAnalysis Package for julia language
#   v 0.0.1 - last update 24th of September 2019
#
#   MIT License
#   Copyright (c) 2019, Marco Congedo, CNRS, Grenobe, France:
#   https://sites.google.com/site/marcocongedo/home

# ? CONTENTS :
#   This example shows how to compute cross-spectra
#   and how to extract inforation from them.

#   ~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~  #
#                                                                             #
#   ~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~~¤~  #

using FourierAnalysis, FFTW, LinearAlgebra, Statistics, Plots, Plots.Measures

# add module for reading the two EEG text files to be used ater
push!(LOAD_PATH, @__DIR__)
using IOtxt

# Get EEG file names with complete path (they have extension .txt)
S=getFilesInDir(@__DIR__; ext=(".txt",))

# read the two EEG data files and put them in a Matrix object
X1=readEEG(S[1])
X2=readEEG(S[2])

# Cross-Spectra of EEG data
##########################################

t, sr, slide, tapering = 512, 128, 64, harris4

# gather some attributes to obtain nice spectra plots
spectraArgs=(left_margin   = 2mm,
             bottom_margin = 2mm,
             xtickfont     = font(10, "Times"),
             ytickfont     = font(10, "Times"))

# spectra
S=spectra(X1, sr, t; tapering=tapering, func=√)
plot(S; ytitle="Amplitude (\\muV)", spectraArgs...)
# smoothed spectra
S2=spectra(X1, sr, t; tapering=tapering, smoothing=blackmanSmoother, func=√)
plot(S2; ytitle="Amplitude (\\muV)", spectraArgs...)

#cross-spectra
𝙎=crossSpectra(X1, sr, t; tapering=tapering, tril=true)
# smooth a-posteriori the cross-spectra
𝙎2=smooth(blackmanSmoother, 𝙎)

# mean cross-spectra in 8Hz-12Hz range
alpha=mean(𝙎, (8, 12))

# extract all cross-spectra in 8Hz-12Hz range
E=extract(𝙎, (8, 12))

# mean smoothed cross-spectra in 8Hz-12Hz range
alpha=mean(𝙎2, (8, 12))

# extract smoothed cross-spectra in 8Hz-12Hz range
E=extract(𝙎2, (8, 12))

# get amplitude spectra from cross-spectra and compare with S (the long way)
S__=sqrt.(Real.([𝙎.y[i][j, j] for i=1:length(𝙎.y), j=1:size(X1, 2)]))
# the short way using FourierAnalysis
S_=Spectra(𝙎; func=√)

# check they are the same
norm(S.y-S_.y)

# cross-spectra of several data matrix at once
t, sr, slide, tapering = 1024, 128, 512, harris4
𝐗=[X1, X2]

# Compute the cross-spectra altogether
𝓢=crossSpectra(𝐗, sr, t; tapering=slepians(sr, t))

# mean cross-spectrum in 8Hz-12Hz range for each cross-spectra in 𝓢
alpha=mean(𝓢, (8, 12))
# mean of the above cross-spectra in one pass
alpha=mean(mean(𝓢, (8, 12)))
# the above computation without checking for homogeneity of elements in 𝓢 (faster)
alpha=mean(mean(𝓢, (8, 12), check=false))

# extract cross-spectra in 8Hz-12Hz range for all cross-spectra in 𝓢
E=extract(𝓢, (8, 12))

# average cross-spectra in 8Hz-12Hz range for 𝓢[1] only
Y=mean(𝓢[1], (8, 12))

# This (8 is the frequency in Hz)
Y=mean(𝓢[1], 8)
# is equivalent to this (64 is the bin where discrete Fourier Frequency 8Hz is)
𝓢[1].y[64]
# and equivalent to this
Y=extract(𝓢[1], 8)
# and equivalent to this
Y=extract(𝓢[1], (8, 8))

# Compute the lower-trianguler part of the cross-spectra
𝓢=crossSpectra(𝐗, sr, t; tapering=slepians(sr, t), tril=true)

# average cross-spectra in 8Hz-12Hz range for all 𝕃Vector in 𝓢
Y=mean(𝓢, (8, 12))

# extract cross-spectra in 8Hz-12Hz range for all 𝕃Vector in 𝓢
E=extract(𝓢, (8, 12))

# cross-spectra 𝓢[1] averaged in 1Hz band-pass regions
Z=bands(𝓢[1], 1)

# all cross-spectra in 𝓢 averaged in 1Hz band-pass regions
Z=bands(𝓢, 1)

# get amplitude spectra from cross-spectra (the long way)
S_=sqrt.(Real.([𝓢[1].y[i][j, j] for i=1:length(𝓢[1].y), j=1:size(X1, 2)]))*1.5687746
plot(S_)

# the short way using FourierAnalysis
S_sl=Spectra(𝓢[1]; func=x->sqrt(x)*1.5687746)
norm(S_sl.y-S_) # must be zero

# Compute non-linear cross-spectra
𝓢=crossSpectra(𝐗, sr, t; tapering=slepians(sr, t), tril=true, nonlinear=true)


using Distributions
using Optim

function readAndProcCoinData(filename)
 data = readdlm(filename)
 ndata = length(data)
 heads = zeros(ndata)
 for i=1:ndata
   heads[i] = length(matchall(r"H", data[i]))
 end
 return int64(heads)
end

function sigmoid(x)
 return 1.0./(1.0.+exp(-x))
end

function loglik(x)
 dists = Any[]
 theta = x[1:4]
 cs = x[5:end]
 ntheta = length(theta)
 N = 50
 for i=1:ntheta
   push!(dists, Binomial(N, sigmoid(theta[i])))
 end

 cs = reshape(cs, nprocdata, 4)
 cs = sigmoid(cs)
 cs = cs ./ sum(cs,2)

 ll = 0
 for k=1:length(procdata)
 for i=1:ntheta
   ll += logpdf(dists[i], procdata[k])*cs[k,i]
 end
 end
 ll *= -1
end

function getMeSomeMLE(procdata, theta)
 optimize(loglik, theta, show_trace=true, method = :bfgs, ftol = 1e-12, iterations=5000)

end

# read in and process the data
procdata = readAndProcCoinData("coindata.txt")
nprocdata = length(procdata)

# optimize and return the maximum likelihood estimates
theta = rand(4)
cs = rand(nprocdata*4)
x = [theta; cs]
results = getMeSomeMLE(procdata, x)
x_opt = results.minimum
theta_opt = x_opt[1:4]
cs_opt = x_opt[5:end]
println("Maximum Likelihood Estimates: ", sigmoid(theta_opt))


cₚ = 1004.703 # Specific Heat at constant pressure
kₜ = 0.0246295028571 #Thermal conductivity
k_cyl = kₜ * 4
kᵦ = 1.38064852e-23 # Boltzmann constant (m² kg/(s² K))

density = 0.000210322
R = kᵦ * 6.0221409e23 # kg⋅m²/(s²*K*mol)
mol_mass = 28.96 # g/mol
μ = 28.96 / 6.0221409e23 # mean molecular mass (g)
R₀ = R / (mol_mass / 1000)

# Heat diffusion constant in fluid
k₁ = kₜ / (density * cₚ)

# Heat diffusion constant in cylinder
k₂ = k_cyl / (density * cₚ)

kᵨ = 1e-3

ν = 1.716e-5 # 0.0005081150545826

kᵥ = ν# / density

γ = 1 + R₀ / (cₚ - R₀) # Mayer's formula + definition of adiabatic constant
println("γ: $γ")
# T = (γ - 1) * (mol_mass / 1000) / R₀ * e
#e2t = (μ/1000) / kᵦ
#e2t = (γ - 1) * mol_mass / R₀
e2t = 1/(cₚ * density)
@show e2t
e2p = e2t * R₀
#e2p = (γ - 1)
t2e = 1/e2t


module IndexingTests

using Test
using InplaceArrays.TensorValues
using StaticArrays

a = (3,4,5,1)

v = VectorValue{4}(a)

@test eltype(v) == Int
@test eltype(typeof(v)) == Int

@test size(v) == (4,)
@test length(v) == 4

for (k,i) in enumerate(eachindex(v))
  @test v[i] == a[k]
end

t = TensorValue{2}(a)

@test size(t) == (2,2)
@test length(t) == 4

for (k,i) in enumerate(eachindex(t))
  @test t[i] == a[k]
end

@test t[2,1] == 4

@test t[2] == 4

for (k,ti) in enumerate(t)
  @test ti == a[k]
end

v = @SMatrix zeros(2,3)
w = MultiValue(v)
@test CartesianIndices(w) == CartesianIndices(v)
@test LinearIndices(w) == LinearIndices(v)

end # module IndexingTests


using DiscreteElasticRods
using Test
include("unit_tests.jl")
include("gradient_tests.jl")


# Davis, Yin. "A Three-Operator Splitting Scheme and its Optimization
# Applications", Set-Valued and Variational Analysis, vol. 25, no. 4,
# pp. 829–858 (2017).

using Printf
using ProximalCore: Zero
using LinearAlgebra
using Printf

"""
    DavisYinIteration(; <keyword-arguments>)

Iterator implementing the Davis-Yin splitting algorithm [1].

This iterator solves convex optimization problems of the form

    minimize f(x) + g(x) + h(x),

where `f` is smooth.

See also [`DavisYin`](@ref).

# Arguments
- `x0`: initial point.
- `f=Zero()`: smooth objective term.
- `g=Zero()`: proximable objective term.
- `h=Zero()`: proximable objective term.
- `Lf=nothing`: Lipschitz constant of the gradient of h.
- `gamma=nothing`: stepsize to use, defaults to `1/Lf` if not set (but `Lf` is).

# References
1. Davis, Yin. "A Three-Operator Splitting Scheme and its Optimization Applications", Set-Valued and Variational Analysis, vol. 25, no. 4, pp. 829-858 (2017).
"""
Base.@kwdef struct DavisYinIteration{R,C<:Union{R,Complex{R}},T<:AbstractArray{C},Tf,Tg,Th}
    f::Tf = Zero()
    g::Tg = Zero()
    h::Th = Zero()
    x0::T
    lambda::R = real(eltype(x0))(1)
    Lf::Maybe{R} = nothing
    gamma::Maybe{R} = Lf !== nothing ? (1 / Lf) : error("You must specify either Lf or gamma")
end

Base.IteratorSize(::Type{<:DavisYinIteration}) = Base.IsInfinite()

struct DavisYinState{T}
    z::T
    xg::T
    grad_f_xg::T
    z_half::T
    xh::T
    res::T
end

function Base.iterate(iter::DavisYinIteration)
    z = copy(iter.x0)
    xg, = prox(iter.g, z, iter.gamma)
    grad_f_xg, = gradient(iter.f, xg)
    z_half = 2 .* xg .- z .- iter.gamma .* grad_f_xg
    xh, = prox(iter.h, z_half, iter.gamma)
    res = xh - xg
    z .+= iter.lambda .* res
    state = DavisYinState(z, xg, grad_f_xg, z_half, xh, res)
    return state, state
end

function Base.iterate(iter::DavisYinIteration, state::DavisYinState)
    prox!(state.xg, iter.g, state.z, iter.gamma)
    gradient!(state.grad_f_xg, iter.f, state.xg)
    state.z_half .= 2 .* state.xg .- state.z .- iter.gamma .* state.grad_f_xg
    prox!(state.xh, iter.h, state.z_half, iter.gamma)
    state.res .= state.xh .- state.xg
    state.z .+= iter.lambda .* state.res
    return state, state
end

default_stopping_criterion(tol, ::DavisYinIteration, state::DavisYinState) = norm(state.res, Inf) <= tol
default_solution(::DavisYinIteration, state::DavisYinState) = state.xh
default_display(it, ::DavisYinIteration, state::DavisYinState) = @printf("%5d | %.3e\n", it, norm(state.res, Inf))

"""
    DavisYin(; <keyword-arguments>)

Constructs the Davis-Yin splitting algorithm [1].

This algorithm solves convex optimization problems of the form

    minimize f(x) + g(x) + h(x),

where `f` is smooth.

The returned object has type `IterativeAlgorithm{DavisYinIteration}`,
and can be called with the problem's arguments to trigger its solution.

See also: [`DavisYinIteration`](@ref), [`IterativeAlgorithm`](@ref).

# Arguments
- `maxit::Int=10_000`: maximum number of iteration
- `tol::1e-8`: tolerance for the default stopping criterion
- `stop::Function`: termination condition, `stop(::T, state)` should return `true` when to stop the iteration
- `solution::Function`: solution mapping, `solution(::T, state)` should return the identified solution
- `verbose::Bool=false`: whether the algorithm state should be displayed
- `freq::Int=100`: every how many iterations to display the algorithm state
- `display::Function`: display function, `display(::Int, ::T, state)` should display a summary of the iteration state
- `kwargs...`: additional keyword arguments to pass on to the `DavisYinIteration` constructor upon call

# References
1. Davis, Yin. "A Three-Operator Splitting Scheme and its Optimization Applications", Set-Valued and Variational Analysis, vol. 25, no. 4, pp. 829–858 (2017).
"""
DavisYin(;
    maxit=10_000,
    tol=1e-8,
    stop=(iter, state) -> default_stopping_criterion(tol, iter, state),
    solution=default_solution,
    verbose=false,
    freq=100,
    display=default_display,
    kwargs...
) = IterativeAlgorithm(DavisYinIteration; maxit, stop, solution, verbose, freq, display, kwargs...)


function dual_cost(
    rng::Random.AbstractRNG, c, v, μ::DiscreteMeasure, ν::DiscreteMeasure, ε; kwargs...
)
    # compute mean c-transform
    mean_ctransform = sum(p * ctransform(c, v, x, ν, ε) for (x, p) in zip(μ.xs, μ.ps))

    return LinearAlgebra.dot(v, ν.ps) + mean_ctransform
end

function dual_v(
    rng::Random.AbstractRNG,
    c,
    μ::DiscreteMeasure,
    ν::DiscreteMeasure,
    ε;
    stepsize=1,
    maxiters::Int=10_000,
    atol=0,
    rtol=iszero(atol) ? typeof(float(atol))(1//10_000) : 0,
)
    # initial iterates
    k = 1
    sampler = Random.Sampler(rng, 1:length(μ))
    i = rand(rng, sampler)
    d = gradient_step(c, μ.ps[i], ν, μ.xs[i], ε)
    G = zeros(eltype(d), length(d), length(μ))
    copyto!(view(G, :, i), d)

    # initial dual solution
    v = stepsize * d

    # error estimates
    Δv = similar(v)
    errors = @. Δv / (atol + rtol * abs(v))

    converged = false
    while !converged && k < maxiters
        # update iterates
        k += 1
        i = rand(rng, sampler)
        g = view(G, :, i)
        d .-= g
        gradient_step!(c, g, μ.ps[i], v, ν, μ.xs[i], Δv, ε; reset=true)
        d .+= g

        # estimate error
        LinearAlgebra.mul!(Δv, stepsize, d)
        @. errors = Δv / (atol + rtol * abs(v))
        converged = maximum(abs, errors) < 1

        # update dual solution
        v .+= Δv
    end

    if k == maxiters && !converged
        @warn "Optimization algorithm did not converge. Try to adjust the parameters."
    end

    return v
end


fuel(mass::Real) = max(0, fld(mass, 3) - 2)

const input = map(x->parse(Int, x), eachline("input"))

println("Part 1 answer: ", sum(fuel, input))

function totfuel(mass::Real)
    total = 0
    while mass > 0
        mass = fuel(mass)
        total += mass
    end
    total
end

println("Part 2 answer: ", sum(totfuel, input))