using Revise; #nb
push!(LOAD_PATH, "../../src/IGA/nurbs_toolbox/"); #nb
using MagMechFEM_Matlab2Julia.NURBStoolbox; #md
using NURBStoolbox; #nb
using Plots;
default(background_color=:transparent, #hide #md
        foreground_color=:grey, #hide #md
        html_output_format=:svg); #hide #md

# # NURBS Toolbox
#
# This notebook will demonstrate some capabilities of the ported NURBS Toolbox
# by D.M. Spink ([2000Spink](@cite)).
#
# Note that the original Matlab version has not been ported with the same
# syntax and behaviours.As Julia is a different language with different
# features. Skim through those examples to get a grasp on the differences if
# you know the original toolbox.
#
# ## NURBS Data Types
#
# This section demonstrates how to construct NURBS data types. And how to
# identify them.
#
# To build NURBS structures it is advised to use the function `nrbmak()` which
# will accept inputs for surfaces or curves.

line = nrbmak([0.0 1.5; 0.0 3.0],vec([0.0 0.0 1.0 1.0]))
#------------------------------------------------------------------------------
coefs = cat([0.0 0.0; 0.0 1.0],[1.0 1.0; 0.0 1.0],dims=3);
knots = [vec([0.0 0.0 1.0 1.]),vec([0.0 0.0 1.0 1.0])];
plane = nrbmak(coefs,knots)

# The fields of the data structures can be accessed similar to Matlab.

plane.coefs

# Those created data types are typed. And can be therefore used to check for
# certain types.

println( typeof(line ) )
println( typeof(plane) )

# The abstract super type `NURBS` can be used to check for the superset of
# NURBS.

println( typeof(line )<:NURBS )
println( typeof(plane)<:NURBS )

# The types `NURBS1D` and `NURBS2D` can be used to check for NURBS curves or
# surfaces.

println( typeof(line )<:NURBS1D )
println( typeof(plane)<:NURBS1D )
println( typeof(line )<:NURBS2D )
println( typeof(plane)<:NURBS2D )

# ## NURBS Curves
#
# As mentioned creates this toolbox NURBS curves with `nrbmak()`.
# This function uses a point matrix (each column a point and each row a
# coordinate)

pnts = [0.5 1.5 4.5 3.0 7.5 6.0 8.5;
        3.0 5.5 5.5 1.5 1.5 4.0 4.5;
        0.0 0.0 0.0 0.0 0.0 0.0 0.0];

# and a corresponding knot open knot vector

knots = vec([0 0 0 1/4 1/2 3/4 3/4 1 1 1]);

# as input arguments.

crv = nrbmak(pnts,knots);

# Note that this toolbox does works with open NURBS curves!
#
# This curve can also build by using the test function `nrbtestcrv()`.
# To evaluate the curve the function `nrbeval()` can be used.

p1 = nrbeval(crv,collect(range(0,1,length=101)),:cartesian)

# The results can be used for plotting or for further evaluation.

plot(p1[1,:],p1[2,:],
    border=:box,
    label="crv",
    aspect_ratio=:equal,
    linewidth=2)
plot!(crv.coefs[1,:],crv.coefs[2,:],
      linestyle=:dash,
      markershape =:circle,
      markerstrokewidth = 0.5,
      label="crv.coefs",
      aspect_ratio=:equal)

# ## Surfaces
#
# The creation of NURBS surfaces is done similarly to the curves.
# The input for `nrbmak` with the difference that the points are defined with a
# 3D Array.
# The first and second dimension are used equally to the curve for U-direction
# and the third dimension stores the points in V direction.
#
# Since the process is similar we use a shortcut for creating the data
# structure.

srf = nrbtestsrf()

# The evaluation of the surface can also be done with `nrbeval()`.

p2 = nrbeval(srf,[collect(range(0,1,length=20)),
                  collect(range(0,1,length=20))]);

# The plotting of that data can be done with one of the many plotting
# ecosystems.

Plots.pyplot();
plot(p2[1,:,:],p2[2,:,:],p2[3,:,:],c = :jet,
     st=:surface,
     legend = nothing,
     camera=[-30,30])
plot!(srf.coefs[1,:,:],srf.coefs[2,:,:],srf.coefs[3,:,:],
      linewidth=0.5,
      st=:wireframe)

# The toolbox does also provide a plot wrapper `nrbplot()` for the ease of use.

nrbplot(srf,[10;10],
        c=:winter,
        legend = nothing,
        linewidth=0.5,
        camera=[-40,60],
        linecolor = :black)
ϑ = collect(range(0,stop=2*pi,length=180));
t = cat(cos.(ϑ),sin.(ϑ),dims=2) ./2 .+ 0.5
p3 = nrbeval(srf,permutedims(t,[2,1]));
plot!(p3[1,:],p3[2,:],p3[3,:] .- 2,linewidth=2)

# Note that this wrapper `nrbplot()` uses the `Plots.pyplot()` back end for 3D
# plots which limits the functionality to that ecosystem. One drawback e.g. is
# that the depth order/buffer is not considered between chained plot commands.
# This can be observed in the last plot. The curve is plotted `2` units bellow
# the surface.


# Code in this file inspired by NetworkX.

"""
    core_number(g)

Return the core number for each vertex in graph `g`.

A k-core is a maximal subgraph that contains vertices of degree `k` or more.
The core number of a vertex is the largest value `k` of a k-core containing
that vertex.

### Implementation Notes
Not implemented for graphs with self loops.

### References
* An O(m) Algorithm for Cores Decomposition of Networks,
    Vladimir Batagelj and Matjaz Zaversnik, 2003.
    http://arxiv.org/abs/cs.DS/0310049
"""
function core_number(g::AbstractGraph{T}) where T
    has_self_loops(g) && throw(ArgumentError("graph must not have self-loops"))
    degrees = degree(g)
    vs = sortperm(degrees)
    bin_boundaries = [1]
    curr_degree = 0
    for (i, v) in enumerate(vs)
        if degrees[v] > curr_degree
            append!(bin_boundaries, repeat([i], (degrees[v] - curr_degree)))
            curr_degree = degrees[v]
        end
    end
    vertex_pos = sortperm(vs)
    # initial guesses for core is degree
    core = degrees
    nbrs = [Set(all_neighbors(g, v)) for v in vertices(g)]
    for v in vs
        for u in nbrs[v]
            if core[u] > core[v]
                pop!(nbrs[u], v)
                pos = vertex_pos[u]
                bin_start = bin_boundaries[core[u] + 1]
                vertex_pos[u] = bin_start
                vertex_pos[vs[bin_start]] = pos
                vs[bin_start], vs[pos] = vs[pos], vs[bin_start]
                bin_boundaries[core[u] + 1] += 1
                core[u] -= 1
            end
        end
    end
    return core
end

"""
    k_core(g[, k]; corenum=core_number(g))

Return a vector of vertices in the k-core of graph `g`.
If `k` is not specified, return the core with the largest degree.

A k-core is a maximal subgraph that contains vertices of degree `k` or more.

### Implementation Notes
Not implemented for graphs with self loops.

### References
- An O(m) Algorithm for Cores Decomposition of Networks,
    Vladimir Batagelj and Matjaz Zaversnik, 2003.
    http://arxiv.org/abs/cs.DS/0310049
"""
function k_core(g::AbstractGraph, k=-1; corenum=core_number(g))
    if (k == -1)
        k = maximum(corenum) # max core
    end

    return findall(x -> x >= k, corenum)
end

"""
    k_shell(g[, k]; corenum=core_number(g))

Return a vector of vertices in the k-shell of `g`.
If `k` is not specified, return the shell of the core
with the largest degree.

The k-shell is the subgraph of vertices in the `k`-core but not in
the (`k+1`)-core. This is similar to `k_corona` but in that case
only neighbors in the k-core are considered.

### Implementation Notes
Not implemented for graphs with parallel edges or self loops.

### References
- A model of Internet topology using k-shell decomposition
   Shai Carmi, Shlomo Havlin, Scott Kirkpatrick, Yuval Shavitt,
   and Eran Shir, PNAS  July 3, 2007   vol. 104  no. 27  11150-11154
   http://www.pnas.org/content/104/27/11150.full
"""
function k_shell(g::AbstractGraph, k=-1; corenum=core_number(g))
    if k == -1
        k = maximum(corenum)
    end
    return findall(x -> x == k, corenum)
end

"""
    k_crust(g[, k]; corenum=core_number(g))

Return a vector of vertices in the k-crust of `g`.
If `k` is not specified, return the crust of the core with
the largest degree.

The k-crust is the graph `g` with the k-core removed.

### Implementation Notes
This definition of k-crust is different than the definition in References.
The k-crust in References is equivalent to the `k+1` crust of this algorithm.

Not implemented for graphs with self loops.

### References
- A model of Internet topology using k-shell decomposition
   Shai Carmi, Shlomo Havlin, Scott Kirkpatrick, Yuval Shavitt,
   and Eran Shir, PNAS  July 3, 2007   vol. 104  no. 27  11150-11154
   http://www.pnas.org/content/104/27/11150.full
"""
function k_crust(g, k=-1; corenum=core_number(g))
    if k == -1
        k = maximum(corenum) - 1
    end
    return findall(x -> x <= k, corenum)
end

"""
    k_corona(g, k; corenum=core_number(g))

Return a vector of vertices in the k-corona of `g`.

The k-corona is the subgraph of vertices in the k-core which
have exactly `k` neighbors in the k-core.

### Implementation Notes
Not implemented for graphs with parallel edges or self loops.

### References

- k-core (bootstrap) percolation on complex networks:
   Critical phenomena and nonlocal effects,
   A. V. Goltsev, S. N. Dorogovtsev, and J. F. F. Mendes,
   Phys. Rev. E 73, 056101 (2006)
   http://link.aps.org/doi/10.1103/PhysRevE.73.056101
"""
function k_corona(g::AbstractGraph, k; corenum=core_number(g))
    kcore = k_core(g, k)
    kcoreg = g[kcore]
    kcoredeg = degree(kcoreg)

    return kcore[findall(x-> x == k, kcoredeg)]
end


#=  Here I replicate a tiny example given in Azevedo and Leshno (2016). Representing each
    student profile as a unit square and highlighting the portions admitted to each school
    gives us insight into the one-to-one correspondence between market-clearing cutoffs
    and stable matches.                                                         =#

using Plots
using Plots.PlotMeasures
using DeferredAcceptance

function argsort(vec::AbstractArray{<:Real, 1})::AbstractArray{<:Real, 1}
    return invperm(sortperm(vec))
end

students = [1 2; 2 1]
students_dist = [1., 1.]

α = 0.75

capacities = rand(2)
capacities *= 2 * α / sum(capacities)

assn, rdist, cutoffs = nonatomicdeferredacceptance(students, students_dist, nothing, capacities,
                   verbose=true, return_cutoffs=true)

p = plot(xlims=(0, 1),
         ylims=(0, 1),
         xlabel="Score at school 1",
         ylabel="Score at school 2",
         layout=2,
         size=(800, 470),
         bottom_margin=70px,
         legend=nothing)

rect(x, y) = Shape([x, 1, 1, x], [y, y, 1, 1])

plot!([Shape([cutoffs[1], 1, 1, cutoffs[1]], [0, 0, 1, 1]),
       Shape([0, cutoffs[1], cutoffs[1], 0], [cutoffs[2], cutoffs[2], 1, 1])],
      color=[:dodgerblue, :olivedrab],
      title=students[:, 1],
      subplot=1)

plot!([Shape([cutoffs[1], 1, 1, cutoffs[1]], [0, 0, cutoffs[2], cutoffs[2]]),
       Shape([0, 1, 1, 0], [cutoffs[2], cutoffs[2], 1, 1])],
      color=[:dodgerblue, :olivedrab],
      stroke=0,
      title=students[:, 2],
      subplot=2)

annotate!(1.08, -.25, text("Correspondence between school cutoffs and\n"*
                           "stable assignments, after Azevedo and Leshno (2016)"), subplot=1)

# Equivalent discrete problem
n = 100     # Number of students in each profile
students = hcat(repeat([1, 2], 1, n), repeat([2, 1], 1, n))
scores = rand(n * 2, 2)
schools = mapslices(argsort, -scores, dims=1)
capacities = round.(Int, capacities .*= n)
@assert sum(capacities) == 3 * n / 2 "oops"

assn, dist = deferredacceptance(students, schools, capacities)

colors = [:dodgerblue, :olivedrab, :crimson]
markers = [:utriangle, :hexagon, :+]

q = plot(xlims=(0, 1),
         ylims=(0, 1),
         xlabel="Score at school 1",
         ylabel="Score at school 2",
         layout=2,
         size=(800, 470),
         bottom_margin=70px,
         legend=nothing)

scatter!(scores[1:n, 1],
         scores[1:n, 2],
         title=students[:, 1],
         color=colors[assn[1:n]],
         markershape=markers[assn[1:n]],
         msw=0,
         marker=markers[assn[1:n]],
         subplot=1)

scatter!(scores[n + 1:end, 1],
         scores[n + 1:end, 2],
         title=students[:, n + 1],
         color=colors[assn[n + 1:end]],
         markershape=markers[assn[n + 1:end]],
         msw=0,
         subplot=2)

annotate!(1.08, -.25, text("Correspondence between school cutoffs\n"*
                           "and stable assignments, discrete form"), subplot=1)

# savefig(p, string("examples/nonatomic/plot-cutoffs.pdf"))
# savefig(p, string("examples/nonatomic/plot-cutoffs.png"))
#
# savefig(q, string("examples/nonatomic/plot-discrete.pdf"))
# savefig(q, string("examples/nonatomic/plot-discrete.png"))


@testset "50.powx-n.jl" begin
    @test my_pow(2.0, 0) ≈ 1.0
    @test all(my_pow(2.0, i) ≈ 2.0^i for i in 1:20)
    @test all(my_pow(-2.0, i) ≈ (-2.0)^i for i in 1:20)
end


# defining an empty array
l = []

# pushing values into an empty container
push!(l, 1)             # push a value
push!(l, 2, 3)          # or push a couple values
append!(l, [4, 5, 6])   # or append a container

# defining an undefined array
arr1 = Array{Float64}(undef, 1)
arr2 = Array{Int}(undef, 1)

# defining an undefined array of different sizes
arr3 = Array{Real}(undef, 2, 3)         # 2D
arr4 = Array{Number}(undef, 5, 6, 7)    # 3D

# range sets
r = range(1, 10, length=100)    # step range
u = 1:.005:2                    # unit range

# Float64 of zeros and ones
z = zeros(10)
o = ones(5)

# list comprehension
l1 = [i for i in 1:20]
l2 = [2*i for i in 1:20]


# Code in this file inspired by NetworkX.

"""
    cycle_basis(g, root=nothing)

Return a list of cycles which form a basis for cycles of graph `g`, optionally starting at node `root`.

A basis for cycles of a network is a minimal collection of
cycles such that any cycle in the network can be written
as a sum of cycles in the basis.  Here summation of cycles
is defined as "exclusive or" of the edges. Cycle bases are
useful, e.g. when deriving equations for electric circuits
using Kirchhoff's Laws.

Example:
```jldoctest
julia> elist = [(1,2),(2,3),(2,4),(3,4),(4,1),(1,5)]
julia> g = SimpleGraph(SimpleEdge.(elist))
julia> cycle_basis(g)
2-element Array{Array{Int64,1},1}:
 [2, 3, 4]
 [2, 1, 3]
```

### References
* Paton, K. An algorithm for finding a fundamental set of cycles of a graph. Comm. ACM 12, 9 (Sept 1969), 514-518. [https://dl.acm.org/citation.cfm?id=363232]
"""
function cycle_basis(g::AbstractSimpleGraph, root=nothing)
    gnodes = Set(vertices(g))
    cycles = Vector{Vector{eltype(g)}}()
    while !isempty(gnodes)
        if root == nothing
            root = pop!(gnodes)
        end
        stack = [root]
        pred = Dict(root => root)
        keys_pred = Set(root)
        used = Dict(root => [])
        keys_used = Set(root)
        while !isempty(stack)
            z = pop!(stack)
            zused = used[z]
            for nbr in neighbors(g,z)
                if !in(nbr, keys_used)
                    pred[nbr] = z
                    push!(keys_pred, nbr)
                    push!(stack,nbr)
                    used[nbr] = [z]
                    push!(keys_used, nbr)
                elseif nbr == z
                    push!(cycles, [z])
                elseif !in(nbr, zused)
                    pn = used[nbr]
                    cycle = [nbr,z]
                    p = pred[z]
                    while !in(p, pn)
                        push!(cycle, p)
                        p = pred[p]
                    end
                    push!(cycle,p)
                    push!(cycles,cycle)
                    push!(used[nbr], z)
                end
            end
        end
        setdiff!(gnodes,keys_pred)
        root = nothing
    end
    return cycles
end


# A quadratic triangle, defined in 2D.
struct Triangle6_2D <: Face_2D
    # The points are assumed to be ordered as follows
    # p₁ = vertex A
    # p₂ = vertex B
    # p₃ = vertex C
    # p₄ = point on the quadratic segment from A to B
    # p₅ = point on the quadratic segment from B to C
    # p₆ = point on the quadratic segment from C to A
    points::SVector{6, Point_2D}
end

# Constructors
# -------------------------------------------------------------------------------------------------
Triangle6_2D(p₁::Point_2D, p₂::Point_2D, p₃::Point_2D,
             p₄::Point_2D, p₅::Point_2D, p₆::Point_2D
            ) = Triangle6_2D(SVector(p₁, p₂, p₃, p₄, p₅, p₆))

# Methods
# -------------------------------------------------------------------------------------------------
# Interpolation
function (tri6::Triangle6_2D)(r::Real, s::Real)
    # See Fhe Visualization Toolkit: An Object-Oriented Approach to 3D Graphics, 4th Edition
    # Chapter 8, Advanced Data Representation, in the interpolation functions section
    rₜ = Float64(r); sₜ = Float64(s)
    return (1 - rₜ - sₜ)*(2(1 - rₜ - sₜ) - 1)*tri6[1] +
                                   rₜ*(2rₜ-1)*tri6[2] +
                                   sₜ*(2sₜ-1)*tri6[3] +
                            4rₜ*(1 - rₜ - sₜ)*tri6[4] +
                                     (4rₜ*sₜ)*tri6[5] +
                            4sₜ*(1 - rₜ - sₜ)*tri6[6]
end

# Interpolation using a point instead of (r,s)
function (tri6::Triangle6_2D)(p::Point_2D)
    r,s = p
    return (1 - r - s)*(2(1 - r - s) - 1)*tri6[1] +
                                 r*(2r-1)*tri6[2] +
                                 s*(2s-1)*tri6[3] +
                           4r*(1 - r - s)*tri6[4] +
                                   (4r*s)*tri6[5] +
                           4s*(1 - r - s)*tri6[6]
end

area(tri6::Triangle6_2D) = area(tri6, Val(3))
function area(tri6::Triangle6_2D, ::Val{N}) where {N}
    # Numerical integration required. Gauss-Legendre quadrature over a triangle is used.
    # Let F(r,s) be the interpolation function for tri6,
    #                             1 1-r                          N
    # A = ∬ ||∂F/∂r × ∂F/∂s||dA = ∫  ∫ ||∂F/∂r × ∂F/∂s|| ds dr = ∑ wᵢ||∂F/∂r(rᵢ,sᵢ) × ∂F/∂s(rᵢ,sᵢ)||
    #      D                      0  0                          i=1
    #
    # N is the number of points used in the quadrature.
    w, r, s = gauss_legendre_quadrature(tri6, Val(N))
    return sum(@. w * abs( ∇(tri6, r, s) |> x->x[1] × x[2] ))
end

centroid(tri6::Triangle6_2D) = centroid(tri6, Val(6))
function centroid(tri6::Triangle6_2D, ::Val{N}) where {N}
    # Numerical integration required. Gauss-Legendre quadrature over a triangle is used.
    # Let F(r,s) be the interpolation function for tri6,
    #                             1 1-r                          N
    # A = ∬ ||∂F/∂r × ∂F/∂s||dA = ∫  ∫ ||∂F/∂r × ∂F/∂s|| ds dr = ∑ wᵢ||∂F/∂r(rᵢ,sᵢ) × ∂F/∂s(rᵢ,sᵢ)||
    #      D                      0  0                          i=1
    #
    # C_x = (∫∫ x dA)/A, C_y = (∫∫ y dA)/A
    #         D                  D
    w, r, s = gauss_legendre_quadrature(tri6, Val(N))
    # We can reuse our computed weighted derivative cross products, since we need these
    # in the C_y, C_y, and A.
    weighted_vals = @. w * abs( ∇(tri6, r, s) |> x->x[1] × x[2] )
    points = tri6.(r, s)
    A = sum(weighted_vals)
    C_x = sum(getindex.(points, 1) .* weighted_vals)
    C_y = sum(getindex.(points, 2) .* weighted_vals)
    return Point_2D(C_x/A, C_y/A)
end

function gradient(tri6::Triangle6_2D, r::Real, s::Real)
    # Let F(r,s) be the interpolation function for tri6
    # Returns ∂F/∂r, ∂F/∂s
    rₜ = Float64(r); sₜ = Float64(s)
    ∂F_∂r = (4rₜ + 4sₜ - 3)*tri6[1] +
                  (4rₜ - 1)*tri6[2] +
            4(1 - 2rₜ - sₜ)*tri6[4] +
                      (4sₜ)*tri6[5] +
                     (-4sₜ)*tri6[6]

    ∂F_∂s = (4rₜ + 4sₜ - 3)*tri6[1] +
                  (4sₜ - 1)*tri6[3] +
                     (-4rₜ)*tri6[4] +
                      (4rₜ)*tri6[5] +
            4(1 - rₜ - 2sₜ)*tri6[6]
    return ∂F_∂r, ∂F_∂s
end

function in(p::Point_2D, tri6::Triangle6_2D)
    # If the point is to the left of every edge
    #  3<-----2
    #  |     ^
    #  | p  /
    #  |   /
    #  |  /
    #  v /
    #  1
    return isleft(p, QuadraticSegment_2D(tri6[1], tri6[2], tri6[4])) &&
           isleft(p, QuadraticSegment_2D(tri6[2], tri6[3], tri6[5])) &&
           isleft(p, QuadraticSegment_2D(tri6[3], tri6[1], tri6[6]))
end

# # Slower method than above.
# function in(p::Point_2D, tri6::Triangle6_2D)
#     return in(p, tri6, 30)
# end
#
# function in(p::Point_2D, tri6::Triangle6_2D, N::Int64)
#     # Determine if the point is in the triangle using the Newton-Raphson method
#     # N is the max number of iterations of the method.
#     p_rs = real_to_parametric(p, tri6, N)
#     ϵ = parametric_coordinate_ϵ
#     # Check that the r coordinate and s coordinate are in [-ϵ,  1 + ϵ] and
#     # r + s ≤ 1 + ϵ
#     # These are the conditions for a valid point in the triangle ± some ϵ
#     if (-ϵ ≤ p_rs[1] ≤ 1 + ϵ) &&
#        (-ϵ ≤ p_rs[2] ≤ 1 + ϵ) &&
#        (p_rs[1] + p_rs[2] ≤ 1 + ϵ)
#         return true
#     else
#         return false
#     end
# end

function intersect(l::LineSegment_2D, tri6::Triangle6_2D)
    # Create the 3 quadratic segments that make up the triangle and intersect each one
    edges = SVector(QuadraticSegment_2D(tri6[1], tri6[2], tri6[4]),
                    QuadraticSegment_2D(tri6[2], tri6[3], tri6[5]),
                    QuadraticSegment_2D(tri6[3], tri6[1], tri6[6]))
    ipoints = MVector(Point_2D(), Point_2D(), Point_2D(),
                      Point_2D(), Point_2D(), Point_2D())
    n_ipoints = 0x00000000
    # We need to account for 6 points returned
    for k ∈ 1:3
        npoints, points = l ∩ edges[k]
        for i ∈ 1:npoints
            n_ipoints += 0x00000001
            ipoints[n_ipoints] = points[i]
        end
    end
    return n_ipoints, SVector(ipoints)
end

function jacobian(tri6::Triangle6_2D, r::Real, s::Real)
    # Return the 2 x 2 Jacobian matrix
    ∂F_∂r, ∂F_∂s = ∇(tri6, r, s)
    return SMatrix{2, 2}(∂F_∂r.x, ∂F_∂r.y,
                         ∂F_∂s.x, ∂F_∂s.y)
end

function real_to_parametric(p::Point_2D, tri6::Triangle6_2D)
    return real_to_parametric(p, tri6, 30)
end

function real_to_parametric(p::Point_2D, tri6::Triangle6_2D, N::Int64)
    # Convert from real coordinates to the triangle's local parametric coordinates using the
    # the Newton-Raphson method. N is the max number of iterations
    # If a conversion doesn't exist, the minimizer is returned.
    r = 0.3333333333333333 # Initial guess at triangle centroid
    s = 0.3333333333333333
    for i ∈ 1:N
        err = p - tri6(r, s)
        # Inversion is faster for 2 by 2 than \
        Δr, Δs = inv(jacobian(tri6, r, s)) * err
        r += Δr
        s += Δs
        if abs(Δr) + abs(Δs) < 1e-6
            break
        end
    end
    return Point_2D(r, s)
end

function triangulate(tri6::Triangle6_2D, N::Int64)
    # N is the number of divisions of each edge
    triangles = Vector{Triangle_2D}(undef, (N+1)*(N+1))
    if N === 0
        triangles[1] = Triangle_2D(tri6[1], tri6[2], tri6[3])
    else
        i = 1
        for S = 1:N, R = 0:N-S
            triangles[i]   = Triangle_2D(tri6(    R/(N+1),     S/(N+1)),
                                         tri6((R+1)/(N+1),     S/(N+1)),
                                         tri6(    R/(N+1), (S+1)/(N+1)))
            triangles[i+1] = Triangle_2D(tri6(    R/(N+1),     S/(N+1)),
                                         tri6((R+1)/(N+1), (S-1)/(N+1)),
                                         tri6((R+1)/(N+1),     S/(N+1)))
            i += 2
        end
        j = (N+1)*N + 1
        for S = 0:0, R = 0:N-S
            triangles[j] = Triangle_2D(tri6(    R/(N+1),     S/(N+1)),
                                       tri6((R+1)/(N+1),     S/(N+1)),
                                       tri6(    R/(N+1), (S+1)/(N+1)))
            j += 1
        end
    end
    return triangles
end

# Plot
# -------------------------------------------------------------------------------------------------
if enable_visualization
    function convert_arguments(LS::Type{<:LineSegments}, tri6::Triangle6_2D)
        q₁ = QuadraticSegment_2D(tri6[1], tri6[2], tri6[4])
        q₂ = QuadraticSegment_2D(tri6[2], tri6[3], tri6[5])
        q₃ = QuadraticSegment_2D(tri6[3], tri6[1], tri6[6])
        qsegs = [q₁, q₂, q₃]
        return convert_arguments(LS, qsegs)
    end

    function convert_arguments(LS::Type{<:LineSegments}, T::Vector{Triangle6_2D})
        point_sets = [convert_arguments(LS, tri6) for tri6 ∈ T]
        return convert_arguments(LS, reduce(vcat, [pset[1] for pset ∈ point_sets]))
    end

    function convert_arguments(P::Type{<:Mesh}, tri6::Triangle6_2D)
        triangles = triangulate(tri6, 13)
        return convert_arguments(P, triangles)
    end

    function convert_arguments(M::Type{<:Mesh}, T::Vector{Triangle6_2D})
        triangles = reduce(vcat, triangulate.(T, 13))
        return convert_arguments(M, triangles)
    end
end


using CairoMakie, Random
Random.seed!(113)
n = 100
x = randn(n)
y = cumsum(x) .- minimum(cumsum(x))
leftPoints =  Point2f.(tuple.(1, LinRange(minimum(y), maximum(y),n)))
rightPoints = Point2f.(tuple.(n, LinRange(minimum(y), maximum(y),n)))
fig = Figure(resolution = (600, 400))
ax = Axis(fig[1,1])
band!(ax, leftPoints, rightPoints, color = 1:length(leftPoints),
    colormap = :devon)
band!(ax, 1:n, y, fill(maximum(y), n); color = :white)
lines!(ax, y; color = :grey20)
foreach(ele -> translate!(ax.elements[ele], 0, 0, 9),
    [:ygridlines, :xgridlines])
save("bandHorizontal.png", fig, px_per_unit = 2)
fig


"""
    GeneralizedInverseGaussian(a, b, p)
The *Generalized Inverse Gaussian distribution* has probability density function
```math
f(x; a,b,p) = \\frac{(a/b)^{p/2}}{2K_p(\\sqrt{ab})}x^{(p-1)}e^{-(ax + b/x)/2}
x > 0
```

```julia
GeneralizedInverseGaussian(a,b,p)   # GeneralizedInverseGaussian distribution with parametrs a, b, and p

params(d)                           # Get the parameters, i.e. (a, b, p)
```

External Links

* [Generalized inverse Gaussian distribution on Wikipedia](https://en.wikipedia.org/wiki/Generalized_inverse_Gaussian_distribution)
* [Sampling implementation paper (Hörmann & Leydold)](https://doi.org/10.1007/s11222-013-9387-3)
* [Ratio-of-Uniforms with Mode Shift sampling (Dagpunar)](https://doi.org/10.1080/03610918908812785)
"""
struct GeneralizedInverseGaussian{T<:Real} <: ContinuousUnivariateDistribution
    a::T
    b::T
    p::T
    GeneralizedInverseGaussian{T}(a::T, b::T, p::T) where T = new{T}(a,b,p)
end

function GeneralizedInverseGaussian(a::T,b::T,p::T,check_args=true) where {T<:Real}
    check_args && @check_args(GeneralizedInverseGaussian, a > zero(a) && b > zero(b))
    return GeneralizedInverseGaussian{T}(a,b,p)
end

GeneralizedInverseGaussian(a::Real, b::Real, p::Real) = GeneralizedInverseGaussian(promote(a,b,p)...)
GeneralizedInverseGaussian(a::Integer, b::Integer, p::Integer) = GeneralizedInverseGaussian(float(a), float(b), float(p))

@distr_support GeneralizedInverseGaussian 0.0 Inf

#### Conversions
function convert(::Type{GeneralizedInverseGaussian{T}}, a::Real, b::Real, p::Real) where T<:Real
    GeneralizedInverseGaussian(T(a),T(b),T(p))
end

function convert(::Type{GeneralizedInverseGaussian{T}}, d::GeneralizedInverseGaussian{S}) where {T <: Real, S<: Real}
    GeneralizedInverseGaussian(T(d.a), T(d.b), T(d.p))
end

#### Parameters

params(d::GeneralizedInverseGaussian) = (d.a, d.b, d.p)
@inline partype(d::GeneralizedInverseGaussian{T}) where {T<:Real} = T

#### Statistics

mean(d::GeneralizedInverseGaussian) = ((a,b,p) = params(d); (sqrt(b) * besselk(p+1,sqrt(a*b))) / (sqrt(a) * besselk(p,sqrt(a*b))))

mode(d::GeneralizedInverseGaussian) = ((a,b,p) = params(d); ((p - 1) + sqrt((p - 1)^2 + a*b) / a))

function var(d::GeneralizedInverseGaussian)
    (a,b,p) = params(d)
    left = besselk(p+2,sqrt(a*b)) / besselk(p, sqrt(a*b))
    right = besselk(p+1,sqrt(a*b)) / besselk(p, sqrt(a*b))
    return (b/a) * (left - right^2)
end

#### Evaluation

function pdf(d::GeneralizedInverseGaussian{T}, x::Real) where {T<:Real}
    if x > 0
        (a,b,p) = params(d)
        top = (a/b)^(p/2)
        bot = 2*besselk(p,sqrt(a*b))
        right = x^(p-1) * exp(-(a*x + (b/x))/2)
        return (top/bot)*right
    end
    return zero(T)
end

cdf(d::GeneralizedInverseGaussian{T}, x::Real) where {T<:Real} = throw(MethodError(cdf, (d, x)))

function logpdf(d::GeneralizedInverseGaussian{T}, x::Real) where {T<:Real}
    if x > 0
        (a,b,p) = params(d)
        top = (p/2)*(log(a) - log(b))
        bot = log(2*besselk(p,sqrt(a*b)))
        left = (p - 1)*log(x)
        right = -(a*x + (b/x))/2
        return top - bot + left + right
    end
    return -T(Inf)
end

#### Sampling

#Extract a sample from the GeneralizedInverseGaussian distribution 'd'.
#The sampling procedure is implemented from [1].
#Different algorithms are used for the following scenarios:

#1. β > 1 or p > 1
#2. min(1/2, (2/3)*sqrt(1-p)) ≤ a*b ≤ 1 and abs(p) < 1
#3. 0 < a*b < min(1/2, (2/3)*sqrt(1-p)) and abs(p) < 1

#[1] Hörmann, W., Leydold, J. (2014).
#Generating generalized inverse Gaussian random variates.
#Stat Comput 24, 547–557.
#https://doi.org/10.1007/s11222-013-9387-3
function rand(rng::Random.AbstractRNG,d::GeneralizedInverseGaussian)
    (a,b,p) = params(d)
    α = sqrt(a/b)
    β = sqrt(a*b)
    λ = abs(p)
    if β > 1 || λ > 1
        x = sample_unif_mode_shift(λ,β)
    else
        β_bound = min(1/2, (2/3)*sqrt(1 - p))
        if β < 1 && β >= β_bound
            x = sample_unif_no_mode_shift(λ,β)
        elseif β < β_bound && β > 0
            x = concave_sample(λ,β)
        else
            throw(ArgumentError("None of the required conditions on the parameters are satisfied"))
        end
    end
    if p >= 0
        return x/α
    else
        return 1 / (α*x)
    end
end

#Sample the 2-parameter GeneralizedInverseGaussian distribution with parameters p and β using the Rejection method
#as described by Hörmann & Leydold (2014).
function concave_sample(p::Real,β::Real)
    m = β/((1 - p) + sqrt((1 - p)^2 + β^2))
    x_naut = β/(1 - p)
    x_star = max(x_naut,2/β)
    k1 = g(m,p,β)
    A1 = k1 * x_naut
    k2 = 0
    A2 = 0
    if x_naut < 2/β
        k2 = exp(-β)
        if p > 0
            A2 = k2 * ((2/β)^p - x_naut^p) / p
        else
            A2 = k2 * log(2/β^2)
        end
    end
    k3 = x_star^(p-1)
    A3 = 2 * k3 * exp(-x_star * β / 2) / β
    A = A1 + A2 + A3

    while true
        u = rand(Uniform(0,1))
        v = rand(Uniform(0,1))*A
        h = Inf
        if v <= A1
            x = x_naut * v / A1
            h = k1
        elseif v <= A1 + A2
            v = v - A1
            if p > 0
                x = (x_naut^p + (v*p/k2))^(1/p)
            else
                x = β*exp(v * exp(β))
            end
            h = k2 * x^(p-1)
        else
            v = v - (A1 + A2)
            x = -2 * log(exp(-x_star * β / 2) - (v * β) / (2 * k3)) / β
            h = k3 * exp(-x * β / 2)
        end
        if (u * h) <= g(x,p,β)
            return x
        end
    end
end

#Sample the 2-parameter GeneralizedInverseGaussian distribution with parameters p and β using the
#Ratio-of-Uniforms without mode shift as described by Hörmann & Leydold (2014).
function sample_unif_no_mode_shift(p::Real,β::Real)
    m = β/((1 - p) + sqrt((1 - p)^2 + β^2))
    x⁺= ((1 + p) + sqrt((1 + p)^2 + β^2))/β
    v⁺= sqrt(g(m,p,β))
    u⁺= x⁺ * sqrt(g(x⁺,p,β))
    while true
        u = rand(Uniform(0,1))*u⁺
        v = rand(Uniform(0,1))*v⁺
        x = u/v
        if v^2 <= g(x,p,β)
            return x
        end
    end
end

#Sample the 2-parameter GeneralizedInverseGaussian distribution with parameters p and β using the
#Ratio-of-Uniforms with mode shift as described by Hörmann & Leydold (2014) (originally from Dagpunar (1989)).
function sample_unif_mode_shift(p::Real,β::Real)
    m = (sqrt((p - 1)^2 + β^2) + (p-1)) / β
    a = -(2*(p+1)/β) - m
    b = (2*(p-1)/β) * m - 1
    p2 = b - (a^2)/3
    q = (2*a^3)/27 - (a*b/3) + m
    ϕ = acos(-(q/2) * sqrt(-27 / (p2^3)))
    x⁻ = sqrt((-4/3)*p2)*cos(ϕ/3 + (4/3)*π) - (a / 3)
    x⁺ = sqrt((-4/3)*p2)*cos(ϕ/3) - (a/3)
    v⁺ = sqrt(g(m,p,β))
    u⁻ = (x⁻ - m)*sqrt(g(x⁻,p,β))
    u⁺ = (x⁺ - m)*sqrt(g(x⁺,p,β))

    while true
        u = rand(Uniform(0,1))*(u⁺ - u⁻) + u⁻
        v = rand(Uniform(0,1))*v⁺
        x = (u / v) + m
        if x > 0 && v^2 <= g(x,p,β)
            return x
        end
    end
end

function g(x::Real,p::Real,β::Real)
    x^(p-1)*exp(-(β/2)*(x + (1/x)))
end


type LSTM{T} <: Layer{T}
    name::Symbol
    W::Matrix{T} # 4H×(D+H)
        # consists of Wx and Wh
        #  Wx: Input-to-hidden weights, of shape (4H, D)
        #  Wh: Hidden-to-hidden weights, of shape (4H, H)
    b::Vector{T} # 4H
    forget_bias::T

    input::Vector{T} # input, [D]
    state::Vector{T} # state (2H), contains [cell state, hidden state]

    # preallocated memory
    c::Vector{T} # [H] cell state
    m::Vector{T} # [H] member state
    cell_inputs::Vector{T}  # [D+H]
    lstm_mult_res::Vector{T} # [4H]
    i::Vector{T} # [H] input gate
    j::Vector{T} # [H] new input
    f::Vector{T} # [H] forget gate
    o::Vector{T} # [H] output gate
end
name(a::LSTM) = a.name
output{T}(a::LSTM{T}) = a.m
zero!{T}(a::LSTM{T}) = fill!(a.state, zero(T))
function Base.push!{T}(net::ForwardNet{T}, ::Type{LSTM},
    name::Symbol,
    parent_index::NameOrIndex,
    H::Int; # hidden layer size
    forget_bias::T = one(T),
    )

    input = output(net[parent_index])::Vector{T}
    D = length(input)

    W = Array{T}(4H, D+H)
    b = Array{T}(4H)
    state = Array{T}(2H)

    c = Array{T}(H)
    m = Array{T}(H)
    cell_inputs = Array{T}(D+H)
    lstm_mult_res = Array{T}(4H)
    i = Array{T}(H)
    j = Array{T}(H)
    f = Array{T}(H)
    o = Array{T}(H)

    node = LSTM(name, W, b, forget_bias, input, state, c, m, cell_inputs, lstm_mult_res, i, j, f, o)
    push!(net, node, parent_index)
end
function forward!{T}(a::LSTM{T})

    H = length(a.c)
    D = length(a.input)

    copy!(a.c, 1, a.state, 1, H)
    copy!(a.m, 1, a.state, H+1, H)

    copy!(a.cell_inputs, 1, a.input, 1, D)
    copy!(a.cell_inputs, D+1, a.m, 1, H)

    # lstm_matrix = a.W * a.cell_inputs + a.b
    copy!(a.lstm_mult_res, a.b) # y ← b
    Base.LinAlg.BLAS.gemv!('N', one(T), a.W, a.cell_inputs, one(T), a.lstm_mult_res) # y ← W*x + y

    copy!(a.i, 1, a.lstm_mult_res,    1, H)
    copy!(a.j, 1, a.lstm_mult_res,  H+1, H)
    copy!(a.f, 1, a.lstm_mult_res, 2H+1, H)
    copy!(a.o, 1, a.lstm_mult_res, 3H+1, H)

    # compute c_next and m_next
    for k in 1 : H
        a.f[k] += a.forget_bias
        c = sigmoid(a.f[k])*a.c[k] + sigmoid(a.i[k])*tanh(a.j[k]) # c_next
        a.state[k] = a.c[k] = c
        a.state[k+H] = a.m[k] = sigmoid(a.o[k])*tanh(c) # m_next
    end

    a
end
function restore!{T}(a::LSTM{T}, filename_W::String, filename_b::String)
    H = length(a.c)
    D = length(a.input)

    vec = open(io->read_binary_vec(io, T), filename_W)
    a.W[:] = convert_to_column_major_array(vec, (D+H,4H))'

    copy!(a.b, open(read_binary_vec, filename_b))

    a
end

#=
u = f(W_xu * x + W_hu*h_{t-1} + b_u)
r = f(W_xr * x + W_hr*h_{t-1} + b_r)
c = tanh(W_xc * x + (W_hc*h_{t-1}) .* r + b_c)
h_t = (1-u) .* h_{t-1} + u .* c
=#
type GRU{T} <: Layer{T}
    name::Symbol
    W_x::Matrix{T} # 3H×D
    W_h::Matrix{T} # 3H×H
    b::Vector{T} # 3H

    input::Vector{T} # input, [D]

    # preallocated memory
    xb_ruc::Vector{T}  # [3H]
    xb_r::Vector{T} # [H]
    xb_u::Vector{T} # [H]
    xb_c::Vector{T} # [H]

    h_ruc::Vector{T} # [3H]
    h_r::Vector{T} # [H]
    h_u::Vector{T} # [H]
    h_c::Vector{T} # [H]

    r::Vector{T} # [H] input gate
    u::Vector{T} # [H] new input
    c::Vector{T} # [H] forget gate
    h::Vector{T} # [H] GRU state
    h_prev::Vector{T} # [H] previous state
end
name(a::GRU) = a.name
output{T}(a::GRU{T}) = a.h
zero!{T}(a::GRU{T}) = fill!(a.h_prev, zero(T))
function Base.push!{T}(net::ForwardNet{T}, ::Type{GRU},
    name::Symbol,
    parent_index::NameOrIndex,
    H::Int; # hidden layer size
    )

    input = output(net[parent_index])::Vector{T}
    D = length(input)

    W_x = Array{T}(3H, D)
    W_h = Array{T}(3H, H)
    b = Array{T}(3H)

    xb_ruc = Array{T}(3H)
    xb_r = Array{T}(H)
    xb_u = Array{T}(H)
    xb_c = Array{T}(H)

    h_ruc = Array{T}(3H)
    h_r = Array{T}(H)
    h_u = Array{T}(H)
    h_c = Array{T}(H)

    r = Array{T}(H)
    u = Array{T}(H)
    c = Array{T}(H)
    h = Array{T}(H)
    h_prev = Array{T}(H)

    node = GRU(name, W_x, W_h, b, input, xb_ruc, xb_r, xb_u, xb_c, h_ruc, h_r, h_u, h_c, r, u, c, h, h_prev)
    push!(net, node, parent_index)
end

function forward!{T}(a::GRU{T})

    H = length(a.h_prev)
    D = length(a.input)

    copy!(a.xb_ruc, a.b) # y ← b
    Base.LinAlg.BLAS.gemv!('N', one(T), a.W_x, a.input, one(T), a.xb_ruc) # y ← W*x + y

    copy!(a.h_ruc, Base.LinAlg.BLAS.gemv('N', one(T), a.W_h, a.h_prev)) # y ← W*x

    copy!(a.xb_r, 1, a.xb_ruc,    1, H)
    copy!(a.xb_u, 1, a.xb_ruc,  H+1, H)
    copy!(a.xb_c, 1, a.xb_ruc, 2H+1, H)

    copy!(a.h_r, 1, a.h_ruc,    1, H)
    copy!(a.h_u, 1, a.h_ruc,  H+1, H)
    copy!(a.h_c, 1, a.h_ruc, 2H+1, H)

    # compute u, r, c, and h
    for k in 1 : H
        a.r[k] = sigmoid(a.xb_r[k] + a.h_r[k])
        a.u[k] = sigmoid(a.xb_u[k] + a.h_u[k])
        a.c[k] = tanh(a.xb_c[k] + a.r[k]*a.h_c[k])
        h_old = a.h[k]
        a.h[k] = (1 - a.u[k])*a.h_prev[k] + a.u[k]*a.c[k]
        a.h_prev[k] = h_old
    end

    a
end
function restore!{T}(a::GRU{T}, filename_Wx::String, filename_Wh::String, filename_b::String)
    H = length(a.h_prev)
    D = length(a.input)

    vec = open(io->read_binary_vec(io, T), filename_Wx)
    a.W_x[:] = convert_to_column_major_array(vec, (D,3H))'

    vec = open(io->read_binary_vec(io, T), filename_Wh)
    a.W_h[:] = convert_to_column_major_array(vec, (H,3H))'

    copy!(a.b, open(read_binary_vec, filename_b))

    a
end


export
    Creation,
    create!,
    create,
    Annihilation,
    annihilate!,
    annihilate,

    ComplexVec,
    α,
    ξ,

    Displacement,
    displace!,

    Squeezing,
    squeeze!

############
# a† and a #
############

"""
    Creation(; dim=DIM)

Creation operator in matrix representation

``\\hat{a}^{\\dagger}``
"""
Creation(T::Type{<:Number}; dim=DIM) = diagm(-1 => sqrt.(T.(1:dim-1)))
Creation(; dim=DIM) = Creation(ComplexF64, dim=dim)

"""
    create!(state::AbstractState)

Apply creation operator on the quantum state.

# Examples
```jldoctest
julia> state = VacuumState();

julia> create!(state);

julia> vec(state) == vec(SinglePhotonState())
true
```
"""
function create!(state::StateVector{T}) where {T}
    dim = state.dim
    state.v = Creation(T, dim=dim) * state.v

    return state
end

function create!(state::StateMatrix{T}) where {T}
    dim = state.dim
    𝐜 = Creation(T, dim=dim)
    state.𝛒 = 𝐜 * state.𝛒 * 𝐜'

    return state
end

"""
    create(state::AbstractState)

Apply creation operator on the new instance of the quantum state.

# Examples
```jldoctest
julia> state = VacuumState();

julia> new_state = create(state);

julia> vec(state) == vec(VacuumState())
true

julia> vec(new_state) == vec(SinglePhotonState())
true
```
"""
create(state::AbstractState) = create!(copy(state))

"""
    Annihilation(; dim=DIM)

Annihilation operator in matrix representation

``\\hat{a}``
"""
Annihilation(T::Type{<:Number}; dim=DIM) = diagm(1 => sqrt.(T.(1:dim-1)))
Annihilation(; dim=DIM) = Annihilation(ComplexF64, dim=dim)

"""
    annihilate!(state::AbstractState)

Apply annihilation operator on the quantum state.

# Examples
```jldoctest
julia> state = SinglePhotonState();

julia> annihilate!(state);

julia> vec(state) == vec(VacuumState())
true
```
"""
function annihilate!(state::StateVector{T}) where {T}
    dim = state.dim
    state.v = Annihilation(T, dim=dim) * state.v

    return state
end

function annihilate!(state::StateMatrix{T}) where {T}
    dim = state.dim
    𝐚 = Annihilation(T, dim=dim)
    state.𝛒 = 𝐚 * state.𝛒 * 𝐚'

    return state
end

"""
    annihilate!(state::AbstractState)

Apply annihilation operator on the new instance of the quantum state.

# Examples
```jldoctest
julia> state = SinglePhotonState();

julia> new_state = annihilate(state);

julia> vec(state) == vec(SinglePhotonState())
true

julia> vec(new_state) == vec(VacuumState())
true
```
"""
annihilate(state::AbstractState) = annihilate!(copy(state))

###########
# α and ξ #
###########

"""
    ComplexVec{T <: Real}(r::T, θ::T)

Vector in polar coordinate for complex plane.

``v = r e^{-i\\theta}``
"""
struct ComplexVec{T<:Real}
    r::T
    θ::T
end

function Base.show(io::IO, complexvec::ComplexVec{T}) where {T}
    print(io, "ComplexVec{$T}($(complexvec.r)exp(-$(complexvec.θ)im))")
end

z(complexvec::ComplexVec) = complexvec.r * exp(-im * complexvec.θ)

"""
    α(r::Real, θ::Real)

Eigenvalue of annihilation operator.

``\\hat{a} | \\alpha \\rangle = \\alpha | \\alpha \\rangle``

# Examples
```jldoctest
julia> α(1.5, π/4)
ComplexVec{Float64}(1.5exp(-0.7853981633974483im))
```
"""
α(T::Type{<:Real}, r::Real, θ::Real) = ComplexVec{T}(T(r), T(θ))
α(r, θ) = α(Float64, r, θ)

"""
    ξ(r::Real, θ::Real)

# Examples
```jldoctest
julia> ξ(1.5, π/4)
ComplexVec{Float64}(1.5exp(-0.7853981633974483im))
```
"""
const ξ = α

################
# displacement #
################

"""
    Displacement(α::ComplexVec{<:Real}; dim=DIM)

Displacement operator in matrix representation

``\\hat{D}(\\alpha) = exp(\\alpha \\hat{a}^{\\dagger} - \\alpha^{*} \\hat{a})``
"""
function Displacement(T::Type{<:Number}, α::ComplexVec; dim=DIM)
    return exp(z(α) * Creation(T, dim=dim) - z(α)' * Annihilation(T, dim=dim))
end

Displacement(α::ComplexVec; dim=DIM) = Displacement(ComplexF64, α, dim=dim)

"""
    displace!(state::AbstractState)

Apply displacement operator on the quantum state.

# Examples
```jldoctest
julia> state = VacuumState();

julia> displace!(state, α(5., π/4));

julia> vec(state) == vec(CoherentState(α(5., π/4)))
true
```
"""
function displace!(state::StateVector{T}, α::ComplexVec) where {T}
    dim = state.dim
    state.v = Displacement(T, α, dim=dim) * state.v

    return state
end

function displace!(state::StateMatrix{T}, α::ComplexVec) where {T}
    dim = state.dim
    𝐝 = Displacement(T, α, dim=dim)
    state.𝛒 = 𝐝 * state.𝛒 * 𝐝'

    return state
end

#############
# squeezing #
#############

"""
    Squeezing(ξ::ComplexVec{<:Real}; dim=DIM)

Squeezing operator in matrix representation

``\\hat{S}(\\xi) = exp(\\frac{1}{2} (\\xi^{*} \\hat{a}^{2} - \\xi \\hat{a}^{\\dagger 2}))``
"""
function Squeezing(T::Type{<:Number}, ξ::ComplexVec; dim=DIM)
    return exp(0.5 * z(ξ)' * Annihilation(T, dim=dim)^2 - 0.5 * z(ξ) * Creation(T, dim=dim)^2)
end

Squeezing(ξ::ComplexVec; dim=DIM) = Squeezing(ComplexF64, ξ, dim=dim)

"""
    squeeze!(state::AbstractState)

Apply squeezing operator on the quantum state.

# Examples
```jldoctest
julia> state = VacuumState();

julia> squeeze!(state, α(0.5, π/4));

julia> vec(state) == vec(SqueezedState(ξ(0.5, π/4)))
true
```
"""
function squeeze!(state::StateVector{T}, ξ::ComplexVec) where {T}
    dim = state.dim
    state.v = Squeezing(T, ξ, dim=dim) * state.v

    return state
end

function squeeze!(state::StateMatrix{T}, ξ::ComplexVec) where {T}
    dim = state.dim
    𝐬 = Squeezing(T, ξ, dim=dim)
    state.𝛒 = 𝐬 * state.𝛒 * 𝐬'

    return state
end

###################
# BHD measurement #
###################

##### for arb. state in intensity-to-measurement-phase quadrature coordinate #####

# |θ, x⟩ = ∑ₙ |n⟩ ⟨n|θ, x⟩ = ∑ₙ ψₙ(θ, x) |n⟩
# ⟨n|θ, x⟩ = ψₙ(θ, x) = exp(im n θ) (2/π)^(1/4) exp(-x^2) Hₙ(√2 x)/√(2^n n!)
function ψₙ(n::Integer, θ::Real, x::Real)
    return (2/π)^(1/4) * exp(im*n*θ - x^2) * hermiteh(n, sqrt(2)x) / sqrt(2^n * factorial(n))
end

function 𝛑̂!(result::Matrix{<:Complex}, θ::Real, x::Real; dim=DIM)
    view(result, :, 1) .= ψₙ.(big(0):big(dim-1), θ, x)
    result .= view(result, :, 1) * view(result, :, 1)'

    return result
end

function 𝛑̂(T::Type{<:Complex}, θ::Real, x::Real; dim=DIM)
    result = Matrix{T}(undef, dim, dim)

    return 𝛑̂!(result, θ, x, dim=dim)
end

𝛑̂(θ::Real, x::Real; dim=DIM) = 𝛑̂(ComplexF64, θ, x, dim=dim)

##### for Gaussian state in intensity-to-measurement-phase quadrature coordinate #####

# π̂ₓ = (â exp(-im θ) + â† exp(im θ)) / 2

tr_mul(𝐚, 𝐛) = sum(𝐚[i, :]' * 𝐛[:, i] for i in 1:size(𝐚, 1))
create_μ(state::StateMatrix{T}) where {T} = tr_mul(Creation(T, dim=state.dim), state.𝛒)
create²_μ(state::StateMatrix{T}) where {T} = tr_mul(Creation(T, dim=state.dim)^2, state.𝛒)
annihilate_μ(state::StateMatrix{T}) where {T} = tr_mul(Annihilation(T, dim=state.dim), state.𝛒)
annihilate²_μ(state::StateMatrix{T}) where {T} = tr_mul(Annihilation(T, dim=state.dim)^2, state.𝛒)
create_annihilate_μ(state::StateMatrix{T}) where {T} = tr_mul(
    Creation(T, dim=state.dim) * Annihilation(T, dim=state.dim),
    state.𝛒
)

# ⟨π̂ₓ²⟩ = ⟨ââ exp(-2im θ) + â†â† exp(2im θ) + ââ† + â†â⟩ / 4
# ⟨π̂ₓ²⟩ = (exp(-2im θ)⟨â²⟩ + exp(2im θ)⟨â†²⟩ + 1 + 2⟨ââ†⟩) / 4
# here, ⟨ââ† + â†â⟩ = 1 + 2⟨ââ†⟩ due to the commutation relation
function π̂ₓ²_μ(θs::AbstractVector{<:Number}, state::StateMatrix)
    return (
        exp.(-2im*θs) .* annihilate²_μ(state) .+
        exp.(2im*θs) .* create²_μ(state) .+
        1 .+ 2create_annihilate_μ(state)
    ) ./ 4
end

# ⟨π̂ₓ⟩ = ⟨â exp(-im θ) + â† exp(im θ)⟩ / 2
# ⟨π̂ₓ⟩ = (exp(-im θ)⟨â⟩ + exp(im θ)⟨â†⟩) / 2
function π̂ₓ_μ(θs::AbstractVector{<:Number}, state::StateMatrix)
    return (
        exp.(-im*θs) .* annihilate_μ(state) .+
        exp.(im*θs) .* create_μ(state)
    ) ./ 2
end


# coding: utf-8

# In[1]:

#i would recommend you to read algorithm and data structure before messing with hashtable
# http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html
#this script features the hash function described in the book, which is mod eleven %11
#we apply hash function to all the values in a given list
#we shall obtain the output value for each element
#and assuming this is a perfect hash function
#each value from the list should have a unique hash value
#we can create a dictionary based on the unique hash value
#and assign all the values from the list into dictionary
#when we wanna search for something
#we just need to apply hash function to the target
#and make query on the dictionary for that particular hash value
#O(1), simple AF, not really...
#we could encounter the same hash value for different values from the list
#this is so called hash collision
#there are three ways of solving hash collision in the book
#which are chaining, linear probing and quadratic probing
#lets deliberately create an imperfect hash function to see how it goes

# In[2]:

arr=[21,55,89,67,12,12]

# In[3]:

#hash chaining
#the easiest one
#basically stack all the collision together
#form a list under that hash value
#does it make search easier?
#not really, if the list under one hash value gets too large
#it would slow down the search
#the good thing about this method is that you always have all the values in the dictionary
#for other methods, you either have to increase hash table size or drop values

# In[4]:

#the first function is to create a dictionary
#assign values from the list to the dictionary based on hash value
function create_hashtable(arr,default=11)

    #create hashtable
    hashtable=Dict()

    #initialize
    for i in 0:(default-1)
        hashtable[i]=[]
    end

    #add value into hashtable
    #using folding method
    for i in arr
        push!(hashtable[i%default],i)
    end

    return hashtable
end

# In[5]:

#now its the search part
#we just apply hash function on target and get hash value
#we look up the hash value in dictionary
function hash_search(target,arr,default=11)

    #get hashtable
    hashtable=create_hashtable(arr,default)

    #find value through hash value
    if target in hashtable[target%default]
        return true
    else
        return false
    end
end

# In[6]:

hash_search(12,arr)

# In[7]:

#linear probing
#when collision occurs, we try to find the next empty slot to store it
#it sounds reasonable but it is also trouble
#what if we run through the whole dictionary
#and there is no slot?
#we can choose to drop the values
#or we can reset the hash function or expand the dictionary
#in the best case, it is faster than chaining
#in the worst case, it is slower

# In[8]:

#the first function is to create a dictionary
#assign values from the list to the dictionary based on hash value
function create_hashtable(arr,default=11)

    #create hashtable
    hashtable=Dict()

    #unwanted is a temporary list to append collision items
    unwanted=[]

    #keep track of values with no allocation
    badhash=[]

    #initialize
    for i in 0:(default-1)
        hashtable[i]=""
    end

    #add value into hashtable
    for i in arr
        if hashtable[i%default]==""
            hashtable[i%default]=i
        else
            #make sure every collision will be tracked
            push!(unwanted,i)
        end
    end

    for i in unwanted

        #c is a counter
        #in the inner loop
        #c is to determine whether we have gone through the entire list
        c=0
        modnumber=i%default
        stop=false

        while !stop && c<=default-1

            #when modnumber exceeds ten, we return it to 0
            #alternatively we can use mod eleven %11
            modnumber+=1
            if modnumber>default-1
                modnumber=0
            end

            #when the next slot isnt empty, we keep iterating
            if hashtable[modnumber]==""
                hashtable[modnumber]=i
                stop=true
            end

            c+=1
        end

        #make sure that we will print out those items which didnt get assigned
        if !stop
            push!(badhash,i)
        end
    end

    #if the hashing is imperfect, we print out badhash list
    if !isempty(badhash)
        println(badhash)
    end

    return hashtable
end

# In[9]:

#the search part is very similar to the chaining one
function hash_search(target,arr,default=11)

    #get hashtable
    hashtable=create_hashtable(arr,default)

    #find value through hash value
    modnumber=target%default
    if target in hashtable[modnumber]
        return true
    else

        #when we cannot find the value at hash value
        #we begin our linear probing
        #its the same process as the hash function
        #except we only need to return T/F
        counter=0

        while counter<=default-1
            modnumber+=1

            if modnumber>default-1
                modnumber=0
            end

            if target==hashtable[modnumber]
                return true
            end
            counter+=1
        end
    end
    return false
end

# In[10]:

hash_search(89,arr)

# In[11]:

#quadratic probing
#it sounds math intensive with the word quadratic
#as a matter of fact, it is simple AF
#we just replace the add one method with add quadratic values

# In[12]:

#the first function is to create a dictionary
#assign values from the list to the dictionary based on hash value
function create_hashtable(arr,default=11)

    #create hashtable
    hashtable=Dict()

    unwanted=[]
    badhash=[]

    #initialize
    for i in 0:(default-1)
        hashtable[i]=""
    end

    #add value into hashtable
    for i in arr
        if hashtable[i%default]==""
            hashtable[i%default]=i
        else
            push!(unwanted,i)
        end
    end

    for i in unwanted
        c=1
        modnumber=i%default
        stop=false

        #the loop is basically the same as linear probing
        #except we add quadratic value
        #note that its quite difficult
        #to determine whether we have been through the entire list
        #so i still set counter limit at 10
        while !stop && c<=default

            modnumber+=c^2

            #note that i use mod eleven %11 when iteration exceeds hash table size
            if modnumber>default-1
                modnumber=modnumber%default
            end

            if hashtable[modnumber]==""
                hashtable[modnumber]=i
                stop=true
            end

            c+=1
        end

        if !stop
            push!(badhash,i)
        end
    end

    if !isempty(badhash)
        println(badhash)
    end

    return hashtable
end

# In[13]:

#the search is basically the same as linear probing
#except linear part is substituted with quadratic
function hash_search(target,arr,default=11)

    #get hashtable
    hashtable=create_hashtable(arr,default)

    #find value through hash value
    modnumber=target%default
    if target in hashtable[modnumber]
        return true
    else
        counter=1

        while counter<=default
            modnumber+=counter^2

            if modnumber>default-1
                modnumber=modnumber%default
            end

            if target==hashtable[modnumber]
                return true
            end
            counter+=1
        end
    end
    return false
end

# In[14]:

hash_search(78,[21,55,89,67,12,12,12,12,12,12,12,12,12,12,78])

# In[15]:

#we get False in the end
#its quite interesting that for the same hash value 67,12,78
#we can store 67 in hash table but not 78
#67 and 12 are processed earlier than 78
#quadratic probing doesnt iterate through all slots
#all empty slots we can iterate have been occupied by the time we reach 78


#Bootstrap confidence band for univariate nonparametric regression
function bootstrapCB(xdata::RealVector, ydata::RealVector;
    xeval::RealVector=xdata, B::Int64=500, reg::Function=locallinear, lb::Real=-Inf, ub::Real=Inf, kernel::Function=gaussiankernel, h::Real=-Inf, alpha::Real=0.05)

    n=length(xdata)
    y_matrix=zeros(B, length(xeval))
    cb=zeros(2, length(xeval))

    xdata, xeval, kernel = boundit(xdata, xeval, kernel, lb, ub)
    if h <= 0
        h = bwreg(xdata, ydata, reg, kernel)
    end

    yhat=reg(xdata, ydata, xeval=xdata, h=h, kernel=kernel)
	#in the wild bootstrap setting, new error terms are generated from a distribution with 0 mean, equal second and third moment
	#usually work well unless the error terms are extremly small, then the cb may not cover prediction.
    e = ydata .- yhat
    coef=[(1-sqrt(5))/2,(1+sqrt(5))/2]

    for b in 1:B
        boot_coef=coef[rand(Categorical([(5+sqrt(5))/10, 1-(5+sqrt(5))/10]), n)]
        boot_e=boot_coef .* e
        boot_y=yhat .+ boot_e
        y_matrix[b, :]=reg(xdata, boot_y, xeval=xeval, h=h, kernel=kernel)
    end
    for i in 1:length(xeval)
        cb[:, i]=quantile(y_matrix[:, i], [alpha/2, 1-alpha/2])
    end
    cb
end

# #Bootstrap confidence band for multivariate nonparametric regression
# function BootstrapCB(B::Int64, xeval::Matrix{Float64},
#   xdata::Matrix{Float64}, ydata::Vector{Float64}, reg::Function=LP0, kernel::Function=GaussianKernel,
# h::Vector{Float64}=BandwidthLSCVReg(xdata,ydata,reg,kernel))

#   (n, p)=size(xdata)
#   (m,p1 )= size(xeval)
#   if p != p1
#     error("xeval should have same dimension as xdata")
#   end

#   y_matrix=zeros(B, m)
#   cb=zeros(2, m)

#   yhat=reg(xdata, xdata, ydata, kernel, h)
#   mhat=reg(xdata, xdata, ydata, kernel, h)
#   e = ydata .- yhat
#   coef=[-1,0,2]

#   for b in 1:B
# #     print(b, "\t")
#     boot_coef=coef[rand(Categorical([1/3, 1/2, 1/6]), n)]
#     boot_e=boot_coef .* e
#     boot_y=mhat .+ boot_e
#     y_matrix[b, :]=reg(xeval, xdata, boot_y,kernel, h)
#   end
#   for i in 1:m
#     cb[:, i]=quantile(y_matrix[:, i], [.975, .025])
#   end
#   cb
# end


@doc raw"""
Axisymmetric tokamak equilibrium in (r,θ,ϕ) coordinates with covariant
components of the vector potential given by
```math
A (r, \theta, \phi) = B_0 \, \bigg( 0 , \, \frac{r R_0}{\cos (\theta)} - \bigg( \frac{R_0}{\cos (\theta)} \bigg)^2 \, \ln \bigg( \frac{R}{R_0} \bigg) , \, - \frac{r^2}{2 q_0} \bigg)^T ,
```
resulting in the magnetic field with covariant components
```math
B (r, \theta, \phi) = \frac{B_0}{q_0} \, \bigg( 0 , \, \frac{r^2}{R}, \, q_0 R_0 \bigg)^T ,
```
where $R = R_0 + r \cos \theta$.

Parameters:
 * `R₀`: position of magnetic axis
 * `B₀`: B-field at magnetic axis
 * `q₀`: safety factor at magnetic axis
"""
module AxisymmetricTokamakToroidal

    using RecipesBase

    import ..ElectromagneticFields
    import ..ElectromagneticFields: AnalyticEquilibrium, code

    export  AxisymmetricTokamakToroidalEquilibrium

    const DEFAULT_R₀ = 1.0
    const DEFAULT_B₀ = 1.0
    const DEFAULT_q₀ = 2.0

    struct AxisymmetricTokamakToroidalEquilibrium{T <: Number} <: AnalyticEquilibrium
        name::String
        R₀::T
        B₀::T
        q₀::T

        function AxisymmetricTokamakToroidalEquilibrium{T}(R₀::T, B₀::T, q₀::T) where T <: Number
            new("AxisymmetricTokamakEquilibriumToroidal", R₀, B₀, q₀)
        end
    end

    AxisymmetricTokamakToroidalEquilibrium(R₀::T=DEFAULT_R₀, B₀::T=DEFAULT_B₀, q₀::T=DEFAULT_q₀) where T <: Number = AxisymmetricTokamakToroidalEquilibrium{T}(R₀, B₀, q₀)

    function init(R₀=DEFAULT_R₀, B₀=DEFAULT_B₀, q₀=DEFAULT_q₀)
        AxisymmetricTokamakToroidalEquilibrium(R₀, B₀, q₀)
    end

    function ITER()
        AxisymmetricTokamakToroidalEquilibrium(ITER_R₀, ITER_B₀, ITER_q₀)
    end

    macro code(R₀=DEFAULT_R₀, B₀=DEFAULT_B₀, q₀=DEFAULT_q₀)
        code(init(R₀, B₀, q₀); escape=true)
    end

    macro code_iter()
        code(ITER(); escape=true)
    end

    function Base.show(io::IO, equ::AxisymmetricTokamakToroidalEquilibrium)
        print(io, "Axisymmetric Tokamak Equilibrium in Toroidal Coordinates with\n")
        print(io, "  R₀ = ", equ.R₀, "\n")
        print(io, "  B₀ = ", equ.B₀, "\n")
        print(io, "  q₀ = ", equ.q₀)
    end

    r(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = x[1]
    θ(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = x[2]
    ϕ(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = x[3]
    R(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = equ.R₀ + r(x,equ) * cos(θ(x,equ))
    X(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = R(x,equ) * cos(ϕ(x,equ))
    Y(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = R(x,equ) * sin(ϕ(x,equ))
    Z(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = r(x,equ) * sin(θ(x,equ))

    ElectromagneticFields.J(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = r(x,equ) * R(x,equ)

    ElectromagneticFields.A₁(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = + equ.B₀ * equ.R₀ * ( Z(x,equ) / R(x,equ) * cos(θ(x,equ)) - log(R(x,equ) / equ.R₀) * sin(θ(x,equ)) ) / 2
    ElectromagneticFields.A₂(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = - equ.B₀ * equ.R₀ * ( Z(x,equ) / R(x,equ) * sin(θ(x,equ)) + log(R(x,equ) / equ.R₀) * cos(θ(x,equ)) ) * r(x,equ) / 2
    ElectromagneticFields.A₃(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = + equ.B₀ * r(x,equ)^2 / equ.q₀ / 2

    ElectromagneticFields.x¹(ξ::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = X(ξ,equ)
    ElectromagneticFields.x²(ξ::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = Y(ξ,equ)
    ElectromagneticFields.x³(ξ::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = Z(ξ,equ)

    ElectromagneticFields.ξ¹(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = sqrt((sqrt(x[1]^2 + x[2]^2)-equ.R₀)^2 + x[3]^2)
    ElectromagneticFields.ξ²(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = atan(x[3], sqrt(x[1]^2 + x[2]^2)-equ.R₀)
    ElectromagneticFields.ξ³(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = atan(x[2], x[1])

    ElectromagneticFields.g₁₁(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = one(eltype(x))
    ElectromagneticFields.g₂₂(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = r(x, equ)^2
    ElectromagneticFields.g₃₃(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium) = R(x, equ)^2

    ElectromagneticFields.get_functions(::AxisymmetricTokamakToroidalEquilibrium) = (X=X, Y=Y, Z=Z, R=R, r=r, θ=θ, ϕ=ϕ)

    function ElectromagneticFields.periodicity(x::AbstractVector, equ::AxisymmetricTokamakToroidalEquilibrium)
        p = zero(x)
        p[2] = 2π
        p[3] = 2π
        return p
    end

    @recipe function f(equ::AxisymmetricTokamakToroidalEquilibrium;
                       nx = 100, ny = 120, levels = 50, size = (400,400),
                       xlims = (  0.5 * equ.R₀,   1.5 * equ.R₀),
                       ylims = (- 0.5 * equ.R₀, + 0.5 * equ.R₀))

        xgrid = LinRange(xlims[1], xlims[2], nx)
        zgrid = LinRange(ylims[1], ylims[2], ny)
        rgrid = [ElectromagneticFields.ξ¹([xgrid[i], 0.0, zgrid[j]], equ) for i in eachindex(xgrid), j in eachindex(zgrid)]
        θgrid = [ElectromagneticFields.ξ²([xgrid[i], 0.0, zgrid[j]], equ) for i in eachindex(xgrid), j in eachindex(zgrid)]
        pot   = [ElectromagneticFields.A₃([rgrid[i,j], θgrid[i,j], 0.0], equ) / xgrid[i] for i in eachindex(xgrid), j in eachindex(zgrid)]

        seriestype   := :contour
        aspect_ratio := :equal
        size   := size
        xlims  := xlims
        ylims  := ylims
        levels := levels
        legend := :none

        (xgrid, zgrid, pot')
    end

end


"""
    NegativeMonomials(n) = [ x-> x^-k for k = 1:n ]
"""
struct NegativeMonomials <: AbstractBasis
    n::Int
end

Base.length(B::NegativeMonomials) = B.n

# approxpoints(B::NegativeMonomials) = TODO
# approxtransform(B::NegativeMonomials,f) = TODO

function iterate_basis(B::NegativeMonomials, x)
    length(B) == 0 && return nothing
    p = inv(x)
    return p,(2,p)
end
function iterate_basis(B::NegativeMonomials, x, (i,p))
    i > length(B) && return nothing
    p = x\p
    return p,(i+1,p)
end

evaluate_basis(B::NegativeMonomials,i,x) = x^-i


my_f(x,y) = 2x+3y
derivative_of_my_f(x,y) = ForwardDiff.derivative(x->my_f(x,y),x)


__precompile__()

module QuickShiftClustering

isinstalled(a) = isa(Pkg.installed(a), VersionNumber)
isinstalled("PyPlot") && begin
    using PyPlot
end

using NearestNeighbors, ProgressMeter, Distances, FunctionalData

export quickshift, quickshiftlabels, quickshiftplot

mutable struct QuickShift
    rootind
    links
    sigma
end

function dist(a::Array{Float32,2}, i::Int, b::Array{Float32,2}, j::Int)
    sum = 0f0
    for d = 1:size(a,1)
        sum += (a[d,i]-b[d,j])^2
    end
    sum
end

function link(i, inds, G, data)
    mindist = typemax(eltype(data))
    minind = 0
    for n = inds
        if G[n]>G[i]
            d = dist(data,i,data,n)
            if d < mindist
                mindist = d
                minind = n::Int
            end
        end
    end
    minind, mindist
end

function gauss(data, n, ind, factor1, factor2)
    s = 0f0
    for m = ind
        s += exp(-dist(data,n,data,m)*factor1)
    end
    factor2 * s
end

quickshift(data, a...) = quickshift(convert(Array{Float32,2},data), a...)
quickshift(data::Array{Float32,2}, sigma) = quickshift(data, convert(Float32,sigma))
function quickshift(data::Array{Float32,2}, sigma::Float32=convert(Float32,median(pairwise(Euclidean(), randsample(data,1000)))/100))
    tree = KDTree(data)
    # @show sigma
    N = len(data)
    factor1 = 1f0 / (2*sigma^2) ::Float32
    factor2 = 1/(2*pi*sigma^2*N)
    G = zeros(Float32, 1, N)
    nninds = [Array(Int,0) for n in 1:N]
    @showprogress 1 "Computing kernel distances ... " for n = 1:N
        knnind = @p knn tree vec(at(data,n)) 100 true | fst
        ind = length(knnind) > 10 ? knnind : 1:N
        nninds[n] = ind
        G[n] = gauss(data, n, ind, factor1, factor2)
    end
    # println("median lenghts:",(@p map nninds length | flatten | median))
    links = [Any[] for i in 1:N]
    rootind = -1
    inflength = typemax(eltype(G))
    Nrange = 1:N
    minind = 0
    mindist = inflength
    @showprogress 1 "Linking ... " for i in 1:N
        for inds = [nninds[i]; Nrange]
            minind, mindist = link(i, inds, G, data)
            if minind != 0
                break
            end
        end
        if mindist == inflength
            if rootind < 0
                rootind = i
            else
                push!(links[rootind], (sqrt(mindist), i))
            end
        else
            push!(links[minind], (sqrt(mindist), i))
        end
    end
    QuickShift(rootind, links, sigma)
end

function quickshiftlabels(a::QuickShift, maxlength = 10*a.sigma)
    labels = zeros(Int32,length(a.links))
    cut_internal(a.rootind, a.links, labels, maxlength, 1, 2)
    labels
end

function cut_internal(ind, links, labels, maxlength, label, maxlabel)
    labels[ind] = label
    for x in links[ind]
        if x[1] > maxlength
            maxlabel += 1
        end
        maxlabel = max(label, cut_internal(x[2], links, labels, maxlength, x[1] > maxlength ? maxlabel : label, maxlabel))
    end
    maxlabel
end

function quickshiftplot(a::QuickShift, data, labels)
    if !isdefined(:PyPlot)
        error("quickshiftplot needs PyPlot installed and loaded using 'using PyPlot'")
    end
    if size(data,1) != 2
        error("quickshiftplot only works on 2D data, i.e. size(data,1)==2")
    end

    for i = 1:len(a.links)
        from = data[:,i]
        for x = a.links[i]
            to = data[:,x[2]]
            p = hcat(from,to)'
            plot(fst(p),snd(p),"b-")
        end

    end
    scatter(data[1,:],data[2,:], c = Array{Int, 1}(labels), edgecolor = "none")
end

end


module Weno
export weno!,WenoData
using LinearAlgebra
#using Devectorize
#===================================================
Weno 5  method in 1d.
===================================================#
struct WenoData
    c::Array{Float64,2}
    dright::Array{Float64,1}
    dleft::Array{Float64,1}
    b0::Float64
    b1::Float64
    epsilon::Float64
    size::Int32
    InC::Array{Float64,1}
    right::Array{Float64,1}
    left::Array{Float64,1}
    reconstructed::Array{Float64,1}
    work::Array{Float64,1}
    alpharight::Array{Float64,1}
    alphaleft::Array{Float64,1}
    beta::Array{Float64,1}
    WenoData(size)=new(transpose(reshape(
        [11.0/6.0,-7.0/6.0,1.0/3.0,1.0/3.0,5.0/6.0,-1.0/6.0,
         -1.0/6.0,5.0/6.0,1.0/3.0,1.0/3.0,-7/6.0,11.0/6.0],3,4)),
               Array([3.0/10.0,3.0/5.0,1.0/10.0]),
               Array([1.0/10.0,3.0/5.0,3.0/10]),
               13.0/12.0,
               1.0/4.0,
                   1.0e-6,
                   size,
                   Array{Float64}(undef,size+4), #InC
                   Array{Float64}(undef,3),Array{Float64}(undef,3),#right,left
                   Array{Float64}(undef,2*size+8),#reconstructed
                   Array{Float64}(undef,size+2),#work
                   Array{Float64}(undef,3),Array{Float64}(undef,3),#alpha{right,left}
                   Array{Float64}(undef,3)#beta
               )

end

function weno!(W,F,L,In::Array{Float64},Out::Array{Float64})

    size=W.size

    h1 = -1.0/(L/size)

    # build an extended array with phantom cells to deal with periodicity:
    W.InC[1]=In[size-1]
    W.InC[2]=In[size]

    # same computing time (on my machine) with A and B:

    # A:
    @views W.InC[3:2+size]=In[:] #slower!
    # B:
    # @simd for i=1:size
    #    W.InC[2+i]=In[i]
    # end
    W.InC[size+3]=In[1]
    W.InC[size+4]=In[2]
    # precompute for regularity coefficients:
    @simd for vol=1:size+2
        W.work[vol]= (W.InC[vol]-2.0*W.InC[vol+1]+W.InC[vol+2])^2
    end
    # lets's start computation:
    @views for vol= 3:2+size
        # @simd for r= 0:2
        #     W.left[r+1] =dot(W.c[r+1,:],W.InC[vol-r:vol-r+2])
        #     W.right[r+1]=dot(W.c[r+2,:],W.InC[vol-r:vol-r+2])
        # end

        # faster than above, as it does not allocate memory:----
        for r=0:2
            W.left[r+1]=0.0
            W.right[r+1]=0.0
            k=vol-r-1
            @simd for i=1:3
                 W.left[r+1]+=W.c[r+1,i]*W.InC[k+i]
            end
            @simd for i=1:3
                W.right[r+1]+=W.c[r+2,i]*W.InC[k+i]
            end
        end
        #-------------------------------------------
        # regularity coefficients
        W.beta[1]=W.b0* W.work[vol]+
	W.b1*(3.0*W.InC[vol]-4.0*W.InC[vol+1]+W.InC[vol+2])^2

        W.beta[2]=W.b0* W.work[vol-1]+
        W.b1*(W.InC[vol-1]-W.InC[vol+1])^2

        W.beta[3]=W.b0*W.work[vol-2]+
	W.b1*(W.InC[vol-2]-4.0*W.InC[vol-1]+3*W.InC[vol])^2

        sleft=0.0
        sright=0.0
        @simd for r=1:3
            s=1.0/(W.epsilon+W.beta[r])^2
            W.alphaleft[r]=W.dleft[r]*s
            W.alpharight[r]=W.dright[r]*s
            sleft+=W.alphaleft[r]
            sright+=W.alpharight[r]
        end

        recleft=dot(W.alphaleft,W.left)
        recright=dot(W.alpharight,W.right)
        W.reconstructed[2*vol-1]  = recleft/sleft
        W.reconstructed[2*vol]= recright/sright
    end

    W.reconstructed[2*size+5:2*size+8]=W.reconstructed[5:8]
    #Numerical flux at boundaries of volumes:
    @simd for vol in 1:size
        W.work[vol+1]=F(W.reconstructed[2*vol+4], W.reconstructed[2*vol+5])
    end

    W.work[1]=W.work[size+1]
    # result:
    @simd for vol in 1:size
        Out[vol]=h1*(W.work[vol+1]-W.work[vol])
    end
    # slower:
    #@. Out[1:size] = h1*(W.work[2:size+1]-W.work[1:size])
    nothing
end

end


a = 1
for b = 2:20
  global a
  a = Int(a*b / gcd(a, b))
end
println(a)


export
    Quadrature,
    quadrature,
    quadrature_normal

"""
Lightweight wrapper for the nodes and weights of a quadrature. Also
include the domain.
"""
immutable Quadrature{T <: Real}
    domain::Interval{T}
    nodes::Vector{T}
    weights::Vector{T}
end

function Base.show(io::IO, q::Quadrature)
    print(io, "Quadrature of $(length(q.nodes)) points on $(q.domain)")
end

"""
Integrate a univariate function using the quadrature.
"""
function (q::Quadrature)(f)
    sum(weight*f(node) for (node, weight) in zip(q.nodes, q.weights))
end

@recipe function f{T <: Quadrature}(q::T)
    seriestype --> :scatter
    ylim --> (0, 1.05*maximum(q.weights))
    q.nodes, q.weights
end

"""
Return a quadrature rule for calculating an integral using the density
function of distribution `d`, truncated to `domain`.

Uses Legendre quadrature on a finite interval.
"""
function quadrature(n::Int, distribution::Distribution{Univariate,Continuous},
                    domain::Interval)
    @assert isfinite(domain)
    a, b = domain.lo, domain.hi
    y_nodes, weights = gausslegendre(n)
    x_nodes = fma.(y_nodes, (b-a)/2, middle(a,b))
    Quadrature(domain, x_nodes, # FIXME: remove [] around distribution in 0.6
               normalize!(pdf.([distribution], x_nodes) .* weights, 1))
end

function quadrature{T <: Distribution{Univariate,Continuous}}(n::Int, d::Truncated{T,Continuous})
    quadrature(n, d.untruncated, d.lower..d.upper)
end

"""
Return a quadrature that integrates a function under the `Normal(μ,σ)`
distribution using `N` nodes.
"""
function quadrature_normal(N::Int, μ, σ)
    nodes, weights = gausshermite(N)
    Quadrature(-Inf..Inf, nodes*√2*σ + μ, weights/√π)
end


import FiniteDiff
import Calculus
import ForwardDiff

# TODO: derivative, second_derivative

include("../test_functions.jl")
include("../gradient_functions.jl")
include("../hessian_functions.jl")
include("../max_error.jl")

io = open("errors.tsv", "w")

@printf(io, "timestamp\tf\tx\tmethod\terr\tlatency\n")

for test_func in test_funcs
    for x1 in test_func.x1s
        for x2 in test_func.x2s
            x = [x1, x2]

            g_true = ForwardDiff.gradient(test_func.f, x)
            h_true = ForwardDiff.hessian(test_func.f, x)

            # TODO: Make these latencies less crazy for the first run of each
            # function.
            for grad_func in grad_funcs
                latency = @elapsed g_approx = grad_func.f(test_func.f, x)
                @printf(
                    io,
                    "%s\t%s\t%s\t%s\t%s\t%s\n",
                    Dates.now(),
                    test_func.name,
                    x,
                    grad_func.name,
                    max_error(g_true, g_approx),
                    latency,
                )
            end

            for hess_func in hess_funcs
                latency = @elapsed h_approx = hess_func.f(test_func.f, x)
                @printf(
                    io,
                    "%s\t%s\t%s\t%s\t%s\t%s\n",
                    Dates.now(),
                    test_func.name,
                    x,
                    hess_func.name,
                    max_error(h_true, h_approx),
                    latency,
                )
            end
        end
    end
end

close(io)


# Computes the function cel(kc,p,a,b) from Bulirsch (1969).
function cel(k2, p, a, b)
    @assert k2 ≤ 1
    ca = sqrt(eps(k2))
    # Avoid undefined k2=1 case:
    if k2 != 1.0
        kc = sqrt(1.0 - k2)
    else
        kc = eps(k2)
    end
    return cel(k2, kc, p, a, b)
end

# Version called with kc (this is to improve precision of computation):
function cel(k2, kc, p, a, b)
    @assert k2 ≤ 1
    ca = sqrt(eps(k2))
    # Avoid undefined k2=1 case:
    if k2 == 1.0 || kc == 0.0
        kc = eps(k2)
    end
    # Initialize values:
    ee = kc
    m = 1.0
    if p > 0.0
        p = sqrt(p)
        pinv = inv(p)
        b *= pinv
    else
        q = k2
        g = 1.0 - p
        f = g - k2
        q *= (b - a * p)
        ginv = inv(g)
        p = sqrt(f * ginv)
        a = (a - b) * ginv
        pinv = inv(p)
        b = -q * ginv^2 * pinv + a * p
    end
# Compute recursion:
    f = a
    a += b * pinv
    g = ee * pinv
    b += f * g
    b += b
    p += g
    g = m
    m += kc
    iter = 0; itmax = 50
    while abs(g - kc) > g * ca && iter < itmax
        kc = sqrt(ee)
        kc += kc
        ee = kc * m
        f = a
        pinv = inv(p)
        a += b * pinv
        g = ee * pinv
        b += f * g
        b += b
        p += g
        g = m
        m += kc
        iter += 1
    end
    if iter == itmax
        @error "k2 $k2 kc $kc abs(g-kc) $(abs(g - kc)) g*ca $(g * ca) cel $(0.5 * π * (a1 * m + b1) / (m * (m + p)))"
    end
    return 0.5 * π * (a * m + b) / (m * (m + p))
end

# Vector version to attempt to improve speed for multiple elliptic integrals with same k_c:
function cel(k2, kc, p, a1, a2, a3, b1, b2, b3)
    # This assumes first value of a and b uses p; the rest have p=1.
    @assert k2 ≤ 1
    ca = sqrt(eps(k2))
    # Avoid undefined k2=1 case:
    if k2 == 1.0 || kc == 0.0
        kc = eps(k2)
    end
    # Initialize values:
    ee = kc
    m = 1.0
    if p > 0.0
        p = sqrt(p)
        pinv = inv(p)
        b1 *= pinv
    else
        q = k2
        g = 1.0 - p
        f = g - k2
        q *= (b1 - a1 * p)
        ginv = inv(g)
        p = sqrt(f * ginv)
        a1 = (a1 - b1) * ginv
        pinv = inv(p)
        b1 = -q * ginv^2 * pinv + a1 * p
    end
    # Compute recursion:
    f1 = a1
    # First compute the first integral with p:
    a1 += b1 * pinv
    g = ee * pinv
    b1 += f1 * g
    b1 += b1
    p += g
    # Next, compute the remainder with p = 1:
    p1 = 1
    g1 = ee
    f2 = a2
    f3 = a3
    a2 += b2
    b2 += f2 * g1
    b2 += b2
    a3 += b3
    b3 += f3 * g1
    b3 += b3
    p1 += g1
    g = m
    m += kc
    iter = 0
    itmax = 50
    while (abs(g - kc) > g * ca) && iter < itmax
        kc = sqrt(ee)
        kc += kc
        ee = kc * m
        f1 = a1
        f2 = a2
        f3 = a3
        pinv = inv(p)
        pinv1 = inv(p1)
        a1 += b1 * pinv
        a2 += b2 * pinv1
        a3 += b3 * pinv1
        g = ee * pinv
        g1 = ee * pinv1
        b1 += f1 * g
        b2 += f2 * g1
        b3 += f3 * g1
        b1 += b1
        b2 += b2
        b3 += b3
        p  += g
        p1 += g1
        g  = m
        m += kc
        iter += 1
    end
    if iter == itmax
        @error "k2 $k2 kc $kc abs(g-kc) $(abs(g - kc)) g*ca $(g * ca) cel $(0.5 * π * (a1 * m + b1) / (m * (m + p)))"
    end
    f1 = 0.5 * π * (a1 * m + b1) / (m * (m + p))
    f2 = 0.5 * π * (a2 * m + b2) / (m * (m + p1))
    f3 = 0.5 * π * (a3 * m + b3) / (m * (m + p1))
    return f1, f2, f3
end


"""
    function grm(gt; nz=0.)
---
This is an instance of Van Raden method I.
Genotypes are of value 0, 1 or 2, and of ID column majored.
They should be stored per ID per column.
This function uses vanRaden method I.
I do no genotype and frequency check here,
As it should be done somewhere else.
"""
function grm(g; nz=0.)
    twop = mean(g, dims = 2)
    s2pq = (1 .- .5 .* twop)'twop # one element array
    r2pq = 1. / s2pq[1]
    Z = g .- twop
    G = Z'Z .* r2pq
    !iszero(nz) && (G += nz .* I)
    G
end


using MittagLefflerFunctions
import MittagLefflerFunctions.Quadrature.MLQuad
using PyPlot

α_vals = [ 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0 ]
β = 1.0
G(t) = (1-t)/(1+t)
t = range(-1, 1, length=400)
x = G.(t)
Eα = Vector{MLQuad}(undef, 10)

style = ("-", "--", ":", "-.", "-")

figure(1)
for j = 1:5
    α = α_vals[j]
    Eα[j] = MLQuad(α, β, 12, :hyperbola)
    plot(t, Eα[j].(-x), style[j], label="α = $α")
end
grid(true)
legend()
xlabel(L"$t$", fontsize=12)
savefig("figure12L.pdf")

figure(2)
s = range(0, 1, length=400)
t = 2 * (s.^2) .- 1
x = G.(t)
for j = 6:10
    α = α_vals[j]
    Eα[j] = MLQuad(α, β, 12, :hyperbola)
    plot(t[2:end], Eα[j].(-x[2:end]), style[j-5], label="α = $α")
end
grid(true)
legend()
xlabel(L"$t$", fontsize=12)
savefig("figure12R.pdf")


@testset "hermite polynomials" begin
  x = -5:0.5:5
  @test hermite.(0, x) ≈ ones(size(x))
  @test hermite.(1, x) ≈ x
  @test hermite.(2, x) ≈ @. x^2 - 1
  @test hermite.(3, x) ≈ @. x^3 - 3 * x
  @test hermite.(4, x) ≈ @. x^4 - 6 * x^2 + 3
  @test hermite.(5, x) ≈ @. x^5 - 10 * x^3 + 15 * x
  @test hermite.(6, x) ≈ @. x^6 - 15 * x^4 + 45 * x^2 - 15
  @test hermite.(7, x) ≈ @. x^7 - 21 * x^5 + 105 * x^3 - 105 * x
  @test hermite.(8, x) ≈ @. x^8 - 28 * x^6 + 210 * x^4 - 420 * x^2 + 105
  @test hermite.(9, x) ≈ @. x^9 - 36 * x^7 + 378 * x^5 - 1260 * x^3 + 945 * x
  @test hermite.(10, x) ≈ @. x^10 - 45 * x^8 + 630 * x^6 - 3150 * x^4 + 4725 * x^2 - 945
end

@testset "HermiteBasis dimensions" begin
  # all have at most length 3, so should not depend higher-order components
  indices = [[0, 1, 2], [3, 4, 1], [1, 1, 1], [0], [1, 2], [5, 8]]
  basis = HermiteBasis(indices)
  @test length(basis) == length(indices)
  for _=1:100
    theta_base = randn(3)
    theta_useless = randn(5)
    theta = [theta_base..., theta_useless...]
    theta_prime = [theta_base..., similar(theta_useless)...]
    @test basis(theta) ≈ basis(theta_prime)
  end
end

function finite_diff(f, epsilon, i)
  function diff(x)
    epsilon_vec = zeros(length(x))
    epsilon_vec[i] = epsilon
    (f(x .+ epsilon_vec)[i] - f(x)[i]) / epsilon
  end
end

@testset "TransportFamily{HermiteBasis} inversion" begin
  # create a typical Hermite family
  T = TransportFamily([
    HermiteBasis([[0], [1], [2], [3]]),
    HermiteBasis([[0, 1], [1, 0], [1, 1]]),
    HermiteBasis([[0, 0, 2], [0, 2, 0], [2, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]),
  ])
  r = randn(3)
  while true
    # sample a gamma that suggests the map is monotonic
    gamma = [randn(4), randn(3), randn(6)]
    Tgam = T(gamma)
    points = map(_ -> randn(3), 1:1000)
    if !all(i -> all(point -> finite_diff(Tgam, 1e-5, i)(point) > 1e-8, points), 1:3)
      continue
    end

    # with such a gamma, it is very likely the map is invertible: test inversion
    @test Tgam(inverse(Tgam, r)) ≈ r
    break
  end
end


using Base.Test
using celerite

omega = 2pi/12.203317
alpha_r = [1.0428542, 0.38361831, 0.30345984/2, 0.30345984/2]
alpha_i = [0. ,               0. , 0.1, 0.1]
beta = [complex(0.1229159,0.0),complex(0.48922908,0.0),complex(0.09086397,omega),complex(0.09086397,-omega)]
nt = 512
#t = collect(linspace(0,nt-1,nt))
t = sort!(rand(nt))
acf = zeros(nt)
p = length(alpha_r)

# First solve in traditional manner:
w = 0.03027 * ones(nt)
A = zeros(Float64,nt,nt)
for i=1:nt
  A[i,i] += w[i]
  for j=1:nt
    for k=1:p
      A[i,j] += alpha_r[k] * exp(-real(beta[k])*abs(t[j]-t[i]))*cos(imag(beta[k])*(t[j]-t[i]))
      A[i,j] += alpha_i[k] * exp(-real(beta[k])*abs(t[j]-t[i]))*sin(abs(imag(beta[k])*(t[j]-t[i])))
    end
  end
end

# Compute a realization of correlated noise with this covariance matrix.
y=randn(nt)
# First, do Cholesky decomposition:
sqrta = chol(A)
# Now make a realization of the correlated noise:
corrnoise=*(transpose(sqrta),y);

# Now, solve for A*y = x to get inverse of Kernel (A) times the correlated noise:
x2 = \(A,corrnoise)

# Take dot product with correlated noise, and compare with original noise realization:
@test_approx_eq dot(y,y) dot(corrnoise,x2)
println("Dot product of white noise:                          ",dot(y,y))
println("Dot product of correlated noise with inverse kernel: ",dot(corrnoise,x2))

# Now use Ambikarasan O(N) method:
y = corrnoise
n = nt
alpha_final = [1.0428542, 0.38361831, 0.30345984]
alpha_imag =  [0. ,  0. ,  0.2]
beta_real_final = [0.1229159,0.48922908,0.09086397]
beta_imag_final = [0.0,0.0,omega]
w0 = 0.03027
p_final = 3
p0_final = 2
nex_final = (4(p_final-p0_final)+2p0_final+1)*(n-1)+1
m1_final = 2(p_final-p0_final)+p0_final+2
if p0_final == p_final
  m1_final = p0_final + 1
end
width_final = 2m1_final+1
aex_final = zeros(Float64,width_final,nex_final)
al_small_final = zeros(Float64,m1_final,nex_final)
indx_final= collect(1:nex_final)
tic()
logdeta_final = compile_matrix_symm(alpha_final,alpha_imag,beta_real_final,beta_imag_final,w0,t,nex_final,aex_final,al_small_final,indx_final)
tic()
bex = zeros(Float64,nex_final)
log_like_final = compute_likelihood(p_final,p0_final,y,aex_final,al_small_final,indx_final,logdeta_final,bex)

# Now select solution:
#x1 = zeros(n)
x3 = zeros(n)
log_like3 = 0.0
for i=1:n
  log_like3 += x2[i]*y[i]
end
log_like3 = -0.5 * log_like3
log_like3 += -0.5*logdet(A) - 0.5*n*log(2*pi)

@test_approx_eq logdeta_final logdet(A)
@test_approx_eq log_like_final log_like3
println("Log Determinant: ",logdeta_final," ",logdet(A))
println("Log Likelihood:  ",log_like_final," ",log_like3)


## Elementary Rand Variables
## =========================

abstract type ElementaryRandVar{T} <: SymbolicRandVar{T} end
dims(X::ElementaryRandVar) = union([Set(X.dim), map(dims, args(X))...]...)::Set{Int}
has_single_dim(::ElementaryRandVar) = true
num_params{T <: ElementaryRandVar}(X::Type{T}) = length(fieldnames(X)) - 1

abstract type ClosedFormQuantileRandVar{T} <: ElementaryRandVar{T} end

## Continuous RandVars
## ===================

"Arcsine"
immutable ArcsineRandVar{T <: Real, A <: SymbolicRandVar, B <: SymbolicRandVar} <: ElementaryRandVar{T}
  dim::Id
  a::A
  b::B
  ArcSineRandVar{T1<:Real}(dim::Id, a::SymbolicRandVar{T1}, b::SymbolicRandVar{T1}) = new{T1, T1, T1}(dim, a, b)
end

"Uniformly distributed RandVar"
immutable UniformRandVar{T <: Real, A <: Real, B <: Real} <: ClosedFormQuantileRandVar{T}
  dim::Id
  lb::SymbolicRandVar{A}
  ub::SymbolicRandVar{B}
end

quantile_expr(x::UniformRandVar) = (x.lb - x.ub) * omega_component(x.dim) + x.lb

args(X::UniformRandVar) = tuple(X.lb, X.ub)

# "Normally distributed RandVar"
# immutable NormalRandVar{T <: Real, A <: Real, B <: Real} <: ElementaryRandVar{T}
#   dim::Id
#   μ::SymbolicRandVar{A}
#   σ::SymbolicRandVar{B}
# end
#
# # param_types(X::Type{NormalRandVar}) = [Float64, Float64]
#
# args(X::NormalRandVar) = tuple(X.μ, X.σ)

# "Beta distributed RandVar"
# immutable BetaRandVar{T <: Real, A <: SymbolicRandVar, B <: SymbolicRandVar} <: ElementaryRandVar{T}
#   dim::Id
#   α::A
#   β::B
# end

## Discrete Distritbuions
## ======================
"Bernoulli Distribution"
immutable BernoulliRandVar{Bool, P <: Real} <: ClosedFormQuantileRandVar{Bool}
  dim::Id
  p::SymbolicRandVar{P}
end

args(X::BernoulliRandVar) = tuple(X.p)
quantile_expr(X::BernoulliRandVar) = X.p >= omega_component(X.dim)

# # FIXME: Closed form?
# "Binomial RandVar"
# immutable BinomialRandVar{T <: Integer, N <: Integer, P <: Real} <: ElementaryRandVar{T}
#   dim::Id
#   n::SymbolicRandVar{N}
#   p::SymbolicRandVar{P}
# end
#
# args(X::BinomialRandVar) = tuple(X.n, X.p)

# "Categorical Distribution"
# immutable CategoricalRandVar{T <: Integer, p::}
# end

"Discrete Uniform RandVar"
immutable DiscreteUniformRandVar{T <: Integer, A <: Integer, B <: Integer} <: ElementaryRandVar{T}
  dim::Id
  a::SymbolicRandVar{A}
  b::SymbolicRandVar{B}
end

args(X::DiscreteUniformRandVar) = tuple(X.a, X.b)

# "Uniformly distributed RandVar"
# immutable PoissonRandVar{T <: Real, A <: Real}
#   dim::Id
#   λ::SymbolicRandVar{A}
# end

# args(X::PoissonRandVar) = tuple(X.λ)


function toric_ed(A)
    d, n = size(A)
    @polyvar t[1:d] y[1:n] u[1:n]

    φ = map(j -> prod(i -> t[i]^A[i,j], 1:d), 1:n)
    Dφ = [differentiate(φ[i], t[j]) for i=1:n, j=1:d]

    F = [φ + y - u; Dφ' * y]

    # We sample a random t, find a normal vector on this point and then assemble
    # an u where we know the solution to.
    t_rand = randn(Float64, size(A, 1))
    null = nullspace(map(fi -> fi(t => t_rand), Dφ'))
    y_rand = null * randn(Float64, size(null, 2))
    u₀ = map(fi -> fi(t => t_rand), φ) + y_rand

    x₀ = complex.([t_rand; y_rand])

    F, u, u₀, x₀
end

@testset "Monodromy" begin

    @testset "monodromy_solve" begin
        F, p, p₀, x₀ = toric_ed([3 2 1 0; 0 1 2 3])

        # test that timeout works
        Random.seed!(51232)
        result = monodromy_solve(F, x₀, p₀, parameters=p, target_solutions_count=21, timeout=1e-12)
        @test length(result.solutions) < 21

        result = monodromy_solve(F, x₀, p₀, parameters=p,
                target_solutions_count=21,
                maximal_number_of_iterations_without_progress=20)
        @test result.returncode == :success
        @test length(solutions(result)) == 21
        @test length(solutions(result, onlyreal = true)) >= 1
        @test result.statistics.ntrackedpaths ≥ 21
        @test result.statistics.nparametergenerations ≥ 1
        @test length(HC.UniquePoints(result.solutions).points) == 21
        @test isempty(sprint(show, result)) == false

        # test input of length > 1
        result = monodromy_solve(F, [x₀ for _ in 1:30], p₀, parameters=p)
        @test length(solutions(result)) == 21

        # test with false input
        result = monodromy_solve(F, [rand(6) for _ in 1:10], p₀, parameters=p)
        @test result.returncode == :invalid_startvalue
        result = monodromy_solve(F, [x₀, rand(6)], p₀, parameters=p)
        @test length(solutions(result)) == 21
        result = monodromy_solve(F, [x₀, rand(6)], p₀, parameters=p, check_startsolutions = false)
        @test result.returncode == :invalid_startvalue

        # different distance function
        result = monodromy_solve(F, x₀, p₀, parameters=p, distance = (x,y) -> 0.0)
        @test length(solutions(result)) == 1

        # Test stop heuristic using too high target_solutions_count
        result = monodromy_solve(F, x₀, p₀, parameters=p, target_solutions_count=25)
        @test result.returncode == :heuristic_stop
        # Test stop heuristic with no target solutions count
        result = monodromy_solve(F, x₀, p₀, parameters=p)
        @test result.returncode == :heuristic_stop
        # Test stop heuristic with no target solutions count
        result = monodromy_solve(F, x₀, p₀, parameters=p, strategy=Triangle(useweights=true))
        @test result.returncode == :heuristic_stop

        # By group_actions=nothing we force that complex conjugation is not used.
        result2 = monodromy_solve(F, x₀, p₀, parameters=p,
                        target_solutions_count=21, complex_conjugation=false,
                        maximal_number_of_iterations_without_progress=100)
        @test result2.returncode == :success

        result = monodromy_solve(F, x₀, p₀, parameters=p, target_solutions_count=21,
                done_callback=((_, _) -> true))
        @test length(result.solutions) == 2

        roots_of_unity(s) = begin
            t = cis(π*2/3)
            t² = t * t
            (vcat(t * s[1], t * s[2], s[3:end]),
             vcat(t² * s[1], t² * s[2], s[3:end]))
        end

        result = monodromy_solve(F, x₀, p₀, parameters=p, target_solutions_count=21,
            maximal_number_of_iterations_without_progress=100,
            equivalence_classes=false,
            group_action=roots_of_unity)
        @test length(result.solutions) == 21

        result = monodromy_solve(F, x₀, p₀, parameters=p, target_solutions_count=21,
            maximal_number_of_iterations_without_progress=100,
            complex_conjugation=false, # disable complex conjugation to test it as a group action.
            equivalence_classes=false,
            group_actions=(roots_of_unity, s -> (conj.(s),)))
        @test length(result.solutions) == 21
        @test length(solutions(result)) == 21
        @test length(realsolutions(result)) < 21
        test_treeviews(result)

        # group_actions as a vector
        result = monodromy_solve(F, x₀, p₀, parameters=p, target_solutions_count=21,
            maximal_number_of_iterations_without_progress=100,
            complex_conjugation=false,
            equivalence_classes=false,
            group_actions=[roots_of_unity, s -> (conj.(s),)])
        @test length(result.solutions) == 21

        # equivalence classes
        result = monodromy_solve(F, x₀, p₀, parameters=p,
            equivalence_classes=true,
            target_solutions_count=7,
            maximal_number_of_iterations_without_progress=100,
            group_actions=roots_of_unity)
        @test length(result.solutions) == 7
        # Test that equivalence classes are on by default if we supply a group action
        result = monodromy_solve(F, x₀, p₀, parameters=p,
                            group_action=roots_of_unity,
                            maximal_number_of_iterations_without_progress=20)
        @test length(result.solutions) == 7

        # Test affine tracking
        result = monodromy_solve(F, x₀, p₀, parameters=p, affine_tracking=true,
                        group_action=roots_of_unity,
                        target_solutions_count=7,
                        maximal_number_of_iterations_without_progress=200)
        @test length(result.solutions) == 7

        # AbstractSystem as input
        F_p = SPSystem(F; parameters=p)
        result = monodromy_solve(F_p, x₀, p₀, affine_tracking=true,
                        group_action=roots_of_unity,
                        target_solutions_count=7,
                        maximal_number_of_iterations_without_progress=200)
        @test length(result.solutions) == 7
    end

    @testset "Method of Moments" begin
        Random.seed!(130793)
        @polyvar a[1:3] x[1:3] s[1:3]

        f0 = a[1]+a[2]+a[3];
        f1 = a[1]*x[1]+a[2]*x[2]+a[3]*x[3];
        f2 = a[1]*(x[1]^2+s[1])+a[2]*(x[2]^2+s[2])+a[3]*(x[3]^2+s[3]);
        f3 = a[1]*(x[1]^3+3*s[1]*x[1])+a[2]*(x[2]^3+3*s[2]*x[2])+a[3]*(x[3]^3+3*s[3]*x[3]);
        f4 = a[1]*(x[1]^4+6*s[1]*x[1]^2+3*s[1]^2)+a[2]*(x[2]^4+6*s[2]*x[2]^2+3*s[2]^2)+
        	a[3]*(x[3]^4+6*s[3]*x[3]^2+3*s[3]^2);
        f5= a[1]*(x[1]^5+10*s[1]*x[1]^3+15*x[1]*s[1]^2)+a[2]*(x[2]^5+10*s[2]*x[2]^3+15*x[2]*s[2]^2)+a[3]*(x[3]^5+10*s[3]*x[3]^3+15*x[3]*s[3]^2);
        f6= a[1]*(x[1]^6+15*s[1]*x[1]^4+45*x[1]^2*s[1]^2+15*s[1]^3)+
        	a[2]*(x[2]^6+15*s[2]*x[2]^4+45*x[2]^2*s[2]^2+15*s[2]^3)+
        	a[3]*(x[3]^6+15*s[3]*x[3]^4+45*x[3]^2*s[3]^2+15*s[3]^3);
        f7= a[1]*(x[1]^7+21*s[1]*x[1]^5+105*x[1]^3*s[1]^2+105*x[1]*s[1]^3)+
        	a[2]*(x[2]^7+21*s[2]*x[2]^5+105*x[2]^3*s[2]^2+105*x[2]*s[2]^3)+
        	a[3]*(x[3]^7+21*s[3]*x[3]^5+105*x[3]^3*s[3]^2+105*x[3]*s[3]^3);
        f8= a[1]*(x[1]^8+28*s[1]*x[1]^6+210*x[1]^4*s[1]^2+420*x[1]^2*s[1]^3+105*s[1]^4)+
        	a[2]*(x[2]^8+28*s[2]*x[2]^6+210*x[2]^4*s[2]^2+420*x[2]^2*s[2]^3+105*s[2]^4)+
        	a[3]*(x[3]^8+28*s[3]*x[3]^6+210*x[3]^4*s[3]^2+420*x[3]^2*s[3]^3+105*s[3]^4)

        f = [f0, f1, f2, f3, f4, f5, f6, f7, f8]

        S₃ = SymmetricGroup(3)
        relabeling(v) = map(p -> (v[1:3][p]..., v[4:6][p]..., v[7:9][p]...), S₃)

        @polyvar p[1:9]

        function sample_moments(_=nothing)
        	a₀ = rand(3)
        	a₀ ./= sum(a₀)
        	x₀ = randn(3)
        	s₀ = rand(3) .* 2

        	y₀ = [a₀; x₀; s₀]
        	p₀ = [fᵢ(a => a₀, x => x₀, s => s₀) for fᵢ in f]
        	y₀, p₀
        end

        y₀, p₀ = sample_moments()

        R = monodromy_solve(f - p, y₀, p₀;
        			parameters=p, group_action=relabeling,
        			parameter_sampler=last ∘ sample_moments,
                    show_progress=false,
                    maximal_number_of_iterations_without_progress=5)
        @test length(solutions(R)) ≤ 225

        R = monodromy_solve(f - p, y₀, p₀;
        			parameters=p, group_action=relabeling,
        			target_solutions_count=225,
                    affine_tracking=true,
        			parameter_sampler=last ∘ sample_moments,
                    show_progress=false,
                    maximal_number_of_iterations_without_progress=20)
        @test length(solutions(R)) == 225
    end
end


my_calc(x, y) = x^y
my_deri(x,y) = ForwardDiff.derivative(x->my_calc(x,y),x)


module MRIBloch

	using LinearAlgebra

	include("rotation_matrices.jl")

	# TODO: MAKE GENERATED?

	function simulate(mode::Val{:pulse_frame}, m0::NTuple{3, <: Real}, ω1::AbstractVector{<: Real}, Δω0::AbstractVector{<: Real}, dt::Real)
		# Memory for magnetisation
		m = Array{Float64}(undef, 3, 1+length(Δω0))
		m[:, 1] .= m0
		# Precompute flip angles
		α = @. dt * sqrt(Δω0^2 + ω1^2)
		# Run
		R = Matrix{Float64}(undef, 3, 3)
		for t = 1:length(α)
			# Compute rotation axis and matrix
			axis = (ω1[t], -Δω0[t])
			axis = axis ./ norm(axis)
			rotation_matrix(Val(:xz), axis, α[t]; out=R)
			# Apply
			@views m[:, t+1] = R * m[:, t]
		end
		return m
	end

	#function simulate(mode::Val{:B0_frame}, m0::NTuple{3, <: Real}, ω1::AbstractVector{<: Real}, ϕ::AbstractVector{<: Real}, dt::Real)
	#	# Memory for magnetisation
	#	m = Array{Float64}(undef, 3, 1+length(ϕ))
	#	m[:, 1] .= m0
	#	# Run
	#	R = Matrix{Float64}(undef, 3, 3)
	#	for t = 1:length(α)
	#		# Compute rotation axis and matrix
	#		α = dt * ω1[t]
	#		rotation_matrix(Val(:xy), ϕ[t], α; out=R)
	#		# Apply
	#		@views m[:, t+1] = R * m[:, t]
	#	end
	#	return m
	#end

	function simulate(mode::Val{:B0_frame}, m0::NTuple{3, <: Real}, ω1::AbstractVector{<: Complex}, dt::Real)
		# Memory for magnetisation
		m = Array{Float64}(undef, 3, 1+length(ω1))
		m[:, 1] .= m0
		# Run
		R = Matrix{Float64}(undef, 3, 3)
		for t = 1:length(ω1)
			# Compute rotation axis and matrix
			abs_ω1 = abs(ω1[t])
			α = dt * abs_ω1
			axis = ω1[t] / abs_ω1
			rotation_matrix(Val(:xy), reim(axis), α; out=R)
			# Apply
			@views m[:, t+1] = R * m[:, t]
		end
		return m
	end
end


if !isdefined(Main, :Groebner)
    import Groebner
end

import AbstractAlgebra
using BenchmarkTools
using Logging
global_logger(ConsoleLogger(stderr, Logging.Error))

function benchmark_system_my(system)
    system = Groebner.change_ordering(system, :degrevlex)
    Groebner.groebner([system[1]])
    @btime gb = Groebner.groebner($system, reduced=false)
end

function run_f4_ff_degrevlex_benchmarks(ground)
    systems = [
        ("cyclic 12", Groebner.rootn(12, ground=ground)),
        ("cyclic 13", Groebner.rootn(13, ground=ground)),
        ("katsura 9", Groebner.katsura9(ground=ground)),
        ("katsura 10",Groebner.katsura10(ground=ground)),
        ("noon 6"    ,Groebner.noonn(6, ground=ground)),
        ("noon 7"    ,Groebner.noonn(7, ground=ground))
    ]

    for (name, system) in systems
        println("$name")
        benchmark_system_my(system)
    end
end

ground = AbstractAlgebra.GF(2^31 - 1)
run_f4_ff_degrevlex_benchmarks(ground)

#=
cyclic 12
68.460 ms (184276 allocations: 29.25 MiB)
cyclic 13
255.064 ms (459892 allocations: 74.68 MiB)
katsura 9
344.615 ms (82539 allocations: 22.35 MiB)
katsura 10
2.725 s (250078 allocations: 74.80 MiB)
noon 6
36.954 ms (101244 allocations: 21.19 MiB)
noon 7
289.916 ms (494536 allocations: 100.91 MiB)
=#


using LinearAlgebra: iszero
using PWS
using Distributions
using LinearAlgebra
using DataFrames
using CSVFiles

data = DataFrame(map(range(0,20,step=0.1)) do duration
    if iszero(duration)
        return (DiscreteTimes=duration, Value=0.0)
    end

    system = GaussianSystem(delta_t=0.05, duration=duration)

    n_dim = size(system.joint.Σ, 1) ÷ 2
    c_xx = Hermitian(system.joint.Σ[n_dim + 1:end, n_dim + 1:end])
    c_ss = Hermitian(system.joint.Σ[1:n_dim, 1:n_dim])
    c_z = Hermitian(system.joint.Σ)

    (DiscreteTimes=duration, Value=0.5 * (logdet(c_ss) + logdet(c_xx) - logdet(c_z)))
end)

save(joinpath(pwd(), "plots", "figure_gene_expr", "data", "gene-expression_2021-07-19_gaussian.csv"), data)

marginal = MvNormal(c_xx)
val1 = logpdf(marginal, initial[n_dim + 1:end]) # this is the analytically correct value of the log marginal


function TestCart()
nx=4;
nz=4;
xL=0;
xU=10;
zL=0;
zU=10;
Boundary.BT='FreeSlip';
Boundary.WE='FreeSlip';
Param.hS='AgnesiCart';
Param.hC=1;
Param.x0C=5;
Param.aC=1;

Param.Grid=CartGrid(nx,nz,xU-xL,zU-zL,xL,zL,OrientFaceCart,Boundary,Param);

DG.OrdPoly=2;
DG.OrdPolyX=2;
DG.OrdPolyY=2;
[DG.w,DG.xw]=GaussLobattoQuad(DG.OrdPoly);
[DG.wX,DG.xwX]=GaussLobattoQuad(DG.OrdPolyX);
[DG.wY,DG.xwY]=GaussLobattoQuad(DG.OrdPolyX);
[DG.DW,DG.DS,DG.DV,DG.DVT,DG.B]=DerivativeMatrixSingle(DG);
[DG.DWX,DG.DSX]=DerivativeMatrixSingle(DG);
[DG.DWY,DG.DSY]=DerivativeMatrixSingle(DG);

Param.X=zeros(DG.OrdPoly+1,DG.OrdPoly+1,3,Param.Grid.NumFaces);
Param.dXdx=zeros(DG.OrdPoly+1,DG.OrdPoly+1,2,2,Param.Grid.NumFaces);
Param.J=zeros(DG.OrdPoly+1,DG.OrdPoly+1,Param.Grid.NumFaces);
for iF=1:Param.Grid.NumFaces
  [Param.X(:,:,:,iF),Param.J(:,:,iF),Param.dXdx(:,:,:,:,iF)]=...
    JacobiDG1(DG,Param.Grid.Faces(iF),Param.Grid,@Cart,Param);
end
Subs=1;
fig=1;
PlotFaceGrid(Param.Grid,Subs,@JacobiCart,fig)
end


deltaH = -93.2
deltaS = -.1983
T = 300

gibbsFreeEnergy = deltaH - T*deltaS
gibbsFreeEnergy2 = -93.2 - (300*-.1983)

print("Gibbs Free Energy: ")
print("$gibbsFreeEnergy2 kJ/mol")


function test_relu_neuron(backend::Backend, T)
  println("-- Testing ReLU neuron on $(typeof(backend)){$T}...")

  eps = 1e-10
  data = rand(T, 3,4,5,6) - 0.5
  data_blob = make_blob(backend, data)
  neuron0 = Neurons.ReLU()
  neuron1 = Neurons.ReLU(1e-6)

  println("    > Forward")
  for (neuron, threshval) in [(neuron0, 0), (neuron1, 1e-6)]
    copy!(data_blob, data)
    forward(backend, neuron, data_blob)
    expected_data = max(data, threshval)
    got_data = zeros(T, size(data))
    copy!(got_data, data_blob)
    @test all(-eps .< got_data - expected_data .< eps)
  end

  println("    > Backward")
  grad = rand(T, size(data))
  grad_blob = make_blob(backend, grad)

  for (neuron, threshval) in [(neuron0, 0), (neuron1, 1e-6)]
    copy!(data_blob, data)
    forward(backend, neuron, data_blob)
    backward(backend, neuron, data_blob, grad_blob)
    expected_grad = grad .* (data .> threshval)
    got_grad = zeros(T, size(expected_grad))
    copy!(got_grad, grad_blob)

    @test all(-eps .< got_grad - expected_grad .< eps)
  end
end
function test_relu_neuron(backend::Backend)
  test_relu_neuron(backend, Float32)
  test_relu_neuron(backend, Float64)
end

if test_cpu
  test_relu_neuron(backend_cpu)
end
if test_gpu
  test_relu_neuron(backend_gpu)
end