#=
    # The four inputs t, b, c, d are defined as follows:

    * t = current time (in any unit measure, but same unit as duration)
    * b = starting value to interpolate
    * c = the total change in value of b that needs to occur
    * d = total time it should take to complete (duration)
=#

# ###########################
# # Linear Easing functions #
# ###########################

EaseLinearNone(t::Real, b::Real, c::Real, d::Real) = c * t/d + b

EaseLinearIn(t::Real, b::Real, c::Real, d::Real) = EaseLinearNone(t, b, c, d)

EaseLinearOut(t::Real, b::Real, c::Real, d::Real) = EaseLinearNone(t, b, c, d)

EaseLinearInOut(t::Real, b::Real, c::Real, d::Real) = EaseLinearNone(t, b, c, d)

# #########################
# # Sine Easing functions #
# #########################

EaseSineIn(t::Real, b::Real, c::Real, d::Real) = -c * cos(t/d * (π/2)) + c + b

EaseSineOut(t::Real, b::Real, c::Real, d::Real) = c * sin(t/d * (π/2)) + b

EaseSineInOut(t::Real, b::Real, c::Real, d::Real) = -c/2 * (cos(π * t/d) - 1) + b

# #############################
# # Circular Easing functions #
# #############################

function EaseCircIn(t::Real, b::Real, c::Real, d::Real)
    t /= d

    return -c * (sqrt(1 - t^2) - 1) + b
end

function EaseCircOut(t::Real, b::Real, c::Real, d::Real)
    t = t/d - 1

    return c * sqrt(1 - t^2) + b
end

function EaseCircInOut(t::Real, b::Real, c::Real, d::Real)
    if (t /= d/2) < 1
        return -c/2 * (sqrt(1 - t^2) - 1) + b
    end

    t -= 2

    return c/2 * (sqrt(1 - t^2) + 1) + b
end

# ##########################
# # Cubic Easing functions #
# ##########################

function EaseCubicIn(t::Real, b::Real, c::Real, d::Real)
    t /= d

    return c * t^3 + b
end

function EaseCubicOut(t::Real, b::Real, c::Real, d::Real)
    t = t/d - 1

    return c * (t^3 + 1) + b
end

function EaseCubicInOut(t::Real, b::Real, c::Real, d::Real)
    if (t /= d/2) < 1
        return c/2 * t^3 + b
    end

    t -= 2

    return c/2 * (t^3 + 2) + b
end

# ##############################
# # Quadratic Easing functions #
# ##############################

function EaseQuadIn(t::Real, b::Real, c::Real, d::Real)
    t /= d

    return c * t^2 + b
end

function EaseQuadOut(t::Real, b::Real, c::Real, d::Real)
    t /= d

    return -c * t * (t - 2) + b
end

function EaseQuadInOut(t::Real, b::Real, c::Real, d::Real)
    if (t /= d/2) < 1
        return c/2 * t^2 + b
    end

    return -c/2 * ((t - 1) * (t - 3) - 1) + b
end

# ################################
# # Exponential Easing functions #
# ################################

function EaseExpoIn(t::Real, b::Real, c::Real, d::Real)
    return (t ≈ 0) ? b : c * 2^(10(t/d - 1)) + b
end

function EaseExpoOut(t::Real, b::Real, c::Real, d::Real)
    return t ≈ d ? b+c : c * (-(2.0^(-10 * t/d)) + 1) + b
end

function EaseExpoInOut(t::Real, b::Real, c::Real, d::Real)
    if t ≈ 0
        return b
    elseif t ≈ d
        return b + c
    elseif (t /= d/2) < 1
        return c/2 * 2^(10(t - 1)) + b
    else
        return c/2 * (-(2^(-10(t - 1))) + 2) + b
    end
end

# #########################
# # Back Easing functions #
# #########################

function EaseBackIn(t::Real, b::Real, c::Real, d::Real)
    s = 1.70158
    postFix = t/=d

    return c * postFix * t * ((s + 1) * t - s) + b
end

function EaseBackOut(t::Real, b::Real, c::Real, d::Real)
    s = 1.70158
    t = t/d - 1

    return c * (t^2 * ((s + 1) * t + s) + 1) + b
end

function EaseBackInOut(t::Real, b::Real, c::Real, d::Real)
    s = 1.70158

    if (t /= d/2) < 1
        s *= 1.525
        return c/2 * t^2 * ((s + 1) * t - s) + b
    end

    postFix = t -= 2
    s *= 1.525

    return c/2 * (postFix * t * ((s + 1) * t + s) + 2) + b
end

# ###########################
# # Bounce Easing functions #
# ###########################

function EaseBounceIn(t::Real, b::Real, c::Real, d::Real)
    return c - EaseBounceOut(d-t, 0, c, d) + b
end

function EaseBounceOut(t::Real, b::Real, c::Real, d::Real)
    if (t /= d) < 1/2.75
        return c * (7.5625 * t^2) + b
    elseif t < 2/2.75
        postFix = t -= 1.5/2.75
        return c * (7.5625postFix * t + 0.75) + b
    elseif t < 2.5/2.75
        postFix = t -= 2.25/2.75
        return c * (7.5625postFix * t + 0.9375) + b
    else
        postFix = t -= 2.625/2.75
        return c * (7.5625postFix * t + 0.984375) + b
    end
end

function EaseBounceInOut(t::Real, b::Real, c::Real, d::Real)
    if (t < d/2)
        return EaseBounceIn(2t, 0, c, d)/2 + b
    end

    return EaseBounceOut(2t-d, 0, c, d)/2 + c/2 + b
end

# ############################
# # Elastic Easing functions #
# ############################

function EaseElasticIn(t::Real, b::Real, c::Real, d::Real)
    (t ≈ 0) && (return b)
    (t ≈ d) && (return b + c)
    t /= d

    p = 0.3d
    a = c
    s = p/4
    postFix = a * 2^(10(t -= 1))

    return -(postFix * sin((t * d - s) * 2π/p )) + b
end

function EaseElasticOut(t::Real, b::Real, c::Real, d::Real)
    (t ≈ 0) && (return b)
    (t  ≈ d) && (return b + c)
    t /= d

    p = 0.3d
    a = c
    s = p/4

    return a * 2^(-10t) * sin((t * d - s) * 2π/p) + c + b
end

function EaseElasticInOut(t::Real, b::Real, c::Real, d::Real)
    (t ≈ 0) && (return b)
    (t ≈ d) && (return b + c)
    t = 2t/d

    p = d * 0.3 * 1.5
    a = c
    s = p/4

    if t < 1
        postFix = a * 2^(10(t -= 1))
        return -0.5(postFix * sin((t*d - s) * 2π/p)) + b
    end

    postFix = a * 2^(-10(t -= 1))
    return postFix * sin((t*d - s) * 2π/p) / 2 + c + b
end


# Test residual block
# Author: Philipp Witte, pwitte3@gatech.edu
# Date: January 2020

using LinearAlgebra, InvertibleNetworks, Test

# Input
nx = 28
ny = 28
n_in = 4
n_hidden = 8
batchsize = 2
k1 = 3
k2 = 3

# Input
X = glorot_uniform(nx, ny, n_in, batchsize)
X0 = glorot_uniform(nx, ny, n_in, batchsize)
dX = X - X0

# Weights
W1 = glorot_uniform(k1, k1, n_in, n_hidden)
W2 = glorot_uniform(k2, k2, n_hidden, n_hidden)
W3 = glorot_uniform(k1, k1, 2*n_in, n_hidden)
b1 = glorot_uniform(n_hidden)
b2 = glorot_uniform(n_hidden)

W01 = glorot_uniform(k1, k1, n_in, n_hidden)
W02 = glorot_uniform(k2, k2, n_hidden, n_hidden)
W03 = glorot_uniform(k1, k1, 2*n_in, n_hidden)
b01 = glorot_uniform(n_hidden)
b02 = glorot_uniform(n_hidden)

dW1 = W1 - W01
dW2 = W2 - W02
dW3 = W3 - W03
db1 = b1 - b01
db2 = b2 - b02

# Residual blocks
RB = ResidualBlock(W1, W2, W3, b1, b2, nx, ny, batchsize)   # true weights

# Observed data
Y = RB.forward(X)

function loss(RB, X, Y)
    Y_ = RB.forward(X)
    ΔY = Y_ - Y
    f = .5f0*norm(ΔY)^2
    ΔX = RB.backward(ΔY, X)
    return f, ΔX, RB.W1.grad, RB.W2.grad, RB.W3.grad, RB.b1.grad, RB.b2.grad
end

###################################################################################################
# Gradient tests

# Gradient test w.r.t. input
f0, ΔX = loss(RB, X0, Y)[1:2]
h = 0.1f0
maxiter = 5
err1 = zeros(Float32, maxiter)
err2 = zeros(Float32, maxiter)

print("\nGradient test convolutions\n")
for j=1:maxiter
    f = loss(RB, X0 + h*dX, Y)[1]
    err1[j] = abs(f - f0)
    err2[j] = abs(f - f0 - h*dot(dX, ΔX))
    print(err1[j], "; ", err2[j], "\n")
    global h = h/2f0
end

@test isapprox(err1[end] / (err1[1]/2^(maxiter-1)), 1f0; atol=1f1)
@test isapprox(err2[end] / (err2[1]/4^(maxiter-1)), 1f0; atol=1f1)

# Gradient test for weights
RB0 = ResidualBlock(W01, W02, W03, b1, b2, nx, ny, batchsize)   # initial weights
f0, ΔX, ΔW1, ΔW2, ΔW3 = loss(RB0, X, Y)[1:5]
h = 0.1f0
maxiter = 5
err3 = zeros(Float32, maxiter)
err4 = zeros(Float32, maxiter)

print("\nGradient test convolutions\n")
for j=1:maxiter
    RB0.W1.data = W01 + h*dW1
    RB0.W2.data = W02 + h*dW2
    RB0.W3.data = W03 + h*dW3
    f = loss(RB0, X, Y)[1]
    err3[j] = abs(f - f0)
    err4[j] = abs(f - f0 - h*dot(dW1, ΔW1) - h*dot(dW2, ΔW2) - h*dot(dW3, ΔW3))
    print(err3[j], "; ", err4[j], "\n")
    global h = h/2f0
end

@test isapprox(err3[end] / (err3[1]/2^(maxiter-1)), 1f0; atol=1f1)
@test isapprox(err4[end] / (err4[1]/4^(maxiter-1)), 1f0; atol=1f1)

# Gradient test for bias
RB0 = ResidualBlock(W1, W2, W3, b01, b02, nx, ny, batchsize)
f0, ΔX, ΔW1, ΔW2, ΔW3, Δb1, Δb2 = loss(RB0, X, Y)
h = 0.1f0
maxiter = 5
err5 = zeros(Float32, maxiter)
err6 = zeros(Float32, maxiter)
print("\nGradient test convolutions\n")
for j=1:maxiter
    RB0.b1.data = b01 + h*db1
    RB0.b2.data = b02 + h*db2
    f = loss(RB0, X, Y)[1]
    err5[j] = abs(f - f0)
    err6[j] = abs(f - f0 - h*dot(db1, Δb1) - h*dot(db2, Δb2))
    print(err5[j], "; ", err6[j], "\n")
    global h = h/2f0
end

@test isapprox(err5[end] / (err5[1]/2^(maxiter-1)), 1f0; atol=1f1)
@test isapprox(err6[end] / (err6[1]/4^(maxiter-1)), 1f0; atol=1f1)


"""
`KernelProduct(kernels::Array{Kernel})`
Create a multiplication of kernels.
One can also use the operator `*`
```
k1 = SqExponentialKernel()
k2 = LinearKernel()
k = KernelProduct([k1,k2])
kernelmatrix(k,X) == kernelmatrix(k1,X).*kernelmatrix(k2,X)
kernelmatrix(k,X) == kernelmatrix(k1*k2,X)
```
"""
struct KernelProduct{T,Tr} <: Kernel{T,Tr}
    kernels::Vector{Kernel}
end

function KernelProduct(kernels::AbstractVector{<:Kernel})
    KernelProduct{eltype(kernels),Transform}(kernels)
end

params(k::KernelProduct) = params.(k.kernels)
opt_params(k::KernelProduct) = opt_params.(k.kernels)
duplicate(k::KernelProduct,θ) = KernelProduct(duplicate.(k.kernels,θ))

Base.:*(k1::Kernel,k2::Kernel) = KernelProduct([k1,k2])
Base.:*(k1::KernelProduct,k2::KernelProduct) = KernelProduct(vcat(k1.kernels,k2.kernels)) #TODO Add test
Base.:*(k::Kernel,kp::KernelProduct) = KernelProduct(vcat(k,kp.kernels))
Base.:*(kp::KernelProduct,k::Kernel) = KernelProduct(vcat(kp.kernels,k))

Base.length(k::KernelProduct) = length(k.kernels)
metric(k::KernelProduct) = metric.(k.kernels) #TODO Add test
transform(k::KernelProduct) = transform.(k.kernels) #TODO Add test
transform(k::KernelProduct,x::AbstractVecOrMat) = transform.(k.kernels,[x]) #TODO Add test
transform(k::KernelProduct,x::AbstractVecOrMat,obsdim::Int) = transform.(k.kernels,[x],obsdim) #TODO Add test

hadamard(x,y) = x.*y

function kernelmatrix(
    κ::KernelProduct,
    X::AbstractMatrix;
    obsdim::Int=defaultobs)
    reduce(hadamard,kernelmatrix(κ.kernels[i],X,obsdim=obsdim) for i in 1:length(κ))
end

function kernelmatrix(
    κ::KernelProduct,
    X::AbstractMatrix,
    Y::AbstractMatrix;
    obsdim::Int=defaultobs)
    reduce(hadamard,_kernelmatrix(κ.kernels[i],X,Y,obsdim) for i in 1:length(κ))
end

function kerneldiagmatrix(
    κ::KernelProduct,
    X::AbstractMatrix;
    obsdim::Int=defaultobs) #TODO Add test
    reduce(hadamard,kerneldiagmatrix(κ.kernels[i],X,obsdim=obsdim) for i in 1:length(κ))
end


"""
    coordinates!(mtg; angle = 45; force = false)

Compute dummy 3d coordinates for the mtg nodes using an alterning phyllotaxy. Used when
coordinates are missing.
Coordinates are just node attributes with reserved names: :XX, :YY and :ZZ.

# Returns

Nothing, mutates the mtg in-place (adds :XX, :YY and :ZZ to nodes).

# Examples

```julia
file = joinpath(dirname(dirname(pathof(MultiScaleTreeGraph))),"test","files","simple_plant.mtg")
mtg = read_mtg(file)
coordinates!(mtg)
DataFrame(mtg, [:XX, :YY, :ZZ])
```
"""
function coordinates!(mtg; angle = 45, force = false)

    coord_in_attributes = [:XX, :YY, :ZZ] .∈ Ref(get_attributes(mtg))

    if !force && any(coord_in_attributes)
        error(
            "Coordinates for $([:XX, :YY, :ZZ][coord_in_attributes]) are already present",
            " in the mtg attributes (at least for some nodes). Use `force = true` if you ",
            "want to overwrite their values."
        )
    end

    phyllotaxy = [-1]
    # This function adds a XX, YY and ZZ coordinates of each node
    traverse!(mtg, new_pos, angle, phyllotaxy)
end

function coordinates_parent!(mtg)
    coord_in_attributes = [:XX, :YY, :ZZ] .∈ Ref(get_attributes(mtg))
    if !all(coord_in_attributes)
        error("No coordinates found in MTG. Use `coordinates!(mtg)` first.")
    end

    transform!(
        mtg,
        :XX => (x -> 1.0) => :ZZ,
        (x -> get(ancestors(x, :XX, recursivity_level = 1), 1, nothing)) => :XX_from,
        (x -> get(ancestors(x, :YY, recursivity_level = 1), 1, nothing)) => :YY_from,
        (x -> get(ancestors(x, :ZZ, recursivity_level = 1), 1, nothing)) => :ZZ_from
    )
end

function new_pos(node, angle, phyllotaxy)

    if isroot(node)
        append!(node, (XX = 0.0, YY = 0.0, ZZ = 0.0))
        return nothing
    end

    parent_node = parent(node)
    great_parent_node = parent(parent_node)

    if great_parent_node === nothing
        great_parent_node_XX = 0.0
        great_parent_node_YY = -1.0
    else
        great_parent_node_XX = great_parent_node[:XX]
        great_parent_node_YY = great_parent_node[:YY]
    end

    if node.MTG.link == "/"
        extend_length = 0.2
    else
        extend_length = 1
    end

    point = extend_pos(great_parent_node_XX, great_parent_node_YY, parent_node[:XX], parent_node[:YY], extend_length)

    if node.MTG.link == "+"
        point =
            rotate_point(
                parent_node[:XX],
                parent_node[:YY],
                point[1],
                point[2],
                phyllotaxy[1] * angle
            )

        # Change phyllotaxy for next node:
        if phyllotaxy[1] == 1
            phyllotaxy[1] = -1
        else
            phyllotaxy[1] = 1
        end
    end
    node[:XX] = point[1]
    node[:YY] = point[2]
    node[:ZZ] = 0.0

    return
end

"""
Add a new point after (x1,y1) using same direction and length relative to it
"""
function extend_pos(x0, y0, x1, y1, extend_length)
    lengthAB = sqrt((x0 - x1)^2 + (y0 - y1)^2)
    x = x1 + (x1 - x0) / lengthAB * extend_length
    y = y1 + (y1 - y0) / lengthAB * extend_length
    return x, y
end

"""
Rotate a point (x1,y1) around (x0, y0) with `angle`.
"""
function rotate_point(x0, y0, x1, y1, angle)
    angle = -angle * pi / 180
    x1 = x1 - x0
    y1 = y1 - y0
    cos_a = cos(angle)
    sin_a = sin(angle)

    x = x1 * cos_a - y1 * sin_a + x0
    y = x1 * sin_a + y1 * cos_a + y0

    return x, y
end

"""
    mtg_coordinates_df(mtg, attr; force = false)
    mtg_coordinates_df!(mtg, attr; force = false)

Extract the coordinates of the nodes of the mtg and the
coordinates of their parents (:XX_from, :YY_from, :ZZ_from) and output
a DataFrame. Optionally you can also provide an attribute to add to the
output DataFrame too by passing its name as a symbol to `attr`.

The coordinates are computed using [`coordinates!`](@ref) if missing, or if
`force = true`.
"""
function mtg_coordinates_df(mtg, attr = :YY; force = false)
    mtg_coordinates_df!(deepcopy(mtg), attr; force = force)
end

function mtg_coordinates_df!(mtg, attr = :YY; force = false)
    coord_in_attributes = [:XX, :YY, :ZZ] .∈ Ref(get_attributes(mtg))
    if !all(coord_in_attributes) || force
        if coord_in_attributes == [true, true, false] && !force
            # Only :ZZ is missing, using a dummy value:
            transform!(
                mtg,
                :XX => (x -> 0.0) => :ZZ,
            )
        else
            # :XX and/or :YY missing, recompute them
            coordinates!(mtg; force = force)
        end
    end

    # Get the coordinates of the parent of the node to draw edges:
    coordinates_parent!(mtg)

    DataFrame(mtg, unique([:XX, :YY, :ZZ, :XX_from, :YY_from, :ZZ_from, attr]))
end

function mtg_XYZ_color(mtg, color, edge_color, colormap; color_missing = RGBA(0, 0, 0, 0.3))
    if Symbol(color) in get_attributes(mtg)
        # Color is an attribute from the mtg:
        df_coordinates = mtg_coordinates_df(mtg, color, force = true)
        max_color = maximum(skipmissing(df_coordinates[:, color]))
        color_var = [i === missing ? color_missing : RGBA(get(colormap, i / max_color)) for i in df_coordinates[:, color]]
        text_color = color_var
    elseif typeof(color) <: Colorant || typeof(color) <: String || typeof(color) <: Symbol
        df_coordinates = mtg_coordinates_df(mtg, force = true)
        color_var = fill(color, size(df_coordinates, 1))
        text_color = color_var
    else
        error(
            "color argument should be of type Colorant ",
            "(see [Colors.jl](https://juliagraphics.github.io/Colors.jl/stable/)), or ",
            "an MTG attribute."
        )
    end

    if Symbol(edge_color) in get_attributes(mtg)
        if edge_color == color
            # If edge_color use the same attribute than color, use color_var (avoid recomputing)
            edge_color_var = [[color_var[i-1], color_var[i]] for i in 2:length(color_var)]
            pushfirst!(edge_color_var, edge_color_var[1])
        else
            # If edge_color is different than color, and is an attribute from the mtg
            df_coordinates = mtg_coordinates_df(mtg, edge_color, force = true)

            ecol = df_coordinates[:, edge_color]
            max_edge_color = maximum(skipmissing(ecol))
            edge_color_var = []
            for i in 2:size(df_coordinates, 1)
                if ecol[i-1] === missing || ecol[i] === missing
                    push!(edge_color_var, color_missing)
                else
                    push!(edge_color_var, RGBA.(get(colormap, [ecol[i-1] / max_edge_color, ecol[i] / max_edge_color])))
                end
            end
            pushfirst!(edge_color_var, edge_color_var[1])
        end
    elseif typeof(edge_color) <: Colorant || typeof(edge_color) <: String || typeof(edge_color) <: Symbol
        # edge_color is just a single color
        edge_color_var = fill([edge_color, edge_color], size(df_coordinates, 1))
    else
        error(
            "edge_color argument should be of type Colorant ",
            "(see [Colors.jl](https://juliagraphics.github.io/Colors.jl/stable/)), or ",
            "an MTG attribute."
        )
    end

    return df_coordinates, color_var, edge_color_var, text_color
end

function attribute_range(mtg, attr)
    vals =
        descendants(
            mtg,
            attr,
            ignore_nothing = true
        )

    if length(vals[1]) == 1
        range_val = extrema(vals)
    else
        range_val = (minimum(minimum.(vals)), maximum(maximum.(vals)))
    end

    return range_val
end


cd(@__DIR__); include("setups/grid7x3.jl");

## ROT2
D = natural_eigdist(𝚽, 𝛌, Q; α = 1.0, distance = :ROT)
E = transform(fit(MDS, D, maxoutdim=2, distances=true))
plt = grid7x3_mds_heatmaps(E, 𝚽; backend = :pyplot,
            annotate_ind = vcat(1:6, 8, 10, 11), plotOrder = N:-1:1)
savefig(plt, "../figs/Grid7x3_MDS_ROT2_alpha1.png")
display(plt)

## TSD (T = :Inf)
D = natural_eigdist(𝚽, 𝛌, Q; T = :Inf, distance = :TSD)
E = transform(fit(MDS, D, maxoutdim=2, distances=true))
E[1, :] .*= sign(E[1, 2]); E[2, :] .*= -sign(E[1, 2])
plt = grid7x3_mds_heatmaps(E, 𝚽; backend = :pyplot,
            annotate_ind = vcat(1:8, 11), plotOrder = vcat(N:-1:9, 6:8, 5:-1:1))
savefig(plt, "../figs/Grid7x3_MDS_TSD_Tinfty.png")
display(plt)

## TSD T from 0.1 to 10
anim = @animate for t ∈ 0.1:0.1:10
    D = natural_eigdist(𝚽, 𝛌, Q; T = t, distance = :TSD)
    E = transform(fit(MDS, D, maxoutdim=2, distances=true))
    E[1, :] .*= sign(E[1, 2])
    E[2, :] .*= sign(E[2, 2]) * ((t <= 1) * 2 - 1)
    grid7x3_mds_heatmaps(E, 𝚽; backend = :pyplot)
    title!("T = $(t)")
end
gif(anim, "../gifs/Grid7x3_MDS_TSD_T01_inf.gif", fps = 5)


using DifferentialEquations, GeometricIntegratorsDiffEq, ParameterizedFunctions, Plots, LinearAlgebra, DelimitedFiles, LaTeXStrings

## 3.1 Determining A Benchmark Value for the Infinite-Time average

include("..\\NewtonCotes\\NewtonCotes11pt.jl")

# Set BigFloat precision. This gives about 425 digits, enough to capture all the precision from Kehlet & Logg
setprecision(1400)

# Extract the z-variable of the reference solution trajectory
# This proved troublesome for accomadating the BigFloat type.
# There may be a cleaner way to do this.
u_ref = readdlm("lorenz\\Kehlet&Logg_solution_u.txt", ' ', String, '\n')
z_ref = Array{BigFloat,1}(undef, size(test)[1])

for i = 1:size(u_ref)[1]
    z_ref[i] = parse(BigFloat, u_ref[i,3])
end

# Integrate and then average over t ∈ [20, 1000]
# Kehlet & Logg's data is provided at time intervals of 0.1
Z_true_Lorenz = NewtonCotes.NewtonCotes11pt(z_ref[201:10001], 0.1)/(1000-20)

## 3.2 Initial Condition Determination

# define the Lorenz system and parameters
lorenz = @ode_def begin
 dx = σ * (y - x)
 dy = x * (ρ - z) - y
 dz = x * y - β*z
end σ ρ β

p = [10.0,28.0,8/3]

# Plots from Fig. 1

probLorenz10a = ODEProblem(lorenz, [1, 0, 0], (0, 10), p)
probLorenz10b = ODEProblem(lorenz, [-1, -1, -1], (0, 10), p)
probLorenz50a = ODEProblem(lorenz, [1, 0, 0], (0, 50), p)
probLorenz50b = ODEProblem(lorenz, [-1, -1, -1], (0, 50), p)

solLorenz10a = solve(probLorenz10a, alg=GIGLRK(4), dt=0.001)
solLorenz10b = solve(probLorenz10b, alg=GIGLRK(4), dt=0.001)
solLorenz50a = solve(probLorenz10a, alg=GIGLRK(4), dt=0.001)
solLorenz50b = solve(probLorenz10b, alg=GIGLRK(4), dt=0.001)

# Figure 1a
plot(solLorenz10a, vars = (1, 2, 3), label=L"u_0 = [1, 0, 0]", linecolor=:blue)
plot!(solLorenz10b, vars = (1, 2, 3), label=L"u_0 = [-1, -1, -1]", linecolor=:orange)

# Figure 1b
plot(solLorenz50a, vars = (1, 2, 3), label=L"u_0 = [1, 0, 0]", linecolor=:blue)
plot!(solLorenz50b, vars = (1, 2, 3), label=L"u_0 = [-1, -1, -1]", linecolor=:orange)

# Figure 2 and new initial condition coordinates
probLorenz22 = ODEProblem(lorenz, [-1, -1, -1], (0, 22), p)
solLorenz22 = solve(probLorenz22, alg=GIGLRK(4), dt=0.001)
plot(solLorenz22, vars = (1, 2, 3), label=L"u_0 = [-1, -1, -1]", linecolor=:orange)

u0Lorenz = solLorenz22.u[end] # = [-5.276131687990789, -8.274832825254473, 16.36301350713599]

## 3.3 Step Size Determination

errors = [0.04 0;
          0.02 0;
          0.01 0;
          0.005 0;
          0.004 0]

probLorenzStep = ODEProblem(lorenz, u0Lorenz, (0, 1), p)

for i=1:size(errors)[1]
    local h = errors[i,1]
    sol1 = solve(probLorenzStep, alg=GIHeun(), dt=h)
    sol2 = solve(probLorenzStep, alg=GIGLRK(4), dt=h)
    errors[i,2] = abs(norm(sol1.u[end] - sol2.u[end])/norm(sol2.u[end]))*100
end

# The errors matrix now shows Table 1

## 3.4 Time-March Testing
# NOTE: At time of initial investigation, the programmer was more experienced with MATLAB
# than Julia. Thus a good portion of the statistical analysis code was written in MATLAB, and
# the workflow for gathering statistics looks like this:
#    1. Run simulations in Julia
#    2. Export simulation results to .txt file
#    3. Import results from .txt file into MATLAB
#    4. Perform statistical analysis and plotting within MATLAB
#
# The code may, in the future, be updated to use Julia entirely. However, the code below shows
# steps 1 and 2 of the above workflow; while step 3 is performed by importer.m, and step 4
# is performed by runner.m and stats_plotter.m
#

probLorenz = ODEProblem(lorenz, u0Lorenz, (0, 300), p)

h = [0.32, 0.16, 0.08, 0.04, 0.02, 0.01, 0.005, 0.0025]
for i = h
    # GLRK stable for all step sizes
    solGLRK = solve(probLorenz, alg=GIGLRK(4), dt=i)
    writedlm("lorenz\\Simulated Solutions\\GLRK(4)_u_"*string(i)*".txt", solGLRK.u)

    # Explicit RK4 stable for h < 0.32
    if i < 0.32
        solRK4 = solve(probLorenz, alg=GIERK4(), dt=i)
        writedlm("lorenz\\Simulated Solutions\\RK4_u_"*string(i)*".txt", solRK4.u)
    end

    # Heun's method stable for h < 0.08
    if i < 0.08
        solHeun = solve(probLorenz, alg=GIHeun(), dt=i)
        writedlm("lorenz\\Simulated Solutions\\Heun_u_"*string(i)*".txt", solHeun.u)
    end
end


# coding: utf-8

# In[1]:

#counting sort is kinda different from other sorts in this repo
#it works the best when there is large quantity of duplicates
#when the maximum value is larger than the length of list
#it is not economical to use counting sort
#counting sort is counting how many times each element has repeatedly showed up
#we create a list of the size of maximum value
#each index of the list represents the value of elements
#each value of the list represents the frequency of elements
#in the end we concatenate each element and its frequency
#simplest sort among all

# In[2]:

function counting_sort(arr)

    maxval=maximum(arr)

    #first we get the maximum value
    #if maximum value is larger than the length of the list
    #we would create a list with a huge space complexity
    #its not worth it
    #we can use a better sort
    if maxval>length(arr)
        printstyled("choose other sorting techniques",color=:red)
        return
    end

    #we create a list to store the frequency of each element
    #note that we use maximum value +1
    #as zero is also taken into consideration
    freq=zeros(maxval+1)

    #this is the calculation of the frequency
    #use i+1 becuz julia index starts from 1
    for i in arr
        freq[i+1]+=1
    end

    #lets clear the original list
    #we add each element and its frequency back to the list
    output=[]

    for i in 1:length(freq)

        #using i-1 prior to i+1 in the lines above
        #use Int32 to ensure freq[i] is int32
        temp=fill(i-1,Int32(freq[i]))
        append!(output,temp)

    end

    return output

end

# In[3]:

#the time complexity for counting sort is o(n+k)
#k is determined by the maximum value
#its easy to see that counting sort easily beats o(n^2)
#however its space complexity is really large

# In[4]:

for _ in 1:100

    #usually we form a non-duplicate list
    #for counting sort, we need massive amount of duplicates
    test_arr=rand(0:100,1000)

    if !(counting_sort(test_arr)==sort(test_arr))
        printstyled("Erreur",color=:red)
    end

end


# This script generates catalogues of objects in disk galaxies
# with the model of uniform distribution in the symmetry plane
# and Laplace distribution on the vertical axis, and it fits
# the same model to the generated data, retrieving parameters
# and evaluating confidence intervals

println('\n', " "^4, "> Loading the packages...")

using Distributions # Probability distributions
using LaTeXStrings # LaTeX strings
using Optim # Optimization
using Plots # Plotting
using Random # Random numbers
using Roots # Finding roots
using Zygote # Derivatives

# Use the PGFPlotsX backend for plots
# (this `@eval` trick is necessary to fool `DaemonMode`)
@eval Main begin
    using Plots
    pgfplotsx()
end

# Change some of the default parameters for plots
default(fontfamily = "Computer Modern", dpi = 300, size = (400, 400), legend = :topright)

# Define the paths to output directories
CURRENT_DIR = @__DIR__
ROOT_DIR = basename(CURRENT_DIR) == "scripts" ? dirname(CURRENT_DIR) : CURRENT_DIR
PLOTS_DIR = joinpath(ROOT_DIR, "plots")
TRACES_DIR = joinpath(ROOT_DIR, "traces")

# Make sure the needed directories exist
mkpath(PLOTS_DIR)
mkpath(TRACES_DIR)

"Probability density function of the Laplace distribution"
laplace_pdf(x, μ, b) = 1 / (2 * b) * exp(-abs(x - μ) / b)

"""
Negative log-likelihood function of the model
(the model is a Laplace distribution)
"""
function nll(z::Vector{F}, θ::Vector{F})::F where {F <: Real}
    μ, b = θ
    return -sum(@. log(laplace_pdf(z, μ, b)))
end

"""
Calculate the minimum of the negative log-likelihood
function while one of the parameters is frozen
"""
function nll_frozen(
    idx::Int,
    value::F,
    z::Vector{F},
    θ₀::Vector{F},
    θₗ::Vector{F},
    θᵤ::Vector{F},
) where {F <: Real}
    # Exclude the frozen parameter from the active ones
    θ₀ = [θ₀[1:idx - 1]; θ₀[idx + 1:end]]
    θₗ = [θₗ[1:idx - 1]; θₗ[idx + 1:end]]
    θᵤ = [θᵤ[1:idx - 1]; θᵤ[idx + 1:end]]

    # Recreate the function, handling a frozen parameter
    function nll_frozen_inner(θ::Vector{F}) where {F <: Real}
        return nll(z, [θ[1:idx - 1]; value; θ[idx:end]])
    end

    # Optimize the new negative log likelihood function
    res = Optim.optimize(
        nll_frozen_inner,
        θ -> Zygote.gradient(nll_frozen_inner, θ)[1],
        θₗ,
        θᵤ,
        θ₀,
        Fminbox(LBFGS()),
        Optim.Options(
            show_trace=false,
            extended_trace=true,
            store_trace=true,
        );
        inplace=false,
    )

    return res.minimum
end

"Plot a histogram of the height sample"
function plot_histogram_height(z, μₜ, bₜ, μ, b, dir)
    # Plot the histogram of the height sample
    histogram(
        z;
        label = "",
        title = "",
        xlabel = L"z",
        ylabel = L"f(z)",
        normalize = true,
        yminorticks = 5,
    )

    # Plot the probability functions over the histogram
    plot!((x) -> laplace_pdf(x, μₜ, bₜ), label = "true", lw = 1.5)
    plot!((x) -> laplace_pdf(x, μ, b), label = "fit", lw = 1.5)

    # Save the figure as PDF and PNG
    savefig(joinpath(dir, "Histogram Z.pdf"))
end

"Create the objective function profiles for all of the parameters"
function profiles(
    z::Vector{F},
    L₀::F,
    θ::Vector{F},
    θ₀::Vector{F},
    θₗ::Vector{F},
    θᵤ::Vector{F},
    labels::Vector{Vector{String}},
    plots_dir::String,
) where {F <: Real}
    for (i, x₀, (label, tex_label)) in zip(1:length(θ), θ, labels)
        println(" "^6, "> Evaluating the confidence intervals of the $label parameter...")

        # Create an alias function for the frozen parameter
        nll_frozen_with(x) = nll_frozen(i, x, z, θ₀, θₗ, θᵤ)

        # Define a function whose roots define confidence intervals
        confidence_intervals(x) = nll_frozen_with(x) - L₀ - 0.5

        # Evaluate the confidence intervals
        r1, r2 = find_zeros(confidence_intervals, x₀ - 0.2, x₀ + 0.2)
        σr1, σr2 = x₀ - r1, r2 - x₀

        # Compute the mean of the sigmas
        σr = (σr1 + σr2) / 2

        # Print the results
        println(
            '\n',
            " "^8, "$label = $(x₀)\n",
            " "^8, "$(label)₋ₕ = $(r1)\n",
            " "^8, "$(label)₊h = $(r2)\n",
            " "^8, "σ$(label)₋ₕ = $(σr1)\n",
            " "^8, "σ$(label)₊ₕ = $(σr2)\n",
        )

        println(" "^6, "> Plotting the objective function profile for the $label parameter...")

        # Plot the profile
        plot(nll_frozen_with, r1 - σr1, r2 + σr2; label="", xlabel=latexstring(tex_label), ylabel=latexstring("L_p($tex_label)"))

        # Add vertical lines to the plot
        plot!([r1, r1], [L₀ - 0.2, L₀ + 0.5]; label="", linestyle=:dash)
        plot!([x₀, x₀], [L₀ - 0.2, L₀]; label="", linestyle=:dash)
        plot!([r2, r2], [L₀ - 0.2, L₀ + 0.5]; label="", linestyle=:dash)

        # Add points to the plot
        scatter!([r1], [L₀ + 0.5]; label="")
        scatter!([x₀], [L₀]; label="")
        scatter!([r2,], [L₀ + 0.5]; label="")

        # Add horizontal lines to the plot
        hline!([L₀ + 0.5]; label="", linestyle=:dash)
        hline!([L₀]; label="", linestyle=:dash)

        # Add annotations to the plot
        rounded = round.([r1, x₀, r2]; digits=2)
        annotate!(
            [
                (r1 + σr * 0.2, L₀ - 0.1, text("$(rounded[1])", 8, "Computer Modern")),
                (x₀, L₀ + 0.1, text("$(rounded[2])", 8, "Computer Modern")),
                (r2 + σr * 0.2, L₀ - 0.1, text("$(rounded[3])", 8, "Computer Modern")),
                (r1 - σr * 0.22, L₀ - 0.1, text("$(round(rounded[1] - rounded[2]; digits=2))", 8, "Computer Modern")),
                (r2 - σr * 0.25, L₀ - 0.103, text("+$(round(rounded[3] - rounded[2]; digits=2))", 8, "Computer Modern")),
                (x₀ - σr1 * 1.80, L₀ + 0.06, text(L"L_0", 8, "Computer Modern")),
                (x₀ - σr1 * (1.80 - 0.21), L₀ + 0.5 + 0.06, text(L"L_0 + 1/2", 8, "Computer Modern")),
            ]
        )

        # Save the figure
        savefig(joinpath(plots_dir, "$label profile.pdf"))
    end
end

# Define a set of initial parameters
θ₀ = [0.0, 0.5]

# Define the lower and upper boundaries, respectively
θₗ = [-Inf, 0]
θᵤ = [Inf, Inf]

"""
Generate a catalogue for a disk with uniform distribution in
the symmetry plane and Laplace distribution on the vertical axis
"""
function fit_uniform_disk(seed; N::Int = 1000, μₜ::F = 2.0, bₜ::F = 0.9) where {F <: Real}
    println(" "^4, "> Fitting a sample (seed = $seed, N = $N) of the uniform disk distribution...")

    # Set a seed for the random generator
    Random.seed!(seed)

    # Define the output directories
    name = "$seed, $N"
    plots_dir = joinpath(PLOTS_DIR, "fit_uniform_disk", name)
    traces_dir = joinpath(TRACES_DIR, name)

    # Make sure the needed directories exists
    mkpath(plots_dir)
    mkpath(traces_dir)

    # Generate a sample of heights from the Laplace distribution
    z = rand(Laplace(μₜ, bₜ), N)

    # Link the negative log-likelihood function with data
    nll_inner(θ) = nll(z, θ)

    # Optimize the negative log likelihood function
    res = Optim.optimize(
        nll_inner,
        θ -> Zygote.gradient(nll_inner, θ)[1],
        θₗ,
        θᵤ,
        θ₀,
        Fminbox(LBFGS()),
        Optim.Options(
            show_trace=false,
            extended_trace=true,
            store_trace=true,
        );
        inplace=false,
    )

    # Unpack the results
    θ = res.minimizer
    μ, b = res.minimizer
    L₀ = res.minimum

    # Save the trace and the results
    open(joinpath(traces_dir, "fit.trace"), "w") do io
        println(io, res.trace)
        println(
            io,
            " * Parameters:\n",
            "    μ = $(μ)\n",
            "    b = $(b)\n",
        )
        show(io, res)
    end

    # Print results
    println(
        '\n',
        " "^6, "μₜ = $(μₜ)\n",
        " "^6, "bₜ = $(bₜ)\n",
        " "^6, "μ = $(μ)\n",
        " "^6, "b = $(b)\n",
    )

    println(" "^6, "> Plotting the histogram...")

    # Plot the histogram of the height sample
    plot_histogram_height(z, μₜ, bₜ, μ, b, plots_dir)

    # Create the objective function profiles for all of the parameters
    profiles(z, L₀, θ, θ₀, θₗ, θᵤ, [["μ", "\\mu"], ["b", "b"]], plots_dir)

    print("\n")
    sleep(0.5)
end

# Plot samples from the uniform disk distribution
fit_uniform_disk(1, N=100)
fit_uniform_disk(1)
fit_uniform_disk(1, N=10000)
fit_uniform_disk(2)
fit_uniform_disk(3)
fit_uniform_disk(4)
fit_uniform_disk(5)


# ---
# title: 659. Split Array into Consecutive Subsequences
# id: problem659
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Heap, Greedy
# link: <https://leetcode.com/problems/split-array-into-consecutive-subsequences/description/>
# hidden: true
# ---
#
# Given an array `nums` sorted in ascending order, return `true` if and only if
# you can split it into 1 or more subsequences such that each subsequence
# consists of consecutive integers and has length at least 3.
#
#
#
# **Example 1:**
#
#
#
#     Input: [1,2,3,3,4,5]
#     Output: True
#     Explanation:
#     You can split them into two consecutive subsequences :
#     1, 2, 3
#     3, 4, 5
#
#
# **Example 2:**
#
#
#
#     Input: [1,2,3,3,4,4,5,5]
#     Output: True
#     Explanation:
#     You can split them into two consecutive subsequences :
#     1, 2, 3, 4, 5
#     3, 4, 5
#
#
# **Example 3:**
#
#
#
#     Input: [1,2,3,4,4,5]
#     Output: False
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 10000`
#
#
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


### A Pluto.jl notebook ###
# v0.19.0

using Markdown
using InteractiveUtils

# ╔═╡ 62b0cf82-e00c-498b-a6d8-124ea9950ef4
using Manopt, Manifolds, Random, BenchmarkTools

# ╔═╡ d5e636f0-533a-11ec-168a-e118d53012cb
md"""
# Illustration how to use mutating gradient functions

When it comes to time critital operations, a main ingredient in Julia are
mutating functions, i.e. those that compute in place without additional Memory
allocations. In the following the illustrate how to do this with `Manopt.jl`.

Let's start with the same function as in [Get Started: Optimize!](https://manoptjl.org/stable/tutorials/MeanAndMedian.html)
and compute the mean of some points. Just that here we use the sphere ``\mathbb S^{30}``
and `n=800` points.

From the just mentioned example, the implementation looks like
"""

# ╔═╡ 967e5caa-1d27-4c24-a882-6f7126053754
begin
    Random.seed!(42)
    m = 30
    M = Sphere(m)
    n = 800
    σ = π / 8
    x = zeros(Float64, m + 1)
    x[2] = 1.0
    data = [exp(M, x, random_tangent(M, x, Val(:Gaussian), σ)) for i in 1:n]
end;

# ╔═╡ 1436cdc7-1e84-4438-937d-2211856348de
md"""
## Classical definition

The variant from the previous tutorial defines a cost ``F(x)`` and its gradient ``gradF(x)``
"""

# ╔═╡ 6f1b04cf-2a76-4660-916c-3b8b8472d3c0
F(M, x) = sum(1 / (2 * n) * distance.(Ref(M), Ref(x), data) .^ 2)

# ╔═╡ e487f38c-334e-4819-b775-a9a44632d7ff
gradF(M, x) = sum(1 / n * grad_distance.(Ref(M), data, Ref(x)))

# ╔═╡ e4a7b318-65e7-40bb-899d-8bdeeefbf304
md"""
we further set the stopping criterion to be a little more strict, then we obtain
"""

# ╔═╡ 5a2d6d9f-20dc-41a2-bfce-32509a1ef106
begin
    sc = StopWhenGradientNormLess(1e-10)
    x0 = random_point(M)
    m1 = gradient_descent(M, F, gradF, x0; stopping_criterion=sc)
    @benchmark gradient_descent($M, $F, $gradF, $x0; stopping_criterion=$sc)
end

# ╔═╡ 306c7706-f399-4104-9159-c2e6b0bca189
md"""
## Inplace computation of the gradient

We can reduce the memory allocations, by implementing the gradient as a [functor](https://docs.julialang.org/en/v1/manual/methods/#Function-like-objects).
The motivation is twofold: On the one hand, we want to avoid variables from global scope,
for example the manifold `M` or the `data`, to be used within the function
For more complicated cost functions it might also be worth considering to do the same.

Here we store the data (as reference) and one temporary memory in order to avoid
reallocation of memory per `grad_distance` computation. We have
"""

# ╔═╡ 4e464076-d477-4e38-aac6-79639c1cb9b5
begin
    struct grad!{TD,TTMP}
        data::TD
        tmp::TTMP
    end
    function (gradf!::grad!)(M, X, x)
        fill!(X, 0)
        for di in gradf!.data
            grad_distance!(M, gradf!.tmp, di, x)
            X .+= gradf!.tmp
        end
        X ./= length(gradf!.data)
        return X
    end
end

# ╔═╡ 15d4c49c-7178-4be1-8e83-00900e384831
md"""
Then we just have to initialize the gradient and perform our final benchmark.
Note that we also have to interpolate all variables passed to the benchmark with a `$`.
"""

# ╔═╡ 90f40202-69ec-4dba-b1d0-905c916a3642
begin
    gradF2! = grad!(data, similar(data[1]))
    m2 = deepcopy(x0)
    gradient_descent!(
        M, F, gradF2!, m2; evaluation=MutatingEvaluation(), stopping_criterion=sc
    )
    @benchmark gradient_descent!(
        $M, $F, $gradF2!, m2; evaluation=$(MutatingEvaluation()), stopping_criterion=$sc
    ) setup = (m2 = deepcopy($x0))
end

# ╔═╡ 3e01469b-670b-47a6-a379-199b70c59207
md"""Mote that the results `m1`and `m2` are of course (approximately) the same."""

# ╔═╡ 10c7b9e5-024c-4815-be58-c524a7a08d25
distance(M, m1, m2)

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
Manifolds = "1cead3c2-87b3-11e9-0ccd-23c62b72b94e"
Manopt = "0fc0a36d-df90-57f3-8f93-d78a9fc72bb5"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[compat]
BenchmarkTools = "~1.2.0"
Manifolds = "~0.7.1"
Manopt = "~0.3.14"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.2"
manifest_format = "2.0"

[[deps.AbstractFFTs]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "485ee0867925449198280d4af84bdb46a2a404d0"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.0.1"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[deps.ArnoldiMethod]]
deps = ["LinearAlgebra", "Random", "StaticArrays"]
git-tree-sha1 = "62e51b39331de8911e4a7ff6f5aaf38a5f4cc0ae"
uuid = "ec485272-7323-5ecc-a04f-4719b315124d"
version = "0.2.0"

[[deps.ArrayInterface]]
deps = ["Compat", "IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "265b06e2b1f6a216e0e8f183d28e4d354eab3220"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "3.2.1"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.BenchmarkTools]]
deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"]
git-tree-sha1 = "61adeb0823084487000600ef8b1c00cc2474cd47"
uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
version = "1.2.0"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "f885e7e7c124f8c92650d61b9477b9ac2ee607dd"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.11.1"

[[deps.ChangesOfVariables]]
deps = ["LinearAlgebra", "Test"]
git-tree-sha1 = "9a1d594397670492219635b35a3d830b04730d62"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.1"

[[deps.ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "a851fec56cb73cfdf43762999ec72eff5b86882a"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.15.0"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[deps.Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "dce3e3fea680869eaa0b774b2e8343e9ff442313"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.40.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[deps.CovarianceEstimation]]
deps = ["LinearAlgebra", "Statistics", "StatsBase"]
git-tree-sha1 = "bc3930158d2be029e90b7c40d1371c4f54fa04db"
uuid = "587fd27a-f159-11e8-2dae-1979310e6154"
version = "0.2.6"

[[deps.DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.DensityInterface]]
deps = ["InverseFunctions", "Test"]
git-tree-sha1 = "80c3e8639e3353e5d2912fb3a1916b8455e2494b"
uuid = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
version = "0.4.0"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[deps.Distributions]]
deps = ["ChainRulesCore", "DensityInterface", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SparseArrays", "SpecialFunctions", "Statistics", "StatsBase", "StatsFuns", "Test"]
git-tree-sha1 = "7f3bec11f4bcd01bc1f507ebce5eadf1b0a78f47"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.34"

[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"

[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[deps.Einsum]]
deps = ["Compat"]
git-tree-sha1 = "4a6b3eee0161c89700b6c1949feae8b851da5494"
uuid = "b7d42ee7-0b51-5a75-98ca-779d3107e4c0"
version = "0.4.1"

[[deps.FillArrays]]
deps = ["LinearAlgebra", "Random", "SparseArrays", "Statistics"]
git-tree-sha1 = "8756f9935b7ccc9064c6eef0bff0ad643df733a3"
uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
version = "0.12.7"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.Graphs]]
deps = ["ArnoldiMethod", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"]
git-tree-sha1 = "92243c07e786ea3458532e199eb3feee0e7e08eb"
uuid = "86223c79-3864-5bf0-83f7-82e725a168b6"
version = "1.4.1"

[[deps.HybridArrays]]
deps = ["LinearAlgebra", "Requires", "StaticArrays"]
git-tree-sha1 = "c1d5b1dcdf2140644e1c6beb9ca09fbed601c241"
uuid = "1baab800-613f-4b0a-84e4-9cd3431bfbb9"
version = "0.4.9"

[[deps.IfElse]]
git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1"
uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173"
version = "0.1.1"

[[deps.Inflate]]
git-tree-sha1 = "f5fc07d4e706b84f72d54eedcc1c13d92fb0871c"
uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
version = "0.1.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.2"

[[deps.IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[deps.Kronecker]]
deps = ["LinearAlgebra", "NamedDims", "SparseArrays", "StatsBase"]
git-tree-sha1 = "a51f46415c844dee694cb8b20a3fcbe6dba342c2"
uuid = "2c470bb0-bcc8-11e8-3dad-c9649493f05e"
version = "0.5.1"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "be9eef9f9d78cecb6f262f3c10da151a6c5ab827"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.5"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"

[[deps.Manifolds]]
deps = ["Colors", "Distributions", "Einsum", "Graphs", "HybridArrays", "Kronecker", "LinearAlgebra", "ManifoldsBase", "Markdown", "Random", "RecipesBase", "RecursiveArrayTools", "Requires", "SimpleWeightedGraphs", "SpecialFunctions", "StaticArrays", "Statistics", "StatsBase"]
git-tree-sha1 = "3811935fd2549b0f5f9b365d6e7173fcdb98ec9a"
uuid = "1cead3c2-87b3-11e9-0ccd-23c62b72b94e"
version = "0.7.1"

[[deps.ManifoldsBase]]
deps = ["LinearAlgebra", "Markdown"]
git-tree-sha1 = "77a5949567437d185ee929c405e3c6c0768118ea"
uuid = "3362f125-f0bb-47a3-aa74-596ffd7ef2fb"
version = "0.12.9"

[[deps.Manopt]]
deps = ["ColorSchemes", "ColorTypes", "Colors", "DataStructures", "Dates", "LinearAlgebra", "ManifoldsBase", "Markdown", "Random", "Requires", "SparseArrays", "StaticArrays", "Statistics", "Test"]
git-tree-sha1 = "e87c818e19f79444fff1c77880cdb43b51974dfe"
uuid = "0fc0a36d-df90-57f3-8f93-d78a9fc72bb5"
version = "0.3.14"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[deps.Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[deps.NamedDims]]
deps = ["AbstractFFTs", "ChainRulesCore", "CovarianceEstimation", "LinearAlgebra", "Pkg", "Requires", "Statistics"]
git-tree-sha1 = "4f34e3ff2fa7f2a1c03fb2c4fdd637380d760bbc"
uuid = "356022a1-0364-5f58-8944-0da4b18d706f"
version = "0.2.42"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

[[deps.OpenLibm_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "05823500-19ac-5b8b-9628-191a04bc5112"

[[deps.OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
version = "0.5.5+0"

[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[deps.PDMats]]
deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
git-tree-sha1 = "ee26b350276c51697c9c2d88a072b339f9f03d73"
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
version = "0.11.5"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "ae4bbcadb2906ccc085cf52ac286dc1377dceccc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.1.2"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.Profile]]
deps = ["Printf"]
uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"

[[deps.QuadGK]]
deps = ["DataStructures", "LinearAlgebra"]
git-tree-sha1 = "78aadffb3efd2155af139781b8a8df1ef279ea39"
uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
version = "2.4.2"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.RecipesBase]]
git-tree-sha1 = "6bf3f380ff52ce0832ddd3a2a7b9538ed1bcca7d"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.2.1"

[[deps.RecursiveArrayTools]]
deps = ["ArrayInterface", "ChainRulesCore", "DocStringExtensions", "FillArrays", "LinearAlgebra", "RecipesBase", "Requires", "StaticArrays", "Statistics", "ZygoteRules"]
git-tree-sha1 = "c944fa4adbb47be43376359811c0a14757bdc8a8"
uuid = "731186ca-8d62-57ce-b412-fbd966d074cd"
version = "2.20.0"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[deps.Rmath]]
deps = ["Random", "Rmath_jll"]
git-tree-sha1 = "bf3188feca147ce108c76ad82c2792c57abe7b1f"
uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
version = "0.7.0"

[[deps.Rmath_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "68db32dff12bb6127bac73c209881191bf0efbb7"
uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
version = "0.3.0+0"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[deps.SimpleTraits]]
deps = ["InteractiveUtils", "MacroTools"]
git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231"
uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
version = "0.9.4"

[[deps.SimpleWeightedGraphs]]
deps = ["Graphs", "LinearAlgebra", "Markdown", "SparseArrays", "Test"]
git-tree-sha1 = "a6f404cc44d3d3b28c793ec0eb59af709d827e4e"
uuid = "47aef6b3-ad0c-573a-a1e2-d07658019622"
version = "1.2.1"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.SpecialFunctions]]
deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
git-tree-sha1 = "f0bccf98e16759818ffc5d97ac3ebf87eb950150"
uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
version = "1.8.1"

[[deps.Static]]
deps = ["IfElse"]
git-tree-sha1 = "e7bc80dc93f50857a5d1e3c8121495852f407e6a"
uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
version = "0.4.0"

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3c76dde64d03699e074ac02eb2e8ba8254d428da"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.13"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.StatsAPI]]
git-tree-sha1 = "0f2aa8e32d511f758a2ce49208181f7733a0936a"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.1.0"

[[deps.StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "2bb0cb32026a66037360606510fca5984ccc6b75"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.13"

[[deps.StatsFuns]]
deps = ["ChainRulesCore", "InverseFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
git-tree-sha1 = "bedb3e17cc1d94ce0e6e66d3afa47157978ba404"
uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
version = "0.9.14"

[[deps.SuiteSparse]]
deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[deps.ZygoteRules]]
deps = ["MacroTools"]
git-tree-sha1 = "8c1a8e4dfacb1fd631745552c8db35d0deb09ea0"
uuid = "700de1a5-db45-46bc-99cf-38207098b444"
version = "0.2.2"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
"""

# ╔═╡ Cell order:
# ╟─d5e636f0-533a-11ec-168a-e118d53012cb
# ╠═62b0cf82-e00c-498b-a6d8-124ea9950ef4
# ╠═967e5caa-1d27-4c24-a882-6f7126053754
# ╟─1436cdc7-1e84-4438-937d-2211856348de
# ╠═6f1b04cf-2a76-4660-916c-3b8b8472d3c0
# ╠═e487f38c-334e-4819-b775-a9a44632d7ff
# ╟─e4a7b318-65e7-40bb-899d-8bdeeefbf304
# ╠═5a2d6d9f-20dc-41a2-bfce-32509a1ef106
# ╟─306c7706-f399-4104-9159-c2e6b0bca189
# ╠═4e464076-d477-4e38-aac6-79639c1cb9b5
# ╟─15d4c49c-7178-4be1-8e83-00900e384831
# ╠═90f40202-69ec-4dba-b1d0-905c916a3642
# ╟─3e01469b-670b-47a6-a379-199b70c59207
# ╠═10c7b9e5-024c-4815-be58-c524a7a08d25
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# ---
# title: 222. Count Complete Tree Nodes
# id: problem222
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Binary Search, Tree
# link: <https://leetcode.com/problems/count-complete-tree-nodes/description/>
# hidden: true
# ---
#
# Given a **complete** binary tree, count the number of nodes.
#
# **Note:**
#
# **_Definition of a complete binary tree
# from[Wikipedia](http://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees):_**
# In a complete binary tree every level, except possibly the last, is completely
# filled, and all nodes in the last level are as far left as possible. It can
# have between 1 and 2h nodes inclusive at the last level h.
#
# **Example:**
#
#
#
#     Input:
#         1
#        / \
#       2   3
#      / \  /
#     4  5 6
#
#     Output: 6
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


using Plots, MAT

nprocs = (2, 2) # nprocs (x, y) dim
nt     = 100

@views function vizme2D_mpi(nprocs, nt)
    dname = "tmp_gif"; if isdir("./out2D/$(dname)")==false mkdir("./out2D/$(dname)") end; loadpath = "./out2D/$(dname)"; anim = Animation(loadpath,String[])
    for it = 1:nt
        T  = []
        ip = 1
        for ipx = 1:nprocs[1]
            for ipy = 1:nprocs[2]
                file = matopen("./out2D/T_$(ip-1)_$(it).mat"); T_loc = read(file, "T"); close(file)
                nx_i, ny_i = size(T_loc,1)-2, size(T_loc,2)-2
                ix1, iy1   = 1+(ipx-1)*nx_i, 1+(ipy-1)*ny_i
                if (ip==1)  T = zeros(nprocs[1]*nx_i, nprocs[2]*ny_i)  end
                T[ix1:ix1+nx_i-1,iy1:iy1+ny_i-1] .= T_loc[2:end-1,2:end-1]
                ip += 1
            end
        end
        heatmap(T', framestyle=:box, aspect_ratio=1, clims=(0.0,1.0), xlims=(1, size(T,1)), ylims=(1, size(T,2)), c=:hot, title="heat diffusion step=$(it)"); frame(anim)
    end
    gif(anim, "heat_2D_mpi_$(nprocs[1]*nprocs[2])procs.gif", fps = 15)
    return
end

vizme2D_mpi(nprocs, nt)


using Distances
using Random

"""
Représente une distance entre deux données identifiées par leur id
"""
mutable struct Distance

    distance::Float64
    ids::Vector{Int}

    function Distance()
        return new()
    end
end

"""
Constructeur d'une distance

Entrées :
- id1 : id de la première donnée
- id2 : id de la seconde donnée
- x   : caractéristique des données d'entraînement
"""
function Distance(id1::Int, id2::Int, x::Matrix{Float64})

    d = Distance()
    d.distance = euclidean(x[id1, :], x[id2, :])
    d.ids = [id1, id2]

    return d

end

##### Norme 1

# function Distance(id1::Int, id2::Int, x::Matrix{Float64})

#     d = Distance()
#     d.distance = sum(abs(x[id1,i] -x[id2,i]) for i in 1:size(x)[2])
#     d.ids = [id1,id2]

#     return d

# end

##### Norme infinie

# function Distance(id1::Int, id2::Int, x::Matrix{Float64})

#     d = Distance()
#     d.distance = max(abs(x[id1,i] -x[id2,i]) for i in 1:size(x)[2])
#     d.ids = [id1,id2]

#     return d

# end

##### Pseudo-norme min

# function Distance(id1::Int, id2::Int, x::Matrix{Float64})

#     d = Distance()
#     d.distance = min(abs(x[id1,i] -x[id2,i]) for i in 1:size(x)[2])
#     d.ids = [id1,id2]

#     return d

# end

##### Norme 2 perturbée

# function Distance(id1::Int, id2::Int, x::Matrix{Float64})

#     d = Distance()
#     acc = euclidean(x[id1, :], x[id2, :])
#     d.distance = acc * (0.6 + 0.4*rand())
#     d.ids = [id1,id2]

#     return d

# end


@testset "Test composition order" begin
  G = DiagonalGroup([2, 2, 2])
  f = hom(G, G, [G[2], G[1], G[3]])
  g = hom(G, G, [G[1], G[3], G[2]])
  h = (f * g)
  @test all(h(x) == g(f(x)) for x in gens(G))
end


using JuAFEM

# Create a quadrature rule
quad_rule = QuadratureRule{2, RefCube}(:legendre, 2)

# Create a function space
func_space = Lagrange{2, RefCube, 1}()

# Use these to create an FEValues object
cell_values = CellScalarValues(quad_rule, func_space)
fieldnames(cell_values) |> display
println()

# Coordinates of 1 cell in grid
x = Vec{2, Float64}[
  Vec{2}((0.0, 0.0)),
  Vec{2}((1.5, 0.0)),
  Vec{2}((2.0, 2.0)),
  Vec{2}((0.0, 1.0))
];

# Use reinit! to update cell_values on an element
reinit!(cell_values, x)

# Query cell_values shape function in quadrature point 3
shape_value(cell_values, 3, 1) |> display
println()

# DErivative of shape function 2 in quadrature point 1
shape_gradient(cell_values, 1, 2) |> display
println()

# We can also evaluate on the finite element basis
T = [0.0, 1.0, 2.0, 1.5]; # nodal values
function_value(cell_values, 3, T) |> display # value of T in 3rd quad point
println()
function_gradient(cell_values, 1, T) |> display # value of grad(T) in 1st quad point
println()

# Or for vector valued functions
u = Vec{2, Float64}[
  Vec{2}((0.0, 0.0)),
  Vec{2}((3.5, 2.0)),
  Vec{2}((2.0, 2.0)),
  Vec{2}((2.0, 1.0))
]; # nodal vectors

function_value(cell_values, 2, u) |> display # value of u in 2nd quad point
println()
function_symmetric_gradient(cell_values, 3, u)  |> display# sym(grad(u)) in 3rd quad point


#=
  From
  "Probabilistic logic programming and its applications"
  Luc De Raedt, Leuven
  https://www.youtube.com/watch?v=3lnVBqxjC88
  @ 3:44

  """
  Mike has a bag of marbles with 4 white, 8 blue, and
  6 red marbles. He pulls out one marble from the bag
  and it is red. What is the probability that the
  second marble he pulls out of the bag is white?

  The answer is 0.234931.
  """

  Cf ~/cplint/bag_of_marbles.pl
     ~/blog/bag_of_marbles.blog
     ~/psi/bag_of_marbles.psi
     ~/webppl/bag_of_marbles.wppl

  Cf bag_of_marbles.jl. This current model use a for loop.

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

#=
draw1: The probability of drawing white is the probability for 1.0

Distributions of variable draw[2] (num:0)
2.00000 =>  188281  (0.470703)
3.00000 =>  117185  (0.292963)
1.00000 =>   94534  (0.236335)
=#

@model function bag_of_marbles()

    white = 1
    blue  = 2
    red   = 3

    start = [4,8,6]

    draw = tzeros(2)
    for t in 1:2
        # We have to use sum(Int8[...]) since sum([]) throws an error
        draw[t] ~ Categorical(
                     simplex([
                            start[white] - sum(Int8[draw[i]==white for i in 1:t-1]),
                            start[blue]  - sum(Int8[draw[i]==blue  for i in 1:t-1]),
                            start[red]   - sum(Int8[draw[i]==red   for i in 1:t-1])
                            ])
                            )
    end

    true ~ Dirac(draw[1] == red)

    return draw[2]==white

end

model = bag_of_marbles()
num_chains = 4

# HH has problem with this!
chains = sample(model, MH(), MCMCThreads(), 100_000, num_chains)
# chains = sample(model, MH(), 10_000)

# chains = sample(model, PG(15), MCMCThreads(), 1_000, num_chains)
# chains = sample(model, SMC(1000), MCMCThreads(), 10_000, num_chains)
# chains = sample(model, SMC(1000), 10_000)

# Note: IS don't generate chains the same way as MH, PG, and SMC!
# chains = sample(model, IS(), MCMCThreads(), 1000, num_chains)

display(chains)

show_var_dist_pct(chains,Symbol("draw[1]"))
println("\ndraw1: The probability of drawing white is the probability for 1.0")
show_var_dist_pct(chains,Symbol("draw[2]"))

# println("\ndraw1==white:")
# genq = generated_quantities(model,chains)
# show_var_dist_pct(genq,20)
# println("quantile:")
# println(quantile(vec(genq), [0.0, 0.025, 0.25, 0.5, 0.75, 0.975, 1.0]))


"""
    W,P,B = vblast(H)

Find a VBLAST decomposition of `H` such that `H = pinv(W)*B*P'` or `B =
W*H*P`.  Here `P` is a permutation matrix, `B` is lower triangular with ones
on the diagonal, and `W` has orthogonal rows.

    W,P,B = vblast(H,mu)

If an SNR argument `mu` is passed in, a regularized ("MMSE") decomposition
is done, with the result that `W` will no longer have orthogonal rows and
`B` is no longer lower triangular.

# Examples
```jldoctest
julia> H= [1. 2; 3 4];W,_ = vblast(H); W
2×2 Matrix{Float64}:
 1.5  -0.5
 0.1   0.3

julia> H= BigFloat.([1.5 2; 3 4]) .+ 2im; W,_= vblast(H); W
2×2 Matrix{Complex{BigFloat}}:
      -2.0+3.0im            2.0-1.5im
 0.0779221-0.103896im  0.155844-0.103896im

```
"""
function vblast(H::Array{T,2},mu=Inf) where {T}

(K,N) = size(H);

W = zeros(T,K,K);
P = zeros(Int,N,N);
Hp = H;
k = zeros(Int,K,1);
indexH = [1:N;];
for i=1:N
    G = inv(Hp'*Hp + I/mu)*Hp'; # Do pseudoinverse
    gn = diag(G*G');
    (mx,mi) = findmin(abs.(gn))
    k[i] = indexH[mi];
    W[i,:] = G[mi,:];
    P[k[i],i]=1;
    indexH = setdiff(indexH,k[i]);
    Hp = H[:,indexH];
end
B = W*H*P  # Calculating B may not be required in some real-world scenarios
return (W,P,B)
end


# Maybe a fancy trick shot isn't the best idea; after all, you only have one
# probe, so you had better not miss.
#
# To get the best idea of what your options are for launching the probe, you
# need to find every initial velocity that causes the probe to eventually be
# within the target area after any step.
#
# In the above example, there are 112 different initial velocity values that
# meet these criteria:
#
# 23,-10  25,-9   27,-5   29,-6   22,-6   21,-7   9,0     27,-7   24,-5
# 25,-7   26,-6   25,-5   6,8     11,-2   20,-5   29,-10  6,3     28,-7
# 8,0     30,-6   29,-8   20,-10  6,7     6,4     6,1     14,-4   21,-6
# 26,-10  7,-1    7,7     8,-1    21,-9   6,2     20,-7   30,-10  14,-3
# 20,-8   13,-2   7,3     28,-8   29,-9   15,-3   22,-5   26,-8   25,-8
# 25,-6   15,-4   9,-2    15,-2   12,-2   28,-9   12,-3   24,-6   23,-7
# 25,-10  7,8     11,-3   26,-7   7,1     23,-9   6,0     22,-10  27,-6
# 8,1     22,-8   13,-4   7,6     28,-6   11,-4   12,-4   26,-9   7,4
# 24,-10  23,-8   30,-8   7,0     9,-1    10,-1   26,-5   22,-9   6,5
# 7,5     23,-6   28,-10  10,-2   11,-1   20,-9   14,-2   29,-7   13,-3
# 23,-5   24,-8   27,-9   30,-7   28,-5   21,-10  7,9     6,6     21,-5
# 27,-10  7,2     30,-9   21,-8   22,-7   24,-9   20,-6   6,9     29,-5
# 8,-2    27,-8   30,-5   24,-7
#
# How many distinct initial velocity values cause the probe to be within the
# target area after any step?

# read input
(targetx1, targetx2, targety1, targety2) = parse.(
  Int,
  match(
    r"x=(-?\d+)..(-?\d+), y=(-?\d+)..(-?\d+)",
    readline("input.txt")
  )
)

# The sum of consecutive integers from 0 to n is n(n+1)/2. Using this, we can
# find the minimum possible starting x velocity by solving n(n+1)/2=targetx1-1.
# Anything equal to or less than that result will fall to a velocity of zero
# before reaching the target zone.
# n(n+1)/2=targetx1-1
# n^2 + n - 2*(targetx1-1) = 0
# n = (-b +- sqrt(b^2 - 4ac)) / 2a
minimum_xvel = floor(Int, sqrt(1 + 8 * (targetx1-1)) / 2)

# Our minimum starting y velocity is equal to the minimum target y because
# anything less than that will overshoot our target y on the very first step.
# See part 1 for an explanation of how to find the maximum starting y velocity.
#
# From a giving y velocity, we can determine how many steps it'll take to reach
# the target y. From that, we can find what x velocities will get us in the
# target x range. Then we increment our starting y velocity and do it again.
cnt = 0
for yvel in targety1:(abs(targety1)-1)
  inityvel = yvel

  # I feel like there's some mathematical way to calculate how many steps it'll
  # take to reach the target y zone with an initial y velocity, but I can't
  # brain it at the moment, so...
  steps = 0
  if yvel > 0
    # I did brain this little optimization: when our initial y velocity is
    # positive, it'll take 2*yvel+1 steps to get to a y position of 0, at which
    # point our velocity will be -yvel-1
    steps = 2 * yvel + 1
    yvel = -yvel - 1
  end

  # find the minimum number of steps required to reach the target y range
  # yvel is guaranteed non-positive at this point
  ypos = 0
  while ypos > targety2
    ypos += yvel
    yvel -= 1
    steps += 1
  end

  # a given initial y velocity may hit the target y range more than once - we
  # need to keep track of the x velocities we've already matched so we don't
  # count them again
  matched_x = Int[]
  while ypos >= targety1
    # Maximum x velocity is the point where steps(max+(max-steps+1))/2=targetx2
    # steps*max + steps*max - steps^2 + steps = 2*targetx2
    # 2*steps*max = 2*targetx2 + steps^2 - steps
    # max = targetx2/steps + steps/2 - 1/2
    # This isn't quite right, but my brain is done.
    for xvel in minimum_xvel:floor(Int, targetx2 / steps + steps / 2 - 0.5)
      initxvel = xvel
      xpos = 0
      for _ in 1:steps
        xpos += xvel
        xvel -= 1
        xvel == 0 && break
      end
      if targetx1 <= xpos <= targetx2 && !in(initxvel, matched_x)
        global cnt += 1
        println(initxvel, ",", inityvel, ": ", steps, " - ", xpos, ",", ypos)
        push!(matched_x, initxvel)
      end
    end

    ypos += yvel
    yvel -= 1
    steps += 1
  end
end

println("Result: ", cnt)


# Parts of this code were taken / derived from Graphs.jl. See LICENSE for
# licensing details.

# A* shortest-path algorithm

function a_star_impl!(
    g::AbstractGraph,# the graph
    t::Integer, # the end vertex
    frontier,               # an initialized heap containing the active vertices
    colormap::Vector{Int},  # an (initialized) color-map to indicate status of vertices
    distmx::AbstractMatrix,
    heuristic::Function    # heuristic fn (under)estimating distance to target
    )

    while !isempty(frontier)
        (cost_so_far, path, u) = dequeue!(frontier)
        if u == t
            return path
        end

        for v in LightGraphs.out_neighbors(g, u)

            if colormap[v] < 2
                dist = distmx[u, v]
                colormap[v] = 1
                new_path = cat(1, path, Edge(u, v))
                path_cost = cost_so_far + dist
                enqueue!(frontier,
                (path_cost, new_path, v),
                path_cost + heuristic(v))
            end
        end
        colormap[u] = 2
    end
    Vector{Edge}()
end

"""
    a_star(g, s, t[, distmx][, heuristic])

Return a vector of edges comprising the shortest path between vertices `s` and `t`
using the [A* search algorithm](http://en.wikipedia.org/wiki/A%2A_search_algorithm).
An optional heuristic function and edge distance matrix may be supplied. If missing,
the distance matrix is set to [`DefaultDistance`](@ref) and the heuristic is set to
`n -> 0`.
"""
function a_star(
    g::AbstractGraph,  # the g

    s::Integer,                       # the start vertex
    t::Integer,                       # the end vertex
    distmx::AbstractMatrix{T} = weights(g),
    heuristic::Function = n -> 0
    ) where T
    # heuristic (under)estimating distance to target
    U = eltype(g)
    frontier = PriorityQueue(Tuple{T,Vector{Edge},U}, T)
    frontier[(zero(T), Vector{Edge}(), s)] = zero(T)
    colormap = zeros(Int, nv(g))
    colormap[s] = 1
    a_star_impl!(g, t, frontier, colormap, distmx, heuristic)
end


using Test
using JuMP
using DisjunctiveProgramming
using Symbolics

@testset "linear constraints" begin

    function minimal()
        m = Model()
        @variable(m, -1<=x<=10)

        @constraint(m, con1, x<=3)
        @constraint(m, con2, 0<=x)
        @constraint(m, con3, x<=9)
        @constraint(m, con4, 5<=x)

        @disjunction(m,(con1,con2),con3,con4,reformulation=:CHR,name=:y)

        @test true
    end

    function simple_example(reform)
        m = Model()
        @variable(m, -10<=x<=10)
        @constraint(m, con1, x<=-1)
        @constraint(m, con2, 1<=x)

        if (reform == :BMR)
            @disjunction(m, con1, con2, reformulation=:BMR, name=:y)
        elseif (reform == :CHR)
            @disjunction(m, con1, con2, reformulation=:CHR, name=:y)
        end
        return m
    end

    function robustness()
        unreg = m -> unregister(m, :original_model_variables)

        function fresh_model()
            m = Model()
            @variable(m, -10<=x<=10)
            @constraint(m, con1, x<=-1)
            @constraint(m, con2, 1<=x)
            return m, con1, con2
        end

        # not enough constraints
        m = fresh_model()[1]
        # @test_throws DomainError @disjunction(m, (con1, con2), reformulation=:BMR, name=:y)
        unreg(m)
        # @test_throws DomainError @disjunction(m, con1, reformulation=:BMR, name=:y)

        # Big-M reformulation without variable bounds defined
        # should only work if user specifies M value
        m = Model()
        @variable(m, x)
        @constraint(m, con1, x<=-1)
        @constraint(m, con2, 1<=x)
        @test @disjunction(m, con1, con2, reformulation=:BMR, name=:y, M=11) == nothing
        unreg(m)
        @test_throws ErrorException @disjunction(m, con1, con2, reformulation=:BMR, name=:y)

        # CHR reformulation without variable bounds defined should fail
        m = Model()
        @variable(m, x)
        @constraint(m, con1, x<=-1)
        @constraint(m, con2, 1<=x)
        @test_throws AssertionError @disjunction(m, con1, con2, reformulation=:CHR, name=:y)

        # empty constraints on one side of disjunction
        m, con1, con2 = fresh_model()
        @test @disjunction(m, con1, nothing, reformulation=:BMR, name=:y) == nothing
        m, con1, con2 = fresh_model()
        @test @disjunction(m, nothing, con2, reformulation=:CHR, name=:z) == nothing
        m, con1, con2 = fresh_model()
        @test @disjunction(m, (con1, con2), nothing, reformulation=:BMR, name=:y) == nothing

    end

    function model_BMR_valid(m)
        # Expecting to see following constraints:
        # x <= -1 + 11 * (1 - y1)
        # -x <= -1 + 11 * (1 - y2)
        # y1 + y2 = 1
        # -10<=x<=10
        cons = (L -> all_constraints(m, L...)).(list_of_constraint_types(m))
        @test sum(length.(cons)) == 5 + 2 # add 2 for y1/y2 binary statements

        con_strings = replace.(constraints_string(REPLMode, m), "[" => "",
            "]" => "", "+ y" => "+y", " y" => "* y",
            "con1 :" => "", "con2 :" => "")
        cons_actual = Meta.parse.(con_strings[1:5])
        cons_expected = [:(y1 + y2 == 1), :(x + 11 * y1 <= 10),
                         :(-x + 11 * y2 <= 10), :(x >= -10), :(x <= 10)]
        matches = sum(map(i->isequal(i[1], i[2]),
            Base.product(cons_expected, cons_actual)))
        @test matches == length(cons_actual)
    end

    function model_CHR_valid(m)
        # Expecting to see following constraints:
        # x_y1 <= -1 * y1
        # -x_y2 <= -1 * y2
        # y1 + y2 = 1
        # x = x_y1 + x_y2
        # -10 * y1 <= x_y1 <= 10 * y1
        # -10 * y2 <= x_y2 <= 10 * y2
        # -10 <= x <= 10
        # -10 <= x_y1 <= 10
        # -10 <= x_y2 <= 10
        cons = (L -> all_constraints(m, L...)).(list_of_constraint_types(m))
        @test sum(length.(cons)) == 14 + 2 # add 2 for y1/y2 binary statements

        con_strings = replace.(constraints_string(REPLMode, m), "[" => "",
            "]" => "", "+ y" => "+y", " y" => "* y",
            "con1 :" => "", "con2 :" => "")
        println.(con_strings)
        cons_expected = [:(y1 + y2 == 1), :(x - x_1 - x_2 == 0), :(x >= -10),
                     :(x_1 + y1 <= 0), :(-x_2 + y2 <= 0), :(x <= 10),
                     :(x_1 >= -10), :(x_1 <= 10), :(x_2 >= -10), :(x_2 <= 10),
                     :(-10 * y1 - x_1 <= 0), :(-10 * y1 + x_1 <= 0),
                     :(-10 * y2 - x_2 <= 0), :(-10 * y2 + x_2 <= 0)]
        cons_actual = Meta.parse.(con_strings[1:14])
        matches = sum(map(i->isequal(i[1], i[2]),
            Base.product(cons_expected, cons_actual)))
        @test matches == length(cons_actual)
    end

    robustness()

    m1 = simple_example(:BMR)
    model_BMR_valid(m1)

    m2 = simple_example(:CHR)
    model_CHR_valid(m2)

end


"""
Test orthonormalization of wavefunction in real space
"""
function test_ortho( dVol, Nstates, psi )
    print("\nTest norm:\n")
    print( "===========================\n")
    for is = 1:Nstates
        print("State: #$(is): $(dot( psi[:,is], psi[:,is] ) * dVol)\n")
    end
    print("\nTest ortho w.r.t state #1:\n")
    print( "===========================\n")
    for is = 2:Nstates
        print("State: #$(is): $(dot( psi[:,is], psi[:,1] ) * dVol)\n")
    end
    print("\n")
end


using Polylogarithms
using SpecialFunctions
using Test
using DataFrames, CSV
import Base.MathConstants: π, pi, ℯ, e, γ, eulergamma, catalan, φ, golden
include("test_defs.jl")
include("../src/gamma_derivatives.jl")

@testset "Derivatives of the gamma function at 1.0" begin

    @testset "    throws errors" begin
        @test_throws DomainError D(2, -1, 1.0)
        @test_throws DomainError D(-1, 2, 1.0)
        # @test_throws MethodError stieltjes(1.5)
    end

    @testset "    types" begin
        # @test typeof(stieltjes(1)) == Float64
    end

    @testset "    values" begin
        @test g1(0) ≈ 1.0
        @test g2(0) ≈ 1.0
        @test g1(1) ≈ -γ
        @test g2(1) ≈ -γ
        for n=2:5
            @test g1(n) ≈ g2(n)
        end
    end

end


# ============================================================================ #
# Line
# ============================================================================ #
abstract type AbstractLine{T} end

struct Line{PointType <: AbstractPoint} # <: AbstractLine
    p1::PointType
    p2::PointType
end

Line(a::A, b::A) where {A <: AbstractArray} = Line( Point(a[1], a[2]), Point(b[1], b[2]) )

# ---------------------------------------------------------------------------- #
# Methods
# ---------------------------------------------------------------------------- #

Base.:(==)(line1::Line, line2::Line) = line1.p1 == line2.p1 && line1.p2 == line2.p2

function shift(line::T, v) where {T <: AbstractLine}
    return T( line.p1 + v, line.p2 + v )
end
function scale(line::T, factor) where {T <: AbstractLine}
    return T( line.p1 * factor, line.p2 * factor )
end

function Base.getindex(line::AbstractLine, index::Int)
    if index == 1
        return line.p1
    elseif index == 2
        return line.p2
    else
        return NaN
    end
end

function vector(line::Line)
    line.p2 - line.p1
end

function line_intersection(line1::Line, line2::Line, abs_tol::Real=0, rel_tol::Real=abs_tol>0 ? 0 : sqrt(eps))
    rhs = line1.p1 - line1.p2
    n1 = orthogonal_vector( vector(line1) )
    n2 = orthogonal_vector( vector(line2) )
    lamdas = n1 ./ n2
    if isapprox(lamdas[1], lamdas[2], atol = abs_tol, rtol = rel_tol)   # lamdas[1] == lamdas[2]
        return Point(v)
    end
    A = [n1, n2]
    t12 = A \ rhs
    v = t12[1] > t12[2] ? n1 .* t12[1] : n2 .* t12[2]
    return Point(v)
end

function segment_intersection(line1_a::P, line1_b::P, line2_a::P, line2_b::P) where {P <: AbstractPoint}

    d1 = point_orientation(line1_a, line1_b, line2_a)
    d2 = point_orientation(line1_a, line1_b, line2_b)
    d3 = point_orientation(line2_a, line2_b, line1_a)
    d4 = point_orientation(line2_a, line2_b, line1_b)

    b12 = (d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)
    b34 = (d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)

    if b12 && b34
        return true

    elseif d1 == 0 && on_segment_paralell(line2_a, line2_b, line1_a)
        return true

    elseif d2 == 0 && on_segment_paralell(line2_a, line2_b, line1_b)
        return true

    elseif d3 == 0 && on_segment_paralell(line1_a, line1_b, line2_a)
        return true

    elseif d4 == 0 && on_segment_paralell(line1_a, line1_b, line2_b)
        return true

    else
        return false
    end

end

function segment_intersection(line1::Line, line2::Line)
    return segment_intersection(line1.p1, line1.p2, line2.p1, line2.p2)
end

function on_segment_paralell(line::Line, point::AbstractPoint)
# a point which lies on the line but not known whether on the segment
    return  (min(line.p1.x, line.p2.x) <= point.x <= max(line.p1.x, line.p2.x)) &&
            (min(line.p1.y, line.p2.y) <= point.y <= max(line.p1.y, line.p2.y))
end

function on_segment_paralell(line_a, line_b, point::AbstractPoint)
# a point which lies on the line but not known whether on the segment
    return  (min(line_a.x, line_b.x) <= point.x <= max(line_a.x, line_b.x)) &&
            (min(line_a.y, line_b.y) <= point.y <= max(line_a.y, line_b.y))
end

function point_orientation(p_base::P, p2::P, p3::P) where {P <: AbstractPoint}
    return cross2(p3 - p_base, p2 - p_base)
end

function discretize_for_plot(line::Line)
    x = [line.p1.x, line.p2.x]
    y = [line.p1.y, line.p2.y]
    return (x, y)
end


 module Factorial2

 function factorial2(number::Int64)
     fact = one(number)
     if number%2 == 0
         for m in 1:number
             if m%2 == 0
                 fact *= m
             end
         end
     elseif number%2 == 1
         for m in 1:number
             if m%2 == 1
                 fact *= m
             end
         end
     end
     return fact
 end

 end # module


function augment_states(m::SmetsWoutersOrig{T}, TTT::Matrix{T}, RRR::Matrix{T}, CCC::Vector{T}) where T<:AbstractFloat
    endo = m.endogenous_states
    endo_addl = m.endogenous_states_augmented
    exo = m.exogenous_shocks

    n_endo = n_states(m)
    n_exo = n_shocks_exogenous(m)
    @assert (n_endo, n_endo) == size(TTT)
    @assert (n_endo, n_exo) == size(RRR)
    @assert n_endo == length(CCC)

    # Initialize augmented matrices
    n_addl_states = length(endo_addl)
    n_addl_eqs = n_addl_states
    TTT_aug = zeros(n_endo + n_addl_eqs, n_endo + n_addl_states)
    TTT_aug[1:n_endo, 1:n_endo] = TTT
    RRR_aug = [RRR; zeros(n_addl_eqs, n_exo)]
    CCC_aug = [CCC; zeros(n_addl_eqs)]

    ### TTT modifications

    # Track Lags
    TTT_aug[endo_addl[:y_t1], endo[:y_t]] = 1.
    TTT_aug[endo_addl[:c_t1], endo[:c_t]] = 1.
    TTT_aug[endo_addl[:i_t1], endo[:i_t]] = 1.
    TTT_aug[endo_addl[:w_t1], endo[:w_t]] = 1.

    return TTT_aug, RRR_aug, CCC_aug
end


@deprecate gaussweights quadweights

quadweights(dmeasure::DiscreteWeight, measure::Weight) =
    quadweights(points(dmeasure), weights(dmeasure), measure)

quadweights(grid::AbstractGrid, weights, measure::Weight) =
    quadweights(grid, measure) ./ weights

quadweights(grid::AbstractGrid, ::Ones, measure::Weight) =
    quadweights(grid, measure)

function quadweights(grid::MappedGrid, measure::MappedWeight)
    @assert iscompatible(forward_map(grid), forward_map(measure))
    sgrid = supergrid(grid)
    quadweights(sgrid, supermeasure(measure))
end

quadweights(grid::ProductGrid, measure::ProductWeight) =
    OuterProductArray(map(quadweights, components(grid), components(measure))...)

quadweights(grid::ChebyshevTNodes{T}, ::ChebyshevTWeight{T}) where {T} =
    GridArrays.ChebyshevTWeights{T}(length(grid))

quadweights(grid::ChebyshevUNodes{T}, ::ChebyshevUWeight{T}) where {T} =
    GridArrays.ChebyshevUWeights{T}(length(grid))

quadweights(grid::LegendreNodes{T}, ::LegendreWeight{T}) where {T} =
    weights(GaussLegendre{T}(length(grid)))

function quadweights(grid::LaguerreNodes{T}, measure::LaguerreWeight{T}) where {T}
	@assert grid.α ≈ measure.α
    weights(GaussLaguerre(length(grid),grid.α))
end

quadweights(grid::HermiteNodes{T}, ::HermiteWeight{T}) where {T} =
    weights(GaussHermite{T}(length(grid)))

function quadweights(grid::JacobiNodes{T}, measure::JacobiWeight{T}) where {T}
    @assert (grid.α ≈ measure.α) & (grid.β ≈ measure.β)
	weights(GaussJacobi(length(grid), grid.α, grid.β))
end

quadweights(grid::FourierGrid{T}, measure::FourierWeight{T}) where {T} =
    NormalizedArray{T}(length(grid))

function quadweights(grid::PeriodicEquispacedGrid{T}, measure::FourierWeight{T}) where {T}
    @assert coverdomain(grid)≈support(measure)
	NormalizedArray{T}(length(grid))
end

function quadweights(grid::MidpointEquispacedGrid{T}, measure::FourierWeight{T}) where {T}
    @assert coverdomain(grid)≈support(measure)
	NormalizedArray{T}(length(grid))
end

function quadweights(grid::PeriodicEquispacedGrid{T}, measure::LebesgueMeasure{T}) where {T}
    @assert coverdomain(grid)≈support(measure)
	DomainSets.width(support(measure))*NormalizedArray{T}(length(grid))
end

function quadweights(grid::MidpointEquispacedGrid{T}, measure::LebesgueMeasure{T}) where {T}
    @assert coverdomain(grid)≈support(measure)
	DomainSets.width(support(measure))*NormalizedArray{T}(length(grid))
end

function quadweights(grid::ChebyshevNodes{T}, ::LegendreWeight{T}) where {T}
	x, w = fejer_first_rule(length(grid), T)
	w
end

function quadweights(grid::ChebyshevExtremae{T}, ::LegendreWeight{T}) where {T}
	x, w = clenshaw_curtis(length(grid)-1, T)
	w
end

function quadweights(grid::ChebyshevNodes{T}, measure::LebesgueMeasure{T}) where {T}
    @assert ≈(map(support, (grid,measure))...)
	quadweights(grid, LegendreWeight{T}())
end

function quadweights(grid::ChebyshevExtremae{T}, measure::LebesgueMeasure{T}) where {T}
    @assert ≈(map(support, (grid,measure))...)
	quadweights(grid, LegendreWeight{T}())
end

quadweights(grid::AbstractGrid, measure::Weight) =
	default_quadratureweights(grid, measure)

function default_quadratureweights(grid, measure)
	@debug "No known quadrature normalization available for grid $(typeof(grid)) with measure $(typeof(measure)) \n choosing Riemann sum normalization"
	riemann_normalization(grid, measure)
end

# Fall back in 1d. We don't know any special properties of the grid: we use a
# Riemann sum and multiply by the weight function of the measure on the grid.
function riemann_normalization(grid::AbstractGrid1d, measure)
	a = infimum(support(measure))
	b = supremum(support(measure))
	riemannsum_weights(grid, a, b) .*  measuresampling(grid, measure)
end

function riemannsum_weights(grid::AbstractGrid, a = leftendpoint(coverdomain(grid)), b = rightendpoint(coverdomain(grid)))
	M = length(grid)
	L = b-a
	t = [grid[end] - L; collect(grid); grid[1]+L]
	T = prectype(grid)
	weights = zeros(T, M)
	for m in 1:M
		weights[m] = (t[m+2]-t[m]) / 2
	end
	weights
end

# For any Lebesgue measure this will be the identity operator
measuresampling(grid::AbstractGrid, measure::LebesgueMeasure) =
	Ones{prectype(grid)}(size(grid))

"""
	measuresampling(grid::AbstractGrid, ::Array, measure::Weight)

Return an array with a weight function of a measure evaluated on a grid.
"""
measuresampling(grid::AbstractGrid, measure::Weight) =
    [unsafe_weightfun(measure, xi) for xi in grid]


#=
Copyright (c) 2015, Intel Corporation
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
=#

using ParallelAccelerator
using DocOpt

include("image-notypes.jl")
using .Image

#ParallelAccelerator.ParallelIR.set_debug_level(3)
#ParallelAccelerator.set_debug_level(3)
#ParallelAccelerator.CGen.set_debug_level(3)
ParallelAccelerator.ParallelIR.PIRNumThreadsMode(2)

@acc begin
# Block Jacobi preconditioner
# Used in singleScaleOpticalFlow below
# Needs to be toplevel function for use with ParallelAccelerator
@inline function blockJacobiPreconditioner(Ix, Iy, ru, rv, lam)
    a = Ix.*Ix.+4.0f0*lam
    b = Ix.*Iy
    c = Iy.*Iy.+4.0f0*lam
    invdet = 1.0f0./(a.*c-b.*b)
    rru = (c.*ru-b.*rv).*invdet
    rrv = (a.*rv-b.*ru).*invdet
    rru, rrv
end

# Solve optical flow problem at one scale
# Flow problem is formulated as the solution of a linear system where matrix is sparse
# Use an iterative method to
# Pre: size(i1)==size(i2)
function singleScaleOpticalFlow(i1, i2, lam, ni)
# Returns two Matrix{Float32} size=size(i1)
  (w, h) = size(i1)
  # Compute the gradient of i1
  # This is basically two stencils, one for x and one for y
  # The stencil is [1 8 0 -8 -1]/12 and leave zeros in the boundary
  Ix = Array(Float32, w, h)
  runStencil(Ix, i1, 1, :oob_dst_zero) do b, a
    b[0,0] = (a[-2,0] + 8.0f0 * a[-1,0] - 8.0f0 * a[1,0] - a[2,0])/12.0f0
  end
  Iy = Array(Float32, w, h)
  runStencil(Iy, i1, :oob_dst_zero) do b, a
    b[0,0] = (a[0,-2] + 8.0f0 * a[0,-1] - 8.0f0 * a[0,1] - a[0,2])/12.0f0
  end
  # Compute the time partial derivative
  It = i1-i2

  # Initialise for iterative solve
  xu = zeros(Float32, w, h)
  xv = zeros(Float32, w, h)
  ru = -Ix.*It
  rv = -Iy.*It
  zu, zv = blockJacobiPreconditioner(Ix, Iy, ru, rv, lam)
  pu, pv = zu, zv
  rsold = sum(ru.*zu)+sum(rv.*zv)

  # Iterate to solution
  for i in 1:ni
    # The matrix vector product is equivalent to a stencil
    # Want:
    #   -lam for (-1,0), (+1,0), (0,-1), and (0,+1)
    #   Ix*Iy for (0,0) of the other array
    #   Ix^2+4*lam and Iy^2+4*lam for (0,0) respectively (Ix for u, Iy for v)
    # At boundaries just include terms that exist
    Apu = Array(Float32, w, h)
    Apv = Array(Float32, w, h)
    runStencil(Apu, Apv, pu, pv, Ix, Iy, :oob_src_zero) do Apu, Apv, pu, pv, Ix, Iy
      ix = Ix[0,0]
      iy = Iy[0,0]
      Apu[0,0] = ix * (ix*pu[0,0] + iy*pv[0,0]) + lam*(4.0f0*pu[0,0]-(pu[-1,0]+pu[1,0]+pu[0,-1]+pu[0,1]))
      Apv[0,0] = iy * (ix*pu[0,0] + iy*pv[0,0]) + lam*(4.0f0*pv[0,0]-(pv[-1,0]+pv[1,0]+pv[0,-1]+pv[0,1]))
    end
    pTAp = sum(pu.*Apu)+sum(pv.*Apv)
    alpha = rsold/pTAp
    xu += alpha*pu
    xv += alpha*pv
    ru -= alpha*Apu
    rv -= alpha*Apv
    zu, zv = blockJacobiPreconditioner(Ix, Iy, ru, rv, lam)
    rsnew = sum(ru.*zu)+sum(rv.*zv)
    beta = rsnew/rsold
    pu = zu + beta*pu
    pv = zv + beta*pv
    rsold = rsnew
  end

  return xu, xv
end

# Solve optical flow problem at all scales
# Pre: size(i1)==size(i2)
@fastmath function multiScaleOpticalFlow(i1, i2, lam, ni, ns)
# Returns two Matrix{Float32} size=size(i1)
  (w, h) = size(i1)
  if ns==1
    return singleScaleOpticalFlow(i1, i2, lam, ni)
  end
  scale = Float32((Float64(w)/50.0)^(-1.0/Float32(ns)))
  local u, v # initialised in the first iteration of the loop
  i = ns
  while i >= 0
    cw = round(Int, Float32(w)*scale^Float32(i))
    ch = round(Int, Float32(h)*scale^Float32(i))
    # println("cw,ch=",cw,",",ch)
    # println("Doing scale: ", cw, "x", ch)
    # Downsample the images
    si1 = Image.downSample(i1, cw, ch)
    si2 = Image.downSample(i2, cw, ch)
    # Interpolate the current flow and warp image 2 by current flow
    if i==ns
      wi = si2
      u = zeros(Float32, cw, ch)
      v = zeros(Float32, cw, ch)
    else
      # Coarsest scale, no flow yet, use zeros, warp is image 2
      u, v = Image.interpolateFlow(u, v, cw, ch)
      wi = Image.warpMotion(si2, u, v, si1)
    end
    # Compute the flow from image 1 to warp
    # println("checksum si1 wi: ", sum(si1), " ", sum(wi))
    du, dv = singleScaleOpticalFlow(si1, wi, lam, (i==0 ? ni*5 : ni))
    # Update the flow
    u += du
    v += dv
    i -= 1
  end
  return u, v
end

function multiFrameOpticalFlow(frames, nframes)
  cartesianarray(Tuple{Matrix{Float32}, Matrix{Float32}}, (nframes - 1,)) do i
    multiScaleOpticalFlow(frames[i], frames[i+1], Float32(0.025), 100, 44)
  end
end

end

function main()
    doc = """opt-flow.jl

Horn-Schunck multi-frame optical flow estimator.

Usage:
  opt-flow.jl -h | --help
  opt-flow.jl [--image-name-prefix=<image-name-prefix>] [--image-name-suffix=<image-name-suffix>] [--num-frames=<num-frames>]

Options:
  -h --help                     Show this screen.
  --image-name-prefix=<image-name-prefix>  Specify an image filename prefix [default: small_0].
  --image-name-suffix=<image-name-suffix>  Specify an image filename prefix [default: .dat].
  --num-frames=<num-frames>                Specify a number of frames (at least 2) [default: 2].

Assumes that image files are in the same directory as opt-flow.jl.
"""
    arguments = docopt(doc)

    fname_prefix = arguments["--image-name-prefix"]
    fname_suffix = arguments["--image-name-suffix"]
    nframes = parse(Int, arguments["--num-frames"])

    images_dir = dirname(@__FILE__)

    filenames = [ string(fname_prefix, string(i < 10 ? "0" : "", i), fname_suffix) for i = 1:nframes ]
    filepaths = [ joinpath(images_dir, i) for i in filenames ]

    println("nframes = ", nframes)
    assert(nframes > 1)
    frames = Array(Matrix{Float32}, nframes)
    for i = 1:nframes
        frames[i] = Image.readImage(filepaths[i])
        if i > 1 && size(frames[i])!=size(frames[i-1])
            write(STDERR, "images are different sizes")
            exit(-1)
        end
    end

    println("filenames = ", filenames)
    println("checksums = ", map(sum, frames))
    println("Image size: ", size(frames[1], 1), "x", size(frames[1], 2))

    tic()
    multiFrameOpticalFlow(frames, 1)
    println("SELFPRIMED ", toq())

    tic()
    u,v = multiFrameOpticalFlow(frames, nframes)
    selftimed = toq()
    Image.writeFlo(u[1], v[1], "out.flo")
    println("checksum: ", sum(u[1]), " ", sum(v[1]))
    println("SELFTIMED ", selftimed)
end

main()


@testset "construction" begin
    @test basetype(QQ{Int}) == ZZ{Int}
    @test lcunit(QQ(-1, 3)) == 1
    @test lcunit(QQ(0)) == 1
    @test copy(QQ(12//15)) == QQ(4, 5)
    @test QQ{Int32}(QQ(3, 4)) isa QQ{Int32}
    @test QQ{Int}(QQ(3, 4)) != nothing
    @test QQ{BigInt}(ZZ(11)) == 11
    @test ZZ(12) // ZZ(13) == QQ(12, 13)

    @test QQ(1, 2) + QQ{Int8}(1, 3) == 5//6
    @test QQ(1, 3) - ZZ(1) == -2//3
    @test QQ(1, 2) * 2 == 1
    @test QQ(1, 2) - 1//3 == 1//6

    @test QQ(1, 2) / QQ(1, 3) == 3//2
    @test -QQ(1) == -1
    @test divrem(QQ(1,2), QQ(1, 3)) == (3//2, 0//1)
    @test div(QQ(1,2), QQ(1, 3)) == 3//2
    @test iszero(rem(QQ(1,2), QQ(1, 3)))
    @test isunit(QQ(23, 24))
    @test !isunit(QQ(0))
    @test isone(QQ(11, 11))
    @test zero(QQ{Int}) == 0
    @test one(QQ{Int8}) == 1
    @test hash(QQ(13, 14)) == hash(13//14)

    @test sprint(show, QQ(12, 13)) == "12/13"
    @test sprint(show, QQ(12, 1)) === "12"

    @test QQ{Int}(3, 6) == 1//2
    @test QQ(3, 6) == 1//2
    @test QQ(2) == QQ(ZZ(2))
    @test QQ(Int8(2)) == QQ{BigInt}(2//1)
    @test QQ{Int8}(2) == QQ(2)
    @test QQ(1//2) == 1//2

    @test_throws ArgumentError QQ(0, 0)
    @test_throws ArgumentError QQ(1, typemin(Int))
    @test QQ(2, typemin(Int)) == QQ(-1, typemin(Int)÷(-2))
    @test QQ(1, typemin(Int16)) == QQ(-1, -Int(typemin(Int16)))
    @test_throws InexactError QQ(1, typemax(UInt))
    @test_throws InexactError QQ(-1, typemax(UInt))
    #= would be nice to have
    @test_broken QQ(-3, typemax(UInt)) == QQ(-1, Int(typemax(UInt)÷3))
    @test_broken QQ(3, typemax(UInt)) == QQ(1, Int(typemax(UInt)÷3))
    @test_broken QQ(typemax(UInt), -3) == QQ(-1, Int(typemax(UInt)÷3))
    @test_broken QQ(typemax(UInt), 3) == QQ(1, Int(typemax(UInt)÷3))
    =#
end

@testset "arithmetic" begin
    @test QQ(2//3) + QQ(1//2) == QQ(7//6)
end

@testset "gcd calculations" begin
    @test gcd(QQ(1//3), QQ(2//5)) == 1 // 15
    @test gcd(QQ((2*3)//(5*7)), QQ((2*5)//(3*7))) == QQ(2, 3*5*7)
    @test gcdx(QQ((2*3)//(5*7)), QQ((2*5)//(3*7))) == (QQ(2, 3*5*7), -11, 4)
    @test lcm(QQ((2*3)//(5*7)), QQ((2*5)//(3*7))) == QQ(2*3*5, 7)
end


replace_post(ex, s, s′) = postwalk(x -> x == s ? s′ : x, ex)

function LinearRecEntry(expr::Expr)
    CoeffT = QQ
    _funcs = Symbol[]
    _args = RExpr[]
    function_walk(expr) do f, x
        @assert length(x) == 1
        push!(_funcs, f)
        push!(_args, x[1])
    end
    _funcs = Base.unique(_funcs)
    @assert length(_funcs) > 0 "Not a recurrence: no functions present"
    argsyms = Base.unique(Iterators.flatten(symbols(a) for a in _args))
    @assert length(argsyms) == 1 "More (or less) than one variable in the function arguments, got: $(argsyms)"

    _argsym = argsyms[1]
    @assert !(_argsym in _funcs) "Ambiguous symbol: $(_argsym) is a function and an argument"

    R, argsym = PolynomialRing(CoeffT, string(_argsym))

    s_expr = SymEngine.Basic(expr)
    s_argsym = SymEngine.Basic(_argsym)
    s_args = map(x->(SymEngine.Basic(x) - s_argsym), _args)
    # minarg = minimum(convert(Rational, x) for x in s_args)
    minarg = convert(Int, minimum(s_args))
    if !iszero(minarg)
        _expr = replace_post(expr, _argsym, :($_argsym - $minarg))
        return LinearRecEntry(_expr)
    end
    maxarg = convert(Int, maximum(s_args))

    hom = zero(SymEngine.Basic)
    dicts = [Dict{Symbol,FracElem}() for _ in 1:maxarg + 1]
    for i in 0:maxarg
        for f in _funcs
            fc = SymEngine.SymFunction(f)(s_argsym + i)
            co = SymEngine.coeff(s_expr, fc)
            if !iszero(co)
                hom += co * fc
                # call R twice to deal with integers
                num = numerator(co)
                den = denominator(co)
                cnum = R(R(convert(Expr, num)))
                cden = R(R(convert(Expr, den)))
                dicts[i+1][f] = cnum // cden
            end
        end
    end

    inhom = R(R(convert(Expr, SymEngine.expand(-(s_expr - hom)))))//R(1)

    (coeffs = dicts, inhom = inhom), argsym
end

macro lrs(input)
    @capture(input, begin fields__ end)
    lrs(Vector{Expr}(fields))
end

function entries(ex::Expr...)
    _entries = []
    _args = []
    _syms = Symbol[]
    for x in ex
        if @capture(x, l_ = r_)
            x = :($(unblock(l)) - $(unblock(r)))
            @assert @capture(l, v_(a_))
            push!(_syms, v)
        end
        entry, arg = LinearRecEntry(x)
        push!(_entries, entry)
        push!(_args, arg)
    end
    _args = Base.unique(_args)
    @assert length(_args) == 1
    _syms, _entries, _args[1]
end

function lrs(exprs::Vector{Expr})
    syms, ls, arg = entries(exprs...)
    lrs = LinearRecSystem(arg, syms)
    push!(lrs, ls...)
    lrs
end


using TensorNetworkCodes
using Test

@testset "five_qubit_code" begin
    code = five_qubit_code()
    @test verify(code)
    @test num_qubits(code) == 5
    @test Int(length(code.logicals) / 2) == 1
    @test find_distance_logicals(code)[1] == 3
end

@testset "five_qubit_surface_code" begin
    code = five_qubit_surface_code()
    @test verify(code)
    @test num_qubits(code) == 5
    @test Int(length(code.logicals) / 2) == 1
    @test find_distance_logicals(code)[1] == 2
end

@testset "steane_code" begin
    code = steane_code()
    @test verify(code)
    @test num_qubits(code) == 7
    @test Int(length(code.logicals) / 2) == 1
    @test find_distance_logicals(code)[1] == 3
end

@testset "random_code" begin
    code = random_code(9, 2)
    @test verify(code)
    @test num_qubits(code) == 9
    @test Int(length(code.logicals) / 2) == 2
    # invalid k > n
    @test_throws ErrorException random_code(5, 6)
end

@testset "random_stabilizer_state" begin
    code = random_stabilizer_state(6)
    @test verify(code)
    @test num_qubits(code) == 6
    @test Int(length(code.logicals) / 2) == 0
end