module TestModels

    using LinearAlgebra

    export p_fun,
        model,
        set_locations,
        set_indices

    export HyperCube,
        Polytope,
        HyperSphere,
        OddShape

    """
        model(parms, args...; kwargs...)

    Model function for test models. It is an identity function.

    # Arguments

    - `parms`: a proposed location

    """
    model(parms, args...; kwargs...) = parms

    """
        HyperCube

    A hypercube oject divided into partitions.

    # Field

    - `p_bounds`: a vector of boundaries of partitions
    """
    struct HyperCube
        p_bounds::Vector{Float64}
    end

    """
        p_fun(location, hypercube::HyperCube, args...; kwargs...)

    Returns the qualitative pattern associated with a hypercube.

    # Arguments

    - 'location`: location of chain in the hypercube
    - `p_bounds`: boundaries of partitions for each dimension

    """
    function p_fun(location, hypercube::HyperCube, args...; kwargs...)
        p_bounds = hypercube.p_bounds
        nb = length(p_bounds)
        nd = length(location)
        vals = fill(-100, nd)
        for j in 1:nd
            for i in 1:(nb-1)
                if (location[j] ≥ p_bounds[i]) && (location[j] ≤ p_bounds[i+1])
                    vals[j] = i
                    continue
                end
            end
        end
        return vals
    end

    """
        Polytope

    A polytope object containing centroid locations for each partition

    # Fields

    - `location`: location of centroid for each partition
    """
    struct Polytope
        location::Vector{Float64}
    end

    """
        p_fun(location, points::Vector{Polytope}, args...; kwargs...)

    Returns the qualitative pattern associated with a polytope.

    # Arguments

    - 'location`: location of chain in the hypercube
    - `points`: a vector of polytopes containing locations

    """
    function p_fun(location, points::Vector{Polytope}, args...; kwargs...)
        distances = map(p -> norm(location .- p.location), points)
        _,idx = findmin(distances)
        return idx
    end

    """
        HyperSphere

    A hypersphere object with arbitrary dimensionality.

    # Fields

    - `location`: a vector representing the centroid of the hypersphere
    - `radius`: the radius of the hypersphere
    """
    mutable struct HyperSphere
        location::Vector{Float64}
        radius::Float64
    end

    function HyperSphere(;location, radius)
        return HyperSphere(location, radius)
    end

    function in_contact(v1::HyperSphere, v2::HyperSphere)
        dist = norm(v1.location - v2.location)
        return dist ≤ max(v1.radius, v2.radius)
    end

    function in_contact(v1::HyperSphere, proposal::Vector{<:Number})
        dist = norm(v1.location - proposal)
        return dist ≤ v1.radius
    end

    not_valid(v, vs)  = any(x -> in_contact(v, x), vs) || out_of_bounds(v)

    out_of_bounds(v) = any((v.location .- v.radius) .< 0) || any((v.location .+ v.radius) .> 1)

    """
        set_locations(r_dist, n_dims, n_obj)

    Sets the location of hypspheres within a unit hypercube

    # Arguments

    - `r_dist`: a function that samples a value for the radius
    - `n_dims`: the number of dimensions of the hypersphere
    - `n_obj`: the number of hyperspheres

    """
    function set_locations(r_dist, n_dims, n_obj)
        hs = [HyperSphere(;location = rand(n_dims), radius = r_dist())]
        for i in 1:(n_obj-1)
            h = HyperSphere(;location = rand(n_dims), radius = r_dist())
            while not_valid(h, hs)
                h.location = rand(n_dims)
            end
            push!(hs, h)
        end
        return hs
    end

    """
        p_fun(location, hyperspheres::Vector{HyperSphere}, args...; kwargs...)

    Returns the qualitative data pattern for hypspheres embedded in a hypercube. -100 is
    returned if the proposed location is not in a hypsphere. Otherwise, an index of the hypersphere
    in which the proposed location is contained.

    # Arguments

    - `location`: a proposed location in a unit hypercube
    - `hyperspheres`: a vector of `HyperSphere` objects
    """
    function p_fun(location, hyperspheres::Vector{HyperSphere}, args...; kwargs...)
        for (i,h) in enumerate(hyperspheres)
            in_contact(h, location) ? (return i) : nothing
        end
        # invalid area (not in a hypersphere)
        return -100
    end

"""
        OddShape

    An odd shape the is found by (1) initializing a shape at random partition and adding it to set of indices, (2)
    selecting random indices from the set, (3) adding an ajecent index to the set of indices, and repeating 2-3.

    # Fields

    - `indices`: a vector of vectors that represent the location of the odd shape
    - `p_bounds`: the partition boundaries for each dimension
    """
    mutable struct OddShape{T1,T2}
        indices::T1
        p_bounds::T2
        shape_ids::Vector{Int}
        n_shapes::Int
    end

    function OddShape(indices, p_bounds, n_cells::Vector{Int})
        shape_ids = vcat(fill.(1:length(n_cells), n_cells)...)
        n_shapes = length(n_cells)
        return OddShape(indices, p_bounds, shape_ids, n_shapes)
    end

    """
        set_locations(r_dist, n_dims, n_obj)

    Sets the location of `OddShape` within a unit hypercube

    # Arguments

    - `n_dims`: the number of dimensions of the hypersphere
    - `n_part`: number of partitions along a dimension
    - `n_cells`: the number of cells per shape
    """
    function set_indices(n_dims, n_part, n_cells)
        n_shapes = length(n_cells)
        indices = Vector{Vector{Int}}()
        for s in 1:n_shapes
            index = rand(1:n_part, n_dims)
            push!(indices, index)
            for i in 1:(n_cells[s]-1)
                while not_valid(indices, index, n_part)
                    index = propose(indices)
                end
                push!(indices, index)
            end
        end
        return indices
    end

    function not_valid(xs, x, n_part)
        return (x ∈ xs) || any(x .< 1) || any(x .> n_part)
    end

    function propose(indices)
        index = deepcopy(rand(indices))
        i = rand(1:length(index))
        index[i] += rand() ≤ .5 ? 1 : -1
        return index
    end

    """
        p_fun(location, shape::OddShape, args...; kwargs...)

    Returns 1 if location is in OddShape and 0 otherwise.

    # Arguments

    - `location`: a proposed location in a unit hypercube
    - `Oddshape`: an odd contiguous shape
    """
    function p_fun(location, shape::OddShape, args...; kwargs...)
        bounds = shape.p_bounds
        indices = shape.indices
        shape_ids = shape.shape_ids
        n_dims = length(location)
        n_b = length(bounds)
        index = fill(0, n_dims)

        for j in 1:n_dims
            for i in 1:(n_b-1)
                if (location[j] ≥ bounds[i]) && (location[j] ≤ bounds[i+1])
                    index[j] = i
                    continue
                end
            end
        end
        return get_id(index, indices, shape_ids)
    end

    function get_id(index, indices, shape_ids)
        for i in 1:length(indices)
            if indices[i] == index
                return shape_ids[i]
            end
        end
        return 0
    end

end


using Contour, Gadfly, DataFrames

using OneDimensional: goldenSectionSearch, fibSearch

f(x, y) = x^4 + y^4 - 5 * (x * y - x^2 * y^2)
fdx(x, y) = 4x^3 - 5y + 10 * x * y^2
fdy(x, y) = 4y^3 - 5x + 10 * y * x^2
▽(x, y) = [fdx(x, y), fdy(x, y)]
L(x, y, λ) = [x, y] .- (λ .* ▽(x, y))

function simpleGradientDescent(f, ▽, x0, y0, ɛ) # down gradient with exponentital slow step
  callCnt, gradCallCnt, xs, ys = 1, 0, [x0], [y0]

  λ = 0.05
  fprev = f(x0, y0)
  while true
    xk, yk = xs[end], ys[end]
    xnew, ynew = L(xk, yk, λ)
    gradCallCnt += 1
    if xnew < 0 || 1 < xnew || ynew < 0 || 1 < ynew
      λ *= 0.75
      continue
    end
    fnew = f(xnew, ynew)
    callCnt += 1
    if fnew > fprev
      λ *= 0.75
      continue
    end
    fprev = fnew
    push!(xs, xnew)
    push!(ys, ynew)
    if norm([xs[end] - xs[end - 1], ys[end] - ys[end - 1]]) < ɛ
      break
    end
  end
  xs, ys, callCnt, gradCallCnt
end

function fastGradienDescent(f, ▽, x0, y0, ɛ) # fastest descent gradient
  callCnt, gradCallCnt, xl, yl = 1, 0, [x0], [y0]

  left, right = 0, 1
  while true
    x_left, y_left = xl[end], yl[end]
    g = ▽(x_left, y_left)
    gradCallCnt += 1
    ax = [(left - x_left) / -g[1], (right - x_left) / -g[1]]
    ay = [(left - y_left) / -g[2], (right - y_left) / -g[2]]
    sort!(ax)
    sort!(ay)
    a = [max(ax[1], ay[1]), min(ax[2], ay[2])]
    λ, cnt, _, _, _ = fibSearch(arg -> f(x_left - arg * g[1], y_left - arg * g[2]), a[1], a[2], ɛ)
    callCnt += cnt
    xnew, ynew = x_left - λ * g[1], y_left - λ * g[2]
    push!(xl, xnew)
    push!(yl, ynew)
    if norm([xl[end] - xl[end - 1], yl[end] - yl[end - 1]]) < ɛ
      break
    end
  end
  xl, yl, callCnt, gradCallCnt
end

ɛ = 0.0001
x0, y0 = 0.2, 0.8
sgx, sgy, cnt1, gcnt1 = simpleGradientDescent(f, ▽, x0, y0, ɛ)
fgx, fgy, cnt2, gcnt2 = fastGradienDescent(f, ▽, x0, y0, ɛ)

@printf("Обычный градиентный спуск: %d вычислений функции, %d вычислений градиента\n", cnt1, gcnt1)
@printf("Наискорейший спуск: %d вычислений функции, %d вычислений градиента\n", cnt2, gcnt2)

Gadfly.set_default_plot_size(18cm, 18cm)
plot(layer(x=[0:0.01:1], y=[0:0.01:1], z=f, Geom.contour(levels=15)),
     layer(x=sgx, y=sgy, Theme(default_color=color("orange")), Geom.point, Geom.line),
     layer(x=fgx, y=fgy, Theme(default_color=color("red")), Geom.point, Geom.line(preserve_order=true)),
     Guide.xticks(ticks=[0:0.2:1.0]), Guide.yticks(ticks=[0:0.2:1.0]),
     Guide.title("Линии уровня функции x^4 + y^4 - 5(xy - x^2 * y^2)"))


#==============================================================================

          Solving the HJB with a state constraint using
                the explicit method

               Translated from matlab code on Ben Moll's website:
                              http://www.princeton.edu/~moll/HACTproject.htm

==============================================================================#

using LinearAlgebra, SparseArrays, Plots

γ = 2 # parameter from CRRA utility
r = 0.03 # the interest rate
ρ = 0.05 # the discount rate

z1=.1
z2 =.2
z= [z1 z2]

λ1 = 0.02
λ2 = 0.03

λ = [λ1 λ2]

H = 500 # number of points in the grid space
amin=-0.02
amax=2

a = LinRange(amin,amax,H)
da = (amax-amin)/(H-1)

aa = [a a]
zz = ones(H,1)*z

maxit = 20000 # the maximum number of iterations we allow the finite differencing algorithm
crit = 10^(-6) # our critical value

dVf=zeros(H,2)
dVb = zeros(H,2)
c= zeros(H,2)

# Inital Guess
v0=zeros(H,2)
v0[:,1] = (z[1] .+ r.*a).^(1-γ)/(1-γ)/ρ
v0[:,2] = (z[2] .+ r.*a).^(1-γ)/(1-γ)/ρ
global v=v0

dist=[]

# The finite differeing loop

for n in 1:maxit
    global V=v
    # Forward differencing
    dVf[1:H-1,:]=(V[2:H,:]-V[1:H-1,:])/da
    dVf[H,:] = zeros(1,2) # will never be used
    # Backward differencing
    dVb[2:H,:]=(V[2:H,:]-V[1:H-1,:])/da
    dVb[1,:]= (z .+r.*amin).^(-γ)

    # Find consumption and savings with the forward difference
    cf = dVf.^(-1/γ)
    sf = zz +r.*aa -cf

    # Find consumption and savings with the backward difference
    cb= dVb.^(-1/γ)
    sb = zz +r.*aa -cb

    # Find consumption and savings at steady state
    c0 = zz + r.*aa
    dV0 = c0.^(-γ)

    # Now implement the upwind scheme in order to select the best differencing method
    If = sf .> 0
    Ib = sb .< 0
    I0 = (1 .- If - Ib)

    # Make sure the backward difference is using at the grid max
    Ib[H,:] = ones(1,2)
    If[H,:] = zeros(1,2)

    # State constraint at amin is automatically implemented

    global dV_Upwind = dVf.*If + dVb.*Ib + dV0.*I0

    global c = dV_Upwind.^(-1/γ)
    global V_switch = [V[:,2] V[:,1]]

    V_change = c.^(1-γ)/(1-γ) + dV_Upwind.*(zz + r.*aa -c) + ones(H,1)*λ.*(V_switch-V)-ρ.*V

    Δ = .9*da/(findmax(z2 .+r.*a)[1])
    global v = v  + Δ*V_change

    push!(dist, findmax(abs.(V_change))[1])

    if dist[n] < crit
        println("Value Function converged, Iteration=")
        println(n)
        break
    end
end

# Graphs
V_err = c.^(1-γ)/(1-γ) + dV_Upwind.*(zz +r.*aa -c) + ones(H,1)*λ.*(V_switch-V)-ρ.*V
adot = zz + r.*aa -c

plot(dist[:], xlabel="Iteration", ylabel="\$\\lvert V^{n+1}-V^{n}\\rvert\$")

plot(a,V_err, xlabel="k",ylabel="Error in HJB Equation", xlims=(amin,amax), legend=false)

plot(a,V, xlabel="a", ylabel="\$V_{i}(a)\$", xlims=(amin,amax),legend=false)

plot(a,c,xlabel="a", ylabel="\$c_{i}(a)\$", xlims=(amin,amax), legend=false)

plot(a, adot, xlabel="a", ylabel="\$s_{i}(a)\$", xlims=(amin,amax))

# Approximation at the borrowing constraint
u1=(z1+r*amin)^(-γ)
u2 = c[1,2]^(-γ)
u11 = -γ*(z1+r*amin)^(-γ-1)
ν = sqrt(-2*((ρ-r)*u1 + λ1*(u1-u2))/u11)
s_approx = -ν*(a.-amin).^(1/2)

plot(a, adot[:,1], xlabel="a", ylabel="\$s_{i}(a)\$", xlims=(amin,amax), label="\$s_{1}(a)\$")
plot!(a, adot[:,2], label="\$s_{2}(a)\$")
plot!(a, zeros(H,1), line=:dash, label="", color=:black)
plot!(a, s_approx, label="Approximatation")


#=

@authors : Spencer Lyon <spencer.lyon@nyu.edu>, John Stachurski

=#

using QuantEcon
using Distributions

"""
The Lucas asset pricing model --- parameters and grid data
"""
struct LucasTree{TF<:AbstractFloat}
    γ::TF                 # coefficient of risk aversion
    β::TF                 # Discount factor in (0, 1)
    α::TF                 # Correlation coefficient in the shock process
    σ::TF                 # Volatility of shock process
    ϕ::Distribution       # Distribution for shock process
    grid::Vector{TF}      # Grid of points on which to evaluate prices
    shocks::Vector{TF}    # Draws of the shock
    h::Vector{TF}         # The h function represented as a vector
end

"""
Constructor for the Lucas asset pricing model
"""
function LucasTree(;γ::AbstractFloat=2.0,
                β::AbstractFloat=0.95,
                α::AbstractFloat=0.9,
                σ::AbstractFloat=0.1,
                grid_size::Integer=100)

    ϕ = LogNormal(0.0, σ)
    shocks = rand(ϕ, 500)

    # == build a grid with mass around stationary distribution == #
    ssd = σ / sqrt(1 - α^2)
    grid_min, grid_max = exp(-4 * ssd), exp(4 * ssd)
    grid = collect(linspace(grid_min, grid_max, grid_size))

    # == set h(y) = β * int u'(G(y,z)) G(y,z) ϕ(dz) == #
    h = similar(grid)
    for (i, y) in enumerate(grid)
        h[i] = β * mean((y^α .* shocks).^(1 - γ))
    end

    return LucasTree(γ,
                     β,
                     α,
                     σ,
                     ϕ,
                     grid,
                     shocks,
                     h)
end

"""
The approximate Lucas operator, which computes and returns updated function
Tf on the grid points.
"""
function lucas_operator(lt::LucasTree, f::Vector)

    # == unpack names == #
    grid, α, β, h = lt.grid, lt.α, lt.β, lt.h
    z = lt.shocks

    Af = LinInterp(grid, f)

    Tf = [h[i] + β * mean(Af.(grid[i]^α.*z)) for i in 1:length(grid)]
    return Tf
end

"""
Compute the equilibrium price function associated with Lucas tree `lt`
"""
function solve_lucas_model(lt::LucasTree;
                           tol::AbstractFloat=1e-6,
                           max_iter::Integer=500)

    # == simplify notation == #
    grid, γ = lt.grid, lt.γ

    i = 0
    f = zeros(grid)  # Initial guess of f
    error = tol + 1

    while (error > tol) && (i < max_iter)
        f_new = lucas_operator(lt, f)
        error = maximum(abs, f_new - f)
        f = f_new
        i += 1
    end

    # p(y) = f(y) * y ^ γ
    price = f .* grid.^γ

    return price
end


import Base.intersect

complex_empty = Set{Complex{Float64}}()
identical_warn = "Intersection of identical clines returns the empty set"

"""
    intersect(C::Cline,D::Cline)
    C ∩ D

Find the points common to the clines `C` and `D` as a set of complex numbers.

Note that if `C==D` a warning is issued and the emptyset is returned.
"""
function intersect(C::Circle, D::Circle)::Set{Complex{Float64}}
    if C == D
        @warn identical_warn
        return complex_empty
    end

    z = center(C)
    r = radius(C)

    w = center(D)
    s = radius(D)

    d = abs(z - w)

    if d > r + s || d + s < r || d + r < s
        return Set{Complex{Float64}}()
    end

    θ = acos((r^2 + d^2 - s^2) / (2 * r * d))
    u = (w - z) / abs(w - z)
    uu = u * exp(θ * im)
    a = z + uu * r
    uu = u * exp(-θ * im)
    b = z + uu * r
    return Set([a, b])
end

function intersect(L::Line, M::Line)::Set{Complex{Float64}}
    if L == M
        @warn identical_warn
        return complex_empty
    end

    # check if parallel
    m1 = slope(L)
    m2 = slope(M)
    if (isinf(m1) && isinf(m2)) || (abs(m1 - m2) < get_tolerance())
        return complex_empty
    end

    a = L.a
    b = L.b

    ax, ay = reim(a)
    bx, by = reim(b)

    aa = M.a
    bb = M.b

    aax, aay = reim(aa)
    bbx, bby = reim(bb)

    MAT = zeros(2, 2)
    MAT[1, 1] = ax - bx
    MAT[1, 2] = bbx - aax
    MAT[2, 1] = ay - by
    MAT[2, 2] = bby - aay

    RHS = [bbx - bx, bby - by]
    st = MAT \ RHS

    s = st[1]
    z = s * L.a + (1 - s) * L.b
    return Set(z)
end

function intersect(C::Cline, D::Cline)::Set{Complex{Float64}}
    if C == D
        @warn identical_warn
        return complex_empty
    end

    F = LFT(C)  # map C to x-axis
    G = inv(F)
    DD = F(D)  # apply that to D and find where DD crosses the x-axis

    # if the images of D is a circle
    if isa(DD, Circle)
        x, y = reim(center(DD))
        r = radius(DD) + get_tolerance()

        if r < abs(y)
            return complex_empty = Set{Complex{Float64}}()
        end

        δ = sqrt(r^2 - y^2)
        a = G(x + δ)
        b = G(x - δ)
        if abs(a - b) < get_tolerance()  # if the points are too close, we say they're the same
            ab = (a + b) / 2
            return Set{Complex{Float64}}(ab)
        else
            return Set{Complex{Float64}}([a, b])
        end
    end

    # otherwise, DD, the image of D, is a line

    X = Line(0, 1)  # x-axis
    S = X ∩ DD
    return G.(S)

    # x, y = reim(DD.a)
    # xx, yy = reim(DD.b)

    # if abs(y - yy) < get_tolerance()  # DD is a horizontal line
    #     return complex_empty()
    # end

    # if abs(x - xx) < get_tolerance()  # DD is a vertical line
    #     z = G((x + xx) / 2)
    #     return Set{Complex{Float64}}(z)
    # end

    # slope = (y - yy) / (x - xx)
    # δ = y / slope
    # z = G(x - δ)
    # return Set{Complex{Float64}}(z)
end


test = (5764801, 17807724)
puzzle = (8987316, 14681524)
modulus = 20201227
seed = 7

mods = let mods = Vector(undef, modulus - 1)
    mods[1] = seed
    for i in 2:modulus - 1
        mods[i] = (7 * mods[i - 1]) % modulus
    end
    mods
end
@assert mods[end] == 1

function q1(card, door)::Int
    loops = (findfirst(==(card), mods), findfirst(==(door), mods))
    return mods[(*(loops...) - 1) % (modulus - 1) + 1]
end

@assert q1(test...) == 14897079

println("Q1: ", q1(puzzle...))


function penstock(Hn, Vdotn, H, theta; method="Bier",eta_nominal=0.9,pipe_roughness=0.05)
    g,rho,mu=parameters()
    P = eta_nominal*rho*g*Vdotn*Hn/1000;
    D = nominal_diameter(method,P,Hn,Vdotn)
    A = pi*D^2/4
    v = Vdotn/A
    NRe = renoldsnum(rho,v,D,mu)
    f = f_Darcy(NRe, pipe_roughness,D)
    L = H/sind(theta)
    hL = f*L*v^2/(2*g*D)
    hGrad = hL/sqrt(L^2-H^2)
    gradAngle = atan(hGrad)
    return sort((Dict("a. Nominal head"=>Hn,
                "b. Nominal dischare"=>Vdotn,
                "c. Nominal hydraulic efficiency"=>eta_nominal,
                "d. Nominal power output"=>P,
                "e. Design based on"=>method,
                "f. Economical diameter"=>D,
                "g. Cross-section"=>A,
                "h. Height"=>H,
                "i. Length"=>L,
                "j. Hydraulic crosssection"=>A,
                "k. Angle of inclination"=>theta,
                "l. Average velocity"=>v,
                "m. Nominal Reynolds number"=>NRe,
                "n. Darcy's friction factor"=>f,
                "o. Nominal allowed head loss"=>hL,
                "p. Hydraulic gradient"=>hGrad,
                "q. Pipe roughness"=>pipe_roughness,
                "r. Gradient angle"=>gradAngle)))
end

function headraceNsurgetank(Hn, Vdotn, H, theta, Hns; method="Bier",eta_nominal=0.9,pipe_roughness=0.05)
    g,rho,mu=parameters()
    headrace_dict = penstock(Hn, Vdotn, H, theta,method=method,eta_nominal=eta_nominal,pipe_roughness=pipe_roughness)
    # surge tank depends on headrace tunnel so
    n = 1.6 # safety factor for Thoma criteria
    # Acessing data of headrace tunnel from dictionary
    Dt,Lt,At,ft,vt = headrace_dict["f. Economical diameter"],headrace_dict["i. Length"],headrace_dict["g. Cross-section"],headrace_dict["n. Darcy's friction factor"],headrace_dict["l. Average velocity"]
    # calculating headrace tunnel friction factor
    beta = ft*Lt/(2*g*Dt)
    As = n*(Lt*At)/(2*g*beta*(Hn-beta*vt^2))
    Ds = sqrt(4*As/pi)
    Ymax = vt*sqrt((Lt*At)/(g*As))
    Hs = 1.2*(Hns+Ymax)
    vs = Vdotn/As
    surgetank_dict = sort((Dict("s. Diameter of surge tank"=>Ds,
                    "t. Cross-section of surge tank"=>As,
                    "u. Maximum height of water oscillation"=>Hns+Ymax,
                    "v. Height of surge tank"=>Hs,
                    "w. Allowed velocity of water inside surge tank"=>vs)))
    return sort(merge(headrace_dict,surgetank_dict))
end
function f2x1y(x,y)
    return 2*x+1*y
end


function hessian(f::Function, x::Array{T, 1}) where T <: AbstractFloat

  m = length(x)

	e = eps(T)^(1/4)*maximum(abs,[x;one(T)])
	dh = Matrix{Float64}(I, m, m)*e
  hess = Array{T}(undef,m,m)

  for i = 1:m
		hess[i,i] = (-f(x+2*dh[:,i])+16*f(x+dh[:,i])-30*f(x)+16*f(x-dh[:,i])-f(x-2*dh[:,i]))[1,1]/(12*e^2)
  end

    for i = 1:m-1
      for j = i+1:m
			  hess[i,j] = (f(x+dh[:,i]+dh[:,j])+f(x-dh[:,i]-dh[:,j])-f(x-dh[:,i]+dh[:,j])-f(x+dh[:,i]-dh[:,j]))[1,1]/(4*e^2)
        hess[j,i] = hess[i,j]
      end
    end

    return hess

end


# # Trade-off curves
using Random
Random.seed!(1)
m = 25;
n = 10;
A = randn(m, n);
b = randn(m, 1);

#-

using Convex, SCS, LinearAlgebra

gammas = exp10.(range(-4, stop = 2, length = 100));

x_values = zeros(n, length(gammas));
x = Variable(n);
for i in 1:length(gammas)
    cost = sumsquares(A * x - b) + gammas[i] * norm(x, 1)
    problem = minimize(cost, [norm(x, Inf) <= 1])
    solve!(problem, SCS.Optimizer; silent_solver = true)
    x_values[:, i] = evaluate(x)
end

#-

# Plot the regularization path.

using Plots
plot(
    title = "Entries of x vs lambda",
    xaxis = :log,
    xlabel = "lambda",
    ylabel = "x",
)
for i in 1:n
    plot!(gammas, x_values[i, :], label = "x$i")
end
plot!()


"""
    PeriodicKernel(; r::AbstractVector=ones(Float64, 1))

Periodic kernel with parameter `r`.

# Definition

For inputs ``x, x' \\in \\mathbb{R}^d``, the periodic kernel with parameter ``r_i > 0`` is
defined[^DM] as
```math
k(x, x'; r) = \\exp\\bigg(- \\frac{1}{2} \\sum_{i=1}^d \\bigg(\\frac{\\sin\\big(\\pi(x_i - x'_i)\\big)}{r_i}\\bigg)^2\\bigg).
```

[^DM]: D. J. C. MacKay (1998). Introduction to Gaussian Processes.
"""
struct PeriodicKernel{T} <: SimpleKernel
    r::Vector{T}
    function PeriodicKernel(; r::AbstractVector{<:Real}=ones(Float64, 1))
        @check_args(PeriodicKernel, r, all(ri > zero(ri) for ri in r), "r > 0")
        return new{eltype(r)}(r)
    end
end

PeriodicKernel(dims::Int) = PeriodicKernel(Float64, dims)

"""
    PeriodicKernel([T=Float64, dims::Int=1])

Create a [`PeriodicKernel`](@ref) with parameter `r=ones(T, dims)`.
"""
PeriodicKernel(T::DataType, dims::Int=1) = PeriodicKernel(; r=ones(T, dims))

@functor PeriodicKernel

metric(κ::PeriodicKernel) = Sinus(κ.r)

kappa(::PeriodicKernel, d::Real) = exp(-0.5d)

function Base.show(io::IO, κ::PeriodicKernel)
    return print(io, "Periodic Kernel, length(r) = $(length(κ.r))")
end


using ComponentArrays
using DifferentialEquations
using Parameters: @unpack

tspan = (0.0, 20.0)

## Lorenz system
function lorenz!(D, u, p, t; f=0.0)
    @unpack σ, ρ, β = p
    @unpack x, y, z = u

    D.x = σ*(y - x)
    D.y = x*(ρ - z) - y - f
    D.z = x*y - β*z
    return nothing
end

lorenz_p = (σ=10.0, ρ=28.0, β=8/3)
lorenz_ic = ComponentArray(x=0.0, y=0.0, z=0.0)
lorenz_prob = ODEProblem(lorenz!, lorenz_ic, tspan, lorenz_p)

## Lotka-Volterra system
function lotka!(D, u, p, t; f=0.0)
    @unpack α, β, γ, δ = p
    @unpack x, y = u

    D.x =  α*x - β*x*y + f
    D.y = -γ*y + δ*x*y
    return nothing
end

lotka_p = (α=2/3, β=4/3, γ=1.0, δ=1.0)
lotka_ic = ComponentArray(x=1.0, y=1.0)
lotka_prob = ODEProblem(lotka!, lotka_ic, tspan, lotka_p)

## Composed Lorenz and Lotka-Volterra system
function composed!(D, u, p, t)
    c = p.c #coupling parameter
    @unpack lorenz, lotka = u

    lorenz!(D.lorenz, lorenz, p.lorenz, t, f=c*lotka.x)
    lotka!(D.lotka, lotka, p.lotka, t, f=c*lorenz.x)
    return nothing
end

comp_p = (lorenz=lorenz_p, lotka=lotka_p, c=0.01)
comp_ic = ComponentArray(lorenz=lorenz_ic, lotka=lotka_ic)
comp_prob = ODEProblem(composed!, comp_ic, tspan, comp_p)

## Solve problem
# We can solve the composed system...
comp_sol = solve(comp_prob)

# ...or we can unit test one of the component systems
lotka_sol = solve(lotka_prob)


@testset "ModelKit - Symbolic" begin
    @testset "SymEngine" begin
        @test Expression(MathConstants.catalan) isa Expression
        @test Expression(MathConstants.e) isa Expression
        @test Expression(MathConstants.pi) isa Expression
        @test Expression(MathConstants.γ) isa Expression
        @test Expression(Float16(2.1)) isa Expression
        @test Expression(Float32(2.1)) isa Expression
        @test Expression(big(2.1)) isa Expression
        @test Expression(Int32(2)) isa Expression
        @test Expression(Int16(2)) isa Expression
        @test Expression(Int128(2)) isa Expression
        @test Expression(2 // 3) isa Expression
        @test convert(BigFloat, Expression(big(2.1))) == big(2.1)
        @test ModelKit.is_number(Expression(2))
        @test ModelKit.to_number(Expression(big(2.1))) isa BigFloat
        @test ModelKit.to_number(Expression(2 // 3)) isa Rational{Int32}
        @test ModelKit.to_number(Expression(2) // Expression(3)) isa Rational{Int32}
        @test ModelKit.to_number(Expression(big(2)^129)) isa BigInt

        @test BigFloat(Expression(big(2.1))) == big(2.1)
        @test Float64(Expression(2)) == 2.0
        @test Rational{Int32}(Expression(2 // 3)) == 2 // 3
        @test UInt(Expression(2)) == UInt(2)

        @test complex(Expression(1), Expression(2)) == Expression(1) + im * 2
        @test complex(Expression(1), 2) == Expression(1) + im * 2
        @test complex(1, Expression(2)) == Expression(1) + im * 2
        @test +Expression(2) == 2
        @test Expression(HC.DoubleDouble.DoubleF64(2)) == big(2.0)
    end

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

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

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

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

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

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

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

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

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

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

    @testset "Differentiation" begin
        @var x y

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        G = subs(F, x => L)

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

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

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

        @var x[1:3]
        f, c = dense_poly(x, 3, coeff_name = :c)
        g = x[1]^3 + x[2]^3 + x[3]^3 - 1
        gc = coeffs_as_dense_poly(g, x, 3)
        @test subs(f, c => gc) == g
    end

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

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

        T = CompiledSystem(F; optimizations = false)
        F2 = System(T)
        @test F == F2
        @test sprint(show, T) == "Compiled: $show_F"
        @test size(T) == size(F) == (2, 2)

        T2 = CompiledSystem(F; optimizations = true)
        F3 = System(T2)
        @test expand.(F.expressions) == expand.(F3.expressions)

        F4 = System(f, parameters = [b, a])
        @test variables(F4) == [x, y]

        @var x y a b
        f = Any[(x+y)^3+x^2+x+5y+3a, 2*x^2+b]
        F = System(f, [x, y], [b, a])
        @test F isa System
    end

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

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

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

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

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

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

        @test sprint(show, H) == show_H

        H_any = Homotopy(convert(Vector{Any}, h), [x, y, z], t)
        @test H_any isa Homotopy

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

    @testset "Optimizations for rational system" begin
        @var y[1:6] q[1:8]
        y₁, y₂, y₃, y₄, y₅, y₆ = y
        q₁, q₂, q₃, q₄, q₅, q₆, q₇, q₈ = q

        f =
            q₁ / y₁ - q₂ / (-y₁ + y₂) +
            q₅ * y₄ / (y₁ * y₄ - y₃ * y₂) +
            q₈ * y₆ / (y₁ * y₆ - y₂ * y₅)
        F = ModelKit.System([f], y, q)
        I = ModelKit.interpreter(ModelKit.optimize(F))
        u = Expression[0]
        ModelKit.execute!(u, I, y, q, ModelKit.InterpreterCache(Expression, I))

        @test expand(u[1] - f) == 0
    end
end


function dbscan(arr::Array{Float64,2} , ϵ::Number, minPoints::Int)

	nPoints = size(arr,1)
	dists = fill(0.0, nPoints, nPoints)
	connectedPaths = fill(0, nPoints, nPoints)

	seeds = Int[]
	counts = Int[]
	clusters = zeros(Int, nPoints)
	visited = zeros(Bool, nPoints)

	# Make superpixel distance matrix and connectivity matrix
	for i in  1:nPoints
		for j in 1:nPoints
			dists[i,j] = euclidean(arr[i,:],arr[j,:])
		end
	end

	reachable = findall(x->x <= ϵ, dists)
	connectedPaths[reachable] .= 1

	function regionQuery(idx)
		neighbors = findall(x -> x == 1, connectedPaths[:,idx])
		return neighbors
	end

	k = 0

	for pt in 1:nPoints
		if clusters[pt] == 0 && !visited[pt]
			visited[pt] == true
			neighbors = regionQuery(pt)
			if length(neighbors) >= minPoints
				k += 1
				clusters[pt] = k
				cnt = 1
				while !isempty(neighbors)
					q = popfirst!(neighbors)
					if !visited[q]
						visited[q] = true
						qNeighbors = regionQuery(q)
						for x in qNeighbors
							if clusters[x] == 0
								push!(neighbors, x)
							end
						end
					end
					if clusters[q] == 0
						clusters[q] = k
						cnt += 1
					end
				end
				push!(seeds, pt)
				push!(counts, cnt)

			end
		end
	end

	return clusters
end


using Distributions, Random, Statistics, Test
import ThorinDistributions
import DynamicPolynomials, Statistics, StatsBase

####################################
# Previous versions of the code, used now for testing purposes only.
####################################
function Bell_partial_matrix(N,K,x)
    M = zeros(eltype(x .+ 1.0),(N+1,K+1))
    for n in 0:N
        for k in 0:n
            if n == 0 && k == 0
                M[n+1,k+1] = 1
            elseif n == 0 || k == 0
                M[n+1,k+1] = 0
            else
                M[n+1,k+1] = sum([binomial(n-1,i-1)*x[i]*M[n-i+1,k] for i in 1:n-k+1])
            end
        end
    end
    return M
end
function cumulants_from_moments(μ, mu0 = 1)
    # we ned to construct kappa that are the derivatives of the log times mu
    n = length(μ)
    M = Bell_partial_matrix(n-1,n-1,μ)
    log_der_fact = [log(mu0),[factorial(big(i-1)) * (-1)^(i-1) * mu0^(-i) for i in 1:(n-1)]...]
    return M''log_der_fact
end
# According to wikipedia, we could also obtain them by recursion.
# see end of paragraph https://en.wikipedia.org/wiki/Cumulant#Cumulants_and_moments
function cum_from_mom_rec!(κ,μ,mu0=nothing)
    if isnothing(mu0)
        mu0 = μ[1]
        μ = μ[2:end]
    end
    # much faster algorithm !
    κ[1] = log(mu0)
    κ[2] = μ[1] ./ mu0
    for n in 2:(length(μ)-1)
        κ[n+1] = (μ[n] - sum(binomial(big(n-1),big(m-1))*κ[m+1]*μ[n-m] for m in 1:(n-1))) / mu0
    end
    return κ
end
function cum_from_mom_rec(μ;mu0)
    if isnothing(mu0)
        mu0 = μ[1]
        μ = μ[2:end]
    end
    κ = Array{Any}(undef, length(μ)) # needs to be Array{Any} so that the test with polynomials passes...
    cum_from_mom_rec!(κ,μ,mu0)
    return κ
end
function old_thorin_moments(D,t_star,m)
    n = length(D)
    mu = sum(D .^(0:(m+1))' .* exp.(t_star .* D),dims=1)/n
    return [log(mu[1]),(cum_from_mom_rec(mu[2:end],mu0=mu[1])[2:end] ./ factorial.(big.(0:(m-1))))...]
end
function old_resemps_thorin_moments(M,D,t_star,m)
    n = length(D)
    T = eltype(D)
    mu_mat = D .^(0:(m+1))' .* exp.(t_star .* D)
    kappa = zeros(T,(M,m+1))
    facts = [T(1),factorial.(T.(0:(m-1)))...]
    for i in 1:M
        @views kappa[i,:] = cum_from_mom_rec!(kappa[i,:], StatsBase.mean(mu_mat[StatsBase.sample(1:n,n,replace=true),:],dims=1)) ./ facts
    end
    return kappa
end
function cum_from_mom_rec_simplified(μ;mu0)
    # for test only
    if isnothing(mu0)
        mu0 = μ[1]
        μ = μ[2:end]
    end
    η = Array{Any}(undef, length(μ)+1)
    τ = Array{Any}(undef, length(μ)+1)
    η .= [mu0,(μ ./ factorial.(big.(1:length(μ))))...]
    η[2:end] ./= mu0
    τ[1] = log.(mu0)
    τ[2] = η[2]
    for k in 3:length(η)
        τ[k] = (k-1)*η[k]
        for j in 1:(k-2)
            τ[k] -= τ[j+1] * η[k-j]
        end
    end
    κ = τ .* [1,factorial.(big.(0:(length(τ)-2)))...]
    return κ[1:end-1]
end

@testset "ThorinMoments.jl tests" begin

    DynamicPolynomials.@polyvar μ[1:10];
    κ1 = cumulants_from_moments(μ,1);
    κ2 = cum_from_mom_rec(μ,mu0=1);
    κ3 = cum_from_mom_rec_simplified(μ,mu0=1);
    κ4 = cumulants_from_moments(μ,0.5);
    κ5 = cum_from_mom_rec(μ,mu0=0.5);
    κ6 = cum_from_mom_rec_simplified(μ,mu0=0.5);

    n = 100
    D = BigFloat.(rand(LogNormal(),n));

    @testset "thorin_moment_function is alright" begin
        τ = ThorinDistributions.thorin_moments(D,-1,9)
        ηs = zeros(eltype(D),(n,11))
        ThorinDistributions.ηs_from_data!(ηs,D,-1)
        η_mom = Statistics.mean(ηs,dims=1) .* factorial.(big.(0:10))'
        τ1 = cum_from_mom_rec(η_mom,mu0=nothing) ./ factorial.([big(0),big.(0:8)...])
        τ2 = cum_from_mom_rec_simplified(η_mom,mu0=nothing) ./ factorial.([big(0),big.(0:8)...])
        τ3 = cumulants_from_moments(η_mom[2:end],η_mom[1]) ./ factorial.([big(0),big.(0:8)...])

        @test τ ≈ τ1
        @test τ ≈ τ2
        @test τ ≈ τ3
    end

    @testset "firsts cumulants corretness, mu0=1" begin
        @test κ1[1] == 0
        @test κ1[2] == μ[1]
        @test κ1[3] == μ[2] - μ[1]^2
        @test κ1[4] == 2.0μ[1]^3 - 3.0μ[1]μ[2] + μ[3]
        @test κ1[5] ==  -6.0μ[1]^4 + 12.0μ[1]^2*μ[2] - 4.0μ[1]μ[3] - 3.0μ[2]^2 + μ[4]
        @test κ1[6] ==   24.0μ[1]^5 - 60.0μ[1]^3*μ[2] + 20.0μ[1]^2*μ[3] + 30.0μ[1]*μ[2]^2 - 5.0μ[1]*μ[4] - 10.0μ[2]*μ[3] + μ[5]
    end

    @testset "cumulants recursivity" begin
        @test κ1 == κ2
        @test κ4 == κ5
    end

    @testset "η/τ simplification" begin
        @test all(κ3 .≈ κ1)
        @test all(κ3 .≈ κ2)
        @test all(κ6 .≈ κ4)
        @test all(κ6 .≈ κ5)
    end

    @testset "Bell partial matrix" begin
        M1 = [1   0      0       0       0       0       0      0     0   0  0
        0   1      0       0       0       0       0      0     0   0  0
        0   2      1       0       0       0       0      0     0   0  0
        0   3      6       1       0       0       0      0     0   0  0
        0   4     24      12       1       0       0      0     0   0  0
        0   5     80      90      20       1       0      0     0   0  0
        0   6    240     540     240      30       1      0     0   0  0
        0   7    672    2835    2240     525      42      1     0   0  0
        0   8   1792   13608   17920    7000    1008     56     1   0  0
        0   9   4608   61236  129024   78750   18144   1764    72   1  0
        0  10  11520  262440  860160  787500  272160  41160  2880  90  1]
        @test Int.(Bell_partial_matrix(10,10,1:30)) == M1
    end

    @testset "Resemp is OK" begin
        M = 10
        Random.seed!(123)
        x1 = old_resemps_thorin_moments(M,D,-1,10)
        Random.seed!(123)
        x2 = ThorinDistributions.resemps_thorin_moments(M,D,-1,10)
        @test x1 ≈ x2
        Random.seed!(123)
        x3 = ThorinDistributions.resemps_thorin_moments(M,[D 2D]',-1,10)
        @test x1 ≈ x3[:,1,:]

    end
end


# Canonical Correlation Analysis

#### CCA Type

struct CCA{T<:Real}
    xmean::Vector{T}  # sample mean of X: of length dx (can be empty)
    ymean::Vector{T}  # sample mean of Y: of length dy (can be empty)
    xproj::Matrix{T}  # projection matrix for X, of size (dx, p)
    yproj::Matrix{T}  # projection matrix for Y, of size (dy, p)
    corrs::Vector{T}  # correlations, of length p

    function CCA(xm::Vector{T},
                 ym::Vector{T},
                 xp::Matrix{T},
                 yp::Matrix{T},
                 crs::Vector{T}) where T<:Real

        dx, px = size(xp)
        dy, py = size(yp)

        isempty(xm) || length(xm) == dx ||
            throw(DimensionMismatch("Incorrect length of xmean."))

        isempty(ym) || length(ym) == dy ||
            throw(DimensionMismatch("Incorrect length of ymean."))

        px == py ||
            throw(DimensionMismatch("xproj and yproj should have the same number of columns."))

        length(crs) == px ||
            throw(DimensionMismatch("Incorrect length of corrs."))

        new{T}(xm, ym, xp, yp, crs)
    end
end

## properties

xindim(M::CCA) = size(M.xproj, 1)
yindim(M::CCA) = size(M.yproj, 1)
outdim(M::CCA) = size(M.xproj, 2)

xmean(M::CCA) = fullmean(xindim(M), M.xmean)
ymean(M::CCA) = fullmean(yindim(M), M.ymean)

xprojection(M::CCA) = M.xproj
yprojection(M::CCA) = M.yproj

correlations(M::CCA) = M.corrs

## use

xtransform(M::CCA, X::AbstractVecOrMat{<:Real}) = transpose(M.xproj) * centralize(X, M.xmean)
ytransform(M::CCA, Y::AbstractVecOrMat{<:Real}) = transpose(M.yproj) * centralize(Y, M.ymean)

## show & dump

function show(io::IO, M::CCA)
    print(io, "CCA (xindim = $(xindim(M)), yindim = $(yindim(M)), outdim = $(outdim(M)))")
end

function dump(io::IO, M::CCA)
    show(io, M)
    println(io)
    println(io, "correlations: ")
    printvecln(io, correlations(M))
    println(io, "xmean:")
    printvecln(io, xmean(M))
    println(io, "ymean:")
    printvecln(io, ymean(M))
    println(io, "xprojection:")
    printarrln(io, xprojection(M))
    println(io, "yprojection:")
    printarrln(io, yprojection(M))
end

#### Perform CCA on data

## ccacov

function ccacov(Cxx::DenseMatrix{T},
                Cyy::DenseMatrix{T},
                Cxy::DenseMatrix{T},
                xmean::Vector{T},
                ymean::Vector{T},
                p::Int) where T<:Real

    # argument checking
    dx, dx2 = size(Cxx)
    dy, dy2 = size(Cyy)
    dx == dx2 || error("Cxx must be a square matrix.")
    dy == dy2 || error("Cyy must be a square matrix.")
    size(Cxy) == (dx, dy) ||
        throw(DimensionMismatch("size(Cxy) should be equal to (dx, dy)"))

    isempty(xmean) || length(xmean) == dx ||
        throw(DimensionMismatch("Incorrect length of xmean."))

    isempty(ymean) || length(ymean) == dy ||
        throw(DimensionMismatch("Incorrect length of ymean."))

    1 <= p <= min(dx, dy) ||
        throw(DimensionMismatch(""))

    _ccacov(Cxx, Cyy, Cxy, xmean, ymean, p)
end

function _ccacov(Cxx, Cyy, Cxy, xmean, ymean, p::Int)
    dx = size(Cxx, 1)
    dy = size(Cyy, 1)

    # solve Px and Py

    if dx <= dy
        # solve Px: (Cxy * inv(Cyy) * Cyx) Px = λ Cxx * Px
        # compute Py: inv(Cyy) * Cyx * Px

        G = cholesky(Cyy) \ Cxy'
        Ex = eigen(Symmetric(Cxy * G), Symmetric(Cxx))
        ord = sortperm(Ex.values; rev=true)
        vx, Px = extract_kv(Ex, ord, p)
        Py = qnormalize!(G * Px, Cyy)
    else
        # solve Py: (Cyx * inv(Cxx) * Cxy) Py = λ Cyy Py
        # compute Px: inv(Cx) * Cxy * Py

        H = cholesky(Cxx) \ Cxy
        Ey = eigen(Symmetric(Cxy'H), Symmetric(Cyy))
        ord = sortperm(Ey.values; rev=true)
        vy, Py = extract_kv(Ey, ord, p)
        Px = qnormalize!(H * Py, Cxx)
    end

    # compute correlations
    # Note: Px' * Cxx * Px == I
    #       Py' * Cyy * Py == I
    crs = coldot(Px, Cxy * Py)

    # construct CCA model
    CCA(xmean, ymean, Px, Py, crs)
end

## ccasvd

function ccasvd(Zx::DenseMatrix{T},
                Zy::DenseMatrix{T},
                xmean::Vector{T},
                ymean::Vector{T},
                p::Int) where T<:Real

    dx, n = size(Zx)
    dy, n2 = size(Zy)
    n == n2 ||
        throw(DimensionMismatch("Zx and Zy must have the same number of columns."))

    isempty(xmean) || length(xmean) == dx ||
        throw(DimensionMismatch("Incorrect length of xmean."))

    isempty(ymean) || length(ymean) == dy ||
        throw(DimensionMismatch("Incorrect length of ymean."))

    1 <= p <= min(dx, dy) ||
        throw(DimensionMismatch(""))

    _ccasvd(Zx, Zy, xmean, ymean, p)
end

# The implementation is partly based on:
#
#   David Weenink.
#   Canonical Correlation Analysis.
#   Institute of Phonetic Sciences, Univ. of Amsterdam,
#   Proceedings 25 (2003), 81-99.
#
#   Note: in this paper, each row is considered as an observation.
#   The algorithm is adpated to the column-major format here.
#
function _ccasvd(Zx::DenseMatrix{T}, Zy::DenseMatrix{T}, xmean::Vector{T}, ymean::Vector{T}, p::Int) where T<:Real
    # svd factorization of Z

    n = size(Zx, 2)

    # svd decomposition
    Sx = svd(Zx)
    Sy = svd(Zy)
    S = svd!(Sx.Vt * transpose(Sy.Vt)) # svd of Vx * Vy'

    # compute Px and Py
    ord = sortperm(S.S; rev=true)
    si = ord[1:p]
    Px = rmul!(Sx.U, Diagonal(1.0 ./ Sx.S)) * S.U[:, si]
    Py = rmul!(Sy.U, Diagonal(1.0 ./ Sy.S)) * S.V[:, si]

    # scale so that Px' * Cxx * Py == I
    #           and Py' * Cyy * Py == I,
    #
    # with Cxx = Zx * Zx' / (n - 1)
    #      Cyy = Zy * Zy' / (n - 1)
    #
    rmul!(Px, sqrt(n-1))
    rmul!(Py, sqrt(n-1))

    # compute correlations
    crs = rmul!(coldot(Zx'Px, Zy'Py), one(T)/(n-1))

    # construct CCA model
    CCA(xmean, ymean, Px, Py, crs)
end

## interface functions

function fit(::Type{CCA}, X::AbstractMatrix{T}, Y::AbstractMatrix{T};
             outdim::Int=min(min(size(X)...), min(size(Y)...)),
             method::Symbol=:svd,
             xmean=nothing,
             ymean=nothing) where T<:Real

    dx, n = size(X)
    dy, n2 = size(Y)

    n2 == n ||
        throw(DimensionMismatch("X and Y should have the same number of columns."))

    (n >= dx && n >= dy) ||
        @warn("CCA would be numerically instable when n < dx or n < dy.")

    xmv = preprocess_mean(X, xmean)
    ymv = preprocess_mean(Y, ymean)

    Zx = centralize(X, xmv)
    Zy = centralize(Y, ymv)

    if method == :cov
        Cxx = rmul!(Zx*transpose(Zx), inv(n - 1))
        Cyy = rmul!(Zy*transpose(Zy), inv(n - 1))
        Cxy = rmul!(Zx*transpose(Zy), inv(n - 1))
        M = ccacov(Cxx, Cyy, Cxy, xmv, ymv, outdim)
    elseif method == :svd
        M = ccasvd(Zx, Zy, xmv, ymv, outdim)
    else
        error("Invalid method name $(method)")
    end

    return M::CCA
end


@testset "Composition" begin
    @testset "total degree" begin
        @polyvar a b c x y z u v
        e = [u + 1, v - 2]
        f = [a * b - 2, a * c - 1]
        g = [x + y, y + 3, x + 2]
        res = solve(e ∘ f ∘ g)
        @test nnonsingular(res) == 2
        @test nnonsingular(solve(e ∘ f ∘ g, system_scaling = nothing)) == 2
        @test nnonsingular(solve(e ∘ f ∘ g, system_scaling = :equations_and_variables)) == 2

        res = solve(e ∘ f ∘ g, system = SPSystem)
        @test nnonsingular(res) == 2
    end

    @testset "polyhedral" begin
        @polyvar x y u v
        result = solve(
            [2y + 3 * y^2 - x * y^3, x + 4 * x^2 - 2 * x^3 * y] ∘ [u, v];
            seed = 32241, start_system = :polyhedral,
        )
        @test nsolutions(result) == 6
    end

    @testset "parameter homotopy" begin
        @polyvar p q a b c x y z u v

        f = [a * b - 2, a * c - 1]
        g = [x + y, y + 3, x + 2]
        res = solve(f ∘ g; system = FPSystem, threading = false)
        @test nnonsingular(res) == 2
        # parameters at the end
        f2 = [a * b - q, a * c - p]
        g = [x + y, y + 3, x + 2]
        r = solve(
            f2 ∘ g,
            solutions(res);
            parameters = [p, q], p₁ = [1, 2], p₀ = [2, 3], threading = false,
        )
        @test nnonsingular(r) == 2

        # parameters at the beginning
        f = [a * b - 2, a * c - 1]
        g2 = [x + y, y + u, x + v]
        r = solve(
            f ∘ g2,
            solutions(res);
            parameters = [u, v], p₁ = [3, 2], p₀ = [-2, 3], threading = false,
        )
        @test nnonsingular(r) == 2

        # parameter in the middle
        e = [u + 1, v - 2]
        res2 = solve(e ∘ f ∘ g; system = SPSystem)
        f2 = [a * b - q, a * c - p]
        r = solve(
            e ∘ f2 ∘ g,
            solutions(res2);
            parameters = [p, q], p₁ = [1, 2], p₀ = [2, 3], threading = false,
        )
        @test nnonsingular(r) == 2
    end
end


# using Distributions, Bijectors

# Define a deterministic distribution
struct Determin{T<:Real} <: ContinuousUnivariateDistribution
  val::T
end

Distributions.rand(rng::AbstractRNG, d::Determin) = d.val
Distributions.logpdf(d::Determin, x::T) where T<:Real = zero(x)

Bijectors.bijector(d::Determin) = Identity{0}() # <= `0` indicates 0-dimensional, i.e. univariate

# # useful functions
function compute_stats(chain::Chains, name::Symbol)
    # x = Array(x)
    # x = DataFrame(x)
    x = Array(group(chain, name))
    mean_x = mean(x, dims=1)
    xlb = mean_x - mapslices(x -> quantile(x, 0.025), x, dims=1)
    xub = mean_x - mapslices(x -> quantile(x, 0.975), x, dims=1)
    return (μ = mean_x, lb = xlb, ub = xub)
end

function compute_stats(x::Array)
    mean_x = mean(x, dims=1)
    xlb = mean_x - mapslices(x -> quantile(x, 0.025), x, dims=1)
    xub = mean_x - mapslices(x -> quantile(x, 0.975), x, dims=1)
    return (μ = mean_x, lb = xlb, ub = xub)
end

function get_omega(chain::Chains)
    w1 = Array(group(chain, :ω1))
    w2 = Array(group(chain, :ω2))
    w = w1 .+ w2
    return (w = w, w1 = w1, w2 = w2)
end

function summarize_omega(chain::Chains)
    w1_array = Array(group(chain, :ω1))
    w2_array = Array(group(chain, :ω2))
    w_array = Array(group(chain, :ω))

    w = compute_stats(w_array)
    w1 = compute_stats(w1_array)
    w2 = compute_stats(w2_array)
    return (w = w, w1 = w1, w2 = w2)
end

invlogit(x) = 1/(1 + exp(-x))

function beta_params_mode(mode, k)
    #= http://doingbayesiandataanalysis.blogspot.com/2012/06/beta-distribution-parameterized-by-mode.html
    mode = (α - 1)/(α + β - 2)
    k = a + b # sample size
    =#
    @assert 0 < mode < 1
    @assert k > 2

    α = mode * (k-2) + 1
    β = (1-mode) * (k-2) + 1
    return α, β
end

function beta_params(μ, σ)
    @assert 0 < μ < 1
    @assert σ > 0

    α = ((1-μ)/σ^2 - (1/μ)) * μ^2
    β = α * (1 / μ - 1)
    return α, β
end


"""
    Base.rand(randomwalk::RandomWalk{<:AbstractArray{<:DataType}, <:AbstractArray{<:Real},
                                     <:Sampleable}, ntimesteps::Integer) -> Matrix{Int}

Simulate `randomwalk` for `ntimesteps` time-steps over an integer lattice, returning one
sample path (i.e. trajectory) of length `ntimesteps`.

The time steps represented in the sample path are `0, 1, ..., ntimesteps-1`, for we start
with an initial state.

##### Complexity
- Time complexity: `O(n)`.
"""
function Base.rand(randomwalk::RandomWalk{<:AbstractArray{<:DataType},
                                          <:AbstractArray{<:Real},
                                          <:Sampleable}, ntimesteps::Integer)
    # allocate memory
    realizationmatrix = Matrix{Int}(undef, ntimesteps,
                                                   length(randomwalk.initialstate))
    realizationmatrix[1,:] = randomwalk.initialstate
    # draw a random step
    change = rand(randomwalk.stepsampler)
    for step in 2:ntimesteps
        realizationmatrix[step,:] = realizationmatrix[step-1,:] + change
        change = rand(randomwalk.stepsampler)
    end
    return realizationmatrix
end

"""
    Base.rand(randomwalk::RandomWalk{<:DataType, <:Real, <:Sampleable,
              ntimesteps::Integer) -> Vector{Int}

Simulate a one-dimensional `randomwalk` for `ntimesteps` time-steps, returning one sample
path (i.e. trajectory) of length `ntimesteps`.

The time steps represented in the sample path are `0, 1, ..., ntimesteps-1`, for we start
with an initial state.

##### Complexity
- Time complexity: `O(n)`.
"""
function Base.rand(randomwalk::RandomWalk{<:DataType, <:Real, <:Sampleable},
                   ntimesteps::Integer)
    # allocate memory
    realizationvec = Vector{eltype(randomwalk)}(undef, ntimesteps)
    realizationvec[1] = randomwalk.initialstate
    # draw a random step
    change = rand(randomwalk.stepsampler)[1]
    for step in 2:ntimesteps
        realizationvec[step] = realizationvec[step-1] + change
        change = rand(randomwalk.stepsampler)[1]
    end
    return realizationvec
end

"""
    Base.rand(randomwalk::RandomWalk{<:AbstractGraph, <:Integer, <:Sampleable},
              ntimesteps::Integer) -> Vector{Int}

Simulate `randomwalk` for `ntimesteps` time-steps over a graph, returning one sample path
(i.e. trajectory) of length `ntimesteps`.

The time steps represented in the sample path are `0, 1, ..., ntimesteps-1`, for we start
with an initial state.

##### Complexity
- Time complexity: `O(n)`.
"""
function Base.rand(randomwalk::RandomWalk{<:AbstractGraph, <:Integer, <:Sampleable},
                   ntimesteps::Integer)
    # allocate memory
    realizationvec = Vector{eltype(randomwalk)}(undef, ntimesteps)
    realizationvec[1] = randomwalk.initialstate
    for step in 2:ntimesteps
        # draw a neighbor at random
        realizationvec[step] = NeighborSampler(randomwalk.statespace,
                                               realizationvec[step-1]) |> rand
    end
    return realizationvec
end


#!jl # # EFTfitter.jl - BLUE Example
#jl # EFTfitter.jl - BLUE Example

#~When using multiple measurements of a single observable and a uniform prior for
#~the parameter representing the combined value, the combination of measurements performed with EFTfitter.jl
#~yields the same results as the Best Linear Unbiased Estimator (BLUE) method.

#~Here, we demonstrate this by using the examples of the paper
#~"How to combine correlated estimates of a single physical quantity"
#~by L. Lyons, D. Gibaut and P. Clifford (https://www.sciencedirect.com/science/article/pii/0168900288900186).
#~All numbers are taken from the example on charm particle lifetime experiments in section 5.
#~A factor of 10^13 is applied for convenience.

using EFTfitter
using BAT
using IntervalSets
using Statistics
using StatsBase
using LinearAlgebra
using Plots

#~We need one parameter for the best estimator and choose
#~a uniform distribution in the range 8 to 14 as prior:
parameters = BAT.NamedTupleDist(
    τ = 8..14,
)

#~When combining multiple measurements of the same observable,
#~only a function returning the combination parameter is needed:
estimator(params) = params.τ

#~In Eq. (17') of the reference paper the following covariance matrix is given:
covariance = [2.74 1.15 0.86 1.31;
              1.15 1.67 0.82 1.32;
              0.86 0.82 2.12 1.05;
              1.31 1.32 1.05 2.93]

#~For using this in EFTfitter.jl, we first need to convert the covariance matrix
#~into a correlation matrix and the corresponding uncertainty values:
corr, unc = EFTfitter.cov_to_cor(covariance)

measurements = (
    τ1 = Measurement(estimator,  9.5, uncertainties = (stat=unc[1],) ),
    τ2 = Measurement(estimator, 11.9, uncertainties = (stat=unc[2],) ),
    τ3 = Measurement(estimator, 11.1, uncertainties = (stat=unc[3],) ),
    τ4 = Measurement(estimator,  8.9, uncertainties = (stat=unc[4],) ),
)

correlations = (
    stat = Correlation(corr),
)

#~construct an `EFTfitterModel`:
model = EFTfitterModel(parameters, measurements, correlations)
posterior = PosteriorDensity(model);

#~sample the posterior with BAT.jl:
algorithm = MCMCSampling(mcalg =MetropolisHastings(), nsteps = 10^6, nchains = 4)
samples = bat_sample(posterior, algorithm).result

#~plot the posterior distribution for the combination parameter τ:
plot(samples, :τ, mean=true)
#md # ![blue plots](plots/plot_blue.png)

#~print numerical results of combination:
println("Mode: $(mode(samples).τ)")
println("Mean: $(mean(samples).τ) ± $(std(samples).τ)")
#md ```
#md Mode: 11.15985
#md Mean: 11.15471 ± 0.80180
#md ```

#!jl # ### Comparison with BLUE method
#jl # comparison with BLUE method:
blue = BLUE(model)
println("BLUE: $(blue.value) ± $(blue.unc)")
println("BLUE weights: $(blue.weights)")
#md ```
#md BLUE: 11.15983 ± 1.28604
#md BLUE weights: [0.145, 0.470, 0.347, 0.038]
#md ```


export birdcageSensitivity

"""
    birdcageSensitivity(N::Int64, ncoils::Int64, relative_radius::Float64)

Computes the sensitivity maps for each coils that are arranged
in a birdcage manner.
"""
function birdcageSensitivity(N::Int64,
                             ncoils::Int64,
                             relative_radius::Float64)

  out = zeros(ComplexF64, N, N, 1, ncoils)
  for c=0:ncoils-1
    coilx = relative_radius*cos(c*(2*pi/ncoils))
    coily = relative_radius*sin(c*(2*pi/ncoils))
    coil_phase = -c*(2*pi/ncoils)

    for y=0:N-1
      y_co = (y - (N/2))/(N/2) - coily
      for x=0:N-1
        x_co = (x - (N/2))/(N/2) - coilx
        rr = sqrt(x_co^2+y_co^2)
        phi = atan(x_co, -y_co) + coil_phase
        out[x+1,y+1, 1, c+1] = 1/(rr) * exp(1im*phi)
      end
    end
  end
 return out
end


function generate_synthetic_data(n, param_frac, sigma; signal_to_noise=10,
    fixed_rank_sparse=false, fixed_rank=1, fixed_sparse=0)
    """
    This function samples synthetic data by adding noise to the sum of a
    random low rank matrix and a random sparse matrix.

    :param n: The row and column dimension of the data to be sampled (Int64).
    :param param_frac: Parameter that controls the rank and sparsity of the
                       sampled low rank and sparse matrix when
                       fixed_rank_sparse=false (Float64).
    :param sigma: Parameter that controls the absolute magnitude of the noise
                  (Float64).
    :param signal_to_noise: Parameter that controls the signal to noise ratio of
                            the sampled data (Float64).
    :param fixed_rank_sparse: If true, use fixed_rank and fixed_sparse as the
                              rank and sparsity respectively of the low rank
                              matrix and the sparse matrix to be sampled (Bool).
    :param fixed_rank: Rank of the low rank matrix to be sampled when
                       fixed_rank_sparse=true.
    :param fixed_sparse: Sparsity of the sparse matrix to be sampled when
                         fixed_rank_sparse=true

    :return: This function returns 5 values:
             1) The sampled n x n data matrix.
             2) The sampled n x n low rank matrix.
             3) The sampled n x n sparse matrix.
             4) The sparsity level of the sparse matrix.
             5) The rank of the low rank matrix.
    """

    if fixed_rank_sparse
        k_sparse = fixed_sparse
        k_rank = fixed_rank
    else
        k_sparse = Int64(floor(param_frac * n^2))
        k_rank = Int64(floor(param_frac * n))
    end

    # Sample a rank k_rank matrix
    d = Normal(0, signal_to_noise * sigma / n^0.5)
    X = rand(d, (n, k_rank))
    Y = rand(d, (n, k_rank))
    L_0 = X * Y'

    # Sample a sparsity k_sparse matrix
    d = Uniform(-5, 5)
    E = rand(d, (n, n))
    S_0 = zeros(n, n)

    indexes = []
    for i = 1:n
        for j = 1:n
            append!(indexes, [(i, j)])
        end
    end

    select_indexes = sample(indexes, k_sparse, replace=false)
    for index in select_indexes
        S_0[index[1], index[2]] = E[index[1], index[2]]
    end

    data = L_0 + S_0

    # Add noise to the sum of the sampled low rank and sparse matrices
    d = Normal(0, sigma^2)
    noise = rand(d, (n, n))
    data = data + noise

    return data, L_0, S_0, k_sparse, k_rank

end;

function generate_synthetic_data_symmetric(n, param_frac, sigma;
    signal_to_noise=10, fixed_rank_sparse=false, fixed_rank=1, fixed_sparse=0)
    """
    This function samples symmetric synthetic data by adding symmetric noise to
    the sum of a random symmetric low rank matrix and a random symmetric sparse
    matrix.

    :param n: The row and column dimension of the data to be sampled (Int64).
    :param param_frac: Parameter that controls the rank and sparsity of the
                       sampled low rank and sparse matrix when
                       fixed_rank_sparse=false (Float64).
    :param sigma: Parameter that controls the absolute magnitude of the noise
                  (Float64).
    :param signal_to_noise: Parameter that controls the signal to noise ratio of
                            the sampled data (Float64).
    :param fixed_rank_sparse: If true, use fixed_rank and fixed_sparse as the
                              rank and sparsity respectively of the low rank
                              matrix and the sparse matrix to be sampled (Bool).
    :param fixed_rank: Rank of the low rank matrix to be sampled when
                       fixed_rank_sparse=true.
    :param fixed_sparse: Sparsity of the sparse matrix to be sampled when
                         fixed_rank_sparse=true

    :return: This function returns 5 values:
             1) The sampled n-by-n data matrix.
             2) The sampled n-by-n low rank matrix.
             3) The sampled n-by-n sparse matrix.
             4) The sparsity level of the sparse matrix.
             5) The rank of the low rank matrix.
    """

    if fixed_rank_sparse
        k_sparse = fixed_sparse
        k_rank = fixed_rank
    else
        k_sparse = Int64(floor(param_frac * n^2))
        k_rank = Int64(floor(param_frac * n))
    end

    # Sample a rank k_rank symmetric matrix
    d = Normal(0, signal_to_noise * sigma / n^0.5)
    X = rand(d, (n, k_rank))
    L_0 = X * X'

    # Sample a sparsity k_sparse symmetric matrix
    d = Uniform(-5, 5)
    E = rand(d, (n, n))
    S_0 = zeros(n, n)

    indexes = []
    for i = 1:(n-1)
        for j = (i+1):n
            append!(indexes, [(i, j)])
        end
    end

    select_indexes = sample(indexes, Int64(floor(k_sparse/2)), replace=false)
    for index in select_indexes
        S_0[index[1], index[2]] = E[index[1], index[2]]
        S_0[index[2], index[1]] = S_0[index[1], index[2]]
    end

    if k_sparse % 2 == 1
        index = sample(1:n, 1)
        S_0[index, index] = E[index, index]
    end

    data = L_0 + S_0

    # Add symmetric noise to the sum of the sampled low rank and sparse matrices
    d = Normal(0, sigma^2)
    noise = rand(d, (n, n))
    for i = 1:(n-1)
        for j = (i+1):n
            noise[j, i] = noise[i, j]
        end
    end
    data = data + noise

    return data, L_0, S_0, k_sparse, k_rank

end;


# In this tutorial, we will learn
#  - How to solve nonlinear multi-field PDEs in Gridap
#  - How to build FE spaces whose functions have zero mean value
#
# ## Problem statement
#
# The goal of this last tutorial is to solve a nonlinear multi-field PDE. As a model problem, we consider a well known benchmark in computational fluid dynamics, the lid-driven cavity for the incompressible Navier-Stokes equations. Formally, the PDE we want to solve is: find the velocity vector $u$ and the pressure $p$ such that
#
# ```math
# \left\lbrace
# \begin{aligned}
# -\Delta u + \mathit{Re}\ (u\cdot \nabla)\ u + \nabla p = 0 &\text{ in }\Omega,\\
# \nabla\cdot u = 0 &\text{ in } \Omega,\\
# u = g &\text{ on } \partial\Omega,
# \end{aligned}
# \right.
# ```
#
# where the computational domain is the unit square $\Omega \doteq (0,1)^d$, $d=2$, $\mathit{Re}$ is the Reynolds number (here, we take $\mathit{Re}=10$), and $(w \cdot \nabla)\ u = (\nabla u)^t w$  is the well known convection operator. In this example, the driving force is the Dirichlet boundary velocity $g$, which is a non-zero horizontal velocity with a value of $g = (1,0)^t$ on the top side of the cavity, namely the boundary $(0,1)\times\{1\}$, and $g=0$ elsewhere on $\partial\Omega$. Since we impose Dirichlet boundary conditions on the entire boundary $\partial\Omega$, the mean value of the pressure is constrained to zero in order have a well posed problem,
#
# ```math
# \int_\Omega q \ {\rm d}\Omega = 0.
# ```
#
# ## Numerical Scheme
#
# In order to approximate this problem we chose a formulation based on inf-sub stable $Q_k/P_{k-1}$ elements with continuous velocities and discontinuous pressures (see, e.g., [1] for specific details). The interpolation spaces are defined as follows.  The velocity interpolation space is
#
# ```math
# V \doteq \{ v \in [C^0(\Omega)]^d:\ v|_T\in [Q_k(T)]^d \text{ for all } T\in\mathcal{T} \},
# ```
# where $T$ denotes an arbitrary cell of the FE mesh $\mathcal{T}$, and $Q_k(T)$ is the local polynomial space in cell $T$ defined as the multi-variate polynomials in $T$ of order less or equal to $k$ in each spatial coordinate. Note that, this is the usual continuous vector-valued Lagrangian FE space of order $k$ defined on a mesh of quadrilaterals or hexahedra.  On the other hand, the space for the pressure is
#
# ```math
# \begin{aligned}
# Q_0 &\doteq \{ q \in Q: \  \int_\Omega q \ {\rm d}\Omega = 0\}, \text{ with}\\
# Q &\doteq \{ q \in L^2(\Omega):\ q|_T\in P_{k-1}(T) \text{ for all } T\in\mathcal{T}\},
# \end{aligned}
# ```
# where $P_{k-1}(T)$ is the polynomial space of multi-variate polynomials in $T$ of degree less or equal to $k-1$. Note that functions in $Q_0$ are strongly constrained to have zero mean value. This is achieved in the code by removing one degree of freedom from the (unconstrained) interpolation space $Q$ and  adding a constant to the computed pressure so that the resulting function has zero mean value.
#
# The weak form associated to these interpolation spaces reads: find $(u,p)\in U_g \times Q_0$ such that $[r(u,p)](v,q)=0$ for all $(v,q)\in V_0 \times Q_0$
# where $U_g$ and $V_0$ are the set of functions in $V$ fulfilling the Dirichlet boundary condition $g$ and $0$  on $\partial\Omega$ respectively. The weak residual $r$ evaluated at a given pair $(u,p)$ is the linear form defined as
#
# ```math
# [r(u,p)](v,q) \doteq a((u,p),(v,q))+ [c(u)](v),
# ```
# with
# ```math
# \begin{aligned}
# a((u,p),(v,q)) &\doteq \int_{\Omega} \nabla v \cdot \nabla u \ {\rm d}\Omega - \int_{\Omega} (\nabla\cdot v) \ p \ {\rm d}\Omega + \int_{\Omega} q \ (\nabla \cdot u) \ {\rm d}\Omega,\\
# [c(u)](v) &\doteq \int_{\Omega} v 	\cdot \left( (u\cdot\nabla)\ u \right)\ {\rm d}\Omega.\\
# \end{aligned}
# ```
# Note that the bilinear form $a$ is associated with the linear part of the PDE, whereas $c$ is the contribution to the residual resulting from the convective term.
#
# In order to solve this nonlinear weak equation with a Newton-Raphson method, one needs to compute the Jacobian associated with the residual $r$. In this case, the Jacobian $j$ evaluated at a pair $(u,p)$ is the bilinear form defined as
#
# ```math
# [j(u,p)]((\delta u, \delta p),(v,q)) \doteq a((\delta u,\delta p),(v,q))  + [{\rm d}c(u)](\delta u,v),
# ```
# where ${\rm d}c$ results from the linearization of the convective term, namely
# ```math
# [{\rm d}c(u)](\delta u,v) \doteq \int_{\Omega} v \cdot \left( (u\cdot\nabla)\ \delta u \right) \ {\rm d}\Omega + \int_{\Omega} v \cdot \left( (\delta u\cdot\nabla)\ u \right)  \ {\rm d}\Omega.
# ```
# The implementation of this numerical scheme is done in Gridap by combining the concepts previously seen for single-field nonlinear PDEs  and linear multi-field problems.
#
# ## Discrete model
#
# We start with the discretization of the computational domain. We consider a $100\times100$ Cartesian mesh of the unit square.

using Gridap
n = 100
domain = (0,1,0,1)
partition = (n,n)
model = CartesianDiscreteModel(domain,partition)

# For convenience, we create two new boundary tags,  namely `"diri1"` and `"diri0"`, one for the top side of the square (where the velocity is non-zero), and another for the rest of the boundary (where the velocity is zero).

labels = get_face_labeling(model)
add_tag_from_tags!(labels,"diri1",[6,])
add_tag_from_tags!(labels,"diri0",[1,2,3,4,5,7,8])

# ## FE spaces
#
# For the velocities, we need to create a conventional vector-valued continuous Lagrangian FE space. In this example, we select a second order interpolation.

D = 2
order = 2
reffeᵤ = ReferenceFE(lagrangian,VectorValue{2,Float64},order)
V = TestFESpace(model,reffeᵤ,conformity=:H1,labels=labels,dirichlet_tags=["diri0","diri1"])

# The interpolation space for the pressure is built as follows

reffeₚ = ReferenceFE(lagrangian,Float64,order-1;space=:P)
Q = TestFESpace(model,reffeₚ,conformity=:L2,constraint=:zeromean)

# With the options `:Lagrangian`, `space=:P`, `valuetype=Float64`, and `order=order-1`, we select the local polynomial space $P_{k-1}(T)$ on the cells $T\in\mathcal{T}$. With the symbol `space=:P` we specifically chose a local Lagrangian interpolation of type "P". Without using `space=:P`, would lead to a local Lagrangian of type "Q" since this is the default for quadrilateral or hexahedral elements. On the other hand, `constraint=:zeromean` leads to a FE space, whose functions are constrained to have mean value equal to zero, which is just what we need for the pressure space. With these objects, we build the trial multi-field FE spaces

uD0 = VectorValue(0,0)
uD1 = VectorValue(1,0)
U = TrialFESpace(V,[uD0,uD1])
P = TrialFESpace(Q)

Y = MultiFieldFESpace([V, Q])
X = MultiFieldFESpace([U, P])

# ## Triangulation and integration quadrature
#
# From the discrete model we can define the triangulation and integration measure

degree = order
Ωₕ = Triangulation(model)
dΩ = Measure(Ωₕ,degree)

# ## Nonlinear weak form
#
# The different terms of the nonlinear weak form for this example are defined following an approach similar to the one discussed for the $p$-Laplacian equation, but this time using the notation for multi-field problems.

const Re = 10.0
conv(u,∇u) = Re*(∇u')⋅u
dconv(du,∇du,u,∇u) = conv(u,∇du)+conv(du,∇u)

# The bilinear form reads
a((u,p),(v,q)) = ∫( ∇(v)⊙∇(u) - (∇⋅v)*p + q*(∇⋅u) )dΩ

# The nonlinear term and its Jacobian are given by
c(u,v) = ∫( v⊙(conv∘(u,∇(u))) )dΩ
dc(u,du,v) = ∫( v⊙(dconv∘(du,∇(du),u,∇(u))) )dΩ

# Finally, the Navier-Stokes weak form residual and Jacobian can be defined as
res((u,p),(v,q)) = a((u,p),(v,q)) + c(u,v)
jac((u,p),(du,dp),(v,q)) = a((du,dp),(v,q)) + dc(u,du,v)

# With the functions `res`, and `jac` representing the weak residual and the Jacobian, we build the nonlinear FE problem:
op = FEOperator(res,jac,X,Y)

# ## Nonlinear solver phase
#
# To finally solve the problem, we consider the same nonlinear solver as previously considered for the  $p$-Laplacian equation.

using LineSearches: BackTracking
nls = NLSolver(
  show_trace=true, method=:newton, linesearch=BackTracking())
solver = FESolver(nls)

# In this example, we solve the problem without providing an initial guess (a default one equal to zero will be generated internally)

uh, ph = solve(solver,op)

# Finally, we write the results for visualization (see next figure).

writevtk(Ωₕ,"ins-results",cellfields=["uh"=>uh,"ph"=>ph])

# ![](../assets/inc_navier_stokes/ins_solution.png)
#
#  ## References
#
#  [1] H. C. Elman, D. J. Silvester, and A. J. Wathen. *Finite elements and fast iterative solvers: with applications in incompressible fluid dynamics*. Oxford University Press, 2005.


# Simulate a Poisson point process on a triangle
# Author: H. Paul Keeler, 2019.
# Website: hpaulkeeler.com
# Repository: github.com/hpaulkeeler/posts
# For more details, see the post:
# hpaulkeeler.com/testing-the-julia-language-with-point-process-simulations/

#Note: Need the .+ for adding a scalar to an array
#Also need . for sqrt, exp, cos, sin etc and assinging scalars to arrays
#Don't confuse 1.-V with 1 .-V for array V
#Best to use vectors instead of 1-D matrices eg x=rand(n),  NOT x=rand(n,1).

using Distributions #for random simulations
using Plots #for plotting

#Simulation window parameters -- points A,B,C of a triangle
xA=0;xB=1;xC=1; #x values of three points
yA=0;yB=0;yC=1; #y values of three points;

#Point process parameters
lambda=100; #ntensity (ie mean density) of the Poisson process

#calculate sides of trinagle
a=sqrt((xA-xB)^2+(yA-yB)^2);
b=sqrt((xB-xC)^2+(yB-yC)^2);
c=sqrt((xC-xA)^2+(yC-yA)^2);
s=(a+b+c)/2; #calculate semi-perimeter

#Use Herron's forumula to calculate area
areaTotal=sqrt(s*(s-a)*(s-b)*(s-c)); #area of triangle

#Simulate a Poisson point process
numbPoints=rand(Poisson(areaTotal*lambda)); #Poisson number of points
U=rand(numbPoints);#uniform random variables
V=rand(numbPoints);#uniform random variables

xx=sqrt.(U).*xA .+sqrt.(U).*(1 .-V).*xB .+sqrt.(U).*V.*xC;#x coordinates of points
yy=sqrt.(U).*yA .+sqrt.(U).*(1 .-V).*yB .+sqrt.(U).*V.*yC;#y coordinates of points

#Plotting
plot1=scatter(xx,yy,xlabel ="x",ylabel ="y", leg=false);
display(plot1);


function compute(coins::Vector{Int}, n::Int)::Int
    combinations = vcat([1], repeat([0], n))
    for i ∈ 1:length(coins)
        for j ∈ coins[i] + 1:n + 1
            combinations[j] += combinations[j - coins[i]]
        end
    end
    return combinations[end]
end


using ODE, Test, LinearAlgebra

tols = [5e-2, 1e-2, 1e-2]

solvers = [
           ## Non-stiff
           # fixed step
           ODE.ode1,
           ODE.ode2_midpoint,
           ODE.ode2_heun,
           ODE.ode4,
           ODE.ode4ms,
           ODE.ode5ms,
           # adaptive
#           ODE.ode21, # this fails on Travis with 0.4?! TODO revert once fixed.
           ODE.ode23,
           ODE.ode45_dp,
           ODE.ode45_fe,
           ODE.ode78,

           ## Stiff
           # fixed-step
           ODE.ode4s_s,
           ODE.ode4s_kr,
           # adaptive
           ODE.ode23s]

dtypes = [Float32, Float64, BigFloat]
for solver in solvers
    println("using $solver")
    for (tol,T) in zip(tols, dtypes)
        if solver==ODE.ode45_fe && T==Float32
            # For some reason ode45_fe hangs with Float32!
            continue
        end

        # dy
        # -- = 6 ==> y = 6t
        # dt
        t,y=solver((t,y)->T(6), T(0), T[0:.1:1;])
        @test maximum(abs.(y-6t)) < tol
        @test eltype(t)==T

        # dy
        # -- = 2t ==> y = t.^2
        # dt
        t,y=solver((t,y)->T(2)*t, T(0), T[0:.001:1;])
        @test maximum(abs.(y-t.^2)) < tol
        @test eltype(t)==T

        # dy
        # -- = y ==> y = y0*e.^t
        # dt
        t,y=solver((t,y)->y, T(1), T[0:.001:1;])
        @test maximum(abs.(y-ℯ.^t)) < tol
        @test eltype(t)==T

        t,y=solver((t,y)->y, T(1), T[1:-.001:0;])
        @test maximum(abs.(y .- ℯ .^ (t.-1))) < tol
        @test eltype(t)==T

        # dv       dw
        # -- = -w, -- = v ==> v = v0*cos(t) - w0*sin(t), w = w0*cos(t) + v0*sin(t)
        # dt       dt
        #
        # y = [v, w]
        t,y=solver((t,y)->[-y[2]; y[1]], T[1, 2], T[0:.001:2*pi;])
        ys = hcat(y...)'   # convert Vector{Vector{T}} to Matrix{T}
        @test maximum(abs.(ys-[cos.(t)-2*sin.(t) 2*cos.(t)+sin.(t)])) < tol
        @test eltype(t)==T
    end
end

# Test negative starting times ODE.ode23s
@test length(ODE.ode23s((t,y)->[-y[2]; y[1]], [1., 2.], [-5., 0])[1]) > 1

# rober testcase from http://www.unige.ch/~hairer/testset/testset.html
let
    println("ROBER test case")
    function f(t, y)
        ydot = similar(y)
        ydot[1] = -0.04*y[1] + 1.0e4*y[2]*y[3]
        ydot[3] = 3.0e7*y[2]*y[2]
        ydot[2] = -ydot[1] - ydot[3]
        ydot
    end
    t = [0., 1e11]
    t,y = ode23s(f, [1.0, 0.0, 0.0], t; abstol=1e-8, reltol=1e-8,
                                        maxstep=1e11/10, minstep=1e11/1e18)

    refsol = [0.2083340149701255e-07,
              0.8333360770334713e-13,
              0.9999999791665050] # reference solution at tspan[2]
    @test LinearAlgebra.norm(refsol-y[end], Inf) < 2e-10
end
include("interface-tests.jl")
include("common.jl")
println("All looks OK")


using Plots
using Statistics

function main()

    x = range(1, 5, length=100)
    l = size(x)
    noise = rand(-2:0.5:2, l)

    y1 = exp.(x) .+ noise
    y2 = exp.(x)
    yerr = std(noise)

    # normal_plot(x, y1, y2, yerr)
    # log_plots(x, y1, y2)
    # subplots(x, y1, y2, yerr)

end

function normal_plot(x, y1, y2, yerr)

    scatter(x, y1, yerr=yerr, markersize=2, markershape=:o, alpha=0.5, label="data")
    plot!(x, y2, color="red", legend=:topleft, label="f(x)", dpi=200)

    annotate!(3, 50, Plots.text("Hello world!", 5, :red))

    xaxis!("x")
    yaxis!("y")
    title!("simple plot")

end

function log_plots(x, y1, y2)

    scatter(x, y1, markersize=1, markershape=:o, label="data")
    plot!(x, y2, xaxis="x", yaxis=("y", :log), legend=:topleft, label="f(x)", dpi=200)

    title!("log plot")

end

function subplots(x, y1, y2, yerr)

    p1 = scatter(x, y1,yerr=yerr, markershape=:o, markersize=1, alpha=0.5, label="data")
    p1 = plot!(x, y2, yaxis="y", legend=:topleft, label="f(x)", title="α data")

    p2 = scatter(x, y1, label="label")
    p2 = plot!(x, y2, xaxis="x", yaxis=("y", :log), legend=:topleft, label="f(x)")

    plot(p1, p2, layout=(2,1), dpi=200)

end

main()


"""

- `⁺pᵢ` : Particle i at next step
- `pᵢ`  : Particle i
- `pⱼ`  : Particle j
- `ϵ`   : Softening parameter
"""
function evaluate_Euler!(⁺aᵢ, pᵢ, pⱼ, ϵ)
    r = pⱼ.r .- pᵢ.r
    r⁻³ = (norm(r)^2 + ϵ^2)^(-3/2)

    @. ⁺aᵢ += G * pⱼ.m * r⁻³ * r
end

function evaluate_Euler!(ps, ϵ)
    for pᵢ in ps
        pᵢ.a .= 0.
        for pⱼ in ps
            pᵢ != pⱼ && evaluate_Euler!(pᵢ.a, pᵢ, pⱼ, ϵ)
        end
    end
end

function update!(p::AbstractParticle, Δt)
    @. p.v += p.a * Δt
    @. p.r += p.v * Δt
end

function update!(ps, Δt)
    for p in ps
        update!(p, Δt)
    end
end

function run_Euler(ps, params_sim)
    @unpack ϵ, Δt, t_end = params_sim

    times = (0:Δt:t_end)
    ts, rs, vs, as = prep_snapshot(ps, params_sim)

    for (i, t) in enumerate(times)
        evaluate_Euler!(ps, ϵ)
        save_snapshot!(ts, rs, vs, as, i, t, ps, params_sim)
        update!(ps, Δt)
    end
    ts, rs, vs, as
end


## Exercise 2-3
## Practice using the Julia REPL as a calculator:

## 1. The volume of a sphere with radius r is (4/3)πr^3. What is the volume of a sphere with radius 5?
println("Ans 1:")
println("the volume of sphere with radius 5 is: " * string((4/3) * π * 5 ^ 3))

## 2. Suppose the cover price of a book is $ 24.95, but bookstores get a 40 % discount. Shipping costs $ 3 for the first copy and 75 cents for each additional copy. What is the total wholesale cost for 60 copies?
println("Ans 2:")
cover_price = 24.95
n_copies = 60
shipping_cost = 3 + (n_copies - 1) * 0.75
books_price = n_copies * cover_price * 0.6
total = shipping_cost + books_price
println(total)

## 3. If I leave my house at 6:52 am and run 1 mile at an easy pace (8:15 per mile), then 3 miles at tempo (7:12 per mile) and 1 mile at easy pace again, what time do I get home for breakfast?
println("Ans 3:")
easy_pace_time = 8*60 + 15
tempo_pace_time = 7*60 + 12

total_time = 2 * easy_pace_time + 3 * tempo_pace_time

seconds = total_time % 60
minutes = total_time ÷ 60

println("you will reach home at 6:52 + ", minutes, " minutes and ", seconds, " seconds")
println("End.")