# Our developed libraries
using CombinatorialSpaces
using CombinatorialSpaces.ExteriorCalculus

using Catlab.Present
using Catlab.Graphics
using Catlab.CategoricalAlgebra
using LinearAlgebra

using Decapodes.Simulations
using Decapodes.Examples
using Decapodes.Diagrams
using Decapodes.Schedules

# Julia community libraries
using MeshIO
using CairoMakie
using Decapodes.Debug
using DifferentialEquations
using Logging: global_logger
using TerminalLoggers: TerminalLogger
global_logger(TerminalLogger())

# Define used quantities
@present Flow2DQuantities(FreeExtCalc2D) begin
  X::Space
  C::Hom(munit(), Form0(X))     # concentration
  dC::Hom(munit(), Form0(X))     # concentration
  V::Hom(munit(), Form1(X))     # Flow field
  dV::Hom(munit(), Form1(X))    # Flow field time derivative
  P::Hom(munit(), DualForm1(X)) # Flow momentum
  p::Hom(munit(), Form0(X))     # pressure field
  dp::Hom(munit(), Form0(X))     # pressure field time derivative
  ϕ::Hom(munit(), DualForm1(X)) # negative diffusion flux
  k::Hom(Form1(X), Form1(X))    # diffusivity (usually scalar multiplication)
  kᵥ::Hom(Form1(X), Form1(X))    # viscosity (usually scalar multiplication)
  kₚ::Hom(Form0(X), Form0(X))    # compressibility (usually scalar multiplication)
  m⁻¹::Hom(DualForm1(X), DualForm1(X))    # diffusivity (usually scalar multiplication)
  L₀::Hom(Form1(X)⊗DualForm2(X), DualForm2(X))
  L₁::Hom(Form1(X)⊗DualForm1(X), DualForm1(X))
  ∂₀::Hom(Form0(X), Form0(X)) # all_wall boundary condition
  ∂₀₋::Hom(Form0(X), Form0(X)) # left/right wall boundary condition
  ∂₀₊::Hom(Form0(X), Form0(X)) # top/bottom wall boundary condition
  ∂₀ₛ::Hom(Form0(X), Form0(X)) # Sticking boundary condition
  ∂₁ₛ::Hom(Form1(X), Form1(X)) # Sticking boundary condition
  ∂₁ₗ₊::Hom(Form1(X), Form1(X)) # Sticking boundary condition
  ∂₁ₑ::Hom(Form1(X), Form1(X)) # In/Out edge flow boundary condition
  dneg₁::Hom(DualForm1(X), DualForm1(X)) # In/Out edge flow boundary condition
  neg₁::Hom(Form1(X), Form1(X)) # In/Out edge flow boundary condition
  dneg₀::Hom(DualForm0(X), DualForm0(X)) # In/Out edge flow boundary condition
  neg₀::Hom(Form0(X), Form0(X)) # In/Out edge flow boundary condition
  mask₁ₑ::Hom(Form1(X), Form1(X)) # In/Out edge flow boundary condition
  mask₀ₗ::Hom(Form0(X), Form0(X)) # In/Out edge flow boundary condition

	# Boundary Conditions (currently does not impact actual BCs)
  bc₀::Hom(munit(), Form0(X))
  bc₁::Hom(munit(), Form1(X))
  bc₂::Hom(munit(), Form1(X))
  bc₃::Hom(munit(), Form0(X))
end

# Define Diffusion/Advection physics

@present DiffAdv2D <: Flow2DQuantities begin
  # Fick's first law
  ϕ == C ⋅ d₀(X) ⋅ k ⋅ ⋆₁(X)
  # Diffusion/advection equation
  dC == ϕ ⋅ dual_d₁(X) ⋅ ⋆₀⁻¹(X) + (V⊗(C⋅⋆₀(X))) ⋅ L₀ ⋅ ⋆₀⁻¹(X) ⋅ neg₀
  C ⋅ ∂ₜ(Form0(X)) == dC
  # Boundary condition
  C ⋅ ∂₀ == bc₀
end

# Extend to incompressible Navier Stokes

@present IncompressibleFlow2D <: DiffAdv2D begin
  P == V ⋅ ⋆₁(X)
  dV == (V ⊗ P) ⋅ L₁ ⋅ dneg₁ ⋅ ⋆₁⁻¹(X) ⋅ mask₁ₑ + V ⋅ Δ₁(X) ⋅ kᵥ + p ⋅ d₀(X) ⋅ neg₁
  V ⋅ ∂ₜ(Form1(X)) == dV
  dp == V ⋅ neg₁ ⋅ δ₁(X) ⋅ kₚ + (V ⊗ (p⋅⋆₀(X))) ⋅ L₀ ⋅ ⋆₀⁻¹(X) ⋅ mask₀ₗ ⋅ neg₀
  V ⋅ ∂₁ₛ == bc₁
  dC ⋅ ∂₀ₛ == bc₃
  dp ⋅ ∂₀₋ == bc₀
  dV ⋅ ∂₁ₗ₊ == bc₁
  p ⋅ ∂ₜ(Form0(X)) == dp
end

diag = eq_to_diagrams(IncompressibleFlow2D)
to_graphviz(diag ; edge_len="1.3")

dwd =diag2dwd(diag)
to_graphviz(dwd, orientation=LeftToRight)

exp_dwd = Examples.expand_dwd(dwd, gen_dec_rules())
to_graphviz(exp_dwd, orientation=LeftToRight)

# Load mesh

s = EmbeddedDeltaSet2D("../meshes/naca0012_8.stl")
sd = dual(s);

# Define non-default operators (multiplication by constants, boundary conditions)
k = 0.1
kₚ = 10.0
kᵥ = 1.0
funcs = sym2func(sd)
∂₀ = Examples.boundary_inds(Val{0}, s)
∂₁ = Examples.boundary_inds(Val{1}, s)

∂ₗ₀ = ∂₀[findall(p-> p[1] <= -50.0, s[∂₀, :point])]
∂ᵣ₀ = ∂₀[findall(p-> p[1] >= 50.0, s[∂₀, :point])]
∂ₜ₀ = ∂₀[findall(p-> p[2] >= 15.0, s[∂₀, :point])]
∂ᵦ₀ = ∂₀[findall(p-> p[2] <= -15.0, s[∂₀, :point])]
∂ₑ₀ = vcat(∂ₗ₀, ∂ᵣ₀, ∂ₜ₀, ∂ᵦ₀)
∂ₒ₀ = ∂₀[findall(p-> -49 <= p[1] <= 49 && -14 <= p[2] <= 14, s[∂₀, :point])]

∂ₗ₁ = Examples.bound_edges(s, ∂ₗ₀)
∂ᵣ₁ = Examples.bound_edges(s, ∂ᵣ₀)
∂ₗ₁₊ = Examples.adj_edges(s, ∂ₗ₀)
∂ᵣ₁₊ = Examples.adj_edges(s, ∂ᵣ₀)
∂ₒ₁ = Examples.bound_edges(s, ∂ₒ₀)

∂ₗ₀₊ = unique(vcat(s[∂ₗ₁₊, :tgt], s[∂ₗ₁₊, :src]))

funcs[:mcopy] = Dict(:operator => I(ne(sd)), :type => MatrixFunc())
funcs[:k] = Dict(:operator => k * I(ne(sd)), :type => MatrixFunc())
funcs[:half] = Dict(:operator => 0.5 * I(ne(sd)), :type => MatrixFunc())
funcs[:kₚ] = Dict(:operator => kₚ * I(nv(sd)), :type => MatrixFunc())
funcs[:kᵥ] = Dict(:operator => kᵥ * I(ne(sd)), :type => MatrixFunc())
funcs[:dneg₁] = Dict(:operator => -1 * I(ne(sd)), :type => MatrixFunc())
funcs[:neg₁] = Dict(:operator => -1 * I(ne(sd)), :type => MatrixFunc())
funcs[:neg₀] = Dict(:operator => -1 * I(nv(sd)), :type => MatrixFunc())
funcs[:∂₀] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₑ₀] .= 0), :type => InPlaceFunc())
funcs[:∂₀₋] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₗ₀] .= 0; x′[∂ᵣ₀] .= 0), :type => InPlaceFunc())
funcs[:∂ₗ₀₊] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₗ₀₊] .= 0), :type => InPlaceFunc())
funcs[:∂₁ₗ₊] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₗ₁₊] .= 0), :type => InPlaceFunc())
funcs[:∂₀ₛ] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₒ₀] .= 0), :type => InPlaceFunc())
funcs[:∂₁ₛ] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₒ₁] .= 0), :type => InPlaceFunc())
funcs[:∂₁ₑ] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₗ₁₊] .= 0;  x′[∂ᵣ₁₊] .= 0), :type => InPlaceFunc())
funcs[:mask₁ₑ] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₗ₁₊] .= 0;  x′[∂ᵣ₁₊] .= 0), :type => InPlaceFunc())
funcs[:mask₀ₗ] = Dict(:operator => (x′,x) -> (x′ .= x; x′[∂ₗ₀₊] .= 0), :type => InPlaceFunc())

# Define initial conditions
c_objs = zeros(nv(s))
c_objs[∂ₒ₀] .= 1e-3
velocity(p) = [2.0 * exp(-(p[1]+50)/5),0.0,0.0]
v = ♭(sd, DualVectorField(velocity.(sd[triangle_center(sd),:dual_point]))).data;
p = [0.0 for p in s[:point]]
u0 = vcat(c_objs, v, p)
GC.gc()

fig, ax, ob = wireframe(s)
ax.aspect = AxisAspect(3)
fig
save("mesh.svg", fig)

# Compress wiring diagram over contiguous matrix multiplications
cont_dwd = deepcopy(exp_dwd)
Examples.contract_matrices!(cont_dwd, funcs)

# Generate simulation function
func, _ = gen_sim(cont_dwd, funcs, sd; autodiff=false);

# Solve problem
prob = ODEProblem(func, u0, (0, 30))
sol = solve(prob, Tsit5(), progress=true, progress_steps=100, p=v);

# Example of debugging simulation

# Key for debugging simulation
sim_key(exp_dwd, orientation=LeftToRight)

exp_func, _ = gen_sim(exp_dwd, funcs, sd; autodiff=false);

# Show the flow of concentration as arrows
fig, ax, ob = draw_wire(s, sd, exp_dwd, exp_func, sol(30.0), 52; axisaspect=1, xlim=(40, 50), ylim=(-5, 5), use_arrows=true, n_arrows=1000)
fig
save("debug_conc_flow.svg")

# Generate animations of simulation

times = range(0, sol.t[end], length=150)
colors = [sol(t)#=[(end-nv(s)+1):end]=# for t in times]

figure, axis, scatter_thing = mesh(s, color=colors[1],
                                   colorrange=(0,1e-3))#minimum(vcat(colors...)),maximum(vcat(colors...))))
axis.aspect = AxisAspect(100.0/30.0)
framerate = 30

record(figure, "conc_flow.gif", collect(1:length(collect(times))); framerate = framerate) do i
  scatter_thing.color = colors[i]
end

times = range(0, sol.t[end], length=150)
colors = [sol(t)[(end-nv(s)+1):end] for t in times]

figure, axis, scatter_thing = mesh(s, color=colors[1],
                                   colorrange=(minimum(vcat(colors...)),maximum(vcat(colors...))))
axis.aspect = AxisAspect(100.0/30.0)
framerate = 30

record(figure, "press_flow.gif", collect(1:length(collect(times))); framerate = framerate) do i
  scatter_thing.color = colors[i]
end


"""Transit function to call the specified diffusion scheme."""
function diffusion!(    u::AbstractMatrix,
                        v::AbstractMatrix,
                        Diag::DiagnosticVars,
                        S::ModelSetup)

    if S.parameters.diffusion == "constant"
        diffusion_constant!(u,v,Diag,S)
    elseif S.parameters.diffusion == "Smagorinsky"
        diffusion_smagorinsky!(u,v,Diag,S)
    else
        throw(error("Diffusion scheme $(S.parameters.diffusion) is unsupported."))
    end
end

"""Biharmonic diffusion operator with constant viscosity coefficient
Viscosity = ν∇⁴ ⃗u. Although constant, the coefficient is actually inside
Viscosity = ∇⋅ν∇∇² ⃗u."""
function diffusion_constant!(   u::AbstractMatrix,
                                v::AbstractMatrix,
                                Diag::DiagnosticVars,
                                S::ModelSetup)

    stress_tensor!(u,v,Diag)
    viscous_tensor_constant!(Diag,S)

    @unpack LLu1,LLu2,LLv1,LLv2 = Diag.Smagorinsky
    @unpack S11,S12,S21,S22 = Diag.Smagorinsky

    ∂x!(LLu1,S11)
    ∂y!(LLu2,S12)
    ∂x!(LLv1,S21)
    ∂y!(LLv2,S22)
end

""" Smagorinsky-like biharmonic viscosity
Viscosity = ∇ ⋅ (cSmag Δ⁴ |D| ∇∇² ⃗u)
The Δ⁴-scaling is omitted as gradient operators are dimensionless."""
function diffusion_smagorinsky!(u::AbstractMatrix,
                                v::AbstractMatrix,
                                Diag::DiagnosticVars,
                                S::ModelSetup)

    @unpack dudx,dvdy,dvdx,dudy = Diag.Vorticity

    ∂x!(dudx,u)
    ∂y!(dvdy,v)
    ∂x!(dvdx,v)
    ∂y!(dudy,u)

    # biharmonic diffusion
    stress_tensor!(u,v,Diag)
    smagorinsky_coeff!(Diag,S)
    viscous_tensor_smagorinsky!(Diag)

    @unpack LLu1,LLu2,LLv1,LLv2 = Diag.Smagorinsky
    @unpack S11,S12,S21,S22 = Diag.Smagorinsky

    ∂x!(LLu1,S11)
    ∂y!(LLu2,S12)
    ∂x!(LLv1,S21)
    ∂y!(LLv2,S22)
end

"""νSmag = cSmag * |D|, where deformation rate |D| = √((∂u/∂x - ∂v/∂y)^2 + (∂u/∂y + ∂v/∂x)^2).
The grid spacing Δ is omitted here as the operators are dimensionless."""
function smagorinsky_coeff!(Diag::DiagnosticVars,S::ModelSetup)

    @unpack dudx,dudy,dvdx,dvdy = Diag.Vorticity
    @unpack DS,DS_q,DT,νSmag,νSmag_q = Diag.Smagorinsky
    @unpack ep = S.grid
    @unpack cSmag = S.constants

    # horizontal shearing strain squared
    m,n = size(DS_q)
    @boundscheck (m+ep,n) == size(dudy) || throw(BoundsError())
    @boundscheck (m,n) == size(dvdx) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            DS_q[i,j] = (dudy[i+ep,j] + dvdx[i,j])^2
        end
    end

    Ixy!(DS,DS_q)

    # horizontal tension squared
    m,n = size(DT)
    @boundscheck (m+ep,n+2) == size(dudx) || throw(BoundsError())
    @boundscheck (m+2,n) == size(dvdy) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            DT[i,j] = (dudx[i+ep,j+1] + dvdy[i+1,j])^2
        end
    end

    # viscosity = Smagorinsky coefficient times deformation rate
    m,n = size(νSmag)
    @boundscheck (m,n) == size(DS) || throw(BoundsError())
    @boundscheck (m,n) == size(DT) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            νSmag[i,j] = cSmag*sqrt(DS[i,j] + DT[i,j])
        end
    end

    Ixy!(νSmag_q,νSmag)
end

"""Biharmonic stress tensor ∇∇²(u,v) = (∂/∂x(∇²u), ∂/∂y(∇²u); ∂/∂x(∇²v), ∂/∂y(∇²v))"""
function stress_tensor!(u::AbstractMatrix,v::AbstractMatrix,Diag::DiagnosticVars)
    @unpack Lu,Lv,dLudx,dLudy,dLvdx,dLvdy = Diag.Laplace
    ∇²!(Lu,u)
    ∇²!(Lv,v)
    ∂x!(dLudx,Lu)
    ∂y!(dLudy,Lu)
    ∂x!(dLvdx,Lv)
    ∂y!(dLvdy,Lv)
end

"""Biharmonic stress tensor times Smagorinsky coefficient
νSmag * ∇∇² ⃗u = (S11, S12; S21, S22)."""
function viscous_tensor_smagorinsky!(Diag::DiagnosticVars)

    @unpack dLudx,dLudy,dLvdx,dLvdy = Diag.Laplace
    @unpack νSmag,νSmag_q,S11,S12,S21,S22 = Diag.Smagorinsky
    @unpack ep = Diag.Smagorinsky

    m,n = size(S11)
    @boundscheck (m+2-ep,n) == size(νSmag) || throw(BoundsError())
    @boundscheck (m,n) == size(dLudx) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            S11[i,j] = νSmag[i+1-ep,j] * dLudx[i,j]
        end
    end

    m,n = size(S12)
    @boundscheck (m,n) == size(νSmag_q) || throw(BoundsError())
    @boundscheck (m+ep,n) == size(dLudy) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            S12[i,j] = νSmag_q[i,j] * dLudy[i+ep,j]
        end
    end

    m,n = size(S21)
    @boundscheck (m,n) == size(νSmag_q) || throw(BoundsError())
    @boundscheck (m,n) == size(dLvdx) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            S21[i,j] = νSmag_q[i,j] * dLvdx[i,j]
        end
    end

    m,n = size(S22)
    @boundscheck (m,n+2) == size(νSmag) || throw(BoundsError())
    @boundscheck (m,n) == size(dLvdy) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            S22[i,j] = νSmag[i,j+1] * dLvdy[i,j]
        end
    end
end

"""Biharmonic stress tensor times constant viscosity coefficient
νB * ∇∇² ⃗u = (S11, S12; S21, S22)"""
function viscous_tensor_constant!(  Diag::DiagnosticVars,
                                    S::ModelSetup)

    @unpack dLudx,dLudy,dLvdx,dLvdy = Diag.Laplace
    @unpack S11,S12,S21,S22 = Diag.Smagorinsky
    @unpack ep = S.grid
    @unpack νB = S.constants

    m,n = size(S11)
    @boundscheck (m,n) == size(dLudx) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            S11[i,j] = νB * dLudx[i,j]
        end
    end

    m,n = size(S12)
    @boundscheck (m+ep,n) == size(dLudy) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            S12[i,j] = νB * dLudy[i+ep,j]
        end
    end

    m,n = size(S21)
    @boundscheck (m,n) == size(dLvdx) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            S21[i,j] = νB * dLvdx[i,j]
        end
    end

    m,n = size(S22)
    @boundscheck (m,n) == size(dLvdy) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            S22[i,j] = νB * dLvdy[i,j]
        end
    end
end

"""Update u with bottom friction tendency (Bu,Bv) and biharmonic viscosity."""
function add_drag_diff_tendencies!( u::Array{Tprog,2},
                                    v::Array{Tprog,2},
                                    Diag::DiagnosticVars{T,Tprog},
                                    S::ModelSetup{T,Tprog}) where {T,Tprog}

    @unpack Bu,Bv = Diag.Bottomdrag
    @unpack LLu1,LLu2,LLv1,LLv2 = Diag.Smagorinsky
    @unpack halo,ep,Δt_diff = S.grid

    m,n = size(u) .- (2*halo,2*halo)
    @boundscheck (m+2-ep,n+2) == size(Bu) || throw(BoundsError())
    @boundscheck (m,n+2) == size(LLu1) || throw(BoundsError())
    @boundscheck (m+2-ep,n) == size(LLu2) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
            u[i+2,j+2] += Δt_diff*(Tprog(Bu[i+1-ep,j+1]) + Tprog(LLu1[i,j+1]) + Tprog(LLu2[i+1-ep,j]))
        end
    end

    m,n = size(v) .- (2*halo,2*halo)
    @boundscheck (m+2,n+2) == size(Bv) || throw(BoundsError())
    @boundscheck (m,n+2) == size(LLv1) || throw(BoundsError())
    @boundscheck (m+2,n) == size(LLv2) || throw(BoundsError())

    @inbounds for j ∈ 1:n
        for i ∈ 1:m
             v[i+2,j+2] += Δt_diff*(Tprog(Bv[i+1,j+1]) + Tprog(LLv1[i,j+1]) + Tprog(LLv2[i+1,j]))
        end
    end
end


struct Line
    start::CartesianIndex
    stop::CartesianIndex
end

function length(l::Line)
    return norm(l.stop - l.start)
end

function isvertical(l::Line)
    return l.start[1] == l.stop[1]
end

function ishorizontal(l::Line)
    return l.start[2] == l.stop[2]
end

function isdiagonal(l::Line)
    return abs(l.stop[1] - l.start[1]) == abs(l.stop[2] - l.start[2])
end

function getPointsOnLine(l::Line)
    points = CartesianIndex[]
    if isvertical(l)
        ystart = line.start[2] + 1
        ystop = line.stop[2] + 1
        step = 1
        if ystart > ystop
            step = -1
        end
        x = line.start[1] + 1
        y = ystart
        while y <= ystop
            points = push!(points, CartesianIndex(x,y))
            y += step
        end
    elseif ishorizontal(l)
        xstart = line.start[1] + 1
        xstop = line.stop[1] + 1
        step = 1
        if xstart > xstop
            step = -1
        end
        y = line.start[2] + 1
        x = xstart
        while x <= xstop
            points = push!(points, CartesianIndex(x,y))
            x += step
        end
    elseif isdiagonal(l)
        xstep = 1
        ystep = 1
        xstart = line.start[1] + 1
        ystart = line.start[2] + 1
        xstop = line.stop[1] + 1
        ystop = line.stop[2] + 1
        if xstop > xstart
            xstep = -1
        end
        if ystop > ystart
            ystep = -1
        end
        x = xstart
        y = ystart
        step = 1
        while step <= abs(xstop - xstart) + 1
            points = push!(points, CartesianIndex(x,y))
            x += xstep
            y += ystep
            step += 1
        end
    end
end

function solve(filename)
    #load txt file
    values = readlines(filename*".txt")
    lines = Line[]
    max_x = 0
    max_y = 0
    for line in values
        parts = split(line, '-')
        first = split(parts[1], ',')
        second = split(parts[2], ',')
        x1 = parse(Int, first[1])
        if x1 > max_x
            max_x = x1
        end
        x2string = replace(second[1], '>' => ' ')
        x2 = parse(Int, x2string)
        if x2 > max_x
            max_x = x2
        end
        y1 = parse(Int, first[2])
        if y1 > max_y
            max_y = y1
        end
        y2 = parse(Int, second[2])
        if y2 > max_y
            max_y = y2
        end
        start = CartesianIndex(x1, y1)
        stop = CartesianIndex(x2, y2)
        lines = push!(lines, Line(start, stop))
    end

    map = zeros(max_x + 1, max_y + 1)
    for line in lines
        #println(line.start)
        #println(line.stop)
        if isvertical(line)
            #println("vertical")
            ystart = line.start[2] + 1
            ystop = line.stop[2] + 1
            if ystart > ystop
                tmp = ystart
                ystart = ystop
                ystop = tmp
            end
            x = line.start[1] + 1
            y = ystart
            while y <= ystop
                map[x,y] += 1
                y += 1
            end
        elseif ishorizontal(line)
            #println("horizontal")
            xstart = line.start[1] + 1
            xstop = line.stop[1] + 1
            if xstart > xstop
                tmp = xstart
                xstart = xstop
                xstop = tmp
            end
            y = line.start[2] + 1
            x = xstart
            while x <= xstop
                #println("mark: ("*string(x)*", "*string(y)*")")
                map[x,y] += 1
                #println(map[x,y])
                x += 1
            end
        end
    end
    count_morethantwo = 0
    for el in map
        if el >= 2
            count_morethantwo += 1
        end
    end
    println(count_morethantwo)
end

solve("smalltest")
solve("input")

function solve2(filename)
    #load txt file
    values = readlines(filename*".txt")
    lines = Line[]
    max_x = 0
    max_y = 0
    for line in values
        parts = split(line, '-')
        first = split(parts[1], ',')
        second = split(parts[2], ',')
        x1 = parse(Int, first[1])
        if x1 > max_x
            max_x = x1
        end
        x2string = replace(second[1], '>' => ' ')
        x2 = parse(Int, x2string)
        if x2 > max_x
            max_x = x2
        end
        y1 = parse(Int, first[2])
        if y1 > max_y
            max_y = y1
        end
        y2 = parse(Int, second[2])
        if y2 > max_y
            max_y = y2
        end
        start = CartesianIndex(x1, y1)
        stop = CartesianIndex(x2, y2)
        lines = push!(lines, Line(start, stop))
    end

    map = zeros(max_x + 1, max_y + 1)
    for line in lines
        if isvertical(line)
            ystart = line.start[2] + 1
            ystop = line.stop[2] + 1
            if ystart > ystop
                tmp = ystart
                ystart = ystop
                ystop = tmp
            end
            x = line.start[1] + 1
            y = ystart
            while y <= ystop
                map[x,y] += 1
                y += 1
            end
        elseif ishorizontal(line)
            xstart = line.start[1] + 1
            xstop = line.stop[1] + 1
            if xstart > xstop
                tmp = xstart
                xstart = xstop
                xstop = tmp
            end
            y = line.start[2] + 1
            x = xstart
            while x <= xstop
                map[x,y] += 1
                x += 1
            end
        elseif isdiagonal(line)
            x_right = false
            y_down = false
            xstart = line.start[1] + 1
            ystart = line.start[2] + 1
            xstop = line.stop[1] + 1
            ystop = line.stop[2] + 1
            if xstop > xstart
                x_right = true
            end
            if ystop > ystart
                y_down = true
            end
            xstep = 1
            ystep = 1
            if x_right == false
                xstep = -1
            end
            if y_down ==false
                ystep = -1
            end
            x = xstart
            y = ystart
            step = 1
            while step <= abs(xstop - xstart) + 1
                map[x,y] += 1
                x += xstep
                y += ystep
                step += 1
            end
        end
    end
    count_morethantwo = 0
    for el in map
        if el >= 2
            count_morethantwo += 1
        end
    end
    println(count_morethantwo)
end

solve2("smalltest")
solve2("input")


"""
    Heatmap(xedges, yedges; left = true, closed = true)

Create a two dimensional histogram with the bin partition created by `xedges` and `yedges`.
When fitting a new observation, the first value will be associated with X, the second with Y.

- If `left`, the bins will be left-closed.
- If `closed`, the bins on the ends will be closed.  See [Hist](@ref).

# Example

    o = fit!(HeatMap(-5:.1:5, -5:.1:5), eachrow(randn(10^5, 2)))

    using Plots
    plot(o)
"""
mutable struct HeatMap{EX, EY} <: OnlineStat{XY}
    xedges::EX
    yedges::EY
    counts::Matrix{Int}
    out::Int
    left::Bool
    closed::Bool

    function HeatMap(x::T, y::S; left::Bool=true, closed::Bool=true) where {T<:AbstractVector,S<:AbstractVector}
        new{T,S}(x,y, zeros(Int, length(x)-1, length(y)-1), 0, left, closed)
    end
end
nobs(o::HeatMap) = sum(o.counts) + o.out
value(o::HeatMap) = (x=o.xedges, y=o.yedges, z=o.counts)

function _fit!(o::HeatMap, xy)
    i = binindex(o.xedges, xy[1], o.left, o.closed)
    if 1 ≤ i < length(o.xedges)
        j = binindex(o.yedges, xy[2], o.left, o.closed)
        if 1 ≤ j < length(o.yedges)
            o.counts[i, j] += 1
        else
            o.out += 1
        end
    else
        o.out += 1
    end
end

function _merge!(a::HeatMap, b::HeatMap)
    if (a.xedges == b.xedges) && (a.yedges == b.yedges) && (a.left == b.left) && (a.closed == b.closed)
        a.out += b.out
        a.counts .+= b.counts
    else
        @warn "HeatMaps differ in edges or direction of bins being closed.  No merging occurred."
    end
end

@recipe function f(o::HeatMap)
    seriestype --> :heatmap
    z = Float64.(o.counts)
    z[z .== 0] .= NaN
    midpoints(o.xedges), midpoints(o.yedges), z
end


# ---
# title: 85. Maximal Rectangle
# id: problem85
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Array, Hash Table, Dynamic Programming, Stack
# link: <https://leetcode.com/problems/maximal-rectangle/description/>
# hidden: true
# ---
#
# Given a `rows x cols` binary `matrix` filled with `0`'s and `1`'s, find the
# largest rectangle containing only `1`'s and return _its area_.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2020/09/14/maximal.jpg)
#
#
#
#     Input: matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
#     Output: 6
#     Explanation: The maximal rectangle is shown in the above picture.
#
#
# **Example 2:**
#
#
#
#     Input: matrix = []
#     Output: 0
#
#
# **Example 3:**
#
#
#
#     Input: matrix = [["0"]]
#     Output: 0
#
#
# **Example 4:**
#
#
#
#     Input: matrix = [["1"]]
#     Output: 1
#
#
# **Example 5:**
#
#
#
#     Input: matrix = [["0","0"]]
#     Output: 0
#
#
#
#
# **Constraints:**
#
#   * `rows == matrix.length`
#   * `cols == matrix.length`
#   * `0 <= row, cols <= 200`
#   * `matrix[i][j]` is `'0'` or `'1'`.
#
#
## @lc code=start
using LeetCode

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


function SOLUTION_is_feasible(sol::Sol)

    debug && DEBUG_correct_solution(sol)

    feasible = true
    dim = 1
    while feasible && dim <= sol.prob.m
        if sol.w[dim] > sol.prob.ω[dim]
            feasible = false
        end
        dim += 1
    end

    return feasible
end

function SOLUTION_is_feasible_after_adding_item(sol::Sol, idx_var::Int)

    debug && DEBUG_correct_solution(sol)

    @assert sol.x[idx_var] == 0 "The item is already in the bag"

    feasible = true
    dim = 1
    while feasible && dim <= sol.prob.m
        if sol.w[dim] + sol.prob.W[dim, idx_var] > sol.prob.ω[dim]
            feasible = false
        end
        dim += 1
    end

    return feasible
end

function SOLUTION_add_item!(sol::Sol, item::Int)

    debug && @assert sol.x[item] == 0 "Item already in the bag"
    debug && DEBUG_correct_solution(sol) # abusive

    sol.x[item] = 1
    sol.z[1] += sol.prob.P[1,item]
    sol.z[2] += sol.prob.P[2,item]
    for dim in 1:sol.prob.m
        sol.w[dim] += sol.prob.W[dim,item]
    end

    debug && DEBUG_correct_solution(sol)

end

function SOLUTION_remove_item!(sol::Sol, item::Int)

    debug && @assert sol.x[item] == 1 "Item not in the bag"
    debug && DEBUG_correct_solution(sol) # abusive

    momkp = sol.prob

    sol.x[item] = 0
    sol.z[1] -= momkp.P[1,item]
    sol.z[2] -= momkp.P[2,item]
    for dim in 1:momkp.m
        sol.w[dim] -= momkp.W[dim,item]
    end

    debug && DEBUG_correct_solution(sol)

end

function SOLUTION_empty_solution(momkp::_MOMKP)

    empty_sol = Sol(momkp, zeros(Int, momkp.n), [0, 0], zeros(Int, momkp.m))

    return empty_sol
end

function SOLUTION_list_of_empty_solutions(nb_solutions::Int, momkp::_MOMKP)

    vec = Vector{Sol}(undef, nb_solutions)
    for i in 1:nb_solutions
        vec[i] = SOLUTION_empty_solution(momkp)
    end

    return vec
end


"""
    PCA{T<:AbstractFloat}

Principal Components Analysis

## Fields

* `covcorr` covariance or correlation matrix
* `sv` singular value decomposition
* `rnames` rownames of the original matrix
* `corr` is this a correlation matrix?
"""
struct PCA{T<:AbstractFloat}
    covcor::Symmetric{T,<:AbstractMatrix{T}}
    sv::SVD{T,T,<:AbstractMatrix{T}}
    rnames::Union{Vector{String},Missing}
    corr::Bool
end

"""
    PCA(::AbstractMatrix; corr::Bool=true)
    PCA(::ReMat; corr::Bool=true)
    PCA(::LinearMixedModel; corr::Bool=true)

Constructs a [`MixedModels.PCA`](@ref]) object from a covariance matrix.

For `LinearMixedModel`, a named tuple of PCA on each of the random-effects terms is returned.

If `corr=true`, then the covariance is first standardized to the correlation scale.
"""

function PCA(covfac::AbstractMatrix, rnames=missing; corr::Bool=true)
    covf = corr ? rownormalize(covfac) : covfac
    return PCA(Symmetric(covf * covf', :L), svd(covf), rnames, corr)
end

function Base.getproperty(pca::PCA, s::Symbol)
    if s == :cumvar
        cumvv = cumsum(abs2.(pca.sv.S))
        cumvv ./ last(cumvv)
    elseif s == :loadings
        pca.sv.U
    else
        getfield(pca, s)
    end
end

function Base.propertynames(pca::PCA, private::Bool=false)
    return (
        :covcor,
        :sv,
        :corr,
        :cumvar,
        :loadings,
        #    :rotation,
    )
end

Base.show(io::IO, pca::PCA; kwargs...) = Base.show(io, MIME"text/plain"(), pca; kwargs...)

function Base.show(
    io::IO,
    ::MIME"text/plain",
    pca::PCA;
    ndigitsmat=2,
    ndigitsvec=2,
    ndigitscum=4,
    covcor=true,
    loadings=true,
    variances=false,
    stddevs=false,
)
    println(io)
    if covcor
        println(
            io,
            "Principal components based on ",
            pca.corr ? "correlation" : "(relative) covariance",
            " matrix",
        )
        # only display the lower triangle of symmetric matrix
        if pca.rnames !== missing
            n = length(pca.rnames)
            cv = string.(round.(pca.covcor, digits=ndigitsmat))
            dotpad = lpad(".", div(maximum(length, cv), 2))
            for i in 1:n, j in (i + 1):n
                cv[i, j] = dotpad
            end
            neg = startswith.(cv, "-")
            if any(neg)
                cv[.!neg] .= " " .* cv[.!neg]
            end
            # this hurts type stability,
            # but this show method shouldn't be a bottleneck
            printmat = Text.([pca.rnames cv])
        else
            # if there are no names, then we cheat and use the print method
            # for LowerTriangular, which automatically covers the . in the
            # upper triangle
            printmat = round.(LowerTriangular(pca.covcor), digits=ndigitsmat)
        end

        Base.print_matrix(io, printmat)
        println(io)
    end
    if stddevs
        println(io, "\nStandard deviations:")
        sv = pca.sv
        show(io, round.(sv.S, digits=ndigitsvec))
        println(io)
    end
    if variances
        println(io, "\nVariances:")
        vv = abs2.(sv.S)
        show(io, round.(vv, digits=ndigitsvec))
        println(io)
    end
    println(io, "\nNormalized cumulative variances:")
    show(io, round.(pca.cumvar, digits=ndigitscum))
    println(io)
    if loadings
        println(io, "\nComponent loadings")
        printmat = round.(pca.loadings, digits=ndigitsmat)
        if pca.rnames !== missing
            pclabs = [Text(""); Text.("PC$i" for i in 1:length(pca.rnames))]
            pclabs = reshape(pclabs, 1, :)
            # this hurts type stability,
            # but this show method shouldn't be a bottleneck
            printmat = [pclabs; Text.(pca.rnames) Matrix(printmat)]
        end

        Base.print_matrix(io, printmat)
    end

    return nothing
end


# Tests for DirichletCategorical distribution

using  Distributions
using Test, Random, LinearAlgebra

Random.seed!(34567)

rng = MersenneTwister(123)

@testset "Testing DirichletCategorical with $key" for (key, func) in
    Dict("rand(...)" => [rand, rand] ) #,
         #"rand(rng, ...)" => [dist -> rand(rng, dist), (dist, n) -> rand(rng, dist, n)])

# Constructors
d1 = DirichletCategorical([0.1, 0.2, 0.3], [1, 2, 3])
d2 = DirichletCategorical([4, 2, 3], [1, 1, 0])
d3 = DirichletCategorical(5)
d4 = DirichletCategorical([1.3, 4.3, 2.6, 3.9])

@testset "Constructors" for d in (d1, d2, d3, d4)
    @test typeof(d) == DirichletCategorical{Float64,Int64}
end

@testset "Constructor throws" begin
    @test_throws ArgumentError DirichletCategorical([-1, 0], [0, 0])
    @test_throws ArgumentError DirichletCategorical([0, 0], [-1, 0])
    @test_throws ArgumentError DirichletCategorical([0], [0, 0])
    @test_throws ArgumentError DirichletCategorical([0, 0], [1, 1])
    @test_throws ArgumentError DirichletCategorical([-1, 2], [0, 0])
    @test_throws ArgumentError DirichletCategorical([1.0, 1.0], [2, -1])
end

@testset "ncategories" for (d,n) in [(d1, 3), (d2, 3), (d3, 5), (d4, 4)]
    @test ncategories(d) == n
end

@testset "length" for (d,n) in [(d1, 3), (d2, 3), (d3, 5), (d4, 4)]
    @test length(d) == n
end

@testset "params" begin
    @test params(d1) == ([0.1, 0.2, 0.3], [1, 2, 3])
    @test params(d2) == ([4, 2, 3], [1, 1, 0])
    @test params(d3) == (ones(5), zeros(Int, 5))
    @test params(d4) == ([1.3, 4.3, 2.6, 3.9], zeros(Int, 4))
end

@testset "partype" for d in (d1, d2, d3, d4)
    @test partype(d) == Float64
end

@testset "rand" begin
    @testset "single" for (d,n) in [(d1, 1), (d2, 1), (d3, 1), (d4, 1)]
        @test rand(d) == n
    end

    @testset "multiple" for (d,n) in [
            (d1, [1, 2, 1, 1, 2, 2, 3, 3, 3, 1]),
            (d2, [1, 1, 1, 1, 1, 1, 3, 2, 3, 1]),
            (d3, [1, 1, 1, 1, 3, 2, 5, 3, 5, 1]),
            (d4, [1, 2, 2, 1, 2, 2, 4, 3, 4, 1])]
        @test rand(d, 10) == n
    end

    @testset "10k" for (d,n) in [
            (d1, [1613, 3334, 5053]),
            (d2, [4504, 2720, 2776]),
            (d3, [1932, 2024, 1978, 2055, 2011]),
            (d4, [1058, 3526, 2147, 3269])]
        @test begin
            r = rand(d, 10000)
            counts = map(x -> sum(r .== x), 1:maximum(d))
            counts == n
        end
    end

end

@testset "pdf" for (d,x,p) in [
        (d1, 1, 0.16666666666666669),
        (d2, 2, 0.2727272727272727),
        (d3, 3, 0.2),
        (d4, 4, 0.32231404958677684),
        (d4, -1, 0),
        (d4, 7, 0)]
    @test pdf(d, x) == p
end

@testset "logpdf" for (d,x,p) in [
        (d1, 1, -1.7917594692280547),
        (d2, 2, -1.2992829841302609),
        (d3, 3, -1.6094379124341003),
        (d4, 4, -1.1322288994670948),
        (d4, -1, -Inf),
        (d4, 9, -Inf)]
    @test logpdf(d, x) == p
end

@testset "cpdf" for (d,x,p) in [
        (d1, 1, 0.16666666666666669),
        (d2, 2, 0.7272727272727273),
        (d3, 3, 0.6000000000000001),
        (d4, 4, 1.0),
        (d4, -1, 0),
        (d3, 5, 1.0)]
    @test cdf(d, x) == p
end

@testset "quantile" for (d,x,p) in [(d1, 1, 0.1), (d2, 1, 0.4), (d3, 3, 0.6), (d4, 4, 0.8)]
     @test quantile(d, p) == x

end

@testset "quantile throws" begin
     @test_throws DomainError quantile(d1, -1.0)
     @test_throws DomainError quantile(d1, 2.0)
end

@testset "minimum" for d in (d1, d2, d3, d4)
    @test minimum(d) == 1
end

@testset "maximum" for (d,n) in [(d1, 3), (d2, 3), (d3, 5), (d4, 4)]
    @test maximum(d) == n
end

@testset "insupport" for (d,s) in [
        (d1, [0, 1, 1, 1, 0, 0, 0]),
        (d2, [0, 1, 1, 1, 0, 0, 0]),
        (d3, [0, 1, 1, 1, 1, 1, 0]),
        (d4, [0, 1, 1, 1, 1, 0, 0])]
    @test map(x -> insupport(d, x), collect(0:6)) == s
end

@testset "mode" for (d,n) in [(d1, 3), (d2, 1), (d4, 2)]
    @test mode(d) == n
end

@testset "mode throws" begin
    @test_throws ErrorException mode(d3)
end

@testset "update!" begin
    @testset "single" for (d,o,n) in [
            (d1, 1, [2, 2, 3]),
            (d2, 2, [1, 2, 0]),
            (d3, 3, [0, 0, 1, 0, 0]),
            (d4, 4, [0, 0, 0, 1])]
        @test begin
            d = deepcopy(d)
            Distributions.update!(d, o)

            d.n == n
        end
    end

    @testset "multiple" for (d,o,n) in [
            (d1, [1, 1, 1, 3, 3, 3, 2, 1, 3, 3], [5, 3, 8]),
            (d2, [3, 2, 3, 2, 3, 2, 3, 3, 3, 3], [1, 4, 7]),
            (d3, [3, 4, 5, 4, 4, 4, 5, 5, 4, 4], [0, 0, 1, 6, 3]),
            (d4, [4, 4, 1, 4, 4, 4, 2, 1, 3, 2], [2, 2, 1, 5])]
        @test begin
            d = deepcopy(d)
            Distributions.update!(d, o)

            d.n == n
        end
    end

    @testset "throws" begin
        @test_throws ArgumentError Distributions.update!(d1, -1)
        @test_throws ArgumentError Distributions.update!(d1, 5)
    end

    @testset "rand" begin
        d = DirichletCategorical([10, 0.0001, 0.0001, 0.0001])
        a = rand(d, 100)
        Distributions.update!(d, ones(Int, 10000) .* 4)
        b = rand(d, 100)

        all(a .== 1) && all(b .== 4)
    end

end

end


using Random, Test
using CollegeEntry

mdl = CollegeEntry;

function max_choice_test()
    rng = MersenneTwister(12);
    vWork = 1.0;
    vCollege_cV = [0.5, 3.0];
    v, ic = mdl.max_choice(vWork, vCollege_cV, [true, true]);
    @test v == 3.0  &&  ic == 2
    v, ic = mdl.max_choice(vWork, vCollege_cV, [true, false]);
    @test v == vWork  &&  ic == 0
    v, ic = mdl.max_choice(vWork, vCollege_cV, [false, false]);
    @test v == vWork  &&  ic == 0

    J = 5;
    nc = 7;
    vWorkV = randn(rng, J);
    vCollege_jcM = rand(rng, J, nc);
    admitV = rand(rng, Bool, nc);
    vV, icV = mdl.max_choices(vWorkV, vCollege_jcM, admitV);
    for j = 1 : J
        v, ic = mdl.max_choice(vWorkV[j], vec(vCollege_jcM[j,:]), admitV);
        @test isapprox(vV[j], v)  &&  isequal(icV[j], ic)
    end
end

function helpers_test()
    @testset "Helpers" begin
        x = rand(4,3,2) ./ 5.0;
        x[2 : 2 : 8] .= -0.000000001;
        x ./= sum(x);
        x .+= 0.000000001;
        mdl.make_valid_probs!(x);
        @test sum(x) <= 1.0
        @test all(x .>= 0.0)
	end
end

function find_class_test()
    @testset "find_class" begin
        cutoffV = [0.1, 0.5, 0.9];
        @test mdl.find_class(0.09, cutoffV) == 1
        @test mdl.find_class(0.11, cutoffV) == 2
        @test mdl.find_class(0.49, cutoffV) == 2
        @test mdl.find_class(0.51, cutoffV) == 3
        @test mdl.find_class(0.94, cutoffV) == 4
    end
end

@testset "Helpers" begin
    max_choice_test()
    helpers_test();
    find_class_test();
end

# -------------


@testset "constant" begin
    @testset "ZeroKernel" begin
        k = ZeroKernel()
        @test eltype(k) == Any
        @test kappa(k,2.0) == 0.0
        @test KernelFunctions.metric(ZeroKernel()) == KernelFunctions.Delta()
        @test repr(k) == "Zero Kernel"
        test_ADs(ZeroKernel)
    end
    @testset "WhiteKernel" begin
        k = WhiteKernel()
        @test eltype(k) == Any
        @test kappa(k,1.0) == 1.0
        @test kappa(k,0.0) == 0.0
        @test EyeKernel == WhiteKernel
        @test metric(WhiteKernel()) == KernelFunctions.Delta()
        @test repr(k) == "White Kernel"
        test_ADs(WhiteKernel)
    end
    @testset "ConstantKernel" begin
        c = 2.0
        k = ConstantKernel(c=c)
        @test eltype(k) == Any
        @test kappa(k,1.0) == c
        @test kappa(k,0.5) == c
        @test metric(ConstantKernel()) == KernelFunctions.Delta()
        @test metric(ConstantKernel(c=2.0)) == KernelFunctions.Delta()
        @test repr(k) == "Constant Kernel (c = $(c))"
        test_ADs(c->ConstantKernel(c=first(c)), [c])
    end
end


# ---
# title: Wide data
# cover: assets/wide_data.png
# description: Working with data in the wide format
# author: "[Pietro Vertechi](https://github.com/piever)"
# ---

using AlgebraOfGraphics, CairoMakie
using AlgebraOfGraphics: density
set_aog_theme!() #src

df = (a=randn(100), b=randn(100), c=randn(100))
labels = ["Trace 1", "Trace 2", "Trace 3"]
plt = data(df) *
    density() *
    mapping([:a, :b, :c] .=> "some label") *
    mapping(color=dims(1) => renamer(labels))
draw(plt)

#

df = (a=rand(100), b=rand(100), c=rand(100), d=rand(100))
labels = ["Trace 1", "Trace 2", "Trace 3"]
layers = linear() + visual(Scatter)
plt = data(df) * layers * mapping(1, 2:4 .=> "value", color=dims(1) => renamer(labels))
draw(plt)

# The wide format is combined with broadcast semantics.

df = (sepal_length=rand(100), sepal_width=rand(100), petal_length=rand(100), petal_width=rand(100))
xvars = ["sepal_length", "sepal_width"]
yvars = ["petal_length" "petal_width"]
layers = linear() + visual(Scatter)
plt = data(df) * layers * mapping(xvars, yvars, col=dims(1), row=dims(2))
fg = draw(plt)

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


function d_up(p,j1,j2,n,m)
    dj = zeros(m)
    for i in 1:n
        if j1[i]>0 && j2[i]>0
            q = p[j1[i]:j2[i]]
            q ./= sum(q)
            dj[j1[i]:j2[i]] += q
        end
    end
    return dj
end

function setbreaks(trow, o=0)
    tj = sort(unique(max.(o, trow)))
    return tj
end

function p_up!(p,n,m,j1,j2,dj)
    bj = zeros(m)
    for i in 1:n
        if j1[i]>0 && j2[i]>0
        q = sum(p[j1[i]:j2[i]])
        if q > 0.0
        b = (1-q)/q
        ind = setdiff(1:m,j1[i]:j2[i])
        r = vec(p[ind])
        r ./= sum(r)
        bj[ind] += b*r
    end
    end
    end
    num = dj + bj
    copy!(p, num ./ sum(num))
end

function acount(L,R,breaks,n,m)
    afirst = zeros(Int,n)
    alast = zeros(Int,n)
    for i in 1:n
        alpha = L[i] .<= breaks[1:(m-1)] .&& breaks[2:m] .<= R[i]
        if any(alpha)
            afirst[i] = findfirst(alpha)
            alast[i] =  findlast(alpha)
        end
    end
    return afirst, alast
end

function bcount(U,breaks,n,m)
    bfirst = zeros(Int,n)
    blast = zeros(Int,n)
    for i in 1:n
        beta = breaks .<= U[i]
        if any(beta)
            bfirst[i] = findfirst(beta)
            blast[i] =  findlast(beta)
        end
    end
    return bfirst, blast
end

function SurvIC(L, R, S, iter=100, tol=1e-8)
    tj = setbreaks([S-L;S-R])
    n = length(L)
    m = length(tj)
    aind = acount(S-R, S-L,tj,n,m)
    p = inv(m)*ones(m)
    dj = d_up(p, aind[1], aind[2], n, m)
    copy!(p, dj ./ sum(dj))
    con = false
    count = 0
    p2 = p #こういうとこcopy!()とか使ったほうがいいですか？
    for it in 1:iter
        count += 1
        dj = d_up(p,aind[1],aind[2],n,m)
        copy!(p2, dj ./ sum(dj))
        con = all(abs.(p2-p) .< tol)
        p = p2
    if con || any(isnan.(p))
        break
    end
    end
    return tj, 1.0 .- cumsum(p), con, count
end

function SurvICRT(L, R, S, Tmax, iter=100, tol=1e-10)
    tj = setbreaks([S-L;S-R])
    n = length(L)
    m = length(tj)
    aind = acount(S-R, S-L,tj,n,m)
    p = inv(m)*ones(m)
    bind = bcount(Tmax.-S,tj,n,m)
    dj = d_up(p,aind[1],aind[2],n,m)
    p_up!(p, n, m, bind[1], bind[2], dj)
    con = false
    count = 0
    p2 = p #こういうとこcopy!()とか使ったほうがいいですか？
    for it in 1:iter
        count += 1
        dj = d_up(p,aind[1],aind[2],n,m)
        p_up!(p, n, m, bind[1], bind[2], dj)
        con = all(abs.(p2-p) .< tol)
        p = p2
    if con || any(isnan.(p))
        break
    end
    end
    return tj, 1.0 .- cumsum(p), con, count
end

function SurvDIC(EL, ER, SL, SR, iter=100, tol=1e-8)
    S = 0.5*(SL+SR)
    tj = setbreaks([S-EL;S-ER])
    n = length(EL)
    m = length(tj)
    aind = acount(S-ER, S-EL,tj,n,m)
    p = inv(m)*ones(m)
    dj = d_up(p,aind[1],aind[2],n,m)
    copy!(p, dj ./ sum(dj))
    con = false
    count = 0
    p2 = p #こういうとこcopy!()とか使ったほうがいいですか？
    for it in 1:iter
        count += 1
        dj = d_up(p,aind[1],aind[2],n,m)
        copy!(p2, dj ./ sum(dj))
        con = all(abs.(p2-p) .< tol)
        p = p2
    if con || any(isnan.(p))
        break
    end
    end
    return tj, 1.0 .- cumsum(p), con, count
end

function SurvDICRT(EL, ER, SL, SR, Tmax, iter=100, tol=1e-8)
    S = 0.5*(SL+SR)
    tj = setbreaks([S-EL;S-ER])
    n = length(EL)
    m = length(tj)
    aind = acount(S-ER, S-EL,tj,n,m)
    p = inv(m)*ones(m)
    bind = bcount(Tmax.-SR,tj,n,m)
    dj = d_up(p,aind[1],aind[2],n,m)
    p_up!(p, n, m, bind[1], bind[2],dj)
    con = false
    count = 0
    p2 = p #こういうとこcopy!()とか使ったほうがいいですか？
    for it in 1:iter
        count += 1
        dj = d_up(p,aind[1],aind[2],n,m)
        p_up!(p, n, m, bind[1], bind[2], dj)
        con = all(abs.(p2-p) .< tol)
        p = p2
    if con || any(isnan.(p))
        break
    end
    end
    return tj, 1.0 .- cumsum(p), con, count
end


#=
BFSParentsMultiSourceTest:
- Julia version: 1.6.2
- Author: samuel
- Date: 2021-09-11
=#

using SuiteSparseGraphBLAS
using SparseArrays
using LinearAlgebra
using Test

include("/Users/samuel/Documents/Jupyter/GraphBlasAlgorithmsFinal/src/BFSParentsMultiSource.jl")
S = GBMatrix(sparse([[1, 0, 0] [0, 0, 0] [0, 3, 0] [0, 0, 4] [0, 0, 0] [0, 0, 0] [0, 0, 0]]))
matrix =  GBMatrix(sparse([[0, 0, 0, 1, 0, 0, 0] [1, 0, 0, 0, 0, 0, 0] [0, 0, 0, 1, 0, 1, 1] [1, 0, 0, 0, 0, 0, 1] [0, 1, 0, 0, 0, 0, 1] [0, 0, 1, 0, 1, 0, 0] [0, 1, 0, 0, 0, 0, 0]]))
@test ms_bfsp(matrix, S, 7, 3) == [[0, nothing, 4] [1, nothing, 1] [4, 0, 4] [1, nothing, 0] [2, nothing, 2] [3, 3, 3] [2, nothing, 2]]


import Base: done, next, start

#### Iterator ####
# An n-dimensional grid iterator. This allows you to do things in
# arbitrary dimensions that in fixed dimensions you might do with a
# comprehension.
#
# For example, for two dimensions the equivalent of
#    p = [ i*j for i = 1:m, j = 1:n ]
# is
#    sz = (m, n)
#    p = Array(Int, sz)
#    index = 1
#    for c = Counter(sz)
#      p[index] = prod(c)
#      index += 1
#    end
# However, with sz appropriately defined, this version works for
# arbitrary dimensions.

type Counter
    max::Vector{Int}
end
Counter(sz::Tuple) = Counter(Int[sz...])

function start(c::Counter)
    N = length(c.max)
    state = ones(Int,N)
    if N > 0
        state[1] = 0 # because of start/done/next sequence, start out one behind
    end
    return state
end
function done(c::Counter, state)
    if isempty(state)
        return true
    end
    # we do the increment as part of "done" to make exit-testing more efficient
    state[1] += 1
    i = 1
    max = c.max
    while state[i] > max[i] && i < length(state)
        state[i] = 1
        i += 1
        state[i] += 1
    end
    state[end] > max[end]
end
next(c::Counter, state) = state, state


function sampleExact(nodepot, edgepot, es::EdgeStruct; Z = -1, nsamples=-1)

  nnodes = size(nodepot, 1)
  nedges = size(edgepot, 3)

  if nsamples < 0
    nsamples = es.maxiter
  end

  samples = zeros(int(nsamples),int(nnodes));

  if Z < 0
    nothing, nothing, nothing, Z = inferExact(nodepot, edgepot, es)
  end

  for s = 1:nsamples
     samples[s,:] = sampleY(nodepot,edgepot,es,Z)
  end
  return samples
end

function sampleY(nodepot, edgepot, es::EdgeStruct, Z)

  nnodes = size(nodepot,1)
  nedges = size(edgepot,3)

  y = ones(1,int(nnodes));
  cumpot = 0
  U = rand()
  i = 1
  while true

      pot = configPotential(y, nodepot, edgepot, es::EdgeStruct)
      cumpot = cumpot + pot;

      if cumpot/Z >= U
          break;
      end

      # next y
      for i = 1:nnodes
          y[i] = y[i] + 1;
          if y[i] <= es.nstates[i]
              break
          else
              y[i] = 1
          end
      end
  end
  return y
end


using WiltonInts84
using StaticArrays
using Test

p1 = SVector(0.0, 0.0, 0.0)
p2 = SVector(1.0, 0.0, 0.0)
p3 = SVector(0.0, 1.0, 0.0)
h = 0.1

x = -0.5 : 0.01 : 1.5
y = -0.5 : 0.01 : 1.5

r, R = 0.0, 0.15
fails = 0
M = zeros(typeof(h), length(x), length(y))
for i in eachindex(x)
    for j in eachindex(y)
        _c = SVector(x[i],y[j],h)
        try
            _I, _K = wiltonints(p1,p2,p3,_c,r,R,Val{0})
            M[i,j] = _I[2]
        catch
            # @show i j _c
            global fails += 1
        end
    end
end

# There are still some cases where the package fails,
# but at least lets make sure that number does not increase...
@test fails == 20


using Optim
using PyPlot
#import celerite
include("../src/celerite.jl")

srand(42)

# The input coordinates must be sorted
t = sort(cat(1, 3.8 * rand(57), 5.5 + 4.5 * rand(68)))
yerr = 0.08 + (0.22-0.08)*rand(length(t))
y = 0.2*(t-5.0) + sin.(3.0*t + 0.1*(t-5.0).^2) + yerr .* randn(length(t))

true_t = linspace(0, 10, 1000)
true_y = 0.2*(true_t-5) + sin.(3*true_t + 0.1*(true_t-5).^2)

PyPlot.clf()
PyPlot.plot(true_t, true_y, "k", lw=1.5, alpha=0.3)
PyPlot.errorbar(t, y, yerr=yerr, fmt=".k", capsize=0)
PyPlot.xlabel("x")
PyPlot.ylabel("y")
PyPlot.xlim(0, 10)
PyPlot.ylim(-2.5, 2.5);

Q = 1.0 / sqrt(2.0)
w0 = 3.0
S0 = var(y) / (w0 * Q)
kernel = celerite.SHOTerm(log(S0), log(Q), log(w0))

# A periodic component
Q = 1.0
w0 = 3.0
S0 = var(y) / (w0 * Q)
kernel = kernel + celerite.SHOTerm(log(S0), log(Q), log(w0))

celerite.TermSum((celerite.SHOTerm(-0.7432145976901582,-0.34657359027997275,1.0986122886681096),celerite.SHOTerm(-1.089788187970131,0.0,1.0986122886681096)))

gp = celerite.Celerite(kernel)
celerite.compute!(gp, t, yerr)
celerite.log_likelihood(gp, y)

#mu, variance = celerite.predict_full_ldlt(gp, y, true_t, return_var=true)
#mu, variance = celerite.predict_full(gp, y, true_t, return_var=true)
mu, variance = celerite.predict(gp, y, true_t, return_var=true)
#mu  = celerite.predict_full(gp, y, true_t, return_cov=false, return_var=false)
#mu  = celerite.predict!(gp, gp.x, y, true_t)
sigma = sqrt.(variance)

PyPlot.plot(true_t, true_y, "k", lw=1.5, alpha=0.3,label="Simulated data")
PyPlot.errorbar(t, y, yerr=yerr, fmt=".k", capsize=0)
PyPlot.plot(true_t, mu, "g",label="Initial kernel")
PyPlot.fill_between(true_t, mu+sigma, mu-sigma, color="g", alpha=0.3)
PyPlot.xlabel("x")
PyPlot.ylabel("y")
PyPlot.xlim(0, 10)
PyPlot.ylim(-2.5, 2.5);
read(STDIN,Char)

PyPlot.clf()
vector = celerite.get_parameter_vector(gp.kernel)
mask = ones(Bool, length(vector))
mask[2] = false  # Don't fit for the first Q
function nll(params)
    vector[mask] = params
    celerite.set_parameter_vector!(gp.kernel, vector)
    celerite.compute!(gp, t, yerr)
    return -celerite.log_likelihood(gp, y)
end;

result = Optim.optimize(nll, vector[mask], Optim.LBFGS())
result

vector[mask] = Optim.minimizer(result)
vector

celerite.set_parameter_vector!(gp.kernel, vector)

#mu, variance = celerite.predict(gp, y, true_t, return_var=true)
#mu, variance = celerite.predict_full_ldlt(gp, y, true_t, return_var=true)
#mu = celerite.predict(gp, y, true_t, return_cov = false, return_var=false)
#mu = celerite.predict!(gp, gp.x, y, true_t)
mu, variance = celerite.predict(gp, y, true_t, return_var=true)
sigma = sqrt.(variance)

PyPlot.plot(true_t, true_y, "k", lw=1.5, alpha=0.3)
PyPlot.errorbar(t, y, yerr=yerr, fmt=".k", capsize=0)
PyPlot.plot(true_t, mu, "r",label="Optimized kernel")
PyPlot.fill_between(true_t, mu+sigma, mu-sigma, color="g", alpha=0.3)
PyPlot.xlabel("x")
PyPlot.ylabel("y")
PyPlot.xlim(0, 10)
PyPlot.ylim(-2.5, 2.5);
PyPlot.legend(loc="upper left")

read(STDIN,Char)
PyPlot.clf()

omega = exp.(linspace(log(0.1), log(20), 5000))
psd = celerite.get_psd(gp.kernel, omega)

for term in gp.kernel.terms
    PyPlot.plot(omega, celerite.get_psd(term, omega), "--", color="orange")
end
PyPlot.plot(omega, psd, color="orange")

PyPlot.yscale("log")
PyPlot.xscale("log")
PyPlot.xlim(omega[1], omega[end])
PyPlot.xlabel("omega")
PyPlot.ylabel("S(omega)");
PyPlot.plot(2*pi/2.0*[1.,1.],[1e-8,1e2])


__precompile__()

module MultivectorAlgebras

import Base: +, -, *, /, \, real, isreal, conj, inv, abs, abs2, show, zero, one, iszero

"Error when finding the inverse of zero."
const ZeroInverse = "inverse of zero"

"Error when finding the inverse of a zero divisor."
const ZeroDivisorInverse = "inverse of zero divisor"

"Error when the denominator in a quotient is zero."
const ZeroDenominator = "denominator is zero"

"Error when the denominator in a quotient is a zero divisor."
const ZeroDivisorDenominator = "denominator is zero divisor"

include("AbstractMultivector.jl")
include("Multivector1.jl")
include("Multivector2.jl")
include("Multivector3.jl")
include("Multivector4.jl")

export AbstractMultivector, asarray, iszerodivisor, commutator, associator, jacobiator, crossratioL, crossratioR, möbiusL, möbiusR
export Multivector1, unreal, cloak, dagger, star, selfstar, antiselfstar, random
export Multivector2
export Multivector3
export Multivector4

end