@testset "Modularity" begin

    # 1. undirected test cases
    n = 10
    m = n * (n - 1) / 2
    c = ones(Int, n)
    gint = CompleteGraph(n)
    for g in testgraphs(gint)
      @test @inferred(modularity(g, c)) == 0
    end

    gint = SimpleGraph(n)
    for g in testgraphs(gint)
      @test @inferred(modularity(g, c)) == 0
    end

    barbell = blockdiag(CompleteGraph(3), CompleteGraph(3))
    add_edge!(barbell, 1, 4)
    c = [1, 1, 1, 2, 2, 2]

    for g in testgraphs(barbell)
        Q1 = @inferred(modularity(g, c))
        @test isapprox(Q1, 0.35714285714285715, atol=1e-3)
        Q2 = @inferred(modularity(g, c, γ=0.5))
        @test isapprox(Q2, 0.6071428571428571, atol=1e-3)
    end

    # 2. directed test cases
    triangle = SimpleDiGraph(3)
    add_edge!(triangle, 1, 2)
    add_edge!(triangle, 2, 3)
    add_edge!(triangle, 3, 1)

    barbell = blockdiag(triangle, triangle)
    add_edge!(barbell, 1, 4)
    c = [1, 1, 1, 2, 2, 2]

    for g in testdigraphs(barbell)
        Q1 = @inferred(modularity(g, c))
        @test isapprox(Q1, 0.3673469387755103, atol=1e-3)

        Q2 = @inferred(modularity(barbell, c, γ=0.5))
        @test isapprox(Q2, 0.6122448979591837, atol=1e-3)
    end

    add_edge!(barbell, 4, 1)
    for g in testdigraphs(barbell)
        @test @inferred(modularity(g, c)) == 0.25
    end

    # 3. weighted test cases
    # 3.1. undirected and weighted test cases
    triangle = SimpleGraph(3)
    add_edge!(triangle, 1, 2)
    add_edge!(triangle, 2, 3)
    add_edge!(triangle, 3, 1)

    barbell = blockdiag(triangle, triangle)
    add_edge!(barbell, 1, 4) # this edge has a weight of 5
    c = [1, 1, 1, 2, 2, 2]
    d = [[0  1  1  5  0  0]
         [1  0  1  0  0  0]
         [1  1  0  0  0  0]
         [5  0  0  0  1  1]
         [0  0  0  1  0  1]
         [0  0  0  1  1  0]]
    for g in testgraphs(barbell)
        Q = @inferred(modularity(g, c, distmx=d))
        @test isapprox(Q, 0.045454545454545456, atol=1e-3)
    end

    # 3.2. directed and weighted test cases
    triangle = SimpleDiGraph(3)
    add_edge!(triangle, 1, 2)
    add_edge!(triangle, 2, 3)
    add_edge!(triangle, 3, 1)

    barbell = blockdiag(triangle, triangle)
    add_edge!(barbell, 1, 4) # this edge has a weight of 5
    c = [1, 1, 1, 2, 2, 2]
    d = [[0  1  0  5  0  0]
         [0  0  1  0  0  0]
         [1  0  0  0  0  0]
         [0  0  0  0  1  0]
         [0  0  0  0  0  1]
         [0  0  0  1  0  0]]
    for g in testdigraphs(barbell)
        Q = @inferred(modularity(g, c, distmx=d))
        @test isapprox(Q, 0.1487603305785124, atol=1e-3)
    end
end


using CompScienceMeshes

h = 1/6
rect = meshrectangle(1.0, 1.0, h, 3)
hole = meshrectangle(1/3, 1/3, h, 3)
translate!(hole, point(1/3, 1/3, 0))

pred = overlap_gpredicate(hole)
all_faces = submesh(c->!pred(chart(rect,c)), rect)

all_edges = skeleton(all_faces, 1)
all_verts = skeleton(all_faces, 0)

bnd_edges = boundary(all_faces)
bnd_verts = skeleton(bnd_edges, 0)

not(f) = x -> !f(x...)

onbnd1 = overlap_tpredicate(bnd_edges)
onbnd0 = overlap_tpredicate(bnd_verts)

interior_edges = submesh(not(onbnd1), all_edges)
interior_verts = submesh(not(onbnd0), all_verts)

@show numcells(interior_edges)
@show numcells(interior_verts)

D0 = connectivity(interior_verts, interior_edges)
D1 = connectivity(interior_edges, all_faces)

nullity(A) = size(A,2) - rank(A')
genus = nullity(full(D1)) - rank(full(D0))


using Distributions, MCMC, ZVSimulator

function C(n::Int, c::Float64)
  X = eye(n)
  [(j <= n-i) ? X[i+j, i] = X[i, i+j] = c^j : nothing for i = 1:(n-1), j = 1:(n-1)]
  X
end

df, npars, a = 5., 4, 0.25
c = ((df-2)/df)*C(npars, a)

mcmodel = model(MvTDist(df, zeros(npars), c), init=zeros(npars))
samplers = [HMCDA() for i in 1:5]
tasks = mcmodel * samplers * SerialMC(steps=10000, burnin=1000)

results = psim_serialmc_vrf(tasks)

# writedlm_vrf(results, "./", ' ')
# writedlm_reformat("_means.txt", "./_means_pprint.txt", fill("%.3e", 3))


module Optimization

using ..Diagram
using ..Fortune
using ..Intersect

pointsₖ = nothing # temporary points array for backtracking

function norm2(v)
   sum = 0

   for vᵢ in v
      sum += vᵢ[1]^2 + vᵢ[2]^2
   end

   return sum
end

function normInf(v)
   max = 0

   for vᵢ in v
      if (vᵢ[1]^2) > max
         max = vᵢ[1]^2
      end
      if (vᵢ[2]^2) > max
         max = vᵢ[2]^2
      end
   end

   return max
end

function errorInTriangle(z, a, b)
   p = [a[1] - z[1], a[2] - z[2]]
   q = [b[1] - z[1], b[2] - z[2]]

   jacobian = abs(p[1]q[2] - p[2]q[1])

   # error = c∫∫x² + y² dudv, see the monograph appendix for a derivation
   # u and v are variables such that Triangle(x,y) -> Triangle(u,v) right triangle with catheti of size 1
   # c is just so that I can take the common factor 1/12 out of both integrals and multiply once

   c∫x²dudv = p[1]^2 + q[1]^2 + p[1]*q[1]
   c∫y²dudv = p[2]^2 + q[2]^2 + p[2]*q[2]

   return jacobian * ((c∫x²dudv + c∫y²dudv) / 12) # testing with area
end

function errorInRegion(region::Diagram.Region)
   z = region.generator

   edge = region.borderHead

   sum = errorInTriangle(z, edge.origin, edge.next.origin)
   edge = edge.next
   while edge != region.borderHead
      sum += errorInTriangle(z, edge.origin, edge.next.origin)
      edge = edge.next
   end

   return sum
end

function defaultf(V::Diagram.DCEL)
   sum = 0
   for region in V.regions
      sum += errorInRegion(region)
   end

   return sum
end

function default∇f(V::Diagram.DCEL, points::Array{Tuple{Real, Real}, 1})
   function weightedDifference(a::Array{Tuple{Real, Real}, 1}, b::Array{Tuple{Real, Real}, 1}, c::Array{Real, 1})
      r = Array{Tuple{Real, Real}}([])

      for i in 1:size(a)[1]
         push!(r, (c[i]*(a[i][1] - b[i][1]), c[i]*(a[i][2] - b[i][2])))
      end

      return r
   end

   centroids, areas = Diagram.centroidsAndAreas(V)

   return weightedDifference(points, centroids, areas)
end

function modf(V::Diagram.DCEL)
   function g(V)
   end

   return defaultf(V) + g(V)
end

function mod∇f(V::Diagram.DCEL, points::Array{Tuple{Real, Real}, 1})
   function ∇g(V, points)
   end

   return default∇f(V, points) + ∇g(V, points)
end

function f(V::Diagram.DCEL)
   return defaultf(V)
end

function ∇f(V::Diagram.DCEL, points::Array{Tuple{Real, Real}, 1})
   return default∇f(V, points)
end

function init(points::Array{Tuple{Real, Real}, 1}, w::Number, h::Number)
   global WIDTH, HEIGHT, pointsₖ

   WIDTH = w
   HEIGHT = h
   pointsₖ = deepcopy(points)

   V = Fortune.compute(points, w, h)
   Intersect.intersect(V, Intersect.Rectangle(WIDTH, HEIGHT))

   f = Optimization.f(V)
   ∇f = Optimization.∇f(V, points)
   ξ = Optimization.normInf(∇f)

   return V, f, ∇f, ξ
end

function lloydIteration(points::Array{Tuple{Real, Real}, 1}, V::Diagram.DCEL)
   points, areas = Diagram.centroidsAndAreas(V)

   V = Fortune.compute(points)
   Intersect.intersect(V, Intersect.Rectangle(WIDTH, HEIGHT))

   f = Optimization.f(V)
   ∇f = Optimization.∇f(V, points)
   ξ = Optimization.normInf(∇f)

   return V, f, ξ
end

function lineSearch(points::Array{Tuple{Real, Real}, 1}, d::Array{Tuple{Real, Real}, 1}, α::Number, λ₀::Number, μ::Number, ϵ::Number, V::Diagram.DCEL, f::Number, ∇f::Array{Tuple{Real, Real}, 1}, ξ)
   global WIDTH, HEIGHT, pointsₖ

   evaluations = 0

   n = size(points)[1]

   # backtracking
   λ = λ₀
   while true
      outOfBounds = false

      for i in 1:n
         x = points[i][1] - λ*d[i][1]
         y = points[i][2] - λ*d[i][2]

         if !(0 <= x <= WIDTH && 0 <= y <= HEIGHT)
            outOfBounds = true

            break
         end

         pointsₖ[i] = (x, y)
      end

      dᵗ∇f = 0
      if outOfBounds
         fₖ = Inf
         ∇fₖ = Inf
         ξₖ = Inf
         V = nothing
      else
         Vₖ = Fortune.compute(pointsₖ)
         Intersect.intersect(Vₖ, Intersect.Rectangle(WIDTH, HEIGHT))
         evaluations += 1

         fₖ = Optimization.f(Vₖ)
         ∇fₖ = Optimization.∇f(Vₖ, pointsₖ)
         ξₖ = Optimization.normInf(∇fₖ)

         for i in 1:n
            dᵗ∇f += d[i][1]*∇f[i][1]
            dᵗ∇f += d[i][2]*∇f[i][2]
         end
      end

      #println("fₖ = ", fₖ, "\t\tf = ", f, "\t\tλ = ", λ, "\t\tξₖ = ", ξₖ)
      # armijo's condition || failsafes
      if fₖ < f - α*λ*dᵗ∇f || ξₖ <= ϵ || λ == 0
         for i in 1:n
            points[i] = pointsₖ[i]
         end

         V = Vₖ
         f = fₖ
         ∇f = ∇fₖ
         ξ = ξₖ
         break
      else
         old = λ
         λ *= μ
         if old == λ
            ξ = ϵ
            break
         end
      end
   end

   return V, f, ∇f, ξ, evaluations
end

end # module


# Advent of Code 2020 - Day 25

test_list= split("5764801
17807724", '\n')

list = split("11239946
10464955", '\n')

list2 = split("11239946
10464955", '\n')

function loop(subject, value = 1)
    return rem(value*subject, 20201227)
end

function get_size(public_key)
    loop_size = 0
    value = 1
    while value != public_key
        loop_size += 1
        value = loop(7, value)
    end
    return loop_size
end

function transform(public_key, loop_size)
    value = 1
    for n = 1:loop_size
        value = loop(public_key, value)
    end
    return value
end

function get_encryptionkey(list)
    card_public_key, door_public_key = parse.(Int, list)
    card_loop_size = get_size(card_public_key)
    door_loop_size = get_size(door_public_key)
    min_loop = argmin([card_loop_size, door_loop_size])
    other_public = min_loop == 1 ? door_public_key : card_public_key
    loop_size = min_loop == 1 ? card_loop_size : door_loop_size
    key = transform(other_public, loop_size)
    return key
end

@show get_encryptionkey(test_list) == 14897079
@show get_encryptionkey(list) == 711945

nothing


using Symbolics

nx = 6
nu = 1
@variables δx[1:nx] δu[1:nu] Bf[1:nx*nu]

function cost(δx, δu, Bf)
    B = reshape(Bf, nx, nu)
    return 1/2 * (δx - B*δu)' * (δx - B*δu)
end

c = cost(δx, δu, Bf)
dc = Symbolics.gradient(c, Bf)
ddc = Symbolics.hessian(c, Bf)

c_fct = eval(Symbolics.build_function([c], δx, δu, Bf)[1])
dc_fct = eval(Symbolics.build_function(dc, δx, δu, Bf)[1])
ddc_fct = eval(Symbolics.build_function(ddc, δx, δu, Bf)[1])

δx0 = rand(nx)
δu0 = rand(nu)
Bf0 = rand(nx*nu)

c_fct(δx0, δu0, Bf0)
dc_fct(δx0, δu0, Bf0)
ddc_fct(δx0, δu0, Bf0)

function leastsquares(δx, δu)
    N = length(δx)
    nx = length(δx[1])
    nu = length(δu[1])
    Bf = zeros(nx*nu)
    e = 0.0
    grad = zeros(nx*nu)
    hess = zeros(nx*nu, nx*nu)
    for i = 1:N
        e += c_fct(δx[i], δu[i], Bf)[1]
        grad += dc_fct(δx[i], δu[i], Bf)
        hess += ddc_fct(δx[i], δu[i], Bf)
    end
    Bf = Bf - hess \ grad
    B = reshape(Bf, (nx, nu))
    return B
end


## rotation + dilation
N = 256; iter = 100000
θx = 2π*rand(N)
rx = sqrt.(rand(N))
x = polar2cart(rx, θx)
disc_plot(x)

# 1. Learning steps
α = 0.01
anim = @animate for n in 1:iter
    global α
    if n > .99*iter
        α -= 0.02/(.01*iter)
    end
    # update y
    global θx, rx, x
    θ = 2π*rand()
    d = (rand()-0.5)*2*log(16)
    θy = (θx .+ θ .+ (d < 0)*π) .% 2π
    ry = abs.(d) .* rx
    y = polar2cart(ry, θy)
    # find Λⱼ(n)
    nnx = Dict()
    for j in 1:N
        nnx[j] = []
    end
    for i in 1:N
        if ry[i] > 1
            continue
        end
        arr = [norm(x[j,:] - y[i,:]) for j in 1:N]
        min_val = minimum(arr)
        min_ind = findall(arr .== min_val)
        for j in min_ind
            nnx[j] = push!(nnx[j], i)
        end
    end
    for j in 1:N
        if nnx[j] != []
            x[j,:] += α * sum([y[i,:]-x[j,:] for i in nnx[j]])
        end
    end
    rx, θx = cart2polar(x)
    rx = min.(rx, 1)
    x = polar2cart(rx, θx)
end

plt = disc_plot(x)
savefig(plt, "figs/selfSimNN_2D_rotdil_100000final.png")


## Iteration utilities

## some algorithms are easier to write using this neat
## package. However, for complicated usage, it proved
## to have issues with recursive usage and precompilation
using ResumableFunctions

# iterator over tuples of length n that sum to tot all non-negative
# used in non-commutative matching algorithm
@resumable function fixed_sum(n, tot, ps=())
    if n == 0
        @yield ()
        return
#    n > tot && return ()
    ## for all (k1, k2, ..., kseq) st sum(ks) = nfree
    elseif n == 1
        @yield (tot, ps...)
    else
        for i = 0:tot
            for v in collect(fixed_sum(n-1, tot-i, tuple(i, ps...)))
                @yield v
            end
        end
    end
end

# This comes from https://github.com/HPAC/matchpy/blob/master/matchpy/utils.py
# MIT licensed
# solve ax + by = c
# XXX: return (-1,-1) for nothing?
@resumable function base_solution_linear(a,b,c)

    @assert a > 0 && b > 0 && c >= 0

    NO_SOL = nothing

    d = gcd(a,b,c)
    a,b,c = div.((a,b,c), d)

    if c == 0
        @yield (0,0)
    else
        d, x0, y0 = gcdx(a, b)  # d = x0*a + y0*b

        !iszero(rem(c,d)) && return NO_SOL

        x, y = c*x0, c*y0

        if x <= 0
            while y >= 0
                if x >= 0
                    @yield (x,y)
                end
                x += b
                y -= a
            end
        else
            while x >=0
                if y >= 0
                    @yield (x,y)
                end
                x -= b
                y += a
            end
        end
    end
    NO_SOL
end

## solve x1*a + x2*b + ... = c for non-negative solutions
@resumable function solve_linear_diophantine(c, xs)
    NO_SOL = nothing

    n = length(xs)
    n == 0 && return NO_SOL
    if n == 1
        d,r = divrem(c, xs[1])
        r == 0 && @yield (d,)
        return NO_SOL
    end
    if n == 2
        u = base_solution_linear(xs[1], xs[2], c)
        for v in u
            @yield v
        end
        return NO_SOL
    end

    d = gcd(xs...)

    # solve xs[1] * x + d * y = c
    for val in base_solution_linear(xs[1], d, c)
        nxs = div.(xs[2:end], d)
        x, y = val
        for val in solve_linear_diophantine(y, nxs)
            @yield tuple(x, val...)
        end
    end

    NO_SOL
end

# trim iterator to yield only unique values
@resumable function just_unique(itr)
    theta = Any[]
    for i in itr
        if !(i in theta)
            @yield i
            push!(theta, i)
        end
    end
end

# Product generator
# for i in g1, j in g2, k in g3 ...
# do something with (i,j,k,...)
# end
# This is Iterators.product, but that was failing or some
# resumable functions
function gen_product(gens...)
    if length(gens) == 1
        [(i,) for i in collect(gens[1])] # not just collect(gens[1])
    elseif length(gens) == 2
        [tuple(g,h) for g in collect(gens[1]), h in gens[2]]
    else
        return [tuple(g, h...) for h in gen_product(gens[2:end]...), g in collect(gens[1])]
    end
end

## Create generator to cover case of
## for i in f1()
##    for j in f2(i)
##       for k in f3(j) ...
##
## Uses a backtracking algorithm
mutable struct GeneratorChain
fs
init
gs
sts
vals
end

function generator_chain(init, fs...)
    GeneratorChain(fs, init, Any[], Any[], Any[])
end

Base.iteratorsize(itertype::GeneratorChain) = Base.SizeUnknown()
function Base.start(o::GeneratorChain)

    g1 = o.fs[1](o.init...)
    st =start(g1)

    empty!(o.gs)
    empty!(o.sts)
    empty!(o.vals)
    push!(o.gs, g1)
    push!(o.sts, st)
    st
end

# advance to next tip
function Base.done(o::GeneratorChain, st)
    # is vals empty? If not, pop!
    !isempty(o.vals) && return false

    # okay, work back down
    while done(o.gs[end], o.sts[end])
        pop!(o.gs)
        pop!(o.sts)
        isempty(o.gs) && return true
    end

    # now back up
    i, n = length(o.gs), length(o.fs)

    val, o.sts[end] = next(o.gs[end], o.sts[end])
    for fn in o.fs[(i+1):n]
        gi = fn(val)
        st = start(gi)
        done(gi, st) && return done(o, st)
        val, st = next(gi, st)
        push!(o.gs, gi)
        push!(o.sts, st)
    end

    push!(o.vals, val)
    return false
end

function Base.next(o::GeneratorChain, st)
    pop!(o.vals), st
end


"""
Extract the principal component time series (PCs)
"""
function pcs(
    eof::EmpiricalOrthogonalFunction;
    n::Union{Nothing,Int} = nothing,
    scaling::Symbol = :none,
)
    if isnothing(n)
        n = length(eof.eigenvals)
    end

    out = eof.pcs[:, 1:n]

    if scaling == :divide
        # Divide by the square-root of the eigenvalue.
        for i = 1:n
            out[:, i] = out[:, i] ./ sqrt(eof.eigenvals[i])
        end
    elseif scaling == :multiply
        # Multiply by the square root of the eigenvalue.
        for i = 1:n
            out[:, i] = out[:, i] .* sqrt(eof.eigenvals[i])
        end
    elseif scaling != :none
        @error(
            "Could not understand scaling option. Select either :none, :divide, or :multiply"
        )
        return
    end

    return out
end

"""
Extract the empirical orthogonal functions (EOFs)
"""
function eofs(
    eof::EmpiricalOrthogonalFunction;
    n::Union{Nothing,Int} = nothing,
    scaling::Symbol = :none,
)
    if isnothing(n)
        n = length(eof.eigenvals)
    end

    out = eof.eofs[:, 1:n]

    if scaling == :divide
        # Divide by the square-root of the eigenvalue.
        for i = 1:n
            out[:, i] = out[:, i] ./ sqrt(eof.eigenvals[i])
        end
    elseif scaling == :multiply
        # Multiply by the square root of the eigenvalue.
        for i = 1:n
            out[:, i] = out[:, i] .* sqrt(eof.eigenvals[i])
        end
    elseif scaling != :none
        @error(
            "Could not understand scaling option. Select either :none, :divide, or :multiply"
        )
        return
    end

    return out
end

"""
Empirical orthogonal functions (EOFs) expressed as the
correlation between the principal component time series (PCs)
and the time series of the `eof` input *dataset* at each grid
point.
"""
function correlationmap(
    eof::EmpiricalOrthogonalFunction;
    outshape::Union{Nothing,Tuple{Int,Int}} = nothing,
    n::Union{Nothing,Int} = nothing,
)

    p = pcs(eof, n = n, scaling = :divide)

    p_center = Statistics.mean(p, dims = 2)

    pcs_cent = p .- p_center
    field_cent = eof.dataset

    pcs_std = Statistics.std(pcs_cent, dims = 1)
    field_std = field_std = Statistics.std(field_cent, dims = 1)

    div = size(pcs_cent, 1)

    cor = transpose(field_cent' * pcs_cent) / div

    cor = cor ./ (pcs_std' * field_std)

    if !isnothing(outshape)
        cor = reshape(cor, (n, outshape...))
    end

    return cor
end

"""
Empirical orthogonal functions (EOFs) expressed as the
covariance between the principal component time series (PCs)
and the time series of the `eof` input *dataset* at each grid
point.
"""
function covariancemap(
    eof::EmpiricalOrthogonalFunction;
    outshape::Union{Nothing,Tuple{Int,Int}} = nothing,
    n::Union{Nothing,Int} = nothing,
    ddof::Int = 1,
    scaling = :none,
)
    p = pcs(eof, n = n, scaling = scaling)

    p_center = Statistics.mean(p, dims = 2)

    pcs_cent = p .- p_center
    field_cent = eof.dataset

    div = size(pcs_cent, 1) - ddof

    cor = transpose(field_cent' * pcs_cent) / div

    if !isnothing(outshape)
        cor = reshape(cor, (n, outshape...))
    end

    return cor
end

"""
Extract the eigenvalues (decreasing variances) associated with each EOF.
"""
function eigenvalues(eof::EmpiricalOrthogonalFunction; n::Union{Nothing,Int} = nothing)
    if isnothing(n)
        n = length(eof.eigenvals)
    end
    return eof.eigenvals[1:n]
end

"""
Fractional EOF mode variances.
The fraction of the total variance explained by each EOF mode,
values between 0 and 1 inclusive.
"""
function variancefraction(
    eof::EmpiricalOrthogonalFunction;
    n::Union{Nothing,Int} = nothing,
)
    return eigenvalues(eof; n = n) / totalanomalyvar(eof)
end

"""
Total variance associated with the field of anomalies (the sum
of the eigenvalues).
"""
function totalanomalyvar(eof::EmpiricalOrthogonalFunction)
    return sum(eof.eigenvals)
end

"""
The method of North et al. (1982) is used to compute the typical
error for each eigenvalue. It is assumed that the number of
times in the input data set is the same as the number of
independent realizations. If this assumption is not valid then
the result may be inappropriate.
"""
function northtest(
    eof::EmpiricalOrthogonalFunction;
    n::Union{Nothing,Int} = nothing,
    scaled::Bool = false,
)

    nrecords = size(eof.dataset, 1)
    factor = sqrt(2.0 / nrecords)

    if scaled
        factor /= totalanomalyvar(eof)
    end

    return eigenvalues(eof; n = n) * factor
end

"""
Project a field onto the EOFs.
Given a data set, projects it onto the EOFs to generate a
corresponding set of pseudo-PCs.
"""
function projectfield(eof_arr::Array{<:Any,2}, field::Array{<:Any,2})
    # Project the data set onto the EOFs using a matrix multiplication.
    return field * eof_arr
end

function projectfield(
    eof::EmpiricalOrthogonalFunction,
    field::Array{<:Any,2};
    n::Union{Nothing,Int} = nothing,
    scaling::Symbol = :none,
)

    # extract out eofs for projection
    eofs_arr = eofs(eof, n = n, scaling = scaling)

    # check to make sure all data with valid index in EOF is valid in the input field
    @assert all(!ismissing(field[:, eof.valididx]))

    return projectfield(eofs_arr, field)
end

function projectfield(
    eof::EmpiricalOrthogonalFunction,
    field::Array{<:Any,3};
    n::Union{Nothing,Int} = nothing,
    scaling::Symbol = :none,
    timedim::Int = 3,
)

    geodims = [x for x in 1:3 if x != timedim]
    shape = size(dataset)
    spatial_shape = shape[geodims]
    time_shape = shape[timedim]

    if timedim == 2
        field = permutedims(field, (2, 1, 3))
    elseif timedim == 3
        field = permutedims(flattened, (3, 1, 2))
    end

    flat_field = reshape(field, (time_shape, prod(spatial_shape)))

    projected_pcs = projectfield(eof, flat_field; n = n, scaling = scaling)

    # reshape spatial field back to the original
    return reshape(projected_pcs, (spatial_shape..., time_shape))

end

"""
Reconstructed input data field based on a subset of EOFs.
"""
function reconstruct(
    eof::EmpiricalOrthogonalFunction;
    n::Union{Nothing,Int} = nothing,
    uncenter::Bool = true,
)

    eofs_arr = eofs(eof, n = n)
    ps = pcs(eof, n = n)

    rval = (ps * eofs_arr')

    if uncenter
        rval = rval .+ eof.center
    end

    return permutedims(rval, (2, 1))
end

function orthorotation(
    components::Array{<:Any,2};
    method::Symbol = :varimax,
    maxiter::Int = 50,
    tol::Float64 = 1e-6,
)
    nrows, ncols = size(components)

    rotation_matrix = I(ncols)
    var = 0
    i = 0

    for _ = 1:maxiter
        comp_rot = components * rotation_matrix
        if method == :varimax
            tmp = comp_rot * transpose(sum((comp_rot .^ 2), dims = 1) / nrows)
        elseif method == :quartimax
            tmp = 0
        end
        x = @. comp_rot^3 - tmp
        u, s, v = svd(transpose(components) * x)
        rotation_matrix = u * v
        var_new = sum(s)
        if var != 0 && var_new < var * (1 + tol)
            break
        end
        var = var_new
        i += 1
    end

    if i == maxiter
        @warn(
            "matrix rotation did not converge, try specifying a larger max iteration value for stable result"
        )
    end

    return components * rotation_matrix
end

"""
Apply orthogonal rotation to EOF data.
After rotation the original dataset will be projected on the rotated EOF
to create new PCs. Additionally new EOFs and PCs are ordered in decreasing
variance
"""
function orthorotation(
    eof::EmpiricalOrthogonalFunction;
    n::Union{Nothing,Int} = nothing,
    method::Symbol = :varimax,
    maxiter::Int = 50,
    tol::Float64 = 1e-6,
)
    components = eofs(eof, n = n)

    valididx = eof.valididx

    reofs = Array{Union{Missing,Float64}}(missing, size(components)...)
    reofs[valididx, :] = orthorotation(
        components[valididx, :];
        method = method,
        maxiter = maxiter,
        tol = tol,
    )

    projected_pcs = projectfield(reofs[valididx, :], eof.dataset[:, valididx])

    rotatedvar = Statistics.var(projected_pcs, dims = 1)[1, :]
    rotatedvarnorm =
        (rotatedvar ./ sum(rotatedvar)) .* sum(variancefraction(eof, n = n))

    sortedidx = sortperm(rotatedvarnorm)[end:-1:1]
    rotatedvar = rotatedvar[sortedidx]

    sorted_reofs = reofs[:, sortedidx]
    sorted_pcs = projected_pcs[:, sortedidx]

    return EmpiricalOrthogonalFunction(
        eof.dataset,
        eof.center,
        eof.valididx,
        sorted_reofs,
        rotatedvar,
        sorted_pcs,
    )
end

"""
Inplace orthorotation that will override the data structure of the input EOF object
"""
function orthorotation!(
    eof::EmpiricalOrthogonalFunction;
    n::Union{Nothing,Int} = nothing,
    method::Symbol = :varimax,
    maxiter::Int = 50,
    tol::Float64 = 1e-6,
)
    if isnothing(n)
        n = length(eof.eigenvals)
    else
        truncate!(eof, n)
    end

    valididx = eof.valididx

    eof.eofs[valididx, :] = orthorotation(
        eof.eofs[valididx, :];
        method = method,
        maxiter = maxiter,
        tol = tol,
    )

    eof.pcs[:, :] =
        projectfield(eof.eofs[valididx, :], eof.dataset[:, valididx])

    rotatedvar = Statistics.var(eof.pcs, dims = 1)[1, :]
    rotatedvarnorm =
        rotatedvarnorm =
            (rotatedvar ./ sum(rotatedvar)) .* sum(variancefraction(eof))

    sortedidx = sortperm(rotatedvarnorm)[end:-1:1]
    eof.eigenvals[:] = rotatedvar[sortedidx]

    eof.eofs[:, :] = eof.eofs[:, sortedidx]
    eof.pcs[:, :] = eof.pcs[:, sortedidx]

    return
end

"""
Truncate the EOF data structure to trim unneccesay modes
"""
function truncate(eof::EmpiricalOrthogonalFunction, n::Int)
    pcs_trunc = eof.pcs[:, 1:n]
    eofs_trunc = eof.eofs[:, 1:n]
    ev_trunc = eof.eigenvals[1:n]

    return EmpiricalOrthogonalFunction(
        eof.dataset,
        eof.center,
        eof.valididx,
        eofs_trunc,
        ev_trunc,
        pcs_trunc,
    )
end

"""
Apply truncate inplace on EOF data structure
"""
function truncate!(eof::EmpiricalOrthogonalFunction, n::Int)
    eof.pcs = eof.pcs[:, 1:n]
    eof.eofs = eof.eofs[:, 1:n]
    eof.eigenvals = eof.eigenvals[1:n]
    return
end


export NwtdirectionSpectral, NewtonSpectralAbs

function NwtdirectionSpectral(H, ∇f; scale_abs :: Bool = true)
    Δ, O = eigen(H)
    # Boost negative values of Δ to 1e-8
    # devise an adaptative value for γ
    γ = 1e-6
    n = length(Δ)
    if scale_abs
        #D = abs.(Δ) + max.((γ .- abs.(Δ)), 0.0) .*ones(n)
        D = max.(abs.(Δ), γ)
    else
        D = max.(Δ, γ)
    end
    d = - O*diagm(1.0 ./ D)*O'*∇f
    #Δ = ones(g)
    #V = ones(H)
    #try
    #    Δ, V = eig(H)
    #catch
    #    Δ, V = eig(H + eye(H))
    #end
    #ϵ2 =  1.0e-8
    #Γ = 1.0 ./ max.(abs.(Δ),ϵ2)

    #d = - (V * diagm(Γ) * V') * (g)
    return d
end

function NewtonSpectralAbs(nlp :: AbstractNLPModel;
                           x :: Vector{T}=copy(nlp.meta.x0),
                           stp :: NLPStopping = NLPStopping(nlp,
                                                            NLPAtX(nlp.meta.x0)),
                           kwargs...) where T
    return  Newton_G_Stop(nlp;
                   x = x,
                   stp = stp,
                   NwtDirection = NwtdirectionSpectral,
                   hessian_rep = hessian_dense,
                   kwargs...)
end

function NewtonSpectralAbsLS(nlp :: AbstractNLPModel;
                               x :: Vector{T}=copy(nlp.meta.x0),
                               stp :: NLPStopping = NLPStopping(nlp,
                                                                NLPAtX(nlp.meta.x0)),
                               LS_algo   :: Function = bracket,
                               LS_logger :: AbstractLogger = Logging.NullLogger(),
                               kwargs...) where T
      return  Newton_G_StopLS(nlp;
                              x = x,
                              stp = stp,
                              LS_algo = LS_algo,
                              LS_logger = LS_logger,
                              NwtDirection = NwtdirectionSpectral,
                              hessian_rep = hessian_dense,
                              kwargs...)
  end


for N in [Float64, Rational{Int}, Float32]
    # ==================================
    # Constructor and interface methods
    # ==================================

    B = BallInf(zeros(N, 3), N(1))
    v = N[1, 0, 0] # translation along dimension 1
    tr = Translation(B, v)

    # test alternative constructors
    @test tr == B + v == B ⊕ v
    # check that translation from the left works as well
    @test tr == v + B == v ⊕ B

    # dimension check
    @test_throws AssertionError Translation(B, N[0, 0])

    @test dim(tr) == 3

    # support vector
    @test σ(N[1, 0, 0], tr) == N[2, 1, 1]

    # support function
    @test ρ(N[1, 0, 0], tr) == N(2)

    # boundedness
    @test isbounded(tr)
    @test !isbounded(Translation(Universe{N}(3), v))

    if N ∈ [Float64]
        @test !isbounded(Translation(HPolyhedron([HalfSpace(N[1, 1], N(1))]), N[0, 0]))
    end

    # function to get an element
    @test an_element(tr) ∈ tr.X

    # emptiness check
    @test !isempty(tr)

    # set membership
    B = Ball1(zeros(N, 2), N(1))
    @test center(B) ∈ B ⊕ zeros(N, 2)

    # concrete linear map
    B = BallInf(ones(N, 2), N(1))
    v = N[1, 0]
    tr = B ⊕ v
    clm = linear_map(N[1 2; 0 1], tr)
    @test ispermutation(vertices_list(clm), [N[5, 2], [1, 0], [3, 0], [7, 2]])

    # concretize
    @test concretize(tr) == translate(B, v)

    # ==================================
    # Type-specific methods
    # ==================================

    # the translation is the origin => constraints remain unchanged
    constraints_list(B ⊕ zeros(N, 2)) == constraints_list(tr.X)

    # one-dimensional case: translation to the left
    Tleft = Translation(HalfSpace(N[1], N(1)), [N(-1)])
    @test constraints_list(Tleft)[1] == HalfSpace(N[1], N(0))

    # one-dimensional case: translation to the right
    Tright = Translation(HalfSpace(N[1], N(1)), [N(1)])
    @test constraints_list(Tright)[1] == HalfSpace(N[1], N(2))

    # the translation of a lazy linear map returns an affine map
    M = N[1 0; 0 2]; B = BallInf(zeros(N, 2), N(1)); v = N[1, 0]
    tr = M * B ⊕ v
    @test tr isa AffineMap && tr.M == M && tr.X == B && tr.v == v
end

for N in [Float64, Float32]
    # translation of a set not represented by a finite number of constraints
    tr = Ball2(zeros(N, 2), N(1)) ⊕ N[1, 0]
    @test ρ(N[1, 0], tr) == N(2)
    @test_throws AssertionError constraints_list(tr)
end


@testset "Discrete identity system" begin
    for sd in 1:3
        s = DiscreteIdentitySystem(sd)
        @test statedim(s) == sd
        @test inputdim(s) == 0
        @test noisedim(s) == 0
        @test islinear(s) && isaffine(s) && !ispolynomial(s)
        @test !isnoisy(s) && !iscontrolled(s) && !isconstrained(s)
    end
end

@testset "Discrete constrained identity system" begin
    for sd in 1:3
        X = Singleton(ones(sd))
        s = ConstrainedDiscreteIdentitySystem(sd, X)
        @test statedim(s) == sd
        @test inputdim(s) == 0
        @test noisedim(s) == 0
        @test stateset(s) == X
        @test islinear(s) && isaffine(s) && !ispolynomial(s)
        @test !isnoisy(s) && !iscontrolled(s) && isconstrained(s)
    end
end

@testset "Discrete linear system" begin
    for sd in 1:3
        s = LinearDiscreteSystem(zeros(sd, sd))
        @test statedim(s) == sd
        @test inputdim(s) == 0
        @test noisedim(s) == 0
        @test islinear(s) && isaffine(s) && !ispolynomial(s)
        @test !isnoisy(s) && !iscontrolled(s) && !isconstrained(s)
    end
end

@testset "Discrete affine system" begin
    for sd in 1:3
        s = AffineDiscreteSystem(zeros(sd, sd), zeros(sd))
        @test statedim(s) == sd
        @test inputdim(s) == 0
        @test noisedim(s) == 0
        @test !islinear(s) && isaffine(s) && !ispolynomial(s)
        @test !isnoisy(s) && !iscontrolled(s) && !isconstrained(s)
    end
end

@testset "Discrete linear control system" begin
    for sd in 1:3
        s = LinearControlDiscreteSystem(zeros(sd, sd), ones(sd, sd))
        @test statedim(s) == sd
        @test inputdim(s) == sd
        @test noisedim(s) == 0
        @test islinear(s) && isaffine(s) && !ispolynomial(s)
        @test !isnoisy(s) && iscontrolled(s) && !isconstrained(s)
    end
end

@testset "Discrete constrained linear system" begin
    A = [1. 1; 1 -1]
    X = Line([1., -1], 0.) # line x = y
    s = ConstrainedLinearDiscreteSystem(A, X)
    @test statedim(s) == 2
    @test inputdim(s) == 0
    @test noisedim(s) == 0
    @test stateset(s) == X
    @test islinear(s) && isaffine(s) && !ispolynomial(s)
    @test !isnoisy(s) && !iscontrolled(s) && isconstrained(s)
end

@testset "Discrete constrained affine system" begin
    A = [1. 1; 1 -1]
    c = [1.; 1.]
    X = Line([1., -1], 0.) # line x = y
    s = ConstrainedAffineDiscreteSystem(A, c, X)
    @test statedim(s) == 2
    @test inputdim(s) == 0
    @test noisedim(s) == 0
    @test stateset(s) == X
    @test !islinear(s) && isaffine(s) && !ispolynomial(s)
    @test !isnoisy(s) && !iscontrolled(s) && isconstrained(s)
end

@testset "Discrete constrained linear control system" begin
    A = [1. 1; 1 -1]
    B = Matrix([0.5 1.5]')
    X = Line([1., -1], 0.)
    U = Interval(0.9, 1.1)
    s = ConstrainedLinearControlDiscreteSystem(A, B, X, U)
    @test statedim(s) == 2
    @test inputdim(s) == 1
    @test noisedim(s) == 0
    @test stateset(s) == X
    @test inputset(s) == U
    @test islinear(s) && isaffine(s) && !ispolynomial(s)
    @test !isnoisy(s) && iscontrolled(s) && isconstrained(s)
end

@testset "Discrete linear algebraic system" begin
    for sd in 1:3
        s = LinearAlgebraicDiscreteSystem(zeros(sd, sd), zeros(sd, sd))
        @test statedim(s) == sd
        @test inputdim(s) == 0
        @test noisedim(s) == 0
        @test islinear(s) && isaffine(s) && !ispolynomial(s)
        @test !isnoisy(s) && !iscontrolled(s) && !isconstrained(s)
    end
end

@testset "Discrete constrained linear algebraic system" begin
    A = [1. 1; 1 -1]
    E = [0. 1; 1 0]
    X = LinearConstraint([0, -1.], 0.) # the set y ≥ 0
    s = ConstrainedLinearAlgebraicDiscreteSystem(A, E, X)
    @test statedim(s) == 2
    @test inputdim(s) == 0
    @test noisedim(s) == 0
    @test stateset(s) == X
    @test islinear(s) && isaffine(s) && !ispolynomial(s)
    @test !isnoisy(s) && !iscontrolled(s) && isconstrained(s)
end

@testset "Polynomial system in discrete time" begin
    @polyvar x y
    p = 2x^2 - 3x + y
    s = PolynomialDiscreteSystem(p)
    @test statedim(s) == 2
    @test inputdim(s) == 0
    @test noisedim(s) == 0
    @test !islinear(s) && !isaffine(s) && ispolynomial(s)
    @test !isnoisy(s) && !iscontrolled(s) && !isconstrained(s)
end

@testset "Polynomial system in discrete time with state constraints" begin
    @polyvar x y
    p = 2x^2 - 3x + y
    X = BallInf(zeros(2), 0.1)
    s = ConstrainedPolynomialDiscreteSystem(p, X)
    @test statedim(s) == 2
    @test inputdim(s) == 0
    @test noisedim(s) == 0
    @test dim(stateset(s)) == dim(X)
    @test !islinear(s) && !isaffine(s) && ispolynomial(s)
    @test !isnoisy(s) && !iscontrolled(s) && isconstrained(s)
end

@testset "Implicit discrete system" begin
    add_one(x) = x + 1
    s = BlackBoxDiscreteSystem(add_one, 1)
    x = 1.0
    @test s.f(x) ≈ 2.0
    @test !islinear(s) && !isaffine(s) && !ispolynomial(s)
    @test !isnoisy(s) && !iscontrolled(s) && !isconstrained(s)
end

@testset "Noisy Discrete constrained linear system" begin
    A = [1. 1; 1 -1]
    D = [1. 2; 0 1]
    X = Line([1., -1], 0.) # line x = y
    W = BallInf(zeros(2), 2.0)
    s = NoisyConstrainedLinearDiscreteSystem(A, D, X, W)
    @test s.A == A
    @test s.D == D
    @test statedim(s) == 2
    @test inputdim(s) == 0
    @test noisedim(s) == 2 == dim(W)
    @test stateset(s) == X
    @test noiseset(s) == W
    @test islinear(s) && isaffine(s) && !ispolynomial(s)
    @test isnoisy(s) && !iscontrolled(s) && isconstrained(s)
end

@testset "Noisy Discrete constrained control linear system" begin
    A = [1. 1; 1 -1]
    B = Matrix([0.5 1.5]')
    D = [1. 2; 0 1]
    X = Line([1., -1], 0.) # line x = y
    U = Hyperrectangle(low=[0.9], high=[1.1])
    W = BallInf(zeros(2), 2.0)
    s = NoisyConstrainedLinearControlDiscreteSystem(A, B, D, X, U, W)
    @test s.A == A
    @test s.B == B
    @test s.D == D
    @test statedim(s) == 2
    @test inputdim(s) == dim(U)
    @test noisedim(s) == 2 == dim(W)
    @test stateset(s) == X
    @test inputset(s) == U
    @test noiseset(s) == W
    @test islinear(s) && isaffine(s) && !ispolynomial(s)
    @test isnoisy(s) && iscontrolled(s) && isconstrained(s)
end

@testset "Noisy Discrete constrained control affine system" begin
    A = [1. 1; 1 -1]
    B = Matrix([0.5 1.5]')
    c = [1.0, 0.5]
    D = [1. 2; 0 1]
    X = Line([1., -1], 0.) # line x = y
    U = Hyperrectangle(low=[0.9], high=[1.1])
    W = BallInf(zeros(2), 2.0)
    s = NoisyConstrainedAffineControlDiscreteSystem(A, B, c, D, X, U, W)
    @test s.A == A
    @test s.B == B
    @test s.c == c
    @test s.D == D
    @test statedim(s) == 2
    @test inputdim(s) == dim(U)
    @test noisedim(s) == 2 == dim(W)
    @test stateset(s) == X
    @test inputset(s) == U
    @test noiseset(s) == W
    @test isaffine(s) && !islinear(s) && !ispolynomial(s)
    @test isnoisy(s) && iscontrolled(s) && isconstrained(s)
end

@testset "Noisy Discrete constrained control blackbox system" begin
    n = 2
    m = 1
    l = 2
    f(x,u,w) = ones(n,n)*x + ones(n,m)*u + ones(n,l)*w
    X = BallInf(zeros(n), 1.0)
    U =  BallInf(zeros(m), 1.0)
    W =  BallInf(zeros(l), 1.0)
    s = NoisyConstrainedBlackBoxControlDiscreteSystem(f, n, m, l, X, U, W)
    @test s.f == f
    @test statedim(s) == n
    @test inputdim(s) == dim(U)
    @test noisedim(s) == l == dim(W)
    @test stateset(s) == X
    @test inputset(s) == U
    @test noiseset(s) == W
    @test !islinear(s) && !isaffine(s) && !ispolynomial(s)
    @test isnoisy(s) && iscontrolled(s) && isconstrained(s)
end

@testset "Constant input in a discrete constrained linear control system" begin
    A = [1. 1; 1 -1]
    B = Matrix([0.5 1.5]')
    X = Line([1., -1], 0.)
    U = ConstantInput(Hyperrectangle(low=[0.9], high=[1.1]))
    s = ConstrainedLinearControlDiscreteSystem(A, B, X, U)
    s = ConstrainedLinearControlDiscreteSystem(A, B, X, U)
    if VERSION < v"0.7-"
        @test next(s.U, 1)[1] isa Hyperrectangle
    else
        @test iterate(s.U)[1] isa Hyperrectangle
    end
end

@testset "Varying input in a discrete constrained linear control system" begin
    A = [1. 1; 1 -1]
    B = Matrix([0.5 1.5]')
    X = Line([1., -1], 0.)
    U = VaryingInput([Hyperrectangle(low=[0.9], high=[1.1]),
                      Hyperrectangle(low=[0.99], high=[1.0])])
    s = ConstrainedLinearControlDiscreteSystem(A, B, X, U)
    @test length(s.U) == 2
    for ui in s.U
        @test ui isa Hyperrectangle
    end
end


"""
Li, Yuhua, 2011.
Selecting training points for one-class support vector machines.
Pattern recognition letters 32.11: 1517-1522.

We dub the method boundary point selector (BPS).

Parameter guideline from the paper, given a data set of size N:
k = floor(10 ln N)
eps ∈ [0, 0.2]
"""
mutable struct BPS <: Sampler
    k::Union{Int, Nothing}
    eps::Real
    function BPS(k::Union{Int, Nothing}=nothing, eps::Real=0.2)
        isa(k, Int) && (k > 0 || throw(ArgumentError("Invalid number of neighbors to consider $(k).")))
        eps >= 0 || throw(ArgumentError("Invalid threshold $(eps)."))
        return new(k, eps)
    end
end

function sample(sampler::BPS, data::DataSet, labels::Vector{Symbol})::BitArray{1}
    if sampler.k === nothing
        sampler.k = round(Int, floor(10 * log(size(data, 2))))
    end
    sampler.k = min(size(data, 2) - 1, sampler.k)
    tree = KDTree(data)
    sample_mask = falses(size(data, 2))

    for i in 1:size(data, 2)
        idx, dist = knn(tree, data[:, i], sampler.k + 1, true)
        v_ij = mapslices(normalize, data[:, i] .- data[:, idx[2:end]], dims=1)
        n_i = sum(v_ij, dims=2)
        θ_ij = sum(v_ij .* n_i, dims=1)
        l_i = 1 / sampler.k * sum(θ_ij .>= 0)
        if l_i >= 1 - sampler.eps
            sample_mask[i] = true
        end
    end
    return sample_mask
end


using Plots
result_triangles=[[[0.0;0.0], [0.5;sqrt(3)/2],[1.0;0.0],[0.0;0.0]]]
for step in 1:5
    newOnes=[]
    for triangle in result_triangles
        first_triangle=[]
        second_triangle=[]
        third_triangle=[]
        for i in 1:4
            vertix=(triangle[i]-triangle[1])/2+triangle[1]
            push!(first_triangle, vertix)
        end
        for i in 1:4
            push!(second_triangle, first_triangle[i]+(first_triangle[2]-first_triangle[1]))
            push!(third_triangle, first_triangle[i]+(first_triangle[3]-first_triangle[1]))
        end
        push!(newOnes, first_triangle)
        push!(newOnes, second_triangle)
        push!(newOnes, third_triangle)
        #println(result_triangles)
    end
    result_triangles=newOnes
    #println(result_triangles)
end
plot()
for little_triangles in result_triangles
    #println(little_triangles)
    plot!(hcat(little_triangles...)[1,:] , hcat(little_triangles...)[2,:], fill=(0,0.5,"orange"), legend=false)
end
plot!()
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter2\\Fig\\SierpinskiTriangle.png")
# plot(hcat(first_triangle...)[1,:],hcat(first_triangle...)[2,:])
# plot!(hcat(second_triangle...)[1,:],hcat(second_triangle...)[2,:])
# plot!(hcat(third_triangle...)[1,:],hcat(third_triangle...)[2,:])


using Base.Test
using PValueAdjust

newPvalues = padjust([0.05, 0.03, 0.01, 0.5], Holm)
@test_approx_eq newPvalues [0.1, 0.09, 0.04, 0.5]

@test_approx_eq padjust([1, 0.5, 0.1, 0.001, 0.0001, 0.002], Holm) [1e+00, 1e+00, 3e-01, 5e-03, 6e-04, 8e-03]
@test_approx_eq padjust([0.05, 0.05, 0.05], Holm) [0.15, 0.15, 0.15]

@test all(padjust([1,1,1], Holm) .<= 1)

@test_throws DomainError padjust([-1], Holm)
@test_throws DomainError padjust([2], Holm)

@test padjust([0.5], Holm) == [0.5]


using OmegaGrav
using MatterPower
using Plots, LaTeXStrings
using Dierckx
using DelimitedFiles, Printf

# %% Cosmological parameters
As, ns, kpivot = 2.097e-9, 0.9652, 0.05
Ωm, ΩΛ, Ωb = 0.315, 0.685, 0.049
Ωk = 1 - Ωm - ΩΛ
h0 = 0.674
ωm, fb = Ωm * h0^2, Ωb / Ωm

# %% Loop over redshifts
redshift = [0, 0.3, 0.5, 0.7, 1, 1.3, 1.5]
Ωgrav_pklin = zeros(7)
Ωgrav_pknl = zeros(7)
Ωgrav_halo = zeros(7)
for ired = 1:7
   # %% Read in linear and non-linear power spectra computed by CLASS
   filename = @sprintf("data/matterpower05_z%1d_pk.dat", ired)
   d = readdlm(filename, comments = true)
   pk = Spline1D(d[:, 1], d[:, 2])
   filename = @sprintf("data/matterpower05_z%1d_pk_nl.dat", ired)
   d = readdlm(filename, comments = true)
   pknl = Spline1D(d[:, 1], d[:, 2])

   # %% Compute `Ωgrav = Ωm * W / 2`
   z = redshift[ired]
   println("redshift = ", z)
   # Linear matter power spectrum
   Ωgrav_pklin[ired] = ograv_pk(pk, z, Ωm)
   println("Ωgrav(linear pk) = ", Ωgrav_pklin[ired])
   # Non-linear matter power spectrum
   Ωgrav_pknl[ired] = ograv_pk(pknl, z, Ωm)
   println("Ωgrav(non-linear pk) = ", Ωgrav_pknl[ired])
   # Halos
   Ωgrav_halo[ired] = ograv_halo(pk, z, Ωm)
   println("Ωgrav(halos) = ", Ωgrav_halo[ired])
end

# %% Plot results and save to "omegagrav_class.pdf"
p = plot(
   redshift,
   -Ωgrav_pklin,
   xlab = "Redshift",
   ylab = L"-\Omega_{\rm grav}(z)~from~CLASS~P(k)",
   lab = "Linear P(k)",
   yaxis = :log10,
   legend = :bottomleft,
   ls = :dash,
   m = 2,
)
p = plot!(redshift, -Ωgrav_pknl, m = 2, lab = "Non-linear P(k)")
p = plot!(redshift, -Ωgrav_halo, m = 2, lab = "Halos")
savefig("omegagrav_class.pdf")
display(p)


using SDPSymmetryReduction
using LinearAlgebra
using Test

@testset "SDPSymmetryReduction.jl" begin

    @test iszero(SDPSymmetryReduction.roundToZero(1e-10))

    M = rand(1:10,10,10)
    @test SDPSymmetryReduction.part(M).n == length(unique(M))

    P1 = Partition(3, [1 2 2; 2 3 3; 2 3 3])
    P2 = Partition(3, [1 1 2; 1 1 2; 1 1 3])
    P3 = Partition(6, [1 2 4; 2 3 5; 2 3 6])
    @test SDPSymmetryReduction.coarsestPart(P1,P2).P == P3.P

    @test SDPSymmetryReduction.part(SDPSymmetryReduction.rndPart(P1)).P == P1.P

    M = rand(10,10)
    @test SDPSymmetryReduction.roundMat(M) ≈ M atol = 1e-4

    A = rand(9,3)
    M = rand(3,3)
    @test maximum(abs.(A \ vec(SDPSymmetryReduction.projectAndRound(M, A; round = false)))) ≈ 0.0 atol=1e-10

    T = M - SDPSymmetryReduction.projectAndRound(M, A; round = false)
    @test all(isapprox.(A*(A\vec(T)) - vec(T), 0, atol=1e-8))

    q = 7
    PG2q = vcat([[0, 0, 1]],
    [[0, 1, b] for b = 0:q-1],
    [[1, a, b] for a = 0:q-1 for b = 0:q-1])
    Adj = [x' * y % q == 0 && x != y for x in PG2q, y in PG2q]

    N = length(PG2q)
    C = ones(N^2)
    A = vcat(vec(Adj)', vec(Matrix{Float64}(I, N, N))')
    b = [0.0, 1.0]

    @test admPartSubspace(C, A, b, true).n == 18

    @test sort(blockDiagonalize(admPartSubspace(C, A, b, true), true).blkSizes) == [2,2,2,2,3]

    @test SDPSymmetryReduction.unSymmetrize(P1).P == Partition(4, [1 3 3; 2 4 4; 2 4 4]).P

    # complex block diagonalization tests
    P = Partition(4,[1 2 3 2; 2 1 2 3; 3 2 1 2; 2 3 2 1])
    @test blockDiagonalize(P; complex = true).blkSizes == [1,1,1]

    function failsBlockDiagonalize()
        try
            blockDiagonalize(P; complex = false).blkSizes == [1,1,1]
        catch
            return true
        end
        return false
    end

    @test failsBlockDiagonalize()
end


## First example block
using Manopt, Manifolds, LinearAlgebra, Random
Random.seed!(42)
M = Sphere(2)
n = 40
p = 1 / sqrt(3) .* ones(3)
B = DefaultOrthonormalBasis()
pts = [exp(M, p, get_vector(M, p, 0.425 * randn(2), B)) for _ in 1:n]

F(M, y) = sum(1 / (2 * n) * distance.(Ref(M), pts, Ref(y)) .^ 2)
gradF(M, y) = sum(1 / n * grad_distance.(Ref(M), pts, Ref(y)))

x_mean = gradient_descent(M, F, gradF, pts[1])

euclidean_mean = mean(pts)
print("Norm of Euclieadn mean:", norm(euclidean_mean), "\n\n")
euclidean_mean_normed = euclidean_mean / norm(euclidean_mean)

## Second example block
o = gradient_descent(
    M,
    F,
    gradF,
    pts[1];
    debug=[:Iteration, " | ", :Change, " | ", :Cost, "\n", :Stop],
    record=[:x, :Change, :Cost],
    return_options=true,
)
x_mean2 = get_solver_result(o)
values = get_record(o) # vector with a tuple per iteration
iterates = get_record(o, :Iteration, :x) # from each iteration get the recorded iterate x.

## Export with Paul Tols colors https://personal.sron.nl/~pault/
using Colors
TolVibrantOrange = RGBA{Float64}(colorant"#EE7733")
TolVibrantTeal = RGBA{Float64}(colorant"#009988")
TolVibrantCyan = RGBA{Float64}(colorant"#33BBEE")
asymptote_export_S2_signals(
    joinpath(@__DIR__, "img/MeanIllustr.asy");
    points=[[x_mean], [euclidean_mean_normed], pts],
    colors=Dict(:points => [TolVibrantOrange, TolVibrantCyan, TolVibrantTeal]),
    dot_sizes=[3.5, 2.5, 3.5],
    camera_position=(0.7, 0.7, 0.5),
)
render_asymptote(joinpath(@__DIR__, "img/MeanIllustr.asy"); render=4)  #src


"""
    scaled_LHC_sampling_plan(search_range::Array{Tuple{Float64,Float64},1},num_samples,sampling_plan_opt_gens;trace::Symbol=:silent)

Create a Latin Hypercube sampling plan scaled to the `search_range` where
`size(plan) = (num_dimensions,num_samples)`.
"""
function scaled_LHC_sampling_plan(search_range::Array{Tuple{Float64,Float64},1},num_samples,sampling_plan_opt_gens;trace::Symbol=:silent)

    #Printing of creation
    (trace == :verbose) && print_lhc_create()

    unscaled_plan, audze = LHCoptim(num_samples,length(search_range),sampling_plan_opt_gens)

    unscaled_plan = unscaled_plan'

    #Verbose printing of resulting plan
    (trace == :verbose) && print_lhc_plan(audze[end],num_samples,length(search_range),sampling_plan_opt_gens)

    plan = Array{eltype(eltype(search_range))}(undef,size(unscaled_plan))
    for (i,sr) in enumerate(search_range)
        plan[i,:] = scale(unscaled_plan[i,:],sr[1],sr[2])
    end
    return plan
end

function print_lhc_create()
    println("Creating optimized Latin Hypercube Sampling Plan")
end

function print_lhc_plan(audze,num_samples,dims,sampling_plan_opt_gens)
    println("    Optimised LHC plan summary")
    println(@sprintf("    %-15s %-40s %-10s %-0s", "Iterations","Audze–Eglais obj. (higher is better)","Samples","Dimensions"))
    println(@sprintf("    %-15.7g %-40.7g %-10.7g %-0.7g \n", sampling_plan_opt_gens,audze,num_samples,dims))
end


export
    ContinuedFraction, NonPeriodicContinuedFraction, PeriodicContinuedFraction,
    confrac,
    ContinuedFractionConvergentsIterator, NonPeriodicContinuedFractionConvergentsIterator, PeriodicContinuedFractionConvergentsIterator,
    convergents

abstract type ContinuedFraction{T<:Integer} end

@auto_hash_equals struct NonPeriodicContinuedFraction{T<:Integer} <: ContinuedFraction{T}
    denominators::Array{T,1}
end

@auto_hash_equals struct PeriodicContinuedFraction{T<:Integer} <: ContinuedFraction{T}
    denominators::Array{T,1}
    periodstart::Int
end

Base.start(::ContinuedFraction) = 1
Base.next(it::NonPeriodicContinuedFraction, state) = it.denominators[state], state + 1
Base.next(it::PeriodicContinuedFraction, state) = it.denominators[state], (state == length(it.denominators) ? it.periodstart : state + 1)
Base.done(it::NonPeriodicContinuedFraction, state) = state > length(it.denominators)
Base.done(::PeriodicContinuedFraction, _) = false

Base.eltype(it::ContinuedFraction) = Base.eltype(typeof(it))
Base.eltype(::Type{NonPeriodicContinuedFraction{T}}) where {T} = T
Base.eltype(::Type{PeriodicContinuedFraction{T}}) where {T} = T

Base.length(it::NonPeriodicContinuedFraction) = length(it.denominators)
Base.iteratorsize(::PeriodicContinuedFraction) = Base.IsInfinite()

Base.show(io::IO, cf::ContinuedFraction) = begin
    print(io, "[")
    for (i, d) in enumerate(cf.denominators)
        i == 2 && print(io, ";")
        i >= 3 && print(io, ",")
        isa(cf, PeriodicContinuedFraction) && i == cf.periodstart && print(io, "(")
        print(io, d)
    end
    isa(cf, PeriodicContinuedFraction) && print(io, ")")
    print(io, "]")
end

Base.rationalize(cf::ContinuedFraction{T}, len::Int = -1) where {T<:Integer} = begin
    len >= 0 || (len = length(cf.denominators))
    len > 0 || return one(T) // zero(T)   # [] = +Inf;  [a; b] = a + 1 / [b] = a + 1 / (b + 1 / []) = a + 1 / (b + 1 / +Inf) = a + 1 / b

    @pipe cf |> convergents |> drop(_, len-1) |> first |> Rational(_...)
end

confrac(d::Array{T,1}) where {T<:Integer} = NonPeriodicContinuedFraction(d)
confrac(d::Array{T,1}, p::Int) where {T<:Integer} = PeriodicContinuedFraction(d, p)

confrac(rat::Rational{T}) where {T<:Integer} = begin
    n, d = rat.num, rat.den

    denominators = T[]
    while d != 0
        a = fld(n, d)
        push!(denominators, a)
        n, d = d, n - a * d
    end

    confrac(denominators)
end

# == (n + sqrt(δ)) / d
confrac(n::T, δ::T, d::T) where {T<:Integer} = begin
    d == 0 && throw(Error("The denominator is zero."))
    δ < 0 && throw(Error("The number is not real, so it does not have a continued fraction expansion."))
    δ == 0 && return confrac(n // d)

    if (δ - n^2) % d != 0
        δ *= d^2
        n *= abs(d)
        d *= abs(d)
    end

    sqrtδ = isqrt(δ)
    sqrtδ^2 == δ && return confrac((n + sqrtδ) // d)

    biP = d
    biK = sqrtδ + (biP > 0 ? 0: 1) + n
    if biK > 0
        biM = d > 0 ? biK ÷ d : (d + 1 - biK) ÷ (-d)
    else
        biM = d > 0 ? (biK + 1 - d) ÷ d : (-biK) ÷ (-d)
    end

    denominators = T[]
    periodstart = 0
    push!(denominators, biM)
    biM = biM * d  - n

    cont = -1
    K = P = L = M = -1

    while (cont < 0) || (K != P) || (L != M)
        if (cont < 0) && (biP > 0) && (biP <= sqrtδ + biM) && (biM > 0) && (biM <= sqrtδ)
            # period starts
            periodstart = length(denominators) + 1
            K = P = biP
            L = M = biM
            cont = 0
        end

        if cont >= 0
            # both numerator and denominator are positive
            P = (δ - M^2) ÷ P
            Z = (sqrtδ + M) ÷ P
            M = Z * P - M
            cont += 1
        else
            biP = (δ - biM^2) ÷ biP
            Z = (sqrtδ + biM + (biP > 0 ? 0 : 1)) ÷ biP
            biM = Z * biP - biM
        end

        push!(denominators, Z)
        #if (cont > 100000)
        #    # too many convergents
        #    break
        #end
    end

    confrac(denominators, periodstart)
end

abstract type ContinuedFractionConvergentsIterator{T<:Integer} end

struct NonPeriodicContinuedFractionConvergentsIterator{T<:Integer} <: ContinuedFractionConvergentsIterator{T}
    cf::NonPeriodicContinuedFraction{T}
end

struct PeriodicContinuedFractionConvergentsIterator{T<:Integer} <: ContinuedFractionConvergentsIterator{T}
    cf::PeriodicContinuedFraction{T}
end

convergents(cf::NonPeriodicContinuedFraction) = NonPeriodicContinuedFractionConvergentsIterator(cf)
convergents(cf::PeriodicContinuedFraction) = PeriodicContinuedFractionConvergentsIterator(cf)

Base.start(it::ContinuedFractionConvergentsIterator{T}) where {T<:Integer} = start(it.cf), one(T), zero(T), zero(T), one(T)
Base.next(it::ContinuedFractionConvergentsIterator, state) = begin
    cfs, p, q, r, s = state

    c, cfs = next(it.cf, cfs)
    p, q, r, s = c*p+r, c*q+s, p, q

    (p, q), (cfs, p, q, r, s)
end
Base.done(it::ContinuedFractionConvergentsIterator, state) = done(it.cf, state[1])

Base.eltype(it::ContinuedFractionConvergentsIterator) = Base.eltype(typeof(it))
Base.eltype(::Type{NonPeriodicContinuedFractionConvergentsIterator{T}}) where {T} = Tuple{T,T}
Base.eltype(::Type{PeriodicContinuedFractionConvergentsIterator{T}}) where {T} = Tuple{T,T}

Base.length(it::NonPeriodicContinuedFractionConvergentsIterator) = length(it.cf.denominators)
Base.iteratorsize(::PeriodicContinuedFractionConvergentsIterator) = Base.IsInfinite()


abstract type RegularizerUnsupervised <: Regularizer end

##############################################################################
# todo: replace with function of xhat,x

#########################################
# Trivial Matrix Regularizer
#########################################
struct NoReg <: RegularizerUnsupervised
end

eval(R::NoReg, X) = 0
prox(R::NoReg, X) = X

#########################################
# Nonnegative Matrix Regularizer
#########################################
struct PosReg <: RegularizerUnsupervised
end

eval(R::PosReg, X) = any(X .< 0) ? Inf : 0

function prox(R::PosReg, X)
    Q = copy(X)
    Q[Q.<0] = 0
    return Q
end


if !isdefined(Main,:IsReduced); IsReduced = FastRationals.IsReduced; end;
if !isdefined(Main,:MayReduce); MayReduce = FastRationals.MayReduce; end;
if !isdefined(Main,:basistype); basistype = FastRationals.basistype; end;

function sums_toward_half(::Type{T}, n; details::Bool=false) where {T}
    one_third = onethird(T)
    one_ninth = one_third * one_third
    s = one_third + one_ninth
    t = one_third * one_ninth

    n = abs(n)
    while n>1
     n -= 1
     s += t
     t *= one_third
    end

    return !details ? s : (t, s) # debugging info
end

function onethird(::Type{Q}) where Q
    T = basistype(Q)
    return Q(one(T), one(T)+one(T)+one(T))
end

slow17 = sums_toward_half(Rational{Int64},17)
# (1743392200//3486784401, 1//3486784401)

fast17 = sums_toward_half(FastRational{Int64, IsReduced},17)
# (1743392200//3486784401, 1//3486784401)


## Load the packages.
using Tullio
using Statistics
using BenchmarkTools

## Create the coarsening function.
function coarsen_field!(lo, hi)
    filter = 1//8 * ones(2, 2, 2)
    @tullio lo[i, j, k] = hi[2(i-1)+l, 2(j-1)+m, 2(k-1)+n] * filter[l, m, n]
end

## Set up the grids.
n_hi = 128
n_lo = 64

a_hi = rand(n_hi, n_hi, n_hi)
a_lo = zeros(n_lo, n_lo, n_lo)

## Compute.
@btime coarsen_field!(a_lo, a_hi)

## Check the output.
println(mean(a_lo) ≈ mean(a_hi))

## Create the refine function.
function refine_field!(hi, lo)
    @tullio hi[2(i-1)+l, 2(j-1)+m, 2(k-1)+n] = lo[i, j, k] (l in 1:2, m in 1:2, n in 1:2)
    return
end

## Set up the grids.
n_hi = 128
n_lo = 64

a_lo = rand(n_lo, n_lo, n_lo)
a_hi = zeros(n_hi, n_hi, n_hi)

## Compute.
@btime refine_field!(a_hi, a_lo)

## Check the output.
println(mean(a_lo) ≈ mean(a_hi))


# Uniform heatmap on the left
subplot(1,2,1)
x = LinRange(-2, 2, 40)
y = LinRange(0, pi, 20)
z = sin.(x') .+ cos.(y)
heatmap(z)
# Non uniform heatmap on the right
subplot(1,2,2)
x = [0, 2, 3, 4.5, 5]
y = [2, 3, 4.5, 5, 6, 8]
z = rand(5, 4)
heatmap(x, y, z)


export move2zero, move2xplus, rotation, reflect_across, same_side
export in_up, up_in

# This code enables LFT's to act on HObjects.

(f::LFT)(p::HPoint) = HPoint(f(getz(p)))

(f::LFT)(L::HSegment) = HSegment(f(L.A), f(L.B))

(f::LFT)(T::HTriangle) = HTriangle(f(T.A), f(T.B), f(T.C))

function (f::LFT)(L::HLine)
    s = L.s
    t = L.t
    w = exp(s*im)
    z = exp(t*im)
    ww = f(w)
    zz = f(z)
    ss = angle(ww)
    tt = angle(zz)
    return HLine(ss,tt)
end

function (f::LFT)(R::HRay)
    z = exp(im*R.t)
    p = R.pt
    zz = f(z)
    tt = angle(zz)
    pp = f(p)
    return HRay(pp,tt)
end

function (f::LFT)(HC::Horocycle)
    p = HC.pt
    t = HC.t

    pp = f(p)
    z = exp(t*im)
    zz = f(z)
    tt = angle(zz)

    return Horocycle(pp,tt)
end

function (f::LFT)(X::HPolygon)
    pts = f.(X.plist)
    return HPolygon(pts)
end

(f::LFT)(H::HPlane) = HPlane()

# Various LFT creation functions.

"""
`in_up` is a `LFT` that maps the Poincaré disk to the upper half plane.
"""
const in_up = LFT(-im, -im, 1, -1)

"""
`up_in` is a `LFT` that maps the upper half plane to the Poincaré disk.
"""
const up_in = LFT(-1, im, -1, -im)

"""
`move2zero(P::Hpoint)`
returns a `LFT` that's an isometry of H^2 that maps `P` to the origin.
"""
function move2zero(z::Complex)::LFT
    # map to upper half plane and find x-displacement
    zz = in_up(z)
    x = real(zz)

    # move horizontally to place above origin
    f = LFT(1, -x, 0, 1)
    zz = f(zz)

    # move down to 0 + im
    y = imag(zz)
    g =  LFT( 1, -y*im, 1, y*im )

    return g*f*in_up
end

move2zero(P::HPoint) = move2zero(getz(P))

"""
`rotation(theta)` is an isometry of H^2 corresponding to a
rotation about the origin of the amount `theta`
"""
rotation(theta::Real)= LFT( exp(im*theta), 0, 0, 1)

"""
`move2xplus(P::HPoint)` returns an isometry of H^2 that maps `P` onto
the positive real axis.
"""
function move2xplus(z::Complex)::LFT
    if z == 0
        return LFT()
    end
    theta = angle(z)
    return rotation(-theta)
end

move2xplus(P::HPoint) = move2xplus(getz(P))

function move2xplus(a::Complex, b::Complex)
    f = move2zero(a)
    bb = f(b)
    theta = angle(bb)
    g = rotation(-theta)
    return g*f
end

"""
`move2xplus(A,B)` or `move2xplus(L::HSegment)`
gives an isometry `f` so that `f(A)` is 0 and `f(B)` is on the
positive real axis.
"""
move2xplus(A::HPoint, B::HPoint) = move2xplus(getz(A),getz(B))
move2xplus(L::HSegment) = move2xplus(endpoints(L)...)

"""
`move2xplus(L::HLine)` returns a linear fractional transformation
that maps points on `L` to the positive x-axis but is *not* an
isometry of the hyperbolic plane.
"""
function move2xplus(L::HLine)
    a = exp(im*L.s)
    b = getz(point_on_line(L))
    c = exp(im*L.t)
    f = LFT(a,b,c)
    return f
end

function move2xplus(R::HRay)
    A = get_vertex(R)
    w = getz(A)
    z = exp(im * R.t)
    return move2xplus(w,z)
end

"""
`reflect_across(X::HObject,L::HSegment/HLine)` returns the object
formed by refecting `X across the line segment/line `L`.
"""
function reflect_across(p::HPoint, L::Union{HLine,HSegment})
    f = move2xplus(L)
    z = getz(p)
    zz = f(z)'
    w = (inv(f))(zz)
    return HPoint(w)
end

function reflect_across(S::HSegment, L::Union{HLine,HSegment})
    A,B = endpoints(S)
    AA = reflect_across(A,L)
    BB = reflect_across(B,L)
    return HSegment(AA,BB)
end

function reflect_across(T::HTriangle, L::Union{HLine,HSegment})
    A,B,C = endpoints(T)
    AA = reflect_across(A,L)
    BB = reflect_across(B,L)
    CC = reflect_across(C,L)
    return HTriangle(AA,BB,CC)
end

function reflect_across(X::HPolygon, L::Union{HLine,HSegment})
    pts = [ reflect_across(p,L) for p in X.plist ]
    return HPolygon(pts)
end

function reflect_across(X::HLine, L::Union{HLine,HSegment})
    a = exp(im * L.s)
    b = getz(point_on_line(L))
    c = exp(im * L.t)
    f = LFT(a,-1,b,0,c,1)

    Y = f(X)
    s = Y.s
    t = Y.t
    Z = HLine(-s,-t)
    return (inv(f))(Z)
end

reflect_across(X::HPlane, L::Union{HLine,HSegment}) = HPlane()

"""
`same_side(P,Q,L)` determines if the points `P` and `Q`
lie in the same (closed) halfplane as determined by `L`.
If either point is on `L` then the result is `true`.
"""
function same_side(P::HPoint, Q::HPoint, L::HLine)::Bool

    if in(P,L) || in(Q,L)
        return true
    end

    f = move2xplus(L)
    a = imag(f(getz(P)))
    b = imag(f(getz(Q)))

    return sign(a) == sign(b)
end

same_side(P::HPoint, Q::HPoint, S::Union{HSegment,HRay}) = same_side(P,Q,HLine(S))

## UNARY MINUS

"""
`-X` where `X` is a hyperbolic object is a new object reflected through
the origin.
"""
(-)(H::HPlane) = HPlane()
(-)(P::HPoint) = HPoint(-getz(P))
(-)(P::HPolygon) = HPolygon((-).(P.plist))
(-)(C::HCircle) = HCircle( -(C.ctr), C.rad )

function (-)(L::HSegment)
    a,b = endpoints(L)
    return HSegment(-a,-b)
end

function (-)(R::HRay)
    p = -R.pt
    t = R.t + pi
    return HRay(p,t)
end

function (-)(T::HTriangle)
    a,b,c = endpoints(T)
    return HTriangle(-a,-b,-c)
end

function (-)(L::HLine)
    s = L.s
    t = L.t
    return HLine(pi+s,pi+t)
end

function (-)(HC::Horocycle)
    pt = -HC.pt
    t  = HC.t + pi
    return Horocycle(pt,t)
end

## ADJOINT
"""
`adjoint(X::HObject)` (that is, `X'`) returns a new `X` that is reflected
across the `x`-axis.
"""
adjoint(H::HPlane) = HPlane()
adjoint(P::HPoint) = HPoint(getz(P)')
adjoint(P::HPolygon) = HPolygon(adjoint.(P.plist))
adjoint(C::HCircle) = HCircle( (C.ctr)', C.rad )

function adjoint(L::HSegment)
    a,b = endpoints(L)
    return HSegment(a',b')
end

function adjoint(L::HLine)
    s = L.s
    t = L.t
    return HLine(-s,-t)
end

function adjoint(R::HRay)
    p = (R.pt)'
    t = -(R.t)
    return HRay(p,t)
end

function adjoint(HC::Horocycle)
    pt = (HC.pt)'
    t  = -HC.t
    return Horocycle(pt,t)
end

function adjoint(T::HTriangle)
    a,b,c = endpoints(T)
    return HTriangle(a',b',c')
end


using Distributed
addprocs(4)
@everywhere using Distributions, Turing, Random, Parameters, MCMCChains
ProjDir = @__DIR__
cd(ProjDir)
path = pwd()
@everywhere include($path*"/LNR.jl")

@everywhere @model model(data, Nr) = begin
    μ = Array{Real,1}(undef,Nr)
    μ ~ [Normal(0,3)]
    s ~ Uniform(0, pi/2)
    σ = tan(s)
    for i in 1:length(data)
        data[i] ~ LNR(μ, σ, 0.0)
    end
end

Random.seed!(343)
Nreps = 50
Nr = 3
Nobs = 10
μ = rand(Normal(0, 3), Nr)
σ = rand(Uniform(.2, 1))
dist = LNR(μ=μ, σ=σ, ϕ=0.0)
data = rand(dist, Nobs)

function estimate1(model,specs,Nsamples;Nchains=4)
    return reduce(chainscat, pmap(x->sample(model, specs, Nsamples), 1:Nchains))
end

pmap(x->sample(model(data, Nr), NUTS(1000, .8), 2000), 1:4)

reduce(chainscat, pmap(x->sample(model(data, Nr),NUTS(1000, .8), 2000), 1:4))

estimate1(model, NUTS(1000, .8), 2000)


using VectorAutoregressions
using CSV, DataFrames
using Test

cd(@__DIR__)

@testset "VAR" begin
    Y = DataFrame(CSV.File("testdata1.csv"))
    v = VAR(Y, 2)

    @test coef(v) == v.B
    @test stderror(v) == v.seB
    @test residuals(v) == v.U
    @test fitted(v) == v.Yhat

    Bhat = [-0.017  0.016  0.013
            -0.320  0.044 -0.002
             0.146 -0.153  0.225
             0.961  0.289 -0.264
            -0.161  0.050  0.034
             0.115  0.019  0.355
             0.934 -0.010 -0.022]
    @test isapprox(coef(v), Bhat, atol=2e-3)

    sigu = [0.00212963  7.16167e-5   0.00012324
            7.16167e-5  0.000137338  6.14587e-5
            0.00012324  6.14587e-5   8.92035e-5]
    @test isapprox(v.ΣU, sigu, atol=1e-8)
end


"""
Module: SymbolicDiff (Symbolic Operation for Arithmetic)
"""

"""
seval(f, env, cache)
Return the value for expr f
"""

function seval(f)
    seval(f, globalenv, SymbolicCache())
end

function seval(f, env::SymbolicEnv)
    seval(f, env, SymbolicCache())
end

function seval(f, cache::SymbolicCache)
    seval(f, globalenv, cache)
end

function seval(f::SymbolicValue{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    f.val
end

function seval(f::SymbolicVariable{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    env[f.var]
end

function seval(f::AbstractNumberSymbolic{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    get(cache, f) do
        retval = _eval(Val(f.op), f, env, cache)
        cache[f] = retval
    end
end

"""
_eval(::Val{xx}, f, env, cache)

Dispached function to evaluate the expr f
"""

function _eval(::Val{:+}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    args = [seval(x, env, cache) for x = f.args]
    +(args...)
end

function _eval(::Val{:-}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    args = [seval(x, env, cache) for x = f.args]
    -(args...)
end

function _eval(::Val{:*}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    args = [seval(x, env, cache) for x = f.args]
    *(args...)
end

function _eval(::Val{:/}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    x,y = [seval(x, env, cache) for x = f.args]
    x/y
end

function _eval(::Val{:^}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    x,y = [seval(x, env, cache) for x = f.args]
    x^y
end

function _eval(::Val{:exp}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    x, = [seval(x, env, cache) for x = f.args]
    exp(x)
end

function _eval(::Val{:log}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    @assert length(f.args) == 1
    x, = [seval(x, env, cache) for x = f.args]
    log(x)
end

function _eval(::Val{:sqrt}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    x, = [seval(x, env, cache) for x = f.args]
    sqrt(x)
end

function _eval(::Val{:sum}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    x, = [seval(x, env, cache) for x = f.args]
    sum(x)
end

function _eval(::Val{:dot}, f::SymbolicExpression{Tv}, env::SymbolicEnv, cache::SymbolicCache)::Tv where Tv
    x,y = [seval(x, env, cache) for x = f.args]
    dot(x,y)
end


using LinearAlgebra, IntervalSets, UnPack
import ClimaCore:
    Domains, Topologies, Meshes, Spaces, Geometry, Operators, Fields

using Test

using StaticArrays, LinearAlgebra

function rotational_field(space, α0 = 45.0)
    coords = Fields.coordinate_field(space)
    map(coords) do coord
        ϕ = coord.lat
        λ = coord.long
        uu = (cosd(α0) * cosd(ϕ) + sind(α0) * cosd(λ) * sind(ϕ))
        uv = -sind(α0) * sind(λ)
        Geometry.UVVector(uu, uv)
    end
end

@testset "Spherical geometry properties" begin
    # test different combinations of odd/even to ensure pole is correctly
    # handled
    for Ne in (4, 5), Nq in (4, 5)
        FT = Float64
        radius = FT(3)

        domain = Domains.SphereDomain(radius)
        mesh = Meshes.EquiangularCubedSphere(domain, Ne)
        grid_topology = Topologies.Topology2D(mesh)
        quad = Spaces.Quadratures.GLL{Nq}()
        space = Spaces.SpectralElementSpace2D(grid_topology, quad)

        @test sum(ones(space)) ≈ 4pi * radius^2 rtol = 1e-3

        for α in [0.0, 45.0, 90.0]
            div = Operators.Divergence()
            u = rotational_field(space, α)
            divu = Spaces.weighted_dss!(div.(u))
            @test norm(divu) < 1e-2

            # test dss on UVcoordinates
            uu = Spaces.weighted_dss!(copy(u))
            @test norm(uu .- u) < 1e-14

            uᵢ = Geometry.transform.(Ref(Geometry.Covariant12Axis()), u)
            uuᵢ = Spaces.weighted_dss!(copy(uᵢ))
            @test norm(uuᵢ .- uᵢ) < 1e-14

            uⁱ = Geometry.transform.(Ref(Geometry.Contravariant12Axis()), u)
            uuⁱ = Spaces.weighted_dss!(copy(uⁱ))
            @test norm(uuⁱ .- uⁱ) < 1e-14
        end
    end
end


x0 = [1., 0.]
P0 = Matrix(1.0I, 2, 2)

Φ = [0.8 0.5; -0.1 0.8]
b = zeros(2)
Q = [0.2 0.0; 0.0 1.0]

yshadow = [0.0]
H = [1.0 0.0]
R = Matrix(1.0I, 1, 1)

E = LinearEvolution(Φ, Gaussian(b, Q))
Obs = LinearObservationModel(H, R)
M = LinearStateSpaceModel(E, Obs)


# Contact between an ellipsoid and a plane
# ========================================
# References
# ----------
# [1] P. P. Klein, On the Ellipsoid and Plane Intersection
#     Equation, Applied Mathematics, 2012, 3, 1634-1640
# -----------------------------------------------------------------------
# ellipsoid is defined by centre x, radii a, b, c, and orientation R;
# plane is defined by point and normal;
# -------------------------------------------------------------
# returned: (conpnt, depth, A, B), where:
# conpnt - contact point (or [0.0, 0.0, 0.0] if no contact)
# depth - positive penetration depth (or -1.0 if no contact)
# A, B - contact ellipse radii (or 0.0, 0.0 if no contact)
# _____________________________________________________________
function ellipsoid_plane_contact(x, a, b, c, R, point, normal)

  # these are 3-vectors
  x = vec(x);
  point = vec(point);
  normal = vec(normal);

  # we move the plane into the natural coordinate system of our input ellipsoid,
  # which is centered at x and oriented according to the R rotation

  point = R'*(point - x); # so that plane's point, relative to the origin of
                          # the ellipsoid is suitably shifted and rotated
                          # and this needs to be followed by

  normal = R' * normal; # to redefine the normal in the natural coordinate
                        # system of the ellipsoid

  C1 = [1.0/a 0.0 0.0; 0.0 1.0/b 0.0; 0.0 0.0 1.0/c]; # this is matrix D1 in [1]

  v = vec(rand(3,1));   # let's generate a random vector first
  r = cross(normal, v); # now r'*normal = 0 as required
  r = r / norm(r);      # lets normalize it (make it's length = 1)
  s = cross(normal, r); # now s'*normal = 0 and s'*r = 0 as required
  q = point;

  # now lets check for condition (7) to be fullfilled

  # by doing this transformation we are already satisfying condition (4-6)
  # as rr and ss are orthogonal to each other and to the normal vector

  # now lets choose omega and fulfill condition (7)

  # here I will define all the dot products needed
  D1 = dot((C1*r),(C1*s));
  D2 = dot((C1*r),(C1*r));
  D3 = dot((C1*s),(C1*s));

  if abs(D2-D3) < 1E-10
    omega = 0.25*pi;
  else
    omega = 0.5 * atan((2*D1)/(D2-D3));
  end

  rr = cos(omega)* r + sin(omega) * s;  # here we are transforming r and
  ss = -sin(omega) * r + cos(omega) * s; # s to fullfill condition (7) in [1]

  r = rr;
  s = ss;

  D2 = dot((C1*r),(C1*r));
  D3 = dot((C1*s),(C1*s));
  D4 = dot((C1*q),(C1*r));
  D5 = dot((C1*q),(C1*s));
  D6 = dot((C1*q),(C1*q));

  # now lets find the ellipse of the intersection
  d = D6 -((D4)^2/D2)-((D5)^2/D3); # this is just a value

  # semi-axes squared radii of the contact ellipse
  Aa = (1-d)/D2;
  Bb = (1-d)/D3;

  if Aa > 0. && Bb > 0. # there is contact
    # radii of the contact ellipse
    A  = sqrt(Aa);
    B  = sqrt(Bb);

    # now the centre of the contact ellipse
    t0 = -D4/D2;
    u0 = -D5/D3;
    x0 = q + t0*r + u0*s;

    # now lets find the contact point (in ellipsoid coordinates)
    alpha = sqrt(1.0/((x0[1]^2/a^2) + (x0[2]^2/b^2) + (x0[3]^2/c^2)));
    conpnt = alpha * x0;

    # and the penetration depth
    depth = dot(normal,  x0 - conpnt);

    # now contact point in input coordinates
    conpnt = R*conpnt + x;

  else # there is no contact
    conpnt = [0.0; 0.0; 0.0];
    depth = -1.0;
    A = 0.0;
    B = 0.0;
  end

  return conpnt, depth, A, B
end


"""
    lif(t::Real, I, V; R, tau)
    lif!(t::AbstractArray{<:Real}, I::AbstractArray{<:Real}, V::AbstractArray{<:Real}; R::AbstractArray{<:Real}, tau::AbstractArray{<:Real})
    lif!(t::CuVector{<:Real}, I::CuVector{<:Real}, V::CuVector{<:Real}; R::CuVector{<:Real}, tau::CuVector{<:Real})

Evaluate a leaky integrate-and-fire neuron.
Use `CuVector` instead of `Vector` to evaluate on GPU.

# Fields
- `t`: time since last evaluation in seconds
- `I`: external current
- `V`: current membrane potential
- `R`: resistance constant
- `vreset`: reset potential
- `tau`: time constant
"""
function lif(t::Real, I, V; R, tau)
    V *= exp(-t / tau)
    V += I * (R / tau)

    return V
end
function lif!(t::AbstractArray{<:Real}, I::AbstractArray{<:Real}, V::AbstractArray{<:Real}; R::AbstractArray{<:Real}, tau::AbstractArray{<:Real})
    # account for leakage
    @. V *= exp(-t / tau)

    # apply update step
    @. V += I * (R / tau)

    return V
end
function lif!(t::CuVector{<:Real}, I::CuVector{<:Real}, V::CuVector{<:Real}; R::CuVector{<:Real}, tau::CuVector{<:Real})
    # account for leakage
    @. V *= exp(-t / tau)

    # apply update step
    @. V += I * (R / tau)

    return V
end


using CyclicDiagonal
using Test
using SpecialMatrices
using LinearAlgebra

@testset "CyclicDiagonal.jl" begin
    # Write your own tests here.

    M = Matrix( Circulant([1;3;2]) )

    # Test simple 3x3 circulant matric
    D1, C, D2 = CyclicDiagonal.iterDCD( M )
    @test norm(D1-I, Inf) < 1e-12

    # Test simple product of a diagonal and circulant matrix
    D = Diagonal(1:3)
    D1, C, D2 = CyclicDiagonal.iterDCD( D*M )
    @test norm( D1*C*D2 - D*M, Inf) < 1e-12

    # Test products of kxk matrices (k=1,...,K), each size N times
    N = 10
    K = 10
    r = 1:50
    for k in 1:K
        for i in 1:N
            D1o = Diagonal( rand(r, k) )
            Co  = Circulant( rand(r, k) )
            D2o = Diagonal( rand(r, k) )
            D1, C, D2 = CyclicDiagonal.iterDCD( D1o * Co * D2o, 1e6, 1e-7 )
            @test norm( D1*C*D2 - D1o*Co*D2o, Inf) < 1e-3
        end
    end
end


function lgd10()
#
#  test polynomial suggested by Goedecker
#   Legendre polynomial of degree 10
#
    p = lgd(10);
    z = [0.97390652851717
   0.86506336668898
   0.67940956829903
  -0.97390652851717
  -0.86506336668899
  -0.67940956829903
   0.43339539412925
  -0.43339539412925
   0.14887433898163
  -0.14887433898163]
    z = [z ones(10)]
    p, PolyZeros(z)
end


#=
# Tests out using a variety of coefficients in different places
=#
if !@isdefined(Femshop)
    include("../Femshop.jl");
    using .Femshop
end
init_femshop("coef");

# Try making an optional log
@useLog("coeflog")

# Set up the configuration
@domain(2, SQUARE, UNSTRUCTURED)    # dimension, geometry, decomposition
@solver(CG)                         # DG, CG, etc.
@functionSpace(LEGENDRE, 2)         # function, order (or use testFunction and trialFunction)
@nodes(LOBATTO)                     # elemental node arrangement

# Specify the problem
@mesh(QUADMESH, 20)                 # .msh file or generate our own

@variable(u)                        # same as @variable(u, SCALAR)

@testSymbol(v)                    # sets the symbol for a test function

@boundary(u, 1, DIRICHLET, 0)

# Write the weak form
@coefficient(a, "x")
@coefficient(b, "3")
@coefficient(c, "y")
@coefficient(d, 0.1)
@coefficient(f, "(-5*pi*pi*x*sin(pi*x)*sin(2*pi*y) + pi*cos(pi*x)*sin(2*pi*y)) + y*sin(pi*x)*sin(2*pi*y)")

@weakForm(u, "-a*b*dot(grad(u), grad(v))*d + c*u*d*v*b - d*f*b*v")

solve(u);

# exact solution is sin(pi*x)*sin(2*pi*y)
# check error
maxerr = 0;
exact(x,y) = sin(pi*x)*sin(2*pi*y);

for i=1:size(Femshop.grid_data.allnodes,2)
    x = Femshop.grid_data.allnodes[1,i];
    y = Femshop.grid_data.allnodes[2,i];
    err = abs(u.values[i] - exact(x,y));
    global maxerr;
    maxerr = max(err,maxerr);
end
println("max error = "*string(maxerr));

using Plots
pyplot();
display(plot(Femshop.grid_data.allnodes[1,:], Femshop.grid_data.allnodes[1,:], u.values[:], st=:surface))

# check
log_dump_config();
log_dump_prob();

@finalize()


"""
std_w

Weighted standard deviation.
Negative weights are not allowed.
"""
function std_w(xM :: Array{T}, wtM :: Array{T}) where T <: AbstractFloat
	xV = vec(xM)
	wtV = AnalyticWeights(vec(wtM))

	## Input check
	if any(wtV .< 0.0)
	  error("Invalid weights")
	end

	# wtSum = sum(wtV);
	# xMean = sum(xV .* wtV) / wtSum;
	# xStd  = sqrt(sum(((xV .- xMean) .^ 2) .* wtV)  /  wtSum);

	xMean, xStd = mean_and_std(xV, wtV, corrected = false);
	# xStd = std(xV, wtV);
	return xStd, xMean
end


## mlutils.jl - a set of ML utilities for parameter initalization, performance metrics, etc.

## initialization

function xavier_init(dim_in, dim_out; c=1)
    low = -c * sqrt(6.0 / (dim_in + dim_out))
    high = c * sqrt(6.0 / (dim_in + dim_out))
    return rand(Uniform(low, high), dim_in, dim_out)
end

## one-hot encoding

function class_to_index(classes)
    classes = sort(classes)
    return Dict(c => i for (i, c) in enumerate(classes))
end

function one_hot(::Type{T}, num_classes::Int, vals::AbstractVector{<:Integer}) where T
    ret = zeros(T, num_classes, length(vals))
    for (i, v) in enumerate(vals)
        ret[v, i] = T(1)
    end
    return ret
end

one_hot(num_classes::Integer, vals) = one_hot(Float64, num_classes, vals)

function one_hot(::Type{T}, c2i::Dict, vals) where T
    result = zeros(T, length(c2i), length(vals))
    for (j, val) in enumerate(vals)
        i = c2i[val]
        result[i, j] = one(T)
    end
    return result
end

one_hot(c2i::Dict, vals) = one_hot(Float64, c2i, vals)

## metrics

function accuracy(ŷ::AbstractMatrix, y::AbstractMatrix)
    return mean(findmax(@view ŷ[:,i])[2] == findmax(@view y[:,i])[2] for i=1:size(ŷ,2))
end

## CUDA support

@require CuArrays begin

    """
    Convert to a corresponding CUDA object.

    For arrays returns an instance of CuArray.
    For structs returns similar struct with all arrays fields converted to CuArray.
    """
    to_cuda(a::AbstractArray{T,N}) where {T,N} = convert(CuArray{Float32,N}, a)
    to_cuda(a::CuArray) = a

    function to_cuda(m)
        if isstruct(m)
            new_m = Espresso.struct_like(m)
            for fld in fieldnames(typeof(m))
                v = getfield(m, fld)
                new_v = to_cuda(v)
                setfield!(new_m, fld, new_v)
            end
            return new_m
        else
            return m
        end
    end

end


function TGEAR(THTL)
      if (THTL<=0.77)
        return tgearv = 64.94*THTL
      else
        return tgearv = 217.38*THTL-117.38
      end
end


# theobins
function theobins_test()
	d = Distributions.Normal()
	b = collect(-3.:1:3);
	n = 100;
	bc = HypoTest.theobins(d,b,n);
	@test trunc.(Int,round.(bc)) == [2,14,34,34,14,2];
	q = [Distributions.quantile(d,0.99),Distributions.quantile(d,0.99999)]
	@test HypoTest.theobins(d,q,n) ≈ [0.999] atol = 0.001
end

# countbins
function countbins_test()
	x = collect(1.:1.:10);
	bc = HypoTest.countbins(x,[0.,1.,9.,10.]);
	@test bc == [0,8,2]; # see PyPlot.plt[:hist](x,[0.,1.,9.,10.])
	@test HypoTest.countbins(x,[1.,1.,9.]) == [0,9];
	@test HypoTest.countbins([-1.001,0.,0.999],[-2.,-1.,0.,1]) == [1,0,2]
end

# histbins
function histbins_test()
	hb = HypoTest.histbins([0.,1.],method=10)
	@test hb == collect(0:0.1:1.)
	hb = HypoTest.histbins([-1.,0.,1.,2.],method="square")
	@test hb == collect(-1:1.5:2)
end

# prepdata
function prepdata_test()
	@test [1,2,3.] == HypoTest.prepdata([1.,2.,3.])
	@test [-1.,0.99] == HypoTest.prepdata([NaN,-1.,NaN,0.99,NaN])
	# two vectors
	x,y = HypoTest.prepdata([1.,2,NaN],[2.,NaN,9]);
	@test x == [1.]
	@test y == [2.];
	x,y = HypoTest.prepdata([1.,2.,3.],[2.,3.,4.]);
	@test x == [1.,2.,3.];
	@test y == [2.,3.,4.];
end

# Run functions
theobins_test();
countbins_test();
histbins_test();
prepdata_test();


using DifferentialEquations
using Plots
using DataFrames
using CSV

abstract type DynamicalSystem end

struct ODE1 <: DynamicalSystem end
struct ODE3 <: DynamicalSystem end
struct LorenzSystem <: DynamicalSystem end
struct LotkaVolterra <: DynamicalSystem end
struct NSECylinder <: DynamicalSystem end
struct HelicopterData <: DynamicalSystem
    filename::String
    HelicopterData(file) = new(file)
end

function noise(u::Vector{Vector{T}}) where T
    for i = 1:length(u)
        for j = 1:length(u[i])
            u[i][j] += 0.1*randn()
        end
    end
    return u
end

function datagen(ds::ODE1)
    function ode1!(du,u,p,t)
        du[1] = -0.1*u[1] + 2*u[2]
        du[2] = -2*u[2] - 0.1*u[2]
    end

    u₀ = [1,1]
    p = [0.7,0.3,0.3,0.4]
    tspan = (0,20)
    t = 0.01:0.01:20
    prob = ODEProblem(ode1!,u₀,tspan,p)
    sol = solve(prob,Tsit5(),saveat=t)
    # noisy = noise(sol.u)
    fig = plot(sol,vars=(1,2),title="ODE System Linear")
    display(fig)
    return sol
end

function datagen(ds::ODE3)
    function ode1!(du,u,p,t)
        du[1] = -0.1*u[1]^3 + 2*u[2]^3
        du[2] = -2*u[2]^3 - 0.1*u[2]^3
    end

    u₀ = [1,1]
    p = [0.7,0.3,0.3,0.4]
    tspan = (0,20)
    t = 0.01:0.01:20
    prob = ODEProblem(ode1!,u₀,tspan,p)
    sol = solve(prob,Tsit5(),saveat=t)
    # noisy = noise(sol.u)
    fig = plot(sol,vars=(1,2),title="ODE System Non Linear")
    display(fig)
    return sol
end

function datagen(data::LorenzSystem)
    function lorenz!(du,u,p,t)
        du[1] = p[1]*(u[2]-u[1])
        du[2] = u[1]*(p[2]-u[3]) - u[2]
        du[3] = u[1]*u[2] - p[3]*u[3]
    end

    u₀ = [1,0,0]
    p = [10,28,8/3] # Change d_lorenz
    tspan = (0,100)
    t = 0.01:0.01:100
    prob = ODEProblem(lorenz!,u₀,tspan,p)
    sol = solve(prob,Tsit5(),saveat=t)
    # noisy = noise(sol.u)
    fig = plot(sol,vars=(1,2,3),title="Lorenz Attractor")
    display(fig)
    return sol
end

function datagen(ds::LotkaVolterra)
    function lv!(du,u,p,t)
        α,β,γ,δ = p
        du[1] = α*u[1] - β*u[1]*u[2]
        du[2] = - γ*u[2] + δ*u[1]*u[2]
    end

    u₀ = [1,1]
    p = [0.7,0.3,0.3,0.4]
    tspan = (0,20)
    t = 0.01:0.01:20
    prob = ODEProblem(lv!,u₀,tspan,p)
    sol = solve(prob,Tsit5(),saveat=t)
    # noisy = noise(sol.u)
    fig = plot(sol,vars=(1,2),title="Lotka Volterra")
    display(fig)
    return sol
end

function datagen(ds::HelicopterData)
    df = CSV.read(ds.filename,DataFrame,delim=",")
    data = Matrix(df)
    data = [data[:,3] data[:,4]]
    my_cgrad = cgrad([:blue, :green, :red])
    x = range(0,stop=131,length=size(data,1))
    fig = plot(data[:,1],data[:,2],lc=my_cgrad,line_z=x,title="Helicopter data")
    display(fig)
    return data
end


using WeightedLS
using LinearAlgebra
using Test

@testset "normal LS" begin
    A = Float64[1 0; 1 -2]
    B = Float64[32; -4]
    x = WeightedLS.normalLS(A,B)

    @test isapprox(norm(x-Float64[32.0; 18.0]), 0.0; atol = 1e-5)

    A = Float64[10 12;20 22;21 0]
    B = Float64[1;2;56]
    x = WeightedLS.normalLS(A,B)
    @test isapprox(norm(x-Float64[2.662964939354169; -2.285446442736200]), 0.0; atol = 1e-5)
end

@testset "weighted LS" begin

    a = Float64[1; 1; 1; 1; 1]
    m = size(a,1)
    W = Float64[1; Inf; Inf; 1; 1]

    @test WeightedLS.checkConstraint(a,W,m) == 2

    a = Float64[1; 0.5; 1; 1; 1]
    m = size(a,1)
    W = Float64[1; Inf; Inf; 1; 1]

    @test WeightedLS.checkConstraint(a,W,m) == 3

    a = Float64[1; 1; 1; 1; 1]
    m = size(a,1)
    W = Float64[1; 1; 1; 1; 1]

    @test WeightedLS.checkConstraint(a,W,m) == -1

    A = Float64[1 0 0 0 0 1;
                0 0 0 0 1 0;
                0 0 1 1 0 0;
                0 1 0 0 0 0;
                0 0 0 0 0 1]
    B = Float64[0;0;0;0;0]
    W = Float64[0;1;0;1;1]
    W = 1 ./W
    # println(W)
    Rd, outweights = WeightedLS.weightedLS(A,B,W)
    # println(outweights)
    # show(stdout, "text/plain", Rd)

    expected_Rd = Float64[1  0  0  0  0  1  0;
                          0  1  0  0  0  0  0;
                          0  0  1  1  0  0  0;
                          0  0  0  0  1  0  0;
                          0  0  0  0  0  1  0;]
    @test isapprox(norm(Rd-expected_Rd), 0.0; atol = 1e-5)

    A = Float64[    1 0 0;
                    0 1 0;
                    0 0 1;
                   -1 0 1;
                    1 0 0;
                    0 1 0;
                    0 0 1;
                    0 -1 1;
                    1 0 0;
                    0 1 0;
                    0 0 1]
    B = zeros(11,1)
    W = ones(11,1)
    W[4] = Inf; W[8] = Inf;
    # println(W)
    Rd, outweights = WeightedLS.weightedLS(A,dropdims(B,dims=2),dropdims(W,dims=2))
    # println(outweights)
    # show(stdout, "text/plain", Rd)

    expected_Rd = Float64[-1  0 1 0;
                           0 -1 1 0;
                           0  0 1 0;]
    @test isapprox(norm(Rd[1:3,:]-expected_Rd), 0.0; atol = 1e-5)

    A = Float64[1 0; 1 -2]
    B = Float64[32; -4]
    W = Float64[1; Inf]
    Rd, outweights = WeightedLS.weightedLS(A,B,W)
    R = Rd[1:2,1:2]
    d = Rd[1:2,3]
    # println(R)
    # println(d)
    x = R\d

    @test isapprox(norm(x-Float64[32.0; 18.0]), 0.0; atol = 1e-5)
end


s = open("input.txt") do file
    readlines(file)
end

function GetID(seatCode)
    charArr = seatCode |> collect;
    binString = String(map(x->ifelse(x=='F','0','1'),charArr[1:7]));

    row = parse(Int, binString, base=2);

    binString = String(map(x->ifelse(x=='L','0','1'),charArr[8:10]));
    col = parse(Int, binString, base=2);

    return row * 8 + col;
end

maximum(map(GetID, s))


using CayleyDickson
using Base.Test: @test, @test_throws

@test begin
    a = CayleyDickson.randomBigFloat()
    isreal(SplitQuaternion(a))
end

@test begin
    a = 1
    !isreal(SplitQuaternion(a, a, a, a))
end

@test begin
    z = random(SplitQuaternion{Int})
    z == +(z)
end

@test begin
    a = 1
    real(SplitQuaternion(a)) == a
end

@test begin
    a = 1
    b = 2
    c = 3
    l = unreal(SplitQuaternion(0, a, b, c))
    r = [a, b, c]
    l == r
end

@test begin
    io = IOBuffer()
    show(io, SplitQuaternion(1, 2, 3, 4))
    l = String(take!(io))
    r = "[1: 1, i: 2, s: 3, is: 4]"
    l == r
end

@test begin
    a = CayleyDickson.randomBigInt()
    b = CayleyDickson.randomBigFloat()
    l = SplitQuaternion(Binion(a, b))
    r = SplitQuaternion(a, b)
    l == r
end

@test begin
    a = CayleyDickson.randomBigInt()
    b = CayleyDickson.randomBigFloat()
    c = CayleyDickson.randomBigInt()
    l = SplitQuaternion(a, b, c)
    r = SplitQuaternion(a, b, c, 0)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigFloat})
    y = random(SplitQuaternion{BigFloat})
    x != y
end

@test begin
    a = CayleyDickson.randomBigInt()
    z = random(SplitQuaternion{BigInt})
    l = a + z
    r = z + a
    l == r
end

@test begin
    a = random(Binion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = a + z
    r = z + a
    l == r
end

@test begin
    a = CayleyDickson.randomBigInt()
    z = random(SplitQuaternion{BigInt})
    l = a - z
    r = -(z - a)
    l == r
end

@test begin
    a = random(Binion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = a - z
    r = -(z - a)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    l = x + y
    r = y + x
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    l = x * y
    r = y * x
    l != r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = associator(x, y, z)
    r = zero(SplitQuaternion{BigInt})
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    l = x - y
    r = -(y - x)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = (x + y) + z
    r = x + (y + z)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = (x * y) * z
    r = x * (y * z)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    conj(conj(x)) == x
end

@test begin
    x = random(SplitQuaternion{Rational{BigInt}})
    inv(inv(x)) == x
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    l = conj(x * y)
    r = conj(y) * conj(x)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    l = conj(x + y)
    r = conj(x) + conj(y)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    l = conj(x - y)
    r = conj(x) - conj(y)
    l == r
end

@test begin
    x = random(SplitQuaternion{Rational{BigInt}})
    y = random(SplitQuaternion{Rational{BigInt}})
    l = inv(x * y)
    r = inv(y) * inv(x)
    l == r
end

@test begin
    x = random(SplitQuaternion{Rational{BigInt}})
    y = random(SplitQuaternion{Rational{BigInt}})
    l = inv(x / y)
    r = y / x
    l == r
end

@test begin
    x = random(SplitQuaternion{Rational{BigInt}})
    y = random(SplitQuaternion{Rational{BigInt}})
    l = inv(x \ y)
    r = y \ x
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = x * (y + z)
    r = (x * y) + (x * z)
    l == r
end

@test begin
    a = CayleyDickson.randomBigInt()
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    l = a * (x + y)
    r = (a * x) + (a * y)
    l == r
end

@test begin
    a = CayleyDickson.randomBigInt()
    x = random(SplitQuaternion{BigInt})
    l = a * x
    r = x * a
    l == r
end

@test begin
    a = random(Binion{BigInt})
    x = random(SplitQuaternion{BigInt})
    l = a * x
    r = x * a
    l != r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = (x + y) * z
    r = (x * z) + (y * z)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = x * (y - z)
    r = (x * y) - (x * z)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    z = random(SplitQuaternion{BigInt})
    l = (x - y) * z
    r = (x * z) - (y * z)
    l == r
end

@test begin
    x = random(SplitQuaternion{BigInt})
    quadrance(x) > big(0) || quadrance(x) < big(0) || quadrance(x) == big(0)
end

@test begin
    x = random(SplitQuaternion{BigInt})
    y = random(SplitQuaternion{BigInt})
    l = quadrance(x * y)
    r = quadrance(x) * quadrance(y)
    l == r
end

@test_throws ErrorException begin
    inv(SplitQuaternion(0, 0.0))
end

@test_throws ErrorException begin
    inv(SplitQuaternion(1, 1, 1, 1))
end

@test_throws ErrorException begin
    inv(SplitQuaternion(1, 2, 2, 1))
end

@test_throws ErrorException begin
    random(SplitQuaternion{Int}) / 0
end

@test_throws ErrorException begin
    0 \ random(SplitQuaternion{Int})
end

@test begin
    a = CayleyDickson.randomBigRational()
    z = random(SplitQuaternion{Rational{BigInt}})
    l = z / a
    r = a \ z
    l == r
end

@test begin
    a = CayleyDickson.randomBigRational()
    z = random(SplitQuaternion{Rational{BigInt}})
    l = a / z
    r = z \ a
    l == r
end

@test begin
    z = random(SplitQuaternion{BigInt})
    z == z + zero(SplitQuaternion{BigInt})
end

@test begin
    z = random(SplitQuaternion{BigInt})
    z == z * one(SplitQuaternion{BigInt})
end

@test begin
    z = random(SplitQuaternion{BigInt})
    z == z + zero(z)
end

@test begin
    z = random(SplitQuaternion{BigInt})
    z == z * one(z)
end


using SpecialFunctions, Nemo, StatsFuns, Memoization
import Nemo.binom, Nemo.gamma

"Session-wide precision"
const precision = 5000 ## Increase for better precision

"Converter from real to arbitrary precision real (Arb)"
const RR = RealField(precision)
const CC = ComplexField(precision)

## common functions
gamma(x::Int64) = Nemo.gamma(RR(x))

#  Factorial function
fac(x::Int64) = Nemo.fac(RR(x))

@memoize function unsigned_Stirling2(n::Integer,k::Integer)
  # special cases
  if k<0 || n<0
    throw(DomainError())
  end
  if k>n
    return big(0)
  end
  if n==0  # and, by logic, k==0
    return big(1)
  end
  if k==0  # and, by logic, n>0
    return big(0)
  end
  # end of special cases, invoke recursion
  return  (k)*unsigned_Stirling2(n-1,k) + unsigned_Stirling2(n-1,k-1)
end

import Nemo.risingfac

function risingfac(r, n)
  if r == 0
    return 0
  else
    return prod(r + i for i in 0:(n-1))
  end
end
function risingfac(r::arb, n)
  if r == 0
    return arb_0
  else
    return prod(r + i for i in 0:(n-1))
  end
end

##functions to compute M function for Fergusson&Klass approximation
using Distributions, Roots

Γ(x) = SpecialFunctions.gamma(x)
using RCall
R"library(expint)"

function gamma_inc_r(a::Float64, x::Float64)
    res::Float64 = R"expint::gammainc($a,$x)"
    return res
end
# Γ(s,x) = SpecialFunctions.gamma_inc_r(s, x, 0)[2] * Γ(s)
Γ(s,x) = gamma_inc_r(s, x)

function log_βnk(β, n, k, σ)
    return log(β) + log(n) - 1/σ*log(k)
end
βnk(β, n, k, σ) = exp(log_βnk(β, n, k, σ))

function logxk(n, k, β, σ)
    if n==1
        return log(k*σ ) + log(GibbsTypePriors.Cnk(n, k+1, σ)) - log(σ) - log(GibbsTypePriors.Cnk(n, k, σ))
    else
       return log(k*σ + βnk(β, n-1, k, σ)) + log(GibbsTypePriors.Cnk(n, k+1, σ)) - log(σ) - log(GibbsTypePriors.Cnk(n, k, σ))
    end
end

function logxk_py(n, k, β, σ)
       return log(k*σ + β) + log(GibbsTypePriors.Cnk(n, k+1, σ)) - log(σ) - log(GibbsTypePriors.Cnk(n, k, σ))
end

function Pkn_NGG_approx_full(n, β, σ, f)
        Axnk = Array{arb}(undef, n-1)
        Axnk[1:n-1] = f.(n, 1:n-1, β, σ)
        Sum_xn= exp(f(n, 1, β, σ))
        for i in (3:n)
             Sum_xn= Sum_xn + exp(sum(Axnk[1:i-1]))
        end
        P1n = Array{arb}(undef, n)
        P1n[1] = exp(- log(1 +Sum_xn))
        for k in 2:(n)
              P1n[k] = exp(log(P1n[k-1]) + Axnk[k-1])
        end
        return P1n
end

Pkn_NGG_pred_approx(n, β, σ) = convert(Array{Float64,1}, Pkn_NGG_approx_full(n,  β, σ, logxk))

function Pkn_NGGM_arb_precomputed(k::N, n::N,H::N,  β::T, σ::T, Pk_NGG::A) where {T<:Number, N<:Integer,A<:Vector}
    H_arb = RR(H)
    return (fac(H) // (H_arb^k * fac(H-k))) * sum([ (1//(H_arb^i))*unsigned_Stirling2(i+k,k)* Pk_NGG[k+i] for i in 0:(n-k)])
end

Pkn_NGGM_precomp(k,n, H, β, σ, Pk_NGG) = convert(Float64, Pkn_NGGM_arb_precomputed(k, n, H, β, σ, Pk_NGG))

## functions to smooth the plot

function quantilepk(pk)
  n = length(pk)
  cdfk = cumsum(pk)
  quantile_grid = (1:n)/(n+1)
  return [argmin(cdfk .< q) for q in quantile_grid]
end

function gamma_qq(pk, α, β)
  n = length(pk)
  quantile_grid = (1:n)/(n+1)
  return sum((quantile.(Gamma(α, 1/β), quantile_grid) - quantilepk(pk)).^2)
end

 function smooth_pk(Pkn)
  lower = [1., 0.0]
  upper = [10000, 10000]
  initial_x = [2.0, 2.0]
  objfun_pkn(x) = gamma_qq(Pkn, x[1], x[2])
  res = optimize(objfun_pkn, lower, upper, initial_x)
  α_, β_ = res.minimizer
  return Pkn__smooth = pdf.(Gamma(α_, 1/β_), eachindex(Pkn))
end

###


import MechanicalSketch
import MechanicalSketch: color_with_lumin, empty_figure, background, sethue, O, EM, FS, finish, PALETTE
import MechanicalSketch: dimension_aligned, settext, arrow, setfont
import MechanicalSketch: ComplexQuantity, generate_complex_potential_vortex, @import_expand, string_polar_form
import MechanicalSketch: place_image, x_y_iterators_at_pixels, ColSchemeNoMiddle
import MechanicalSketch: BinLegend, draw_legend, latexify, @layer, fontsize

let
if !@isdefined m²
    @import_expand(m, s)
end

BACKCOLOR = color_with_lumin(PALETTE[8], 80)
function restart()
    empty_figure(filename = joinpath(@__DIR__, "test_21.png"))
    background(BACKCOLOR)
    sethue(PALETTE[5])
end
restart()

"Vortex position"
p_vortex = (0.0, 2.5)m |> ComplexQuantity

"Vorticity, 2d flow"
K = 1.0m²/s

"2d velocity potential function. Complex quantity domain, real quantity range. "
ϕ_vortex = generate_complex_potential_vortex(; pos = p_vortex, vorticity = K)

physwidth = 20m
physheight = physwidth / 3
xs, ys = x_y_iterators_at_pixels(;physwidth, physheight)
A = [ϕ_vortex(complex(x, y)) for y in ys, x in xs]
legend = BinLegend(;maxlegend = maximum(A), minlegend = minimum(A), noofbins = 20,
    name = :Potential)

colormat = legend.(A);
ulp, lrp = place_image(O, colormat);
legendpos = O + ((lrp[1] - ulp[1]) / 2 + EM, (ulp[2] - lrp[2]) / 2)
@layer begin
    fontsize(0.8FS)
    draw_legend(legendpos, legend)
end

# Add some decoration to the plot
sethue(color_with_lumin(PALETTE[5], 30))
dimension_aligned(O + (-physwidth / 2, physheight / 2), O + (physwidth / 2, physheight / 2))
dimension_aligned(O + p_vortex, O)
dimension_aligned(O + (-physwidth / 2, - physheight / 2 ),  O +  (-physwidth / 2, physheight / 2 ))

@layer begin # Leader for a value
    sethue(color_with_lumin(PALETTE[5], 90))
    p0 = (0.0 + 1.0im)m
    arrow(O + p0 - (3EM, -2EM), O + p0 )
    fvalue = ϕ_vortex(p0)
    strvalue = string(round(typeof(fvalue ), fvalue , digits = 3))
    strargument = string(p0)
    strargumentpol = string_polar_form(round(typeof(p0 ), p0 , digits = 3))
    txt =  "ϕvortex( $strargument ) = ϕvortex( $strargumentpol ) =$strvalue"
    settext(txt, O + p0 + (-12EM, 3EM), markup=true)
end

# Convert the function to latex and display
la1 = latexify(quote
    ϕ_vortex(θ:: Angle)::Potential = $K * θ
end);
pt = O + (-14.0, -5.0)m
ptul, ptlr, scalefactor = place_image(pt, la1, height = 1EM)

# Following text
#setfont("DejaVu Sans", FS)
str = "is, surprisingly, irrotational outside of the centre"
settext(str, ptlr + (EM, 0), markup = true)

# Convert the outer function to latex
la2 = latexify(quote
    ϕ_vortex(p::ComplexQuantity)::Potential = ϕ_vortex(-angle(p - $p_vortex))
end)
place_image(pt + (0, 3EM), la2; scalefactor)

finish()
nothing
end # let