@testset "Consider Kalman filter" begin

    @testset "Measurement update" begin

        x = randn(6)
        PL = randn(6,6)
        P = PL'PL
        P_chol = cholesky(P)
        RL = randn(3,3)
        R = RL'RL
        y = randn(3)
        R_chol = cholesky(R)
        H = randn(3,6)
        h(x) = H * x

        # Comparison measurement update
        # Nulling Kalman Gain for considered states
        # http://www.anuncommonlab.com/articles/how-kalman-filters-work/part2.html
        ỹ = KalmanFilter.calc_innovation(H, x, y)
        PHᵀ = KalmanFilter.calc_P_xy(P, H)
        S = KalmanFilter.calc_innovation_covariance(H, P, R)
        K = KalmanFilter.calc_kalman_gain(PHᵀ, S, [])
        K[5:6,:] .= 0.0
        x_post = KalmanFilter.calc_posterior_state(x, K, ỹ, [])
        # Use Joseph form, because of nulling Kalman gain
        P_post = (I - K * H) * P * (I - K * H)' + K * R * K'

        cons_mu = @inferred measurement_update(x, P, y, H, R, consider = 5:6)
        @test @inferred(get_covariance(cons_mu)) ≈ P_post
        @test @inferred(get_state(cons_mu)) ≈ x_post
        @test @inferred(get_kalman_gain(cons_mu)) ≈ K[1:4,:]

        cons_mu_chol = measurement_update(x, P_chol, y, H, R_chol, consider = 5:6)
        @test @inferred(get_covariance(cons_mu_chol)) ≈ P_post
        @test @inferred(get_state(cons_mu_chol)) ≈ x_post
        @test @inferred(get_kalman_gain(cons_mu_chol)) ≈ K[1:4,:]

        cons_mu_ukf = measurement_update(x, P, y, h, R, consider = 5:6)
        @test @inferred(get_covariance(cons_mu_ukf)) ≈ P_post
        @test @inferred(get_state(cons_mu_ukf)) ≈ x_post
        @test @inferred(get_kalman_gain(cons_mu_ukf)) ≈ K[1:4,:]

        cons_mu_srukf = measurement_update(x, P_chol, y, h, R_chol, consider = 5:6)
        @test @inferred(get_covariance(cons_mu_srukf)) ≈ P_post
        @test @inferred(get_state(cons_mu_srukf)) ≈ x_post
        @test @inferred(get_kalman_gain(cons_mu_srukf)) ≈ K[1:4,:]
    end
end


"""
    CenterPoint(dims) -> cp

Create a fixed point which can be used in `imresize` and `imrotate`
functions in order to keep the value in this point the same, i.e.,

```julia
img[cp] == imgr[cp]
```
"""
struct CenterPoint{N}
    p::CartesianIndex{N}
end

CenterPoint(dims::Dims{N}) where N = CenterPoint{N}(CartesianIndex(dims))
CenterPoint(dims::Int64...) = CenterPoint(Tuple(dims))


# # Block Basics
# ## Table of Contents
# * Construction and Matrix Representation
# * Block Tree Architecture
# * Tagging System
# * Parameter System
# * Differentiable Blocks
# * Time Evolution and Hamiltonian

using Yao, Yao.Blocks
using LinearAlgebra

# ## Construction and Matrix Representation
# Blocks are operations on registers, we call those with matrix representation (linear) `MatrixBlock`.

# A `MatrixBlock` can be
# * isunitary, $O^\dagger O=I$
# * ishermitian, $O^\dagger = O$
# * isreflexive, $O^2 = 1$

@show X
@show X |> typeof
@show isunitary(X)
@show ishermitian(X)
@show isreflexive(X);

# **matrix representation**
mat(X)

# **composite gates**
# Embeding an X gate into larger Hilbert space, the first parameter of most non-primitive constructors are always qubit numbers

@show X2 = put(3, 2=>X)
@show isunitary(X2)
@show ishermitian(X2)
@show isreflexive(X2);

#---------------
mat(X2)
#---------------
@show cx = control(3, 3, 1=>X)
@show isunitary(cx)
@show ishermitian(cx)
@show isreflexive(cx);
#---------------
mat(cx)
# hermitian and reflexive blocks can be used to construct rotation gates
@show rx = rot(X, π/4)
@show isunitary(rx)
@show ishermitian(rx)
@show isreflexive(rx);

#-----------------
mat(rx)

# now let's build a random circuit for following demos
using Yao.Intrinsics: rand_unitary
circuit = chain(5, control(5, 3=>Rx(0.25π)), put(5, (2,3)=>matrixgate(rand_unitary(4))), swap(5, 3, 4), repeat(5, H, 2:5), put(5, 2=>Ry(0.6)))
# to apply it on some register, we can use
reg = zero_state(10)
focus!(reg, 1:5) do reg_focused
    apply!(reg_focused, circuit)
end
@show reg ≈ zero_state(10);   # reg is changed!
# then we reverse the process and check the correctness
focus!(reg, 1:5) do reg_focused
    reg_focused |> circuit'
end
@show reg ≈ zero_state(10);   # reg is restored!
# Here, we have used the pip "eye candy" `reg |> block` to represent applying a block on register, which is equivalent to `apply!(reg, block)`
#---------------

# **Type Tree**
# To see a full list of block types
using InteractiveUtils: subtypes
function subtypetree(t, level=1, indent=4)
   level == 1 && println(t)
   for s in subtypes(t)
     println(join(fill(" ", level * indent)) * string(s))
     subtypetree(s, level+1, indent)
   end
end

subtypetree(Yao.Blocks.AbstractBlock);

# In the top level, we have
# * `MatrixBlock`, linear operators
# * `AbstractMeasure`, measurement operations
# * `FunctionBlock`, a wrapper for register function that take register as input, change the register inplace and return the register.
# * `Sequential`, a container for **block tree**, which is similar to `ChainBlock`, but has less constraints.

# ## Block Tree Architecture
# A block tree is specified the following two APIs
# * subblocks(block), siblings of a block.
# * chsubblocks, change siblings of a node.
crx = circuit[1]
@show crx
@show subblocks(crx)
@show chsubblocks(crx, (Y,));

# if we want to define a function that travals over the tree in depth first order, we can write something like
function print_block_tree(root, depth=0)
    println("  "^depth * "- $(typeof(root).name)")
    print_block_tree.(root |> subblocks, depth+1)
end
print_block_tree(circuit);

# there are some functions defined using this strategy, like `collect(circuit, block_type)`, it can filter out any type of blocks
rg = collect(circuit, RotationGate)

# ## Tagging System
# We proudly introduced our tag system here.
# In previous sections, we have introduced the magic operation `circuit'` to get the dagger a circuit, its realization is closely related to the tagging mechanism of `Yao`.
@show X'    # hermitian gate
@show Pu'   # special gate
@show Rx(0.5)';   # rotation gate

# The dagger of above gates can be translated to other gates easily.
# but some blocks has no predefined dagger operations, then we put a tag for it as a default behavior, e.g.
daggered_gate = matrixgate(randn(4, 4))'
@show daggered_gate |> typeof
daggered_gate
# Here, `Daggered` is a subtype of `TagBlock`.
#--------------------
# Other tag blocks include
#-----------------
# **Scale**, static scaling
2X
# **CachedBlock**, get the matrix representation of a block when applying it on registers, and cache it in memory (or `CacheServer` more precisely).
# This matrix can be useful in future calculation, like boosting time evolution.
put(5, 2=>X) |> cache
# **AbstactDiff**, marks a block as differentiable, either in classical `back propagation` mode (with extra memory cost to store intermediate data)
put(5, 2=>Rx(0.3)) |> autodiff(:BP)
# or non-cheating quantum circuit simulation
put(5, 2=>Rx(0.3)) |> autodiff(:QC)

# ## Parameter System
# using the depth first searching strategy, we can find all parameters in a tree or subtree. Two relevant APIs are
# * parameters(block), get all parameters in a (sub)tree rooted on block
# * dispatch!([func], block, params), dispatch `params` into (sub)tree rooted on `block`, optional parameter `func` can be used to custom parameter update rule.
@show parameters(circuit)
dispatch!(circuit, [0.1, 0.9])
@show parameters(circuit)
dispatch!(+, circuit, [0.1, 0.1])
@show parameters(circuit)
dispatch!(circuit, :zero)
@show parameters(circuit)
dispatch!(circuit, :random)
@show parameters(circuit);

# ### Intrinsic parameters
# Intrinsic parameters are block's net contribution to total paramters, normally, we define these two APIs for subtyping blocks
# * `iparameters(block)`,
# * `setiparameters!(block, params...)`,
@show iparameters(Rx(0.3))
@show setiparameters!(Rx(0.3), 1.2)
@show chain(Rx(0.3), Ry(0.5)) |> iparameters;

# ## Differentiable Blocks
# see the independant chapter [Automatic Differentiation](@ref autodiff)
#-----------------
# ## Time Evolution and Hamiltonian
# docs are under preparation


using PyPlot, PyCall

"""
    t = delaunay(p)

Delaunay triangulation `t` of N x 2 node array `p`.
"""
function delaunay(p)
    tri = pyimport("matplotlib.tri")
    t = tri[:Triangulation](p[:,1], p[:,2])
    return Int64.(t[:triangles] .+ 1)
end

"""
    edges, boundary_indices = all_edges(t)

Find all unique edges in the triangulation `t` (ne x 2 array)
Second output is indices to the boundary edges.
"""
function all_edges(t)
    edges = vcat(t[:,[1,2]], t[:,[2,3]], t[:,[3,1]])
    edges = sortslices(sort(edges, dims=2), dims=1)
    dup = all(edges[2:end,:] - edges[1:end-1,:] .== 0, dims=2)[:]
    keep = .![false;dup]
    edges = edges[keep,:]
    dup = [dup;false]
    dup = dup[keep]
    return edges, findall(.!dup)
end

"""
    e = boundary_nodes(t)

Find all boundary nodes in the triangulation `t`.
"""
function boundary_nodes(t)
    edges, boundary_indices = all_edges(t)
    return unique(edges[boundary_indices,:][:])
end

"""
    tplot(p, t, u=nothing)

If `u` == nothing: Plot triangular mesh with nodes `p` and triangles `t`.
If `u` == solution vector: Plot filled contour color plot of solution `u`.
"""
function tplot(p, t, u=nothing)
    clf()
    axis("equal")
    if u == nothing
        tripcolor(p[:,1], p[:,2], t .- 1, 0*t[:,1],
                  cmap="Set3", edgecolors="k", linewidth=1)
    else
        tricontourf(p[:,1], p[:,2], t .- 1, u, 20)
    end
    draw()
end

"""
    inside = inpolygon(p, pv)

Determine if each point in the N x 2 node array `p` is inside the polygon
described by the NE x 2 node array `pv`.
"""
function inpolygon(p::Array{Any,2}, pv::Array{Float64,2})
    path = pyimport("matplotlib.path")
    poly = path[:Path](pv)
    inside = [poly[:contains_point](p[ip,:]) for ip = 1:size(p,1)]
end

function div_poly(polys_xy0,hmax)
    x0=polys_xy0[1,1]
    y0=polys_xy0[1,2]
    ret = [x0 y0]
    for i = 2:size(polys_xy0)[1]
        #println(polys[i,:])
        x=polys_xy0[i,1]
        y=polys_xy0[i,2]
        len=sqrt((x-x0)^2+(y-y0)^2)
        n=ceil(len/hmax)
        #println("len=",len)
        #println("n=",n)
        #println([x0 y0])
        for j=1:n-1
          nx=(x0*(n-j)+x*j)/n
          ny=(y0*(n-j)+y*j)/n
          #println(nx,",",ny)
          ret = [ret; nx ny]
        end
        ret = [ret; x y]
        x0=x
        y0=y
    end
    return ret
end

function centroids(points, tri)
    ret = reshape([],0,2)
    for i = 1:size(tri)[1]
        nx = (points[tri[i,1],1]+points[tri[i,2],1]+points[tri[i,3],1])/3
        ny = (points[tri[i,1],2]+points[tri[i,2],2]+points[tri[i,3],2])/3
        ret = [ret; nx ny]
    end
    return ret
end

function area(points,tri)
   ret = []
   max = 0.0
   maxindex = -1
   for i = 1:size(tri)[1]
       na = (points[tri[i,1],1]*(points[tri[i,2],2]-points[tri[i,3],2])
           + points[tri[i,2],1]*(points[tri[i,3],2]-points[tri[i,1],2])
           + points[tri[i,3],1]*(points[tri[i,1],2]-points[tri[i,2],2]))/2
       ret = [ret; na]
       if na>max
           max=na
           maxindex=i
       end
   end
   return ret, maxindex, max
end

function circumcenter(p, t, it)
    ct = t[it,:]
    dp1 = p[ct[2], :] - p[ct[1], :]
    dp2 = p[ct[3], :] - p[ct[1], :]

    mid1 = (p[ct[2], :] + p[ct[1], :])/2
    mid2 = (p[ct[3], :] + p[ct[1], :])/2

    rhs = mid2-mid1
    s = [-dp1[2] dp2[2] ; dp1[1] -dp2[1]]\rhs
    cpc = mid1 + s[1] * [-dp1[2], dp1[1]]

    return cpc
end

function get_new_tri(inside,t)
    t_new=zeros(Int64,0,3)
    for i = 1:size(t)[1]
        if inside[i]
            t_new = [t_new;t[i,:]']
        end
    end
    return t_new
end

function pmesh(pv, hmax, nref)
    pvd = div_poly(pv,hmax)
    t = delaunay(pvd)

    centers = centroids(pvd,t)
    inside = inpolygon(centers,pv)
    t = get_new_tri(inside,t)
    areas, maxindex, max = area(pvd,t)

    while (max > 0.5*hmax.^2)
        added = circumcenter(pvd,t,maxindex)
        pvd = [pvd; added']
        t = delaunay(pvd)
        centers = centroids(pvd,t)

        inside = inpolygon(centers,pv)
        t = get_new_tri(inside,t)
        areas, maxindex, max = area(pvd,t)
    end

    for i = 1:nref
        edges = all_edges(t)[1]
        for n = 1:size(edges)[1]
            node = (pvd[edges[n,1],:] + pvd[edges[n,2],:])/2.0
            pvd = [pvd;node']
        end
        t = delaunay(pvd)
        centers = centroids(pvd,t)
        inside = inpolygon(centers,pv)
        t = get_new_tri(inside,t)
    end
    e = boundary_nodes(t)

    tplot(pvd,t)
    return pvd,t,e
end


function slurp_grid(filename)
    permutedims(hcat(map(collect, readlines(filename))...))
end

eg = slurp_grid("3eg.txt")
input = slurp_grid("3a.txt")

flip(bitchar) = bitchar == '1' ? '0' : '1'

gamma(col) = length(filter(==('1'), col)) / length(col) >= 0.5 ? '1' : '0'

function gamma_rate(m)
    map(gamma, eachcol(m))
end

function to_decimal(bits)
    bitstring = string(bits...)
    parse(Int, bitstring, base = 2)
end

function solve1(m)
    g = gamma_rate(m)
    epsilon_rate = map(flip, g)
    to_decimal(g) * to_decimal(epsilon_rate)
end

solve1(eg)

solve1(input)

function select_max(m, idx)
    max = gamma(m[:, idx])
    permutedims(hcat([row for row in eachrow(m) if row[idx] == max]...))
end

function select_min(m, idx)
    min = flip(gamma(m[:, idx]))
    permutedims(hcat([row for row in eachrow(m) if row[idx] == min]...))
end

function oxygen(m)
    s = size(m, 2)
    for idx = 1:s
        m = select_max(m, idx)
        if size(m, 1) == 1
            return m
        end
    end
end

function co2(m)
    s = size(m, 2)
    for idx = 1:s
        m = select_min(m, idx)
        if size(m, 1) == 1
            return m
        end
    end
end

function solve2(m)
    to_decimal(oxygen(m)) * to_decimal(co2(m))
end

solve2(eg)
solve2(input) == 1613181


# # "Gaussian advection-diffusion" Spatial resolution convergence test

using PyPlot

# Define a few utilities for running tests and unpacking and plotting results

include("ConvergenceTests/ConvergenceTests.jl") # we use the GaussianAdvectionDiffusion module here.

run_test = ConvergenceTests.OneDimensionalGaussianAdvectionDiffusion.run_test

""" Run advection-diffusion test for all Nx in resolutions. """
function run_convergence_test(κ, U, resolutions...)

    # Determine save time-step
           Lx = 2.5
    stop_time = 0.25
            h = Lx / maximum(resolutions)
           Δt = min(0.1 * h / U, 0.01 * h^2 / κ)

    # Adjust time-step
    stop_iteration = round(Int, stop_time / Δt)
                Δt = stop_time / stop_iteration

    # Run the tests
    results = [run_test(Nx=Nx, Δt=Δt, stop_iteration=stop_iteration, U=U, κ=κ, width=0.1) for Nx in resolutions]

    return results
end

#####
##### Run test
#####

Nx = 2 .^ (6:8) # N = 64 through N = 256
diffusion_results = run_convergence_test(1e-1, 0, Nx...)
advection_results = run_convergence_test(1e-6, 3, Nx...)
advection_diffusion_results = run_convergence_test(1e-2, 1, Nx...)

#####
##### Plot solution and error profile
#####

all_results = (diffusion_results, advection_results, advection_diffusion_results)
names = ("diffusion only",  "advection only",  "advection-diffusion")
linestyles = ("-", "--", ":")
specialcolors = ("xkcd:black", "xkcd:indigo", "xkcd:wine red")

# Solutions
close("all")

fig, axs = subplots(nrows=2, figsize=(12, 6), sharex=true)

ConvergenceTests.OneDimensionalUtils.plot_solutions!(axs, all_results, names, linestyles, specialcolors)

savefig("figs/gaussian_advection_diffusion_solutions.png", dpi=480)

# Error profile
fig, axs = subplots()

ConvergenceTests.OneDimensionalUtils.plot_error_convergence!(axs, Nx, all_results, names)

savefig("figs/gaussian_advection_diffusion_error_convergence.png", dpi=480)


"""
    projective(x::Vector)

Embeds a vector `x` into projective space via `[1; x]`;
"""
projective(x::Vector{T}) where T = [one(T); x] # TODO: shouldn't we have an option for a random patch?

"""
    affine(x::Vector)

Maps a projective vector `x` into affine space via `x[2:end]/x[1]`;
"""
affine(x::AbstractVector)= x[2:end]/x[1]

"""
    totaldegree(F[, unit_roots=false])

Returns `G`, `solutions` where `F` is a system with the equations ``w^{d_i}-b_i=0`` where ``w`` is a ``d_i``-th unit root with ``d_i = \text{deg}F_i``
If `unit_roots=true` then ``b_i=1`` otherwise ``b_i`` is uniformly random drawn out of the interval [0,1].
"""
function totaldegree(G::PolySystem{T}; kwargs...) where {T<:Number}
    if nvariables(G) != length(G)
        return error("In order to create a total degree start system your input system needs " *
            "to have the same number of variables as number of equations. Currently your system has " *
            "$(nvariables(G)) variables and $(length(G)) equations.")
    end
   totaldegree(promote_type(T, Complex128), variables(G), degrees(G); kwargs...)
end

"""
    totaldegree(T, vars, degrees[, unit_roots=false])

Returns `G`, `solutions` where `G` is a system with the equations ``w^{d_i}-b_i=0`` where ``w`` is a ``d_i``-th unit root.
If `unit_roots=true` then ``b_i=1`` otherwise ``b_i`` is uniformly random drawn out of the interval [0,1].
"""
function totaldegree(::Type{T}, variables::Vector{Symbol}, degrees::Vector{Int}; unit_roots=false ) where {T<:Complex}
    if unit_roots
        b = ones(T, length(degrees))
    else
        b = rand(T, length(degrees))
    end
    totaldegree_helper(variables, degrees, b)
end

function totaldegree_helper(vars::Vector{Symbol}, degrees::Vector{Int}, b::Vector{T}) where {T<:Complex}
    n = length(vars)

    polys = map(i -> begin
        d = degrees[i]
        exponents = zeros(Int, n, 2)
        coeffs =  zeros(T, 2)
        exponents[i, 1] = d
        coeffs[1] = one(T)
        coeffs[2] = -b[i]
        Poly(exponents, coeffs)
    end, 1:length(degrees))
    F = PolySystem(polys, vars)

    sol_preps = Vector{Vector{T}}()
    for (i, d) in enumerate(degrees)
        sol = [b[i]^(1/d) * exp(2π*im/d)^k for k in 0:d-1]
        push!(sol_preps, sol)
    end
    solutions = vec(map(collect, Base.product(sol_preps...)))

    F, solutions
end

"""
    randomsystem([T<:Complex=Complex128,] nequations, nvars; mindegree=3, maxdegree=8, rng=N(0,1))

Creates a random polynomial system of `nequations` equations with `nvars` vars. Each equation has a degree uniformly drawn
from `[mindegree, maxdegree]`. The coeffiencts are drawn from the given rng. It defaults to N(0,1) where the real and
imaginary part are drawn independently.

    randomsystem([T<:Complex=Complex128,] degrees::Vector{Int}, vars::Vector{Symbol}; rng=N(0,1))

Create a random polynomial system with the given `degrees`.
"""
function randomsystem(::Type{Complex{T}}, nequations::Int, nvars::Int; mindegree=3, maxdegree=8, rng=Base.Random.GLOBAL_RNG) where {T}
    degrees = map(_ -> rand(mindegree:maxdegree), 1:nequations)
    vars = map(i -> Symbol("x_$(i)"), 1:nvars)
    randomsystem(Complex{T}, degrees, vars; rng=rng)
end
function randomsystem(nequations::Int, nvars::Int; kwargs...)
    randomsystem(Complex128, nequations, nvars; kwargs...)
end

function randomsystem{T}(::Type{Complex{T}}, degrees::Vector{Int}, vars::Vector{Symbol}; rng=Base.Random.GLOBAL_RNG)
    polys = map(degrees) do degree
        exponents = create_exponents(degree, length(vars))
        coeffs = map(complex, randn(rng, T, length(exponents)), randn(rng, T, length(exponents)))
        Poly(hcat(exponents...), coeffs)
    end
    PolySystem(polys, vars)
end

function randomsystem(degrees::Vector{Int}, vars::Vector{Symbol}; kwargs...)
    randomsystem(Complex128, degrees, vars; kwargs...)
end

function exponents_helper(curr_sum::Int, target_sum::Int, remaining_elements::Int)::Vector{Vector{Int}}
    if remaining_elements == 0
        return [[]]
    end
    if curr_sum == target_sum
        return [zeros(Int, remaining_elements)]
    end
    if remaining_elements == 1
        return map(x-> [x], 0:(target_sum - curr_sum))
    end

    results = []
    for x=0:(target_sum-curr_sum)
        remaining_results = exponents_helper(curr_sum + x, target_sum, remaining_elements - 1)
        append!(results, map(xs -> [x; xs], remaining_results))
    end
    results
end
create_exponents(total_degree, nvars) = exponents_helper(0, total_degree, nvars)


using DocStringExtensions

"""
# plotbounds

Plot regression line and PI interval with alpha=0.11.

$(SIGNATURES)

### Required arguments
```julia
* `df::DataFrame`                      : DataFrame with observed variables and scaled variables
* `xvar::Symbol`                       : X variable in df
* `yvar::Symbol`                       : Y variable in df
* `nt::NamedTuple`                     : NamedTuple with Stan samples
* `linkvars::Vector{Symbol}`           : Initial 2 Symbols are regression coefficients,
                                         3rd - only for :predict bounds indicates σ.
```
### Optional arguments
```julia
* `fnc = link`                         : Link function
* `fig::AbstractString=""`             : File to store the plot. If "", a plot is returned
* `stepsize::Float64=0.01`             : Stepsize for boundary accuracy
* `rescale_axis=true`                  : Display using un-standardized scale
* `title::AbstractString=""`           : Title for plot
* `lab::AbstractString=""`             : X axis variable label
```

This method is primarily intended to the PI region around the mean line.

The 2nd symbol in `linkvars` needs to match the matrix
```julia
linkvars = [:a, :bM]
```

For other options, :quantile and :hpdi, two parameters suffice (typically the itercept
and slope parameters).

"""
function pbounds(
    df::DataFrame,
    xvar::Symbol,
    yvar::Symbol,
    nt::NamedTuple,
    linkvars::Vector{Symbol};
    fnc::Function=link,
    fig::AbstractString="",
    stepsize=0.01,
    rescale_axis=true,
    lab::AbstractString="",
    title::AbstractString="")

    xbar = mean(df[:, xvar])
    xstd = std(df[:, xvar])
    ybar = mean(df[:, yvar])
    ystd = std(df[:, yvar])

    xvar_s = Symbol(String(xvar)*"_s")
    yvar_s = Symbol(String(yvar)*"_s")

    minx = minimum(df[!, xvar_s])
    maxx = maximum(df[!, xvar_s])
    x_s = minx:stepsize:maxx

    k = size(nt[Symbol(linkvars[2])], 1)
    m = fnc(collect(x_s), k)
    y_s = m * nt[Symbol(linkvars[2])]
    y_s .+= nt[Symbol(linkvars[1])]'
    mul = meanlowerupper(y_s)

    p = plot(;title)
    if rescale_axis
        xrange = rescale(x_s, xbar, xstd)
        mm = rescale(mul.mean, ybar, ystd)
        lm = rescale(mul.lower, ybar, ystd)
        um = rescale(mul.upper, ybar, ystd)
        plot!(p, xrange, mm;
            xlab=String(xvar), ylab=String(yvar),
            ribbon=(mm-lm, um-mm),
            lab)
    else
        plot!(p, xrange, mul.mean;
            xlab=String(xvar), ylab=String(yvar),
            ribbon=(mul.mean-mul.lower, mul.upper-mul.mean),
            lab)
    end

    if fig == ""
        return(p)
    else
        savefig(p, fig)
    end
end

nt = nt7_2s
linkvars = [:a, :bA, :sigma]
fnc=create_observation_matrix
lab="$(size(nt[Symbol(vars[2])], 1))-th degree polynomial"
title="R^2 = $(r2_is_bad(nt, df))"

display(pbounds(df, :mass, :brain, nt, linkvars; stepsize=0.05, fnc, lab, title))


# Contains all the data relative to quadrature in the sphere
struct SphereQuadraturervec
    r1Vec::Array{Float64}
    r2Vec::Array{Float64}
    r3Vec::Array{Float64}
    wVec::Array{Float64}
end


@testset "$TEST $G" begin

N = 10
d = 2
points = rand(d, N)
g, w = euclidean_graph(points)
@test nv(g) == N
@test ne(g) == N*(N-1) ÷ 2
@test maximum(x->x[2], w) <= sqrt(d)
@test minimum(x->x[2], w) >= 0
@test maximum(points) <= 1
@test minimum(points) >= 0.

g, w = euclidean_graph(points, bc=:periodic)
@test maximum(x->x[2], w) <= sqrt(d/2)
@test minimum(x->x[2], w) >= 0.

@test_throws ErrorException euclidean_graph(points, L=0.01,  bc=:periodic)
@test_throws ErrorException euclidean_graph(points, bc=:ciao)

end # testset


using Matching
using Test

agent_a = Agent("a")
agent_b = Agent("b")
agent_c = Agent("c")
agent_1 = Agent("1")
agent_2 = Agent("2")
agent_3 = Agent("3")

rankings = RankedPreferences()
add_rank!(rankings, agent_a, [agent_2, agent_1, agent_3])
add_rank!(rankings, agent_b, [agent_3, agent_2, agent_1])
add_rank!(rankings, agent_c, [agent_1, agent_3, agent_2])

add_rank!(rankings, agent_1, [agent_b, agent_a, agent_c])
add_rank!(rankings, agent_2, [agent_c, agent_b, agent_a])
add_rank!(rankings, agent_3, [agent_a, agent_c, agent_b])

matching_1 = gale_shapley([agent_1, agent_2, agent_3], [agent_a, agent_b, agent_c], rankings)
matching_1 = matching_1.results
possible_stable_matching_1 = matching_1[agent_a] == agent_1 && matching_1[agent_b] == agent_2 && matching_1[agent_c] == agent_3
possible_stable_matching_2 = matching_1[agent_a] == agent_3 && matching_1[agent_b] == agent_1 && matching_1[agent_c] == agent_2
@test possible_stable_matching_2 || possible_stable_matching_1

# all from the proposing pool should get their first choice
matching_2 = gale_shapley([agent_a, agent_b, agent_c], [agent_1, agent_2, agent_3], rankings)
print(matching_2)
matching_2 = matching_2.results
@test matching_2[agent_2] == agent_a
@test matching_2[agent_3] == agent_b
@test matching_2[agent_1] == agent_c

# test for non-trivial cycles (i.e. > 2 people) and multiple rounds
rankings= RankedPreferences()
add_rank!(rankings, agent_a, [agent_1, agent_2, agent_3])
add_rank!(rankings, agent_b, [agent_1, agent_3, agent_2])
add_rank!(rankings, agent_c, [agent_1, agent_3, agent_2])

add_rank!(rankings, agent_1, [agent_c, agent_b, agent_a])
add_rank!(rankings, agent_2, [agent_c, agent_b, agent_a])
add_rank!(rankings, agent_3, [agent_c, agent_a, agent_b])
matching_3 = ttc_with_counter([agent_a, agent_b, agent_c], [agent_1, agent_2, agent_3], rankings)
print(matching_3)
matching_3 = matching_3.results
@test matching_3[agent_a] == agent_2
@test matching_3[agent_b] == agent_3
@test matching_3[agent_c] == agent_1

# test capacity > 1
agent_a = Agent("a")
agent_b = Agent("b")
agent_c = Agent("c")
agent_1 = Agent("1", 2, 1)
agent_2 = Agent("2")
agent_3 = Agent("3")
rankings= RankedPreferences()
add_rank!(rankings, agent_a, [agent_1, agent_2, agent_3])
add_rank!(rankings, agent_b, [agent_1, agent_3, agent_2])
add_rank!(rankings, agent_c, [agent_1, agent_3, agent_2])

add_rank!(rankings, agent_1, [agent_c, agent_b, agent_a])
add_rank!(rankings, agent_2, [agent_c, agent_b, agent_a])
add_rank!(rankings, agent_3, [agent_c, agent_a, agent_b])
matching_3 = ttc_with_counter([agent_a, agent_b, agent_c], [agent_1, agent_2, agent_3], rankings)
print(matching_3)
matching_3 = matching_3.results
@test matching_3[agent_a] == agent_2
@test matching_3[agent_b] == agent_1
@test matching_3[agent_c] == agent_1

# test weighted capacity deduction

agent_a = Agent("a")
agent_b = Agent("b")
agent_c = Agent("c", 1, 2)
agent_1 = Agent("1", 2, 1)
agent_2 = Agent("2")
agent_3 = Agent("3")
rankings= RankedPreferences()
add_rank!(rankings, agent_a, [agent_1, agent_2, agent_3])
add_rank!(rankings, agent_b, [agent_1, agent_3, agent_2])
add_rank!(rankings, agent_c, [agent_1, agent_3, agent_2])

add_rank!(rankings, agent_1, [agent_c, agent_b, agent_a])
add_rank!(rankings, agent_2, [agent_c, agent_b, agent_a])
add_rank!(rankings, agent_3, [agent_c, agent_a, agent_b])
matching_3 = ttc_with_counter([agent_a, agent_b, agent_c], [agent_1, agent_2, agent_3], rankings)
print(matching_3)
matching_3 = matching_3.results
@test matching_3[agent_a] == agent_2
@test matching_3[agent_b] == agent_3
@test matching_3[agent_c] == agent_1


@testset "check gradient for IndRNN block in adding problem" begin
    function addingProblemData(T::Int)
        #= 两个长度为 T 的序列组成输入：
        第一个序列在 (0,1)范围内均匀采样，
        第二个序列的前两个为 1，其余都为 0.
        例如：
        x1 = 0.90  0.93  0.90  0.56  0.46
        x2 = 1.0   1.0   0.0   0.0   0.0
        而输出为 sum(x1 .* x2) = 0.90 + 0.93
        =#
        @assert (T>1) "The sequence length should lager than 1"
        x1 = rand(1,T)./3
        x2 = zeros(1,T)
        x2[1] = 1.0
        x2[2] = 1.0
        y = sum(x1 .* x2)
        return [x1;x2],[y]
    end

    TYPE = Array{Float64};
    clear()

    # [0] prepare model
    model = Chain(
    IndRNN(2,128,relu; type=TYPE),
    IndRNN(128,64,cos; type=TYPE),
    IndRNN(64,64,cos;  type=TYPE),
    Dense(64,1,relu;   type=TYPE)
    )

    # [1] prepare input data and its label
    T = 15
    x, s = addingProblemData(T)

    # [2] forward and backward propagation
    resethidden(model)
    for t = 1:T-1
        tmp = forward(model, Variable( reshape(x[:,t], 2,1); type=TYPE) )
        zeroDelta(tmp)
    end
    y = forward(model, Variable( reshape(x[:,T], 2,1); type=TYPE) )
    COST1 = mseCost(y, Variable( reshape(s,1,1); type=TYPE) )
    backward()

    # [3] with a samll change of a weight
    GRAD = model[1].w.delta[1]
    DELTA = 1e-4
    model[1].w.value[1] += DELTA

    # [4] forward and backward propagation again
    resethidden(model)
    for t = 1:T-1
        tmp = forward(model, Variable( reshape(x[:,t], 2,1); type=TYPE) )
        zeroDelta(tmp)
    end
    y = forward(model, Variable( reshape(x[:,T], 2,1); type=TYPE) )
    COST2 = mseCost(y, Variable( reshape(s,1,1); type=TYPE) )
    backward()

    # [5] check if the auto-grad is true or not
    dLdW = (COST2 - COST1)/DELTA
    @test abs(dLdW-GRAD)<1e-4
end


export Ellipse,Circle,Plate,NACA4

"""
    Ellipse(a,b,n) <: Body

Construct an elliptical body with semi-major axis `a` and semi-minor axis `b`,
with `n` points distributed on the body perimeter.
"""
mutable struct Ellipse{N} <: Body{N}
  a :: Float64
  b :: Float64
  cent :: Tuple{Float64,Float64}
  α :: Float64

  x̃ :: Vector{Float64}
  ỹ :: Vector{Float64}

  x :: Vector{Float64}
  y :: Vector{Float64}

end

function Ellipse(a::Float64,b::Float64,N::Int)
    x̃ = zeros(N)
    ỹ = zeros(N)
    θ = range(0,stop=2π,length=N+1)
    @. x̃ = a*cos(θ[1:N])
    @. ỹ = b*sin(θ[1:N])

    Ellipse{N}(a,b,(0.0,0.0),0.0,x̃,ỹ,x̃,ỹ)
end

"""
    Circle(a,n) <: Body

Construct a circular body with radius `a`
and with `n` points distributed on the body perimeter.
"""
Circle(a::Float64,N::Int) = Ellipse(a,a,N)

function Base.show(io::IO, body::Ellipse{N}) where {N}
    if body.a == body.b
      println(io, "Circular body with $N points and radius $(body.a)")
    else
      println(io, "Elliptical body with $N points and semi-axes ($(body.a),$(body.b))")
    end
    println(io, "   Current position: ($(body.cent[1]),$(body.cent[2]))")
    println(io, "   Current angle (rad): $(body.α)")
end

"""
    Plate(length,thick,n,[λ=1.0]) <: Body

Construct a flat plate with length `length` and thickness `thick`,
with `n` points distributed on the body perimeter.

The optional parameter `λ` distributes the points differently. Values between `0.0`
and `1.0` are accepted.

The constructor `Plate(length,n,[λ=1.0])` creates a plate of zero thickness.
"""
mutable struct Plate{N} <: Body{N}
  len :: Float64
  thick :: Float64
  cent :: Tuple{Float64,Float64}
  α :: Float64

  x̃ :: Vector{Float64}
  ỹ :: Vector{Float64}

  x :: Vector{Float64}
  y :: Vector{Float64}

end

function Plate(len::Float64,N::Int;λ::Float64=1.0)

    # set up points on plate
    #x = [[len*(-0.5 + 1.0*(i-1)/(N-1)),0.0] for i=1:N]

    Δϕ = π/(N-1)
    Jϕa = [sqrt(sin(ϕ)^2+λ^2*cos(ϕ)^2) for ϕ in range(π-Δϕ/2,stop=Δϕ/2,length=N-1)]
    Jϕ = len*Jϕa/Δϕ/sum(Jϕa)
    x̃ = -0.5*len .+ Δϕ*cumsum([0.0; Jϕ])
    ỹ = zero(x̃)

    Plate{N}(len,0.0,(0.0,0.0),0.0,x̃,ỹ,x̃,ỹ)

end

function Plate(len::Float64,thick::Float64,N::Int;λ::Float64=1.0)
    # input N is the number of panels on one side only

    # set up points on flat sides
    Δϕ = π/N
    Jϕa = [sqrt(sin(ϕ)^2+λ^2*cos(ϕ)^2) for ϕ in range(π-Δϕ/2,stop=Δϕ/2,length=N)]
    Jϕ = len*Jϕa/Δϕ/sum(Jϕa)
    xtopface = -0.5*len .+ Δϕ*cumsum([0.0; Jϕ])
    xtop = 0.5*(xtopface[1:N] + xtopface[2:N+1])

    Δsₑ = Δϕ*Jϕ[1]
    Nₑ = 2*floor(Int,0.25*π*thick/Δsₑ)
    xedgeface = [0.5*len + 0.5*thick*cos(ϕ) for ϕ in range(π/2,stop=-π/2,length=Nₑ+1)]
    yedgeface = [          0.5*thick*sin(ϕ) for ϕ in range(π/2,stop=-π/2,length=Nₑ+1)]
    xedge = 0.5*(xedgeface[1:Nₑ]+xedgeface[2:Nₑ+1])
    yedge = 0.5*(yedgeface[1:Nₑ]+yedgeface[2:Nₑ+1])

    x̃ = Float64[]
    ỹ = Float64[]
    for xi in xtop
      push!(x̃,xi)
      push!(ỹ,0.5*thick)
    end
    for i = 1:Nₑ
      push!(x̃,xedge[i])
      push!(ỹ,yedge[i])
    end
    for xi in reverse(xtop,dims=1)
      push!(x̃,xi)
      push!(ỹ,-0.5*thick)
    end
    for i = Nₑ:-1:1
      push!(x̃,-xedge[i])
      push!(ỹ,yedge[i])
    end

    Plate{length(x̃)}(len,thick,(0.0,0.0),0.0,x̃,ỹ,x̃,ỹ)

end

function Base.show(io::IO, body::Plate{N}) where {N}
    println(io, "Plate with $N points and length $(body.len) and thickness $(body.thick)")
    println(io, "   Current position: ($(body.cent[1]),$(body.cent[2]))")
    println(io, "   Current angle (rad): $(body.α)")
end

"""
    NACA4(cam,pos,thick[;np=20][,Xc=(0.0,0.0)][,len=1.0]) <: Body{N}

Generates points in the shape of a NACA 4-digit airfoil of chord length 1. The
relative camber is specified by `cam`, the position of
maximum camber (as fraction of chord) by `pos`, and the relative thickness
by `thick`.

The optional parameter `np` specifies the number of points on the upper
or lower surface. The optional parameter `Zc` specifies the mean position of
the vertices (which is set to the origin by default). The optional parameter
`len` specifies the chord length.

# Example

```jldoctest
julia> w = Bodies.NACA4(0.0,0.0,0.12);
```
"""
mutable struct NACA4{N} <: Body{N}
  len :: Float64
  camber :: Float64
  pos :: Float64
  thick :: Float64

  cent :: Tuple{Float64,Float64}
  α :: Float64

  x̃ :: Vector{Float64}
  ỹ :: Vector{Float64}

  x :: Vector{Float64}
  y :: Vector{Float64}

end

function NACA4(cam::Number,pos::Number,t::Number;np=20,Xc=(0.0,0.0),len=1.0)

# Here, cam is the fractional camber, pos is the fractional chordwise position
# of max camber, and t is the fractional thickness.

npan = 2*np-2

# Trailing edge bunching
an = 1.5
anp = an+1
x = zeros(np)

θ = zeros(size(x))
yc = zeros(size(x))

for j = 1:np
    frac = Float64((j-1)/(np-1))
    x[j] = 1 - anp*frac*(1-frac)^an-(1-frac)^anp;
    if x[j] < pos
        yc[j] = cam/pos^2*(2*pos*x[j]-x[j]^2)
        if pos > 0
            θ[j] = atan(2*cam/pos*(1-x[j]/pos))
        end
    else
        yc[j] = cam/(1-pos)^2*((1-2*pos)+2*pos*x[j]-x[j]^2)
        if pos > 0
            θ[j] = atan(2*cam*pos/(1-pos)^2*(1-x[j]/pos))
        end
    end
end

xu = zeros(size(x))
yu = xu
xl = xu
yl = yu

yt = t/0.20*(0.29690*sqrt.(x)-0.12600*x-0.35160*x.^2+0.28430*x.^3-0.10150*x.^4)

xu = x-yt.*sin.(θ)
yu = yc+yt.*cos.(θ)

xl = x+yt.*sin.(θ)
yl = yc-yt.*cos.(θ)

rt = 1.1019*t^2;
θ0 = 0
if pos > 0
    θ0 = atan(2*cam/pos)
end
# Center of leading edge radius
xrc = rt*cos(θ0)
yrc = rt*sin(θ0)
θle = collect(0:π/50:2π)
xlec = xrc .+ rt*cos.(θle)
ylec = yrc .+ rt*sin.(θle)

# Assemble data
coords = [xu yu xl yl x yc]
cole = [xlec ylec]

# Close the trailing edge
xpanold = [0.5*(xl[np]+xu[np]); reverse(xl[2:np-1],dims=1); xu[1:np-1]]
ypanold = [0.5*(yl[np]+yu[np]); reverse(yl[2:np-1],dims=1); yu[1:np-1]]

xpan = zeros(npan)
ypan = zeros(npan)
for ipan = 1:npan
    if ipan < npan
        xpan1 = xpanold[ipan]
        ypan1 = ypanold[ipan]
        xpan2 = xpanold[ipan+1]
        ypan2 = ypanold[ipan+1]
    else
        xpan1 = xpanold[npan]
        ypan1 = ypanold[npan]
        xpan2 = xpanold[1]
        ypan2 = ypanold[1]
    end
    xpan[ipan] = 0.5*(xpan1+xpan2)
    ypan[ipan] = 0.5*(ypan1+ypan2)
end
w = ComplexF64[1;reverse(xpan,dims=1)+im*reverse(ypan,dims=1)]*len
w .-= mean(w)

x̃ = real.(w)
ỹ = imag.(w)

NACA4{length(x̃)}(len,cam,pos,t,Xc,0.0,x̃,ỹ,x̃,ỹ)

end

function Base.show(io::IO, body::NACA4{N}) where {N}
    println(io, "NACA 4-digit airfoil with $N points and length $(body.len) and thickness $(body.thick)")
    println(io, "   Current position: ($(body.cent[1]),$(body.cent[2]))")
    println(io, "   Current angle (rad): $(body.α)")
end


# ---
# title: 938. Range Sum of BST
# id: problem938
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Tree, Depth-first Search, Recursion
# link: <https://leetcode.com/problems/range-sum-of-bst/description/>
# hidden: true
# ---
#
# Given the `root` node of a binary search tree, return _the sum of values of
# all nodes with a value in the range`[low, high]`_.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2020/11/05/bst1.jpg)
#
#
#
#     Input: root = [10,5,15,3,7,null,18], low = 7, high = 15
#     Output: 32
#
#
# **Example 2:**
#
# ![](https://assets.leetcode.com/uploads/2020/11/05/bst2.jpg)
#
#
#
#     Input: root = [10,5,15,3,7,13,18,1,null,6], low = 6, high = 10
#     Output: 23
#
#
#
#
# **Constraints:**
#
#   * The number of nodes in the tree is in the range `[1, 2 * 104]`.
#   * `1 <= Node.val <= 105`
#   * `1 <= low <= high <= 105`
#   * All `Node.val` are **unique**.
#
#
## @lc code=start
using LeetCode

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


using Base.Test
using MPFR
using MPC

# real constructors
x = MPCComplex{53,53}()
x = MPCComplex(12)
y = MPCComplex(x)
@test x == y
y = MPCComplex(0xc)
@test x == y
y = MPCComplex(12.)
@test x == y
y = MPCComplex(BigInt(12))
@test x == y
y = MPCComplex(BigFloat(12))
@test x == y
y = MPCComplex(MPFRFloat(12))
@test x == y
y = MPCComplex("12")
@test x == y
y = MPCComplex(float32(12.))
@test x == y
y = MPCComplex(12//1)
@test x == y

# complex constructors
x = MPCComplex(12, 42)
y = MPCComplex(x)
@test x == y
y = MPCComplex(0xc, 0x2a)
@test x == y
y = MPCComplex(12., 42.)
@test x == y
y = MPCComplex(BigInt(12), BigInt(42))
@test x == y
y = MPCComplex(BigFloat(12), BigFloat(42))
@test x == y
y = MPCComplex(MPFRFloat(12), MPFRFloat(42))
@test x == y
y = MPCComplex("(12 42)")
@test x == y
y = MPCComplex(float32(12.), float32(42))
@test x == y
y = MPCComplex(12//1, 42//1)
@test x == y
y = MPCComplex(12 + 42im)
@test x == y

# real/imag
x = MPCComplex(12, 42)
y = MPFRFloat(12)
z = MPFRFloat(42)
@test real(x) == y
@test imag(x) == z
y = MPCComplex(x)
@test real(x) == real(y)
@test imag(x) == imag(y)

# +
x = MPCComplex(12,42)
@test (x + x) == MPCComplex(24, 84)
@test (x + 2) == MPCComplex(14, 42)
@test (x + (2+ 1im)) == MPCComplex(14, 43)
@test (x + 2 + 1im) == MPCComplex(14, 43)

# integer_valued
@test !integer_valued(MPCComplex(2,3))
@test integer_valued(MPCComplex(2))


# ---
# title: Pre-grouped data
# cover: assets/pregrouped_data.png
# description: Working with arrays of arrays.
# author: "[Pietro Vertechi](https://github.com/piever)"
# ---

using AlgebraOfGraphics, CairoMakie
set_aog_theme!() #src

x = [rand(10) for i in 1:3]
y = [rand(10) for i in 1:3]
z = [rand(10) for i in 1:3]
c = ["a", "b", "c"]

m = mapping(x, y, color=c => (t -> "Type " * t ) => "Category")
draw(m)

#

m = mapping(x, (y, z) => (+) => "sum", color=c => (t -> "Type " * t ) => "Category")
draw(m)

#

m = mapping(x, [y z], color=dims(1) => renamer(["a", "b", "c"]))
draw(m)

#

m = mapping(x, [y z], color=["1" "2"])
layers = visual(Scatter) + linear()
fg = draw(m * layers)

# save cover image #src
mkpath("assets") #src
save("assets/pregrouped_data.png", fg) #src


export SuperpositionMeasure

@doc raw"""
    struct SuperpositionMeasure{X,NT} <: AbstractMeasure
        components :: NT
    end
Superposition of measures is analogous to mixture distributions, but (because
measures need not be normalized) requires no scaling.
The superposition of two measures μ and ν can be more concisely written as μ + ν.
Superposition measures satisfy

    basemeasure(μ + ν) == basemeasure(μ) + basemeasure(ν)

```math
    \begin{aligned}\frac{\mathrm{d}(\mu+\nu)}{\mathrm{d}(\alpha+\beta)} & =\frac{f\,\mathrm{d}\alpha+g\,\mathrm{d}\beta}{\mathrm{d}\alpha+\mathrm{d}\beta}\\
     & =\frac{f\,\mathrm{d}\alpha}{\mathrm{d}\alpha+\mathrm{d}\beta}+\frac{g\,\mathrm{d}\beta}{\mathrm{d}\alpha+\mathrm{d}\beta}\\
     & =\frac{f}{1+\frac{\mathrm{d}\beta}{\mathrm{d}\alpha}}+\frac{g}{\frac{\mathrm{d}\alpha}{\mathrm{d}\beta}+1}\\
     & =\frac{f}{1+\left(\frac{\mathrm{d}\alpha}{\mathrm{d}\beta}\right)^{-1}}+\frac{g}{\frac{\mathrm{d}\alpha}{\mathrm{d}\beta}+1}\ .
    \end{aligned}
```
"""
struct SuperpositionMeasure{NT} <: AbstractMeasure
    components::NT
end

function Pretty.tile(d::SuperpositionMeasure)
    result = Pretty.literal("SuperpositionMeasure(")
    result *= Pretty.list_layout([Pretty.tile.(d.components)...])
    result *= Pretty.literal(")")
end

testvalue(μ::SuperpositionMeasure) = testvalue(first(μ.components))

# SuperpositionMeasure(ms :: AbstractMeasure...) = SuperpositionMeasure{X,length(ms)}(ms)

# SuperpositionMeasure(m::NTuple{N, Measure{X}}) where {N,X} = SuperpositionMeasure(m...)

# Base.length(::SuperpositionMeasure{X,N}) where {X,N} = N

# function Base.:+(μ::SuperpositionMeasure{N1}, ν::SuperpositionMeasure{N2}) where {N1,N2}
#     components = (μ.components..., ν.components...)
#     SuperpositionMeasure{X, N1+N2}(components)
# end

# function Base.:+(μ::AbstractMeasure, ν::SuperpositionMeasure{X,N}) where {X,N}
#     components = (μ, ν.components...)
#     SuperpositionMeasure{X,N+1}(components)
# end

# function Base.:+(μ::SuperpositionMeasure{X,N}, ν::AbstractMeasure) where {X,N}
#     components = (μ.components..., ν)
#     SuperpositionMeasure{X,N+1}(components)
# end

function Base.:+(μ::AbstractMeasure, ν::AbstractMeasure)
    components = (μ, ν)
    superpose(components)
end

logdensity(μ::SuperpositionMeasure, x) = logsumexp((logdensity(m, x) for m in μ.components))

basemeasure(μ::SuperpositionMeasure) = superpose(map(basemeasure, μ.components))

# TODO: Fix `rand` method (this one is wrong)
# function Base.rand(μ::SuperpositionMeasure{X,N}) where {X,N}
#     return rand(rand(μ.components))
# end


using LinearAlgebra, SparseArrays
using JuMP
using CSV, DataFrames
using Base.Filesystem
using Printf
using JLD2

function load_sdpa_file(filepath)
    info = CSV.File(joinpath(dirname(filepath), "sdplib_info.csv"))  |> DataFrame
    objectives = Dict(zip(info[1], info[4]))
    filename =  join(split(basename(filepath), ".")[1:end-1], ".")
    objective = haskey(objectives, filename) ? objectives[filename] : NaN
    @printf("Problem: %s with optimal objective: %.4e\n", filename, objective)
    # Load Problem
    jld_filepath = join([split(filepath, ".")[1:end-1]; "jld2"], ".")
    if isfile(jld_filepath) # Attempt to load saved jld2 file
        @load jld_filepath c F A b
    else # Load directly from dat-s file
        print("Loading SDPA file. This might take some time... ")
        t = @elapsed c, F, A, b = _load_sdpa_file(string(jld_filepath[1:end-5], ".dat-s"))
        println(string("Done in ", t, " seconds!"))
        @save jld_filepath c F A b
    end
    return c, F, A, b, objective
end

function _load_sdpa_file(filename)
    #=
    Load problems of the SDPA format from "dat-s" files, i.e. problems of the form

    minimize c'x
    subject to  F1*x1 + ... + Fm*xm - F0 ⪰ 0

    with the real m-dimensional vector x as the decision variable.
    See https://freesoft.dev/program/129366650#sdpa-sparse-format for a detailed description.

    Note that the coefficient matrices Fi have common block diagonal structure. Furthermore, some of
    these blocks are themselves diagonal. Thus we can transform the original problem into

    minimize c'x
    subject to  F1,1*x1 + ... + Fm,1*xm - F0,1 ⪰ 0
                ...
                F1,l*x1 + ... + Fm,l*xm - F0,l ⪰ 0
                A*x ≥ b

    where Fi,j denotes the j-th diagonal block of the i-th coefficient matrix and the linear inequality constraints
    arised from subblocks of the coefficient matrices that have diagonal structure.

    The function returns
    - c::Vector the linear cost
    - F::Array{SparseMatrixCSC,2} F[i, j] is the j-th block of the (i-1)-th coefficient matrix
    - A::SparseMatrixCSC, b::Vector: for the linear inequality constraints
    - opt_val::Float64 the optimal value of the problem. Only supported from problems in the SDPLIB collection.
        These are extracted from a README file of the SDPLIB suite.
        Contains NaN if README is not present or if the extraction of the optimal value failed.
    =#
    file = open(filename);
    lines = readlines(file)
    close(file)

    # Discard all lines that contain comments
    indices = findall(line->length(line) == 0 || (line[1] != '*' && line[1] != '"'), lines)
    lines = lines[indices]

    m = parse(Int, strip(lines[1])) # length of decision variable
    blocks_sizes_string = split(replace(strip(lines[3], ['{','}','(',')']), "+" => ""))
    block_sizes = [parse(Int, str) for str in blocks_sizes_string]
    @assert length(block_sizes) == parse(Int, strip(lines[2]))

    # try first to split by whitespace as delimiter (also remove certain trailing, ending characters)
    c_strings_vector = split(replace(strip(lines[4], ['{','}','(',')']), "+" => ""))
    # otherwise try comma as delimiter
    if length(c_strings_vector) != m
        c_strings_vector = split(replace(strip(lines[4], ['{','}','(',')']), "+" => ""), ",")
    end
    c = [parse(Float64, str) for str in c_strings_vector]

    @assert length(c) == m
    indices = Vector(1:length(block_sizes));
    indices_map = [indices[block_sizes .> 1]; indices[block_sizes .<= 1]]
    diag_sizes = abs.(block_sizes[block_sizes .<= 1])
    block_sizes = block_sizes[block_sizes .> 1]

    F = [spzeros(block_sizes[j], abs(block_sizes[j])) for i = 1:m + 1, j = 1:length(block_sizes)]
    A = spzeros(sum(diag_sizes), m)
    b = zeros(size(A, 1))
    for line in lines[5:end]
        parsed_line = [parse(Float64, str) for str in split(line)]
        matrix_number = Int(parsed_line[1]) + 1
        block_number = findfirst(indices_map .== Int(parsed_line[2]))

        i = Int(parsed_line[3])
        j = Int(parsed_line[4])
        entry = parsed_line[5]

        if block_number <= length(block_sizes)
            F[matrix_number, block_number][i, j] = entry
            F[matrix_number, block_number][j, i] = entry
        else
            # Diagonal block - convert to linear inequalities
            @assert i == j
            idx = i + sum(diag_sizes[1:block_number - length(block_sizes) - 1])

            if matrix_number == 1
                b[idx] = entry
            else
                A[idx, matrix_number - 1] = entry
            end
        end
    end

    return c, F, A, b
end

function extract_upper_triangle(A::SparseMatrixCSC{Tv,Ti}, scaling_factor::Tv = one(Tv)) where {Tv,Ti}
   	result_nnz = Ti(nnz(A) / 2 + nnz(diag(A)) / 2)
   	nzind = zeros(Ti, result_nnz)
   	nzval = zeros(Tv, result_nnz)
   	n = size(A, 1)
    counter = 0
   	for j in 1:n, idx in A.colptr[j]:A.colptr[j + 1] - 1
        i = A.rowval[idx]
      		if i <= j
            counter += 1
            nzind[counter] = Ti(j * (j - 1) / 2 + i)
            if i == j
                nzval[counter] = A.nzval[idx]
            else
                nzval[counter] = scaling_factor * A.nzval[idx]
         			end
      		end
    end
   	return SparseVector{Tv,Ti}(Ti(n * (n + 1) / 2), nzind, nzval)
end

function solve_sdpa_jump(c, F, A, b, solver; kwargs...)
    m = length(c)
    model = Model(with_optimizer(solver; kwargs...))
    @variable(model, x[1:m])
    @constraint(model, A * x .>= b)
    for block_id in 1:size(F, 2)
        A_ = hcat([extract_upper_triangle(F[i + 1, block_id]) for i = 1:m]...)
        b_ = Vector(extract_upper_triangle(F[1, block_id]))
        block_size = size(F[1, block_id], 1)
        @constraint(model, A_ * x - b_ in MOI.PositiveSemidefiniteConeTriangle(block_size))
    end
    @objective(model, Min, dot(c, x))
    JuMP.optimize!(model)
    raw_solver = model.moi_backend.optimizer.model.optimizer.inner
    # raw_solver = MOI.get(model, MOI.RawSolver())
    return value.(x), JuMP.objective_value(model), JuMP.termination_status(model), raw_solver
end

function solve_sdpa_jump_dual(c, F, A, b, solver; kwargs...)
    model = Model(with_optimizer(solver; kwargs...))

    @variable(model, y[1:size(A, 1)] >= 0)
    num_blocks = size(F, 2)
    block_sizes = [size(F[1, block_id], 1) for block_id in 1:num_blocks]
    Y = [@variable(model, [1:block_size, 1:block_size], PSD) for block_size in block_sizes]

    constraint_sum = A' * y
    objective_sum = dot(y, b)
    for block_id in 1:num_blocks
        # Normally, we should also avoid the matrix dot product below, but in most solvers this doesn't matter
        add_to_expression!.(objective_sum, dot(F[1, block_id], Y[block_id]))
        for i = 1:length(c)
            f = SparseVector(F[i + 1, block_id][:])
            add_to_expression!(constraint_sum[i], dot(f.nzval, Y[block_id][f.nzind]))
        end
    end
    @constraint(model, constraint_sum .== c)
    @objective(model, Max, objective_sum)

    JuMP.optimize!(model)
    raw_solver = model.moi_backend.optimizer.model.optimizer.inner
    # raw_solver = MOI.get(model, MOI.RawSolver())

    return [value.(Y[i]) for i = 1:length(block_sizes)], value.(y),
        JuMP.objective_value(model),
        JuMP.termination_status(model),
        raw_solver
end

#=
function solve_sdpa_jump_b(c, F, A, b; solver=SCS.Optimizer, kwargs...)
    # A version of solve_sdpa_jump_b that does not explicitly construct A_, b_
    # It is significantly slower than the original function, solve_sdpa_jump.
    m = length(c)
    model = Model(with_optimizer(solver; kwargs...))
    @variable(model, x[1:m])
    @constraint(model, A*x .>= b)
    for block_id in 1:size(F, 2)
        block_size = size(F[1, block_id], 1)
        X = @variable(model, [1:block_size, 1:block_size], PSD) .- F[1, block_id]
        for i = 1:m
            add_to_expression!.(X, x[i].*F[i + 1, block_id])
        end
        @constraint(model, X .== 0)
    end
    @objective(model, Min, dot(c, x))
    JuMP.optimize!(model)
    return value.(x), JuMP.objective_value(model), JuMP.termination_status(model)
end
=#


using Unitful#, UnitfulPlots
using OrdinaryDiffEq, DiffEqBase
NON_IMPLICIT_ALGS = filter((x)->isleaftype(x) && !OrdinaryDiffEq.isimplicit(x()),union(subtypes(OrdinaryDiffEqAlgorithm),subtypes(OrdinaryDiffEqAdaptiveAlgorithm)))

f = (t,y) -> 0.5*y / 3.0u"s"
u0 = 1.0u"N"
prob = ODEProblem(f,u0,(0.0u"s",1.0u"s"))

sol =solve(prob,Euler(),dt=1u"s"/10)

sol =solve(prob,Midpoint(),dt=1u"s"/10)

sol =solve(prob,ExplicitRK())

for alg in NON_IMPLICIT_ALGS
  if !(alg <: DP5Threaded) && !(alg <: Rosenbrock23) && !(alg <: Rosenbrock32)
    @show alg
    sol = solve(prob,alg(),dt=1u"s"/10)
  end
end

println("Units for Number pass")

u0 = [1.0u"N" 2.0u"N"
      3.0u"N" 1.0u"N"]
f = (t,y,dy) -> (dy .= 0.5.*y ./ 3.0u"s")
prob = ODEProblem(f,u0,(0.0u"s",1.0u"s"))

sol =solve(prob,RK4(),dt=1u"s"/10)

sol =solve(prob,ExplicitRK())
sol =solve(prob,DP5())

sol =solve(prob,DP5Threaded())

for alg in NON_IMPLICIT_ALGS
  if !(alg <: Rosenbrock23) && !(alg <: Rosenbrock32)
    @show alg
    sol = solve(prob,alg(),dt=1u"s"/10)
  end
end

println("Units for 2D pass")


# ---
# title: 672. Bulb Switcher II
# id: problem672
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math
# link: <https://leetcode.com/problems/bulb-switcher-ii/description/>
# hidden: true
# ---
#
# There is a room with `n` lights which are turned on initially and 4 buttons on
# the wall. After performing exactly `m` unknown operations towards buttons, you
# need to return how many different kinds of status of the `n` lights could be.
#
# Suppose `n` lights are labeled as number [1, 2, 3 ..., n], function of these 4
# buttons are given below:
#
#   1. Flip all the lights.
#   2. Flip lights with even numbers.
#   3. Flip lights with odd numbers.
#   4. Flip lights with (3k + 1) numbers, k = 0, 1, 2, ...
#
#
#
# **Example 1:**
#
#
#
#     Input: n = 1, m = 1.
#     Output: 2
#     Explanation: Status can be: [on], [off]
#
#
#
#
# **Example 2:**
#
#
#
#     Input: n = 2, m = 1.
#     Output: 3
#     Explanation: Status can be: [on, off], [off, on], [off, off]
#
#
#
#
# **Example 3:**
#
#
#
#     Input: n = 3, m = 1.
#     Output: 4
#     Explanation: Status can be: [off, on, off], [on, off, on], [off, off, off], [off, on, on].
#
#
#
#
# **Note:** `n` and `m` both fit in range [0, 1000].
#
#
## @lc code=start
using LeetCode

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


export elbo, em

function elbo(ks::KalmanSmoother,z,θ,build)
    T = length(z)

    mθ = build(θ)

    μ = mθ.μ
    Σ0 = mθ.Σ0
    F = mθ.F
    Q = mθ.Q
    Θ = mθ.Θ
    Qp = Θ*Q*Θ'
    H = mθ.H
    R = mθ.R

    S11 = ks.S11
    S00 = ks.S00
    S10 = ks.S10

    Syy = ks.Syy

    Sμ = ks.P[1] + (ks.x[1]-μ)*(ks.x[1]-μ)'

    l1 = logdet(Σ0) + tr(Σ0\Sμ)
    l2 = T * logdet(Q) + tr(pinv(Qp)*(S11 - F*S10' - S10*F' + F*S00*F'))
    l3 = T * logdet(R) + tr(pinv(R)*Syy)

    l1 + l2 + l3
end

function elbo(m::LinearStateSpaceModel,z,θ,build)
    kf = kalman_filter(m,z)
    ks = kalman_smoother(kf,m)

    elbo(ks,z,θ,build)
end

# This is not ready for prime time
function elbo_gradient(ks::KalmanSmoother,z,θ)
    T = length(z)

    mθ = SSTFI(θ)
    μ = mθ.μ
    Σ0 = mθ.Σ0
    F = mθ.F
    Q = mθ.Q
    Θ = mθ.Θ
    Qp = Θ*Q*Θ'
    H = mθ.H
    R = mθ.R

    dm = Filtering._gradient(θ)
    dF = dm.dF
    dQ = dm.dQ
    dH = dm.dH
    dR = dm.dR
    dΘ = dm.dΘ
    dμ = dm.dμ
    dΣ0 = dm.dΣ0

    S11 = ks.S11
    S00 = ks.S00
    S10 = ks.S10

    Syy = ks.Syy

    Sμ = ks.P[1] + (ks.x[1]-μ)*(ks.x[1]-μ)'

    # l1
    l1 = logdet(Σ0) + tr(Σ0\Sμ)
    dl1 = zeros(length(θ0))

    # l2
    Θp = pinv(Θ)
    Sf =(S11 - F*S10' - S10*F' + F*S00*F')
    l2 = T*logdet(Q) + tr(pinv(Qp)*Sf)
    dl2 = [T*tr(Q\dQ[:,:,i]) +
        tr(-Θp'*(Q\dQ[:,:,i])*(Q\Θp)*Sf +
            pinv(Qp)*(-dF[:,:,i]*S10' - S10*dF[:,:,i]' + dF[:,:,i]*S00*F' + F*S00*dF[:,:,i]'))
        for i in 1:length(θ)]

    # l3
    l3 = T*logdet(R) + tr(R\Syy)
    dl3 = [T*tr(R\dR[:,:,i]) + tr(-(R\dR[:,:,i])*(R\Syy)) for i in 1:length(θ)]

    dl1 + dl2 + dl3
end

function em_step(build,θ,z)
    opt = optimize(t->elbo(build(θ),z,t,build),θ)
    Optim.minimizer(opt)
end

function em_step2(build,θ,z;optim_options...)
    m = build(θ)
    kf = kalman_filter(m,z)
    ks = kalman_smoother(kf,m)
    opt = optimize(t->elbo(ks,z,t,build),θ,Optim.Options(;optim_options...))
    Optim.minimizer(opt)
end

struct EMResults
    θs
    ls
end

function em(build,θ0,z;tol=1e-4,max_iterations=1000,verbose=false,optim_options...)
    m0 = build(θ0)
    kf0 = kalman_filter(m0,z)
    ks0 = kalman_smoother(kf0,m0)
    ls = zeros(max_iterations)
    ls[1] = filter_likelihood(kf0)
    if verbose
            println("Iteration: ",1)
            println("Log likelihood: ",ls[1])
    end

    θs = zeros(length(θ0),max_iterations)
    θs[:,1] = θ0[:]
    s = 1
    for i in 2:max_iterations
        θs[:,i] = em_step2(build,θs[:,i-1],z;optim_options...)
        ls[i] = filter_likelihood(kalman_filter(build(θs[:,i]),z))
        if verbose
            println("Iteration: ",i)
            println("Log likelihood: ",ls[i])
        end
        if ls[i] > ls[i-1]
            @warn "Expectation maximization increased the log likelihood"
            break
        end
        s+=1
        if (ls[i-1] - ls[i])<tol
            break
        end
    end
    EMResults(θs[:,1:s],ls[1:s])
end


export Gamma

"""
Description:

    A gamma node with shape-rate parameterization:

    f(out,a,b) = Gam(out|a,b) = 1/Γ(a) b^a out^{a - 1} exp(-b out)

Interfaces:

    1. out
    2. a (shape)
    3. b (rate)

Construction:

    Gamma(out, a, b, id=:some_id)
"""
mutable struct Gamma <: SoftFactor
    id::Symbol
    interfaces::Vector{Interface}
    i::Dict{Symbol,Interface}

    function Gamma(out, a, b; id=generateId(Gamma))
        @ensureVariables(out, a, b)
        self = new(id, Array{Interface}(undef, 3), Dict{Symbol,Interface}())
        addNode!(currentGraph(), self)
        self.i[:out] = self.interfaces[1] = associate!(Interface(self), out)
        self.i[:a] = self.interfaces[2] = associate!(Interface(self), a)
        self.i[:b] = self.interfaces[3] = associate!(Interface(self), b)

        return self
    end
end

slug(::Type{Gamma}) = "Gam"

format(dist::ProbabilityDistribution{Univariate, Gamma}) = "$(slug(Gamma))(a=$(format(dist.params[:a])), b=$(format(dist.params[:b])))"

ProbabilityDistribution(::Type{Univariate}, ::Type{Gamma}; a=1.0, b=1.0) = ProbabilityDistribution{Univariate, Gamma}(Dict(:a=>a, :b=>b))
ProbabilityDistribution(::Type{Gamma}; a=1.0, b=1.0) = ProbabilityDistribution{Univariate, Gamma}(Dict(:a=>a, :b=>b))

dims(dist::ProbabilityDistribution{Univariate, Gamma}) = ()

vague(::Type{Gamma}) = ProbabilityDistribution(Univariate, Gamma, a=1.0, b=tiny) # Flat prior leads to more stable behaviour than Jeffrey's prior

unsafeMean(dist::ProbabilityDistribution{Univariate, Gamma}) = dist.params[:a]/dist.params[:b] # unsafe mean

unsafeLogMean(dist::ProbabilityDistribution{Univariate, Gamma}) = digamma(dist.params[:a]) - log(dist.params[:b])

# https://stats.stackexchange.com/questions/457357/what-is-the-expected-value-of-x-logx-of-the-gamma-distribution
unsafeMeanLogMean(dist::ProbabilityDistribution{Univariate, Gamma}) =  (gamma(dist.params[:a]+1)/(gamma(dist.params[:a])*dist.params[:b])) * (digamma(dist.params[:a]+1) - log(dist.params[:b]))

unsafeVar(dist::ProbabilityDistribution{Univariate, Gamma}) = dist.params[:a]/dist.params[:b]^2 # unsafe variance

logPdf(dist::ProbabilityDistribution{Univariate, Gamma}, x) = dist.params[:a]*log(dist.params[:b]) - labsgamma(dist.params[:a]) + (dist.params[:a]-1)*log(x) - dist.params[:b]*x

isProper(dist::ProbabilityDistribution{Univariate, Gamma}) = (dist.params[:a] >= tiny) && (dist.params[:b] >= tiny)

function prod!( x::ProbabilityDistribution{Univariate, Gamma},
                y::ProbabilityDistribution{Univariate, Gamma},
                z::ProbabilityDistribution{Univariate, Gamma}=ProbabilityDistribution(Univariate, Gamma, a=0.0, b=0.0))

    z.params[:a] = x.params[:a] + y.params[:a] - 1.0
    z.params[:b] = x.params[:b] + y.params[:b]

    return z
end

@symmetrical function prod!(x::ProbabilityDistribution{Univariate, Gamma},
                            y::ProbabilityDistribution{Univariate, PointMass},
                            z::ProbabilityDistribution{Univariate, PointMass}=ProbabilityDistribution(Univariate, PointMass, m=0.0))

    (y.params[:m] > 0.0) || error("PointMass location $(y.params[:m]) should be positive")
    z.params[:m] = y.params[:m]

    return z
end

sample(dist::ProbabilityDistribution{Univariate, Gamma}) = gammainvcdf(dist.params[:a], 1/dist.params[:b], rand())

naturalParams(dist::ProbabilityDistribution{Univariate, Gamma}) = [dist.params[:a]-1.0, -dist.params[:b]]

standardDistribution(V::Type{Univariate}, F::Type{Gamma}; η::Vector) = ProbabilityDistribution(V, F, a=η[1]+1.0, b=-η[2])

logNormalizer(::Type{Univariate}, ::Type{Gamma}; η::Vector) = loggamma(η[1]+1.0) - (η[1]+1.0)*log(-η[2])

logPdf(V::Type{Univariate}, F::Type{Gamma}, x::Number; η::Vector) =[log(x), x]'*η - logNormalizer(V, F, η=η)

# Entropy functional
function differentialEntropy(dist::ProbabilityDistribution{Univariate, Gamma})
    labsgamma(dist.params[:a]) -
    (dist.params[:a] - 1.0)*digamma(dist.params[:a]) -
    log(dist.params[:b]) +
    dist.params[:a]
end

# Average energy functional
function averageEnergy(::Type{Gamma}, marg_out::ProbabilityDistribution{Univariate}, marg_a::ProbabilityDistribution{Univariate, PointMass}, marg_b::ProbabilityDistribution{Univariate})
    labsgamma(marg_a.params[:m]) -
    marg_a.params[:m]*unsafeLogMean(marg_b) -
    (marg_a.params[:m] - 1.0)*unsafeLogMean(marg_out) +
    unsafeMean(marg_b)*unsafeMean(marg_out)
end

# By Stirling's approximation
function averageEnergy(::Type{Gamma}, marg_out::ProbabilityDistribution{Univariate}, marg_a::ProbabilityDistribution{Univariate}, marg_b::ProbabilityDistribution{Univariate})
    unsafeMeanLogMean(marg_a) - unsafeMean(marg_a) + 0.5*(log(2*pi)-unsafeLogMean(marg_a)) -
    unsafeMean(marg_a)*unsafeLogMean(marg_b) -
    (unsafeMean(marg_a) - 1.0)*unsafeLogMean(marg_out) +
    unsafeMean(marg_b)*unsafeMean(marg_out)
end


using Gadfly, RDatasets, Distributions

set_default_plot_size(6inch, 3inch)

xs = dataset("datasets", "faithful").Waiting

quantiles = [0.1, 0.9]
p1 = plot(x=xs, Geom.density, Guide.title("quantiles=$quantiles"),
        layer(x=xs, Stat.quantile_bars(quantiles=quantiles), Geom.segment))

bandwidth = 0.6
p2 = plot(x=xs, Stat.density(bandwidth=bandwidth), Geom.line, Guide.title("bandwidth=$bandwidth"),
    layer(x=xs, Stat.quantile_bars(bandwidth=bandwidth), Geom.segment))

p = hstack(p1, p2)


# IMAGES
using Images
function nanfilt!(v::Vector,σ)
    nans = .!(isnan.(v))
    v = view(v,  findfirst(nans):findlast(nans))
    imfilter!(v, v, KernelFactors.IIRGaussian([σ]))
end

function nanfilt(v::Vector,σ)
    t = copy(v)
    nanfilt!(t, σ)
    return t
end

function imfilter_gaussian(img, sigma; kwargs...)
    imfilter(img,
        KernelFactors.IIRGaussian(sigma; kwargs...))
end


using JLD2
using Plots
using Printf
using Statistics

using Oceananigans
using Oceananigans.Advection
using Oceananigans.AbstractOperations
using Oceananigans.OutputWriters
using Oceananigans.Grids
using Oceananigans.Fields
using Oceananigans.Forcings

include("Bickley.jl")

using .Bickley

function run(; Nh = 128,
               output_time_interval = 2,
               stop_time = 200,
               arch = CPU(),
               ν = 0,
               advection = WENO5())

    name = "oceananigans_unstable_bickley_jet_Nh$(Nh)_$(typeof(advection).name.wrapper)"

    grid = RegularCartesianGrid(size=(Nh, Nh, 1),
                                x = (-2π, 2π), y=(-2π, 2π), z=(0, 1),
                                topology = (Periodic, Periodic, Bounded))

    model = IncompressibleModel(architecture = arch,
                                 timestepper = :RungeKutta3,
                                   advection = advection,
                                        grid = grid,
                                     tracers = :c,
                                     closure = IsotropicDiffusivity(ν=ν, κ=ν),
                                    buoyancy = nothing)

    # ** Initial conditions **
    #
    # u, v: Large-scale jet + vortical perturbations
    #    c: Sinusoid

    # Parameters
    ϵ = 0.1 # perturbation magnitude
    ℓ = 0.5 # Gaussian width
    k = 0.5 # Sinusoidal wavenumber

    # Total initial conditions
    uᵢ(x, y, z) = Bickley.U(y) + ϵ * Bickley.ũ(x, y, ℓ, k)
    vᵢ(x, y, z) = ϵ * Bickley.ṽ(x, y, ℓ, k)
    cᵢ(x, y, z) = Bickley.C(y, grid.Ly)

    set!(model, u=uᵢ, v=vᵢ, c=cᵢ)

    progress(sim) = @info(@sprintf("Iter: %d, time: %.1f, Δt: %.3f, max|u|: %.2f",
                                   sim.model.clock.iteration, sim.model.clock.time,
                                   sim.Δt.Δt, maximum(abs, u.data.parent)))

    wizard = TimeStepWizard(cfl=1.0, Δt=1e-4, max_change=1.1, max_Δt=10.0)

    simulation = Simulation(model, Δt=wizard, stop_time=stop_time,
                            iteration_interval=10, progress=progress)

    # Output: primitive fields + computations
    u, v, w, c = primitives = merge(model.velocities, model.tracers)

    ω   = ComputedField( ∂x(v) - ∂y(u)      )
    ω²  = ComputedField( (∂x(v) - ∂y(u))^2  )
    c²  = ComputedField( c^2                )

    ∇c² = @at (Cell, Cell, Cell) ∂x(c)^2 + ∂y(c)^2
    ∇c² = ComputedField(∇c²)

    computations = (ω=ω, ω²=ω², c²=c², ∇c²=∇c²)
    outputs = merge(primitives, computations)

    save_grid = (file, model) -> file["serialized/grid"] = model.grid

    simulation.output_writers[:fields] =
        JLD2OutputWriter(model, merge(model.velocities, model.tracers, (ω=ω, ∇c²=∇c²)),
                                schedule = TimeInterval(output_time_interval),
                                init = save_grid,
                                prefix = name * "_fields",
                                force = true)

    averages = Dict(name => mean(ϕ, dims=(1, 2, 3))
                    for (name, ϕ) in zip(keys(computations), values(computations)))

    simulation.output_writers[:averages] =
        JLD2OutputWriter(model, averages,
                         schedule = TimeInterval(output_time_interval),
                         init = save_grid,
                         prefix = name * "_statistics",
                         force = true)

    @info "Running a simulation of an unstable Bickley jet with $(Nh)² degrees of freedom..."

    run!(simulation)

    return name
end

function analyze(name)
    @info "Analyzing the results of an unstable Bickley jet simulation..."

    statistics_file = jldopen(name * "_statistics.jld2")

    iterations = parse.(Int, keys(statistics_file["timeseries/t"]))
    grid = statistics_file["serialized/grid"]

    ∇c² = [statistics_file["timeseries/∇c²/$iter"][1, 1, 1] for iter in iterations]
    c²  = [statistics_file["timeseries/c²/$iter"][1, 1, 1] for iter in iterations]
    t   = [statistics_file["timeseries/t/$iter"][1, 1, 1] for iter in iterations]

    close(statistics_file)

    plot(t, 2π / grid.Ly .* ∇c² ./ c²)
    savefig(name * ".png")

    return nothing
end

function visualize(name, contours=false)
    @info "Making a fun movie about an unstable Bickley jet..."

    fields_file = jldopen(name * "_fields.jld2")

    iterations = parse.(Int, keys(fields_file["timeseries/t"]))
    grid = fields_file["serialized/grid"]

    xu, yu, zu = nodes((Face, Cell, Cell), grid)
    xω, yω, zω = nodes((Face, Face, Cell), grid)
    xc, yc, zc = nodes((Cell, Cell, Cell), grid)

    anim = @animate for (i, iteration) in enumerate(iterations)

        @info "    Plotting frame $i from iteration $iteration..."

        t = fields_file["timeseries/t/$iteration"]
        ω = fields_file["timeseries/ω/$iteration"][:, :, 1]
        u = fields_file["timeseries/u/$iteration"][:, :, 1]
        c = fields_file["timeseries/c/$iteration"][:, :, 1]

        kwargs = Dict(:xlabel => "x",
                      :ylabel => "y",
                      :aspectratio => 1,
                      :linewidth => 0,
                      :colorbar => true,
                      :clims => (-1, 1),
                      :xlims => (-grid.Lx/2, grid.Lx/2),
                      :ylims => (-grid.Ly/2, grid.Ly/2))

        contours && (kwargs[:levels] = range(-1, 1, length=31))
        plotter = contours ? contourf : heatmap

        u_plot = plotter(xu, yu, clamp.(u, -1, 1)'; color = :balance, kwargs...)
        ω_plot = plotter(xω, yω, clamp.(ω, -1, 1)'; color = :balance, kwargs...)
        c_plot = plotter(xc, yc, clamp.(c, -1, 1)'; color = :thermal, kwargs...)

        u_title = @sprintf("u at t = %.1f", t)
        ω_title = @sprintf("ω at t = %.1f", t)
        c_title = @sprintf("c at t = %.1f", t)

        plot(u_plot, ω_plot, c_plot,
             title = [u_title ω_title c_title],
             layout = (1, 3),
             size = (1600, 400))
    end

    gif(anim, name * ".gif", fps = 8)

    return nothing
end

for Nh in (512, 1024)
    #for advection in (CenteredSecondOrder(), CenteredFourthOrder(), UpwindBiasedThirdOrder(), UpwindBiasedFifthOrder(), WENO5())
    for advection in (WENO5(),)
        name = run(Nh=Nh, advection=advection)
        analyze(name)
        visualize(name)
    end
end


function compute()
	ans=1
	for i=1:20
		a=gcd(i,ans)
		ans*=Int(i/a);

	end
	return ans
end

print(compute())


function dimerdecay(;x = [1_000, 0, 0], c = [1.0, 0.002, 0.5, 0.04])
  m = Network("dimer-decay / Rathiam, Petzold, Cao, Gillespie 2003")

  m <= Species("S1", x[1])
  m <= Species("S2", x[2])
  m <= Species("S3", x[3])

  m <= Reaction("R1", c[1], "S1 --> 0")
  m <= Reaction("R2", c[2], "2 * S1 --> S2")
  m <= Reaction("R3", c[3], "S2 --> 2 * S1")
  m <= Reaction("R4", c[4], "S2 --> S3")

  return m
end

model = dimerdecay()


module Catalan

warn("**Catalan.jl IS DEPRECATED**. Please use the new Combinatorics.jl package.")

using Polynomial

export  bell,
        catalan,
        derangement,
        doublefactorial,
        fibonacci,
        hyperfactorial,
        jacobisymbol,
        lassalle,
        legendresymbol,
        lucas,
        multifactorial,
        multinomial,
        primorial,
        stirlings1,
        subfactorial

include("partitions.jl")
include("youngdiagrams.jl")

# Returns the n-th Bell number
function bell(bn::Integer)
    if bn < 0
        throw(DomainError())
    else
        n = BigInt(bn)
    end
    list = Array(BigInt, div(n*(n+1), 2))
    list[1] = 1
    for i = 2:n
        beg = div(i*(i-1),2)
        list[beg+1] = list[beg]
        for j = 2:i
            list[beg+j] = list[beg+j-1]+list[beg+j-i]
        end
    end
    return list[end]
end

# Returns the n-th Catalan number
function catalan(bn::Integer)
    if bn<0
        throw(DomainError())
    else
        n = BigInt(bn)
    end
    div(binomial(2*n, n), (n + 1))
end

# The number of permutations of n with no fixed points (subfactorial)
function derangement(sn::Integer)
    n = BigInt(sn)
    return num(factorial(n)*sum([(-1)^k//factorial(k) for k=0:n]))
end
subfactorial(n::Integer) = derangement(n)

function doublefactorial(n::Integer)
    if n < 0
        throw(DomainError())
    end
    z = BigInt()
    ccall((:__gmpz_2fac_ui, :libgmp), Void,
        (Ptr{BigInt}, Uint), &z, uint(n))
    return z
end

function fibonacci(n::Integer)
    if n < 0
        throw(DomainError())
    end
    z = BigInt()
    ccall((:__gmpz_fib_ui, :libgmp), Void,
        (Ptr{BigInt}, Uint), &z, uint(n))
    return z
end

# Hyperfactorial
hyperfactorial(n::Integer) = prod([i^i for i = BigInt(2):n])

function jacobisymbol(a::Integer, b::Integer)
    ba = BigInt(a)
    bb = BigInt(b)
    return ccall((:__gmpz_jacobi, :libgmp), Int,
        (Ptr{BigInt}, Ptr{BigInt}), &ba, &bb)
end

#Computes Lassalle's sequence
#OEIS entry A180874
function lassalle(m::Integer)
   A = ones(BigInt,m)
   for n=2:m
       A[n]=(-1)^(n-1) * (catalan(n) + sum([(-1)^j*binomial(2n-1, 2j-1)*A[j]*catalan(n-j) for j=1:n-1]))
   end
   A[m]
end

function legendresymbol(a::Integer, b::Integer)
    ba = BigInt(a)
    bb = BigInt(b)
    return ccall((:__gmpz_legendre, :libgmp), Int,
        (Ptr{BigInt}, Ptr{BigInt}), &ba, &bb)
end

function lucas(n::Integer)
    if n < 0
        throw(DomainError())
    end
    z = BigInt()
    ccall((:__gmpz_lucnum_ui, :libgmp), Void,
        (Ptr{BigInt}, Uint), &z, uint(n))
    return z
end

function multifactorial(n::Integer, m::Integer)
    if n < 0
        throw(DomainError())
    end
    z = BigInt()
    ccall((:__gmpz_mfac_uiui, :libgmp), Void,
        (Ptr{BigInt}, Uint, Uint), &z, uint(n), uint(m))
    return z
end

# Multinomial coefficient where n = sum(k)
function multinomial(k...)
    s = 0
    result = 1
    for i in k
        s += i
        result *= binomial(s, i)
    end
    result
end

function primorial(n::Integer)
    if n < 0
        throw(DomainError())
    end
    z = BigInt()
    ccall((:__gmpz_primorial_ui, :libgmp), Void,
        (Ptr{BigInt}, Uint), &z, uint(n))
    return z
end

# Returns s(n, k), the signed Stirling number of first kind
function stirlings1(n::Integer, k::Integer)
    p = poly(0:(n-1))
    p[n - k + 1]
end

end # module


using Test

using CompScienceMeshes

e0 = point(0,0,0)
e1 = point(1,0,0)
e2 = point(0,1,0)
e3 = point(0,0,1)

p = simplex(e0,e1,e2)

@test isinside(p, (e0+e1+e2)/3) == true
@test isinside(p, point(-0.1, 0.3, 0.0)) == false
@test isinside(p, point(0.3, -0.3, 0.0)) == false
@test isinside(p, point(0.6, 0.6, 0.0)) == false
@test isinside(p, point(0.4, 0.6, 0.0)) == false

@test isinclosure(p, (e0+e1+e2)/3) == true
@test isinclosure(p, point(-0.1, 0.3, 0.0)) == false
@test isinclosure(p, point(0.3, -0.3, 0.0)) == false
@test isinclosure(p, point(0.6, 0.6, 0.0)) == false
@test isinclosure(p, point(0.4, 0.6, 0.0)) == true


import SpecialFunctions
const Γ = SpecialFunctions.gamma

# * Properties
@doc raw"""
    turning_point(η, ℓ)

Computes the turning point ``\rho_{\mathrm{TP}}`` of the Coulomb
functions, which separates the monotonic region
``\rho<\rho_{\mathrm{TP}}`` from the oscillatory region
``\rho>\rho_{\mathrm{TP}}``.

See Eq. (2) of

- Barnett, A. (1982). COULFG: Coulomb and Bessel Functions and Their
  Derivatives, for Real Arguments, by Steed's Method. Computer Physics
  Communications, 27(2),
  147–166. http://dx.doi.org/10.1016/0010-4655(82)90070-4

or

- Barnett, A. R. (1996). The Calculation of Spherical Bessel and
  Coulomb Functions. In (Eds.), Computational Atomic Physics
  (pp. 181–202). : Springer Berlin Heidelberg.
"""
turning_point(η, ℓ) = η + √(η^2 + ℓ*(ℓ+1))

# https://dlmf.nist.gov/33.2
coulomb_normalization(η, ℓ) = 2^ℓ*exp(-π*η/2)*abs(Γ(ℓ+1+im*η))/Γ(2ℓ+2)

# * Continued fractions

coulomb_fraction1(x::T, η::T, n::Integer; kwargs...) where T =
    lentz_thompson((n+1)/x+η/(n+1),
                   k -> -(one(T)+η^2/(n+k)^2),
                   k -> (2(n+1)+1)*(inv(x)+η/((n+k)^2+(n+k))); kwargs...)

function coulomb_fraction2(x::T, η::T, n::Integer, ω; kwargs...) where T
    imω = im*ω
    r = lentz_thompson(x-η,
                       k -> (imω*η - n - 1 + k)*(imω*η + n + k),
                       k -> 2*(x - η + imω*k); kwargs...)
    ((imω/x)*r[1],r[2:end]...)
end

# * Recurrences

function coulomb_downward_recurrence!(F, F′, x⁻¹::T, η, ℓ::UnitRange, cf1, s) where T
    Fₙ = s ? 1 : -1
    F′ₙ = cf1*Fₙ
    η² = η^2
    for (i,n) in zip(reverse(eachindex(ℓ)), reverse(ℓ))
        S = n*x⁻¹ + η/n
        R = √(1 + η²/n^2)

        Fₙ₋₁ = (S*Fₙ + F′ₙ)/R
        F′ₙ₋₁ = S*Fₙ₋₁ - R*Fₙ

        F[i] = Fₙ
        F′[i] = F′ₙ
        Fₙ = Fₙ₋₁
        F′ₙ = F′ₙ₋₁
    end
end

function coulomb_upward_recurrence!(G, G′, x⁻¹::T, η, ℓ::UnitRange) where T
    Gₙ = G[1]
    G′ₙ = G′[1]
    η² = η^2
    for i = 2:length(ℓ)
        n = ℓ[i]

        S = (n+1)*x⁻¹ + η/(n+1)
        R = √(1 + η²/(n+1)^2)

        G[i] = Gₙ₊₁ = (S*Gₙ - G′ₙ)/R
        G′[i] = G′ₙ₊₁ = R*Gₙ - S*Gₙ₊₁

        Gₙ = Gₙ₊₁
        G′ₙ = G′ₙ₊₁
    end
end

coulomb_upward_recurrence!(::Nothing, ::Nothing, args...; _...) = nothing

# * Driver

function coulombs!(F::FF, F′::FF, G::GG, G′::GG, x::T, η::T, ℓ::UnitRange; verbosity=0, kwargs...) where {FF,GG,T<:Number}
    verbose = get(kwargs, :verbose, false)
    ρtp = turning_point(η, first(ℓ))
    ρtp > x && verbosity > 0 &&
        @warn "Turning point ρ_TP = $(ρtp) > $(x) which may result in loss of accuracy, consider decreasing first ℓ below $(first(ℓ))."

    if iszero(x)
        F .= false
        for (i,ℓ) in enumerate(ℓ)
            C = coulomb_normalization(η, ℓ)
            F′[i] = (ℓ+1)*C*x^ℓ
            G[i] = 1/((2ℓ+1)*C) # This seems wrong for ℓ ≥ 1
            G′[i] = -T(Inf) # This is not necessarily true
        end
        return
    end

    verbosity > 1 && @show x, η, ℓ
    cf1,n,_,s,converged = coulomb_fraction1(x, η, ℓ[end]; verbosity=verbosity, kwargs...)
    converged || verbosity > 0 && @warn "Consider increasing ℓmax beyond $(ℓ[end])"

    x⁻¹ = inv(x)
    coulomb_downward_recurrence!(F, F′, x⁻¹, η, ℓ, cf1, s)

    cf2,n,_,s,converged = coulomb_fraction2(x, η, ℓ[1], 1; verbosity=verbosity, kwargs...)

    p,q = real(cf2),imag(cf2)
    Fₘ = F[1]
    F′ₘ = F′[1]
    Gₘ = (F′ₘ-p*Fₘ)/q
    G′ₘ = p*Gₘ - q*Fₘ

    ω₁ = 1/√(F′ₘ*Gₘ - Fₘ*G′ₘ)

    lmul!(ω₁, F)
    lmul!(ω₁, F′)

    if !isnothing(G)
        G[1] = ω₁*Gₘ
        G′[1] = ω₁*G′ₘ

        coulomb_upward_recurrence!(G, G′, x⁻¹, η, ℓ)
    end
end

# * Interface

"""
    coulombs!(F, F′, G, G′, x::AbstractVector; kwargs...)

Loop through all values of `x` and compute all Coulomb
functions, storing the results in the preallocated matrices `F`, `F′`,
`G`, `G′`.
"""
function coulombs!(F, F′, G, G′, x::AbstractVector, η, ℓ::AbstractVector; kwargs...)
    size(F,1) == size(F′,1) == size(G,1) == size(G′,1) == length(x) &&
        size(F,2) == size(F′,2) == size(G,2) == size(G′,2) == length(ℓ) ||
        throw(DimensionError("The dimension of the output arrays do not agree"))
    for (i,x) in enumerate(x)
        coulombs!(view(F, i, :),
                  view(F′, i, :),
                  view(G, i, :),
                  view(G′, i, :),
                  x, η, ℓ; kwargs...)
    end
end

"""
    coulombs(x::AbstractVector, nℓ; kwargs...)

Convenience wrapper around [`coulombs!`](@ref) that preallocates output
matrices of the appropriate dimensions.
"""
function coulombs(x::AbstractVector{T}, η, ℓ::AbstractVector; kwargs...) where T
    nx = length(x)
    nℓ = length(ℓ)
    F = zeros(T, nx, nℓ)
    F′ = zeros(T, nx, nℓ)
    G = zeros(T, nx, nℓ)
    G′ = zeros(T, nx, nℓ)
    coulombs!(F, F′, G, G′, x, η, ℓ; kwargs...)
    F, F′, G, G′
end

coulombs(x::AbstractVector, η, nℓ::Integer; kwargs...) =
    coulombs(x, η, 0:nℓ-1; kwargs...)

export coulombs!, coulombs


using HarwellRutherfordBoeing
using Krylov
using LinearOperators
# using ProfileView

# M = HarwellBoeingMatrix("data/illc1033.rra");
M = HarwellBoeingMatrix("data/illc1850.rra");
A = M.matrix;
(m, n) = size(A);
@printf("System size: %d rows and %d columns\n", n, m);  # We use A'.

# Define a linear operator with preallocation.
Ap = zeros(m);
Atq = zeros(n);
op = LinearOperator(m, n, false, false,
                    p -> A_mul_B!(1.0,  A, p, 0.0, Ap),
                    q -> At_mul_B!(1.0, A, q, 0.0, Atq),
                    q -> At_mul_B!(1.0, A, q, 0.0, Atq));

x_exact = A * ones(n);  # y = ones(n);
x_exact_norm = norm(x_exact);
x_exact /= x_exact_norm;
b = A' * x_exact;
(x, stats) = cgne(op', b);
# @profile (x, stats) = cgne(op', b);
@time (x, stats) = cgne(op', b);
show(stats);
resid = norm(A' * x - b) / norm(b);
@printf("CGNE: Relative residual: %7.1e\n", resid);
@printf("CGNE: ‖x - x*‖₂: %7.1e\n", norm(x - x_exact));

# ProfileView.view()


using Printf
using Oceananigans
using Oceananigans.Models.ShallowWaterModels: VectorInvariantFormulation
using Oceananigans.Advection: VelocityStencil, VorticityStencil
using Statistics: mean

arch = GPU()
Nh = 400
stencil = VelocityStencil

grid = RectilinearGrid(arch, size = (Nh, Nh), x = (0, 1), y = (0, 1), halo = (4, 4), topology = (Bounded, Bounded, Flat))

g = 1.0
f = 5.0
H  = 1.0
R = (g*H)^0.5 / f

model = ShallowWaterModel(grid = grid,
                        gravitational_acceleration = g,
                        coriolis = FPlane(f = f),
                        mass_advection = WENO5(),
                        momentum_advection = WENO5(vector_invariant = stencil()),
                        formulation = VectorInvariantFormulation())

# Model initialization
h₀ = 0.2
σ  = 0.07
d  = 1.4σ

gaussian(x, y, σ) = exp(-(x^2 + y^2)/(2*σ^2))
hᵢ(x, y, z) = H + h₀ * (gaussian(x - d - 0.5, y - 0.5, σ) + gaussian(x - 0.5 + d, y - 0.5, σ))

set!(model, h = hᵢ)
set!(model, u = - ∂y(model.solution.h) / f)
set!(model, v =   ∂x(model.solution.h) / f)
@info "Model initialized"

#####
##### Simulation setup
#####

g = model.gravitational_acceleration
gravity_wave_speed = sqrt(g * maximum(model.solution.h)) # hydrostatic (shallow water) gravity wave speed

# Time-scale for gravity wave propagation across the smallest grid cell
wave_propagation_time_scale = (grid.Lx / grid.Nx) / gravity_wave_speed

Δt = wave_propagation_time_scale * 0.1

simulation = Simulation(model, Δt = Δt, stop_time = 10)
start_time = [time_ns()]

function progress(sim)
    wall_time = (time_ns() - start_time[1]) * 1e-9

    u = sim.model.solution[1]

    @info @sprintf("Time: % 12s, iteration: %d, max(|u|): %.2e ms⁻¹, wall time: %s",
                    prettytime(sim.model.clock.time),
                    sim.model.clock.iteration, maximum(abs, u),
                    prettytime(wall_time))

    start_time[1] = time_ns()

    return nothing
end

simulation.callbacks[:progress] = Callback(progress, IterationInterval(100))

u, v, h = model.solution

Eᵦ = g * H * H

ζ = Field(∂x(v) - ∂y(u))
compute!(ζ)

KE = Field(h * (u^2 + v^2))
PE = Field(g * h^2)
compute!(KE)
compute!(PE)

save_interval = 0.1

simulation.output_writers[:surface_fields] = JLD2OutputWriter(model, (; u, v, h, ζ, KE, PE),
                                                            schedule = TimeInterval(save_interval),
                                                            filename = "vortex_merger_$(Nh)_$stencil",
                                                            overwrite_existing = true)

run!(simulation)

using JLD2

file = jldopen("vortex_merger_$(Nh)_$stencil.jld2")
iterations = parse.(Int, keys(file["timeseries/t"]))

ke2 = zeros(length(iterations))
pe2 = zeros(length(iterations))
z2  = zeros(length(iterations))
en  = zeros(length(iterations))
ett = zeros(length(iterations))
ztt = zeros(length(iterations))

for (idx, iter) in enumerate(iterations)
    ke2[idx] = mean(file["timeseries/KE/" * string(iter)][:, :, 1])
    pe2[idx] = mean(file["timeseries/PE/" * string(iter)][:, :, 1])
    en[idx]  = mean(ke2[idx] .+ pe2[idx])
    z2[idx]  = mean(file["timeseries/ζ/" * string(iter)][:, :, 1] .^ 2)
    ett[idx] = (en[1] - en[idx]) / (en[1] - Eᵦ)
    ztt[idx] = (z2[1] - z2[idx]) / z2[1]
end

jldsave("energy_vorticity_$(Nh)_$stencil.jld2", energy = ett, vorticity = ztt)

file = jldopen("vortex_merger_1600_VelocityStencil.jld2")
using CairoMakie

iter = Observable(0)
iterations = parse.(Int, keys(file["timeseries/t"]))

ζ′ = @lift(file["timeseries/ζ/" * string($iter)][:, 1:end-1, 1])
h′ = @lift(file["timeseries/h/" * string($iter)][:, 1:end-1, 1])
xζ, yζ, z = nodes((Face, Face, Center), grid)
xh, yh, z = nodes((Center, Center, Center), grid)

title = @lift(@sprintf("Vorticity in Shallow Water Model at time = %s", prettytime(file["timeseries/t/" * string($iter)])))
fig = CairoMakie.Figure(resolution = (1000, 600))

ax = CairoMakie.Axis(fig[1,1], xlabel = "longitude", ylabel = "latitude", title=title)
heatmap_plot = CairoMakie.heatmap!(ax, xζ, yζ, ζ′, colormap=Reverse(:balance), colorrange = (-9.0, 4.5))
CairoMakie.Colorbar(fig[1,2], heatmap_plot, width=25)

CairoMakie.record(fig, "vortex_merger.mp4", iterations[1:end], framerate=2) do i
    @info "Plotting iteration $i of $(iterations[end])..."
    iter[] = i
end


using Base.Test
using MachineLearningMetrics

y_true = [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
y_pred = [1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1]

# total_population
@test total_population(y_true, y_pred) == 16

# true_positive
@test true_positive(y_true, y_pred) == 4

# true_negative
@test true_negative(y_true, y_pred) == 4

# false_positive
@test false_positive(y_true, y_pred) == 4

# false_negative
@test false_negative(y_true, y_pred) == 4

# condition_positive
@test condition_positive(y_true, y_pred) == 8

# condition_negative
@test condition_negative(y_true, y_pred) == 8

# predicted_condition_positive
@test predicted_condition_positive(y_true, y_pred) == 8

# predicted_condition_negative
@test predicted_condition_negative(y_true, y_pred) == 8

# accuracy_score
# accuracy
@test accuracy_score(y_true, y_pred) == accuracy(y_true, y_pred) == 0.5

# prevalence
@test accuracy(y_true, y_pred) == 0.5

# positive_predictive_value
# precision
@test positive_predictive_value(y_true, y_pred) == precision(y_true, y_pred) == 0.5

# false_discovery_rate
@test false_discovery_rate(y_true, y_pred) == 0.5

# negative_predictive_value
@test negative_predictive_value(y_true, y_pred) == 0.5

# false_omission_rate
@test false_omission_rate(y_true, y_pred) == 0.5

# true_positive_rate
# sensitivity
# recall
@test positive_predictive_value(y_true, y_pred) == precision(y_true, y_pred) == 0.5

# false_positive_rate
@test false_positive_rate(y_true, y_pred) == 0.5

# false_negative_rate
@test false_negative_rate(y_true, y_pred) == 0.5

# true_negative_rate
# specificity
@test true_negative_rate(y_true, y_pred) == specificity(y_true, y_pred) == 0.5

# positive_likelihood_ratio
@test positive_likelihood_ratio(y_true, y_pred) == 1.0

# negative_likelihood_ratio
@test negative_likelihood_ratio(y_true, y_pred) == 1.0

# diagnostic_odds_ratio
@test diagnostic_odds_ratio(y_true, y_pred) == 1.0

# f1_score
@test f1_score(y_true, y_pred) == 0.5

# matthews_corrcoef
@test matthews_corrcoef(y_true, y_pred) == 0.0


"""
Generate a wavelet iteratively adjusted amplitude adjusted Fourier transform (WIAAFT) [1]
surrogate series for `ts`. Preserves mean and variance structure of the signal, but
randomises nonlinear properties of the signal (i.e. Hurst exponents) [1].

[1] C.J. Keylock (2006). "Constrained surrogate time series with preservation of the mean
and variance structure". Phys. Rev. E. 73: 036707. doi:10.1103/PhysRevE.73.036707.

"""
function wiaaft(ts; n_maxiter = 100, tol = 1e-5, n_windows = 50)

    # Find the maximum number of levels possible with these data
    L = maxtransformlevels(ts)

    # Create a Daubechies wavelet with 16 vanishing moments and periodic boundary conditions
    wt = wavelet(WT.Daubechies{16}(), WT.Periodic)

    # Do the discrete wavelet transform across levels 1:L
    t = [dwt(ts, wt, i) for i = 1:L]

    # Constrained realization of the coefficients at each level by
    # iAAFT algorithm (treating the coefficients as a time series)
    coeff_surrogates = [iaaft(t[i]) for i in 1:L]

    # Mirror the surrogates
    mirror_surrogates = [reverse(x) for x in coeff_surrogates]

    n = length(ts)
    surrogate_coeffs = Vector{Float64}(undef, n)
    mirror_coeffs = Vector{Float64}(undef, n)
    selected_coeffs = Vector{Float64}(undef, n)
    surrogate = Vector{Float64}(undef, n)
    surrogates = Vector{Tuple{Int, Vector{Float64}, Vector{Float64}, Vector{Float64}}}(undef, 0)
    for dyadic_scale = 1:L
        surrogate_coeffs[:] = iaaft(coeff_surrogates[dyadic_scale])
        mirror_coeffs[:] = reverse(surrogate_coeffs)

        # Circularly rotate to minimize error function
        min_errors = Vector{Float64}(undef, 2)
        [min_errors[i] = Inf for i in 1:2]

        errors = Vector{Float64}(undef, 2)
        shift_minimising_errfunc = Vector{Int}(undef, 2)
        for i in 0:n-1
            errors[1] = rmsd(coeff_surrogates[dyadic_scale],
                            circshift(surrogate_coeffs, i))
            errors[2] = rmsd(coeff_surrogates[dyadic_scale],
                            circshift(mirror_coeffs, i))

            if errors[1] < min_errors[1]
                min_errors[1] = errors[1]
                shift_minimising_errfunc[1] = i
            end

            if errors[2] < min_errors[2]
                min_errors[2] = errors[2]
                shift_minimising_errfunc[2] = i
            end
        end
        if min_errors[1] < min_errors[2]
            selected_coeffs[:] = circshift(surrogate_coeffs, shift_minimising_errfunc[1])
        else
            selected_coeffs[:] = circshift(mirror_coeffs, shift_minimising_errfunc[2])
        end

        # Invert the DWT on original
        inverted =  idwt(coeff_surrogates[dyadic_scale], wt, dyadic_scale)
        inverted_surrogate = idwt(selected_coeffs, wt, dyadic_scale)

        surrogate[sortperm(inverted_surrogate)] = sort(inverted)
        surrogate[sortperm(inverted)] = sort(ts)

        push!(surrogates, (dyadic_scale, inverted, inverted_surrogate, surrogate[:]))
    end

    return surrogates
end

export wiaaft


using Distributions, StatsBase
using PyPlot

function qqplot(z; color="black")
    n = length(z)

    grid = [(1 / (n + 1)):(1 / (n + 1)):(1.0 - (1 / (n + 1)));]

    qz = quantile(z, grid)
    qd = quantile.(Ref(Distributions.Normal()), grid)

    lims = 3.290
    x = range(-lims, stop=lims)

    plot(x, x, color="grey", linestyle=":", linewidth=.25)
    scatter(qz, qd, s=.75, color=color)

    xlim([-lims, lims])
    ylim([-lims, lims])

    nothing
end


using NearestNeighbors
using StaticArrays

if VERSION >= v"0.5-"
    using Base.Test
else
    using BaseTestNext
    const Test = BaseTestNext
end

import Distances: Metric, evaluate
immutable CustomMetric1 <: Metric end
evaluate(::CustomMetric1, a::AbstractVector, b::AbstractVector) = maximum(abs(a - b))
function NearestNeighbors.interpolate{V <: AbstractVector}(::CustomMetric1,
                                                           a::V,
                                                           b::V,
                                                           x,
                                                           d,
                                                           ab)
    idx = (abs(b - a) .>= d - x)
    c = copy(a)
    c[idx] = (1 - x / d) .* a[idx] + (x / d) .* b[idx]
    return c, true
end
immutable CustomMetric2 <: Metric end
evaluate(::CustomMetric2, a::AbstractVector, b::AbstractVector) = norm(a - b) / (norm(a) + norm(b))

# TODO: Cityblock()
const metrics = [Chebyshev(), Euclidean(), Minkowski(3.5)]
const fullmetrics = [metrics; Hamming(); CustomMetric1(); CustomMetric2()]
const trees = [KDTree, BallTree]
const trees_with_brute = [BruteTree; trees]

include("test_knn.jl")
include("test_inrange.jl")
include("test_monkey.jl")
include("test_datafreetree.jl")


export Beta

"""
Description:

    Beta factor node

    Real scalars
    a > 0
    b > 0

    f(out, a, b) = Beta(out|a, b) = Γ(a + b)/(Γ(a) Γ(b)) out^{a - 1} (1 - out)^{b - 1}

Interfaces:

    1. out
    2. a
    3. b

Construction:

    Beta(id=:some_id)
"""
mutable struct Beta <: SoftFactor
    id::Symbol
    interfaces::Vector{Interface}
    i::Dict{Symbol,Interface}

    function Beta(out, a, b; id=generateId(Beta))
        @ensureVariables(out, a, b)
        self = new(id, Array{Interface}(undef, 3), Dict{Symbol,Interface}())
        addNode!(currentGraph(), self)
        self.i[:out] = self.interfaces[1] = associate!(Interface(self), out)
        self.i[:a] = self.interfaces[2] = associate!(Interface(self), a)
        self.i[:b] = self.interfaces[3] = associate!(Interface(self), b)

        return self
    end
end

slug(::Type{Beta}) = "Beta"

format(dist::ProbabilityDistribution{Univariate, Beta}) = "$(slug(Beta))(a=$(format(dist.params[:a])), b=$(format(dist.params[:b])))"

ProbabilityDistribution(::Type{Univariate}, ::Type{Beta}; a=1.0, b=1.0) = ProbabilityDistribution{Univariate, Beta}(Dict(:a=>a, :b=>b))
ProbabilityDistribution(::Type{Beta}; a=1.0, b=1.0) = ProbabilityDistribution{Univariate, Beta}(Dict(:a=>a, :b=>b))

dims(dist::ProbabilityDistribution{Univariate, Beta}) = 1

vague(::Type{Beta}) = ProbabilityDistribution(Univariate, Beta, a=1.0, b=1.0)

isProper(dist::ProbabilityDistribution{Univariate, Beta}) = (dist.params[:a] > 0.0) && (dist.params[:b] > 0.0)

unsafeMean(dist::ProbabilityDistribution{Univariate, Beta}) = dist.params[:a]/(dist.params[:a] + dist.params[:b])

unsafeLogMean(dist::ProbabilityDistribution{Univariate, Beta}) = digamma(dist.params[:a]) - digamma(dist.params[:a] + dist.params[:b]) # E[log(X)]

unsafeMirroredLogMean(dist::ProbabilityDistribution{Univariate, Beta}) = digamma(dist.params[:b]) - digamma(dist.params[:a] + dist.params[:b]) # E[log(1 - X)]

unsafeVar(dist::ProbabilityDistribution{Univariate, Beta}) = dist.params[:a]*dist.params[:b]/((dist.params[:a] + dist.params[:b])^2*(dist.params[:a] + dist.params[:b] + 1.0))

logPdf(dist::ProbabilityDistribution{Univariate, Beta}, x) = (dist.params[:a]-1)*log(x) + (dist.params[:b]-1)*log(1.0-x) - loggamma(dist.params[:a]) - loggamma(dist.params[:b]) + loggamma(dist.params[:a]+dist.params[:b])

function prod!( x::ProbabilityDistribution{Univariate, Beta},
                y::ProbabilityDistribution{Univariate, Beta},
                z::ProbabilityDistribution{Univariate, Beta}=ProbabilityDistribution(Univariate, Beta, a=1.0, b=1.0))

    z.params[:a] = x.params[:a] + y.params[:a] - 1.0
    z.params[:b] = x.params[:b] + y.params[:b] - 1.0

    return z
end

@symmetrical function prod!(x::ProbabilityDistribution{Univariate, Beta},
                            y::ProbabilityDistribution{Univariate, PointMass},
                            z::ProbabilityDistribution{Univariate, PointMass}=ProbabilityDistribution(Univariate, PointMass, m=0.0))

    (0.0 <= y.params[:m] <= 1.0) || error("PointMass location $(y.params[:m]) should be between 0 and 1")
    z.params[:m] = y.params[:m]

    return z
end

sample(dist::ProbabilityDistribution{Univariate, Beta}) = betainvcdf(dist.params[:a], dist.params[:b], rand())

# Entropy functional
function differentialEntropy(dist::ProbabilityDistribution{Univariate, Beta})
    labsbeta(dist.params[:a], dist.params[:b]) -
    (dist.params[:a] - 1.0)*digamma(dist.params[:a]) -
    (dist.params[:b] - 1.0)*digamma(dist.params[:b]) +
    (dist.params[:a] + dist.params[:b] - 2.0)*digamma(dist.params[:a] + dist.params[:b])
end

# Average energy functional
function averageEnergy(::Type{Beta}, marg_out::ProbabilityDistribution{Univariate}, marg_a::ProbabilityDistribution{Univariate, PointMass}, marg_b::ProbabilityDistribution{Univariate, PointMass})
    labsbeta(marg_a.params[:m], marg_b.params[:m]) -
    (marg_a.params[:m] - 1.0)*unsafeLogMean(marg_out) -
    (marg_b.params[:m] - 1.0)*unsafeMirroredLogMean(marg_out)
end

# By Stirling's approximation and Monte Carlo summation
function averageEnergy(::Type{Beta}, marg_out::ProbabilityDistribution{Univariate}, marg_a::ProbabilityDistribution{Univariate}, marg_b::ProbabilityDistribution{Univariate})
    unsafeMeanLogMean(marg_a) - unsafeMean(marg_a) + 0.5*(log(2*pi)-unsafeLogMean(marg_a)) +
    unsafeMeanLogMean(marg_b) - unsafeMean(marg_b) + 0.5*(log(2*pi)-unsafeLogMean(marg_b)) -
    (unsafeMean(marg_a)-1)*unsafeLogMean(marg_out) -
    (unsafeMean(marg_b)-1)*unsafeMirroredLogMean(marg_out) -
    sum(loggamma.(sample(marg_a,default_n_samples).+sample(marg_b,default_n_samples)))/default_n_samples
end


#######################################################################
function test_run_bpca(σ, seed, S, maxiter)
#######################################################################

    x, Σ = simulatedatapca(σ, seed)#
    #x, Σ = realresampleddataforpca()#

    posterior = bpca(x', Σ'; maxiter=maxiter, S=S)

    figure(1)

    cla()

    plot3D(x[:,1], x[:,2], x[:,3], "bo", alpha=0.3)

    for i in 1:30

        D = size(x, 2)

        param = rand(posterior)

        W, μ = param[1:D], param[D+1:2*D]

        project(x) = ((W'*W) \ (W'*(x - μ)))[1]

        fwd(z::Real) = vec(W*z + μ)

        z = sort([project(x[i,:]) for i=1:size(x,1)])

        zgrid = collect(LinRange(minimum(z), maximum(z), 100))

        X = reduce(hcat, fwd.(zgrid))'

        plot3D(X[:,1], X[:,2], X[:,3], "-")

    end

    return posterior

end


@testset "more $N" for (N, FR, Ti) = (("FastQ32", FastQ32, Int32), ("FastQ64", FastQ64, Int64))

    @testset "constructors $N" begin
        @test_throws DivideError FR(1, 0)
        @test_throws DomainError FR(Ti(1), typemin(Ti))
        @test_throws DomainError FR(1, typemin(Ti))
        mx = FR(typemin(Ti))
        @test_throws DivideError inv(mx)
        @test FR(typemin(Ti) + 10000) - FR(10000) == mx
        my = FR(typemin(Ti) + 10000) - FR(10000)
        @test_throws DivideError Ti(1) / my
    end

    @testset "avoid overflow during add/subtract $N" begin
        x =  div(typemax(Ti),6)
        @test FR(17, 2x) + FR(17, 3x) == FR(85, 6x)
        @test FR(17, 2x) - FR(17, 3x) == FR(17, 6x)
    end

    @testset "constructors1 $N" begin
        @test FastRational{Ti}(Ti(2),Ti(4),Val(true)).num == 2
        @test FastRational{Ti}(Ti(2),Ti(4),Val(true)).den == 4
        @test FastRational{Ti}(2,4).num == 1
        @test FastRational{Ti}(2,4).den == 2
        @test FR((2,4)).num == 1
        @test FR((2,4)).den == 2
    end

    @testset "convert from Rational $N" begin
        @test FR(1//2) == FR(1, 2)
        @test FR((2, 4)) == FR(1, 2)
    end

    @testset "inverse matrix $N" begin
        B = FR.([(1,2) (1,3); (1,3) (1,4)])
        @test inv(B) == FR.([(18,1) (-24,1); (-24,1) (36,1)])
    end

end


export clustertree

#
# clustertree partitions a collection x by maximizing distances normalized by the
# minimal diameter along coordinate axes in 2D. It uses complex numbers to represent
# points in the plane R². The Admissibility condition for low-rank approximants to
# asymptotically smooth kernels is:
#
# Adm_η(σ×τ) = min{diam(Γ_σ),diam(Γ_τ)} ≤ 2η dist(Γ_σ,Γ_τ),
#
# and clustertree creates partitions that minimize η, the strength of the admissibility.
#

clustertree(x) = x
clustertree(sp::PiecewiseSpace) = Space(clustertree(domain(sp)))
clustertree(d::UnionDomain) = clustertree(d.domains)

clustertree(x::Tuple) = clustertree(collect(x))

function clustertree(x::AbstractVector)
    A = weightedadjacency(x)
    c = map(centroid,x)
    clustertree(A,c,x)
end

clustertree(A::AbstractMatrix,c::AbstractVector,x::AbstractVector) = clustertree(A,c,x,1:size(A,1))

for (TYP,HTYP) in ((:Number,:HierarchicalVector),(:Domain,:HierarchicalDomain))
    @eval begin
        function clustertree{T<:$TYP}(A::AbstractMatrix,c::AbstractVector,x::AbstractVector{T},p::AbstractVector)
            i,j = indmaxp(A,p)

            cmax = c[i]-c[j]
            rbar,ibar = abs2(real(cmax)),abs2(imag(cmax))
            rmid,imid = reim((c[i]+c[j])/2)

            (f,mid) = rbar ≥ ibar ? (real,rmid) : (imag,imid)

            np₁,np₂ = 0,0
            for k in p
                f(c[k]) < mid ? np₁+=1 : np₂+=1
            end
            p₁,p₂ = zeros(Int,np₁),zeros(Int,np₂)
            kp₁,kp₂ = 1,1
            for k in p
                f(c[k]) < mid ? (p₁[kp₁] = k; kp₁+=1) : (p₂[kp₂] = k; kp₂+=1)
            end
            np₁ == 0 && (push!(p₁,p₂[1]); shift!(p₂); np₁+=1; np₂-=1)
            np₂ == 0 && (push!(p₂,p₁[1]); shift!(p₁); np₁-=1; np₂+=1)

            if np₁ == 1 && np₂ == 1
                return $HTYP((x[p₁[1]],x[p₂[1]]))
            elseif np₁ == 1
                return $HTYP((x[p₁[1]],clustertree(A,c,x,p₂)))
            elseif np₂ == 1
                return $HTYP((clustertree(A,c,x,p₁),x[p₂[1]]))
            else
                return $HTYP((clustertree(A,c,x,p₁),clustertree(A,c,x,p₂)))
            end
        end
    end
end

#
# The weighted adjacency matrix computes all the possible distances normalized by the
# minimum of the diameters of the sets. This is related to the Admissibility condition.
#

function weightedadjacency(x::AbstractVector)
    n = length(x)
    A = zeros(real(mapreduce(eltype,promote_type,x)),n,n)
    for j=1:n,i=1:j
        @inbounds A[i,j] = dist2(x[i],x[j])/min(diam2(x[i]),diam2(x[j]))
    end
    for j=1:n,i=1:j-1
        @inbounds A[j,i] = A[i,j]
    end
    A
end

# find the maximum of A over the indices p without allocating A[p,p].

function findmaxp(a,p)
    if isempty(a)
        throw(ArgumentError("collection must be non-empty"))
    end
    m = a[p[1],p[1]]
    mij = p[1],p[1]
    for i in p,j in p
        aij = a[i,j]
        if aij > m || m!=m
            m = aij
            mij = i,j
        end
    end
    return (m, mij)
end
indmaxp(a,p) = findmaxp(a,p)[2]